Skip to content

Debugging and Analysis

Mick edited this page Jul 3, 2026 · 8 revisions

Debugging and Analysis

Breakpoints

Add an unconditional breakpoint with a file and 1-based line number. A conditional breakpoint pauses only when its RuleScript expression evaluates to true.

var engine = new RuleScriptEngine
{
    WorkingDirectory = @"C:\rules"
};

engine.AddBreakpoint("main.rules", 3);
engine.AddBreakpoint("main.rules", 5, "score > 80");

var session = new RuleScriptDebugSession(engine);
Task<RuntimeContext> runTask = session.RunFileAsync("main.rules");

RuleScriptRuntimeEvent pause = await session.WaitForPauseAsync();
Console.WriteLine($"Paused at {pause.Location.File}:{pause.Location.Line}");

session.StepOver();
pause = await session.WaitForPauseAsync();

session.Continue();
RuntimeContext context = await runTask;

Use session.Stop() to cancel a running or paused debug session. At a pause point, CurrentSnapshot exposes the source location, globals, current function locals, and call stack. CurrentPause contains the event that caused the pause.

RuleScriptDebugSnapshot? snapshot = session.CurrentSnapshot;

if (snapshot is not null)
{
    foreach (var variable in snapshot.Globals)
    {
        Console.WriteLine($"global {variable.Key} = {variable.Value.Value}");
    }

    foreach (var frame in snapshot.CallStack)
    {
        Console.WriteLine(frame);
    }
}

Strict Static Analysis

Analyze parses complete source and returns symbols without executing it. Syntax errors throw SyntaxException; semantic errors are returned in Diagnostics.

var engine = new RuleScriptEngine();
RuleScriptAnalysisResult symbols = engine.Analyze(script);

foreach (var variable in symbols.Variables)
{
    Console.WriteLine($"{variable.Name}: {variable.Type}");
}

foreach (var function in symbols.UserFunctions)
{
    Console.WriteLine($"function {function.Name} ({function.Parameters.Count} parameters)");
}

foreach (var diagnostic in symbols.Diagnostics)
{
    Console.WriteLine($"{diagnostic.Severity} {diagnostic.Code}: {diagnostic.Message}");
}

For cursor-aware completion, call Analyze(script, line, column) and read VisibleVariables or VisibleVariableNames. Lines and columns are 1-based.

RuleScriptAnalysisResult atCursor = engine.Analyze(script, line: 8, column: 12);

foreach (var name in atCursor.VisibleVariableNames)
{
    Console.WriteLine(name);
}

Best-Effort Analysis for Editors

TryAnalyze does not throw for recoverable syntax errors. It returns partial symbols and diagnostics, which makes it suitable for text being edited.

RuleScriptAnalysisAttempt attempt = engine.TryAnalyze(editorText, cursorLine, cursorColumn);

foreach (var diagnostic in attempt.Diagnostics)
{
    Console.WriteLine(
        $"{diagnostic.Severity} {diagnostic.Code} " +
        $"at {diagnostic.Line}:{diagnostic.Column}: {diagnostic.Message}");
}

IReadOnlyList<RuleScriptVariableSymbol> completionVariables =
    attempt.Symbols.VisibleVariables;

Success is false when at least one diagnostic has Error severity. Warnings, such as an unresolved runtime variable, do not make it false.

Formatting, Regions, and Documentation (v1.6.0)

Use RuleScriptFormatter.Format(source) from RuleScript.Core.Formatting to normalize valid source with four-space indentation, canonical spacing, normalized block layout, preserved blank lines, and preserved comments/directives.

using RuleScript.Core.Formatting;

var formatted = RuleScriptFormatter.Format(source);

RuleScriptLanguageService.GetRegions(source) returns named, nested source ranges for #region and #endregion editor folding. RuleScriptLanguageService.GetFunctionDocumentation(source, functionName) returns the normalized /// block attached to a user function. The same user-function documentation appears on RuleScriptFunctionSymbol.Documentation.

Semantic Diagnostics

v1.2.0 reports undefined variables and functions, incompatible known types, duplicate declarations, and duplicate parameters. Use codes for program logic and messages for display.

var attempt = engine.TryAnalyze("var result = MissingFunction();");

RuleScriptDiagnostic diagnostic = attempt.Diagnostics.Single(
    d => d.Code == RuleScriptDiagnosticCodes.UndefinedFunction);

Console.WriteLine(diagnostic.Severity); // Error
Console.WriteLine(diagnostic.TokenText); // MissingFunction
Console.WriteLine(diagnostic.Range); // half-open source range when available

Diagnostic code reference:

Code Constant Default severity
RS1000 SyntaxError Error
RS2001 UndefinedVariable Warning
RS2002 UndefinedFunction Error
RS2003 TypeMismatch Error
RS2004 DuplicateDeclaration Error
RS2005 DuplicateParameter Error
RS2006 DuplicateCase Error (since v1.3.0)
RS2007 MissingDefaultBranch Warning (since v1.3.0)
RS2008 PropertyNotFound Error (since v1.4.0)
RS2009 InvalidAssignment Error (since v1.4.0)
RS2010 CannotAssignToReadonly Error (since v1.4.0)
RS2011 IndexTypeError Error (since v1.4.0)
RS2012 NullAccess Error (since v1.4.0)
RS2013 InvalidNullCoalescing Error (since v1.4.0)
RS2014 DuplicateObjectProperty Error (since v1.4.0)
RS3001 ParallelRequiresTask Error (since v1.5.0)
RS3002 TaskOutsideParallel Error (since v1.5.0)
RS3003 ParallelTaskFailed Error (since v1.5.0)
RS3004 ParallelCancelled Error (since v1.5.0)
RS3005 HostFunctionNotThreadSafe Error (since v1.5.0)
RS3006 ParallelReturnTypeMismatch Error (since v1.5.0)
RS3007 InvalidParallelBlock Error (since v1.5.0)
RS3008 InvalidTaskBlock Error (since v1.5.0)

Since v1.3.0, TypeMismatch also reports statically incompatible switch labels and known non-boolean when guards. A guarded duplicate constant is allowed as long as no earlier unguarded label with the same value makes it unreachable.

Host-Provided Analysis Variables

Use SetKnownVariable when a value will be supplied by the host at execution time. This suppresses the undefined-variable warning and lets the analyzer check its type.

engine.SetKnownVariable("Distance", RuleScriptValueType.Number);
engine.SetKnownVariable("CustomerName", RuleScriptValueType.String);

RuleScriptAnalysisAttempt analysis = engine.TryAnalyze("""
    var label = CustomerName + ": " + Distance;
    var alert = Distance > 500;
    """);

var context = new RuntimeContext();
context.Set("Distance", 519);
context.Set("CustomerName", "North Plant");
engine.Execute("result = Distance > 500;", context);

Known variables configure analysis only; the runtime context still needs actual values. KnownVariables returns the current sorted snapshot, RemoveKnownVariable removes one symbol, and ClearKnownVariables resets the schema.

Runtime Limits in v1.2.0

var engine = new RuleScriptEngine
{
    ExecutionTimeout = TimeSpan.FromSeconds(10),
    ExecutionTimeoutEnabled = true,
    MaxCallDepth = 50,
    CallDepthLimitEnabled = true,
    MaxExecutedStatements = 100_000,
    StatementExecutionLimitEnabled = true,
    MaxLoopIterations = 10_000,
    LoopIterationLimitEnabled = true
};

Each enable switch controls only its matching limit. Limit values must remain greater than zero even when a switch is disabled. The configuration applies consistently to synchronous, asynchronous, file, and debug-session execution.

Limit Scope Default
ExecutionTimeout Elapsed time for one run, checked at statement boundaries. 30 seconds
MaxCallDepth Active User Function calls. 100
MaxExecutedStatements Total statements across the complete run. 1,000,000
MaxLoopIterations Iterations of each individual while or foreach. Engine default

An exceeded limit throws RuntimeException; the message identifies the limit and configured value. A caller can also pass a CancellationToken to async execution or call Stop() for explicit cancellation.

Realtime Source Behavior

Every Analyze and TryAnalyze call reads the supplied script text and resolves current import content. Fixing editor text or changing an imported file is reflected on the next call; no source cache needs to be invalidated.

var first = engine.TryAnalyze("var value = MissingFunction();");
var second = engine.TryAnalyze("var value = 1;");

Console.WriteLine(first.Diagnostics.Count);  // contains undefined-function error
Console.WriteLine(second.Diagnostics.Count); // 0

See the API Reference by Class for the complete current API, or the v1.6.0 API Reference and Examples for formatting and editor metadata APIs.

Clone this wiki locally