< Summary

Information
Class: Elsa.Dsl.ElsaScript.Parser.ElsaScriptParser
Assembly: Elsa.Dsl.ElsaScript
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Dsl.ElsaScript/Parser/ElsaScriptParser.cs
Line coverage
94%
Covered lines: 393
Uncovered lines: 22
Coverable lines: 415
Total lines: 630
Line coverage: 94.6%
Branch coverage
68%
Covered branches: 30
Total branches: 44
Branch coverage: 68.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()79.41%343496.02%
Parse(...)25%9433.33%
EvaluateConstantExpressionStatic(...)33.33%7666.66%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Dsl.ElsaScript/Parser/ElsaScriptParser.cs

#LineLine coverage
 1using Elsa.Dsl.ElsaScript.Ast;
 2using Elsa.Dsl.ElsaScript.Contracts;
 3using Parlot;
 4using Parlot.Fluent;
 5
 6namespace Elsa.Dsl.ElsaScript.Parser;
 7
 8/// <summary>
 9/// ElsaScript parser using Parlot for robust parsing.
 10/// </summary>
 11public class ElsaScriptParser : IElsaScriptParser
 12{
 13    private static readonly Parser<ProgramNode> ProgramParser;
 14
 15    static ElsaScriptParser()
 16    {
 17        // Keywords
 218        var useKeyword = Terms.Text("use");
 219        var workflowKeyword = Terms.Text("workflow");
 220        var expressionsKeyword = Terms.Text("expressions");
 221        var listenKeyword = Terms.Text("listen");
 222        var varKeyword = Terms.Text("var");
 223        var constKeyword = Terms.Text("const");
 224        var forKeyword = Terms.Text("for");
 225        var foreachKeyword = Terms.Text("foreach");
 226        var inKeyword = Terms.Text("in");
 227        var toKeyword = Terms.Text("to");
 228        var throughKeyword = Terms.Text("through");
 229        var stepKeyword = Terms.Text("step");
 230        var flowchartKeyword = Terms.Text("flowchart");
 231        var entryKeyword = Terms.Text("entry");
 32
 33        // Basic tokens
 234        var identifier = Terms.Identifier();
 235        var stringLiteral = Terms.String();
 236        var integerLiteral = Terms.Integer();
 237        var decimalLiteral = Terms.Decimal();
 38
 39        // Punctuation
 240        var semicolon = Terms.Char(';');
 241        var comma = Terms.Char(',');
 242        var colon = Terms.Char(':');
 243        var leftParen = Terms.Char('(');
 244        var rightParen = Terms.Char(')');
 245        var leftBrace = Terms.Char('{');
 246        var rightBrace = Terms.Char('}');
 247        var leftBracket = Terms.Char('[');
 248        var rightBracket = Terms.Char(']');
 249        var dot = Terms.Char('.');
 250        var arrow = Terms.Text("=>");
 251        var rightArrow = Terms.Text("->");
 252        var equals = Terms.Char('=');
 53
 54        // Deferred parsers for recursive structures
 255        var expression = Deferred<ExpressionNode>();
 256        var statement = Deferred<StatementNode>();
 57
 58        // Expression parsers
 259        var booleanLiteral = Terms.Text("true").Or(Terms.Text("false"))
 460            .Then<ExpressionNode>(x => new LiteralNode { Value = x.ToString() == "true" });
 61
 262        var numberLiteral = decimalLiteral
 2063            .Then<ExpressionNode>(x => new LiteralNode { Value = x });
 64
 265        var intLiteral = integerLiteral
 266            .Then<ExpressionNode>(x => new LiteralNode { Value = (long)x });
 67
 268        var stringExpr = stringLiteral
 3169            .Then<ExpressionNode>(x => new LiteralNode { Value = x.ToString() });
 70
 271        var identifierExpr = identifier
 472            .Then<ExpressionNode>(x => new IdentifierNode { Name = x.ToString() });
 73
 74        // Array literal: [expr, expr, ...]
 275        var commaSeparatedExpression = expression.And(ZeroOrOne(comma)).Then(x => x.Item1);
 276        var arrayLiteral = Between(leftBracket, ZeroOrMany(commaSeparatedExpression), rightBracket)
 277            .Then<ExpressionNode>(elements => new ArrayLiteralNode { Elements = elements.ToList() });
 78
 79        // Elsa expression: lang => <raw text until matching )>
 80        // We need to capture raw text after => up to the closing parenthesis
 81        // This supports nested parentheses by counting depth
 82        // Use a custom scanner-based parser wrapped in RawExpressionParser
 283        var rawExpressionText = new RawExpressionParser();
 84
 285        var elsaExpressionWithLang = identifier
 286            .And(arrow)
 287            .And(rawExpressionText)
 1088            .Then<ExpressionNode>(x => new ElsaExpressionNode
 1089            {
 1090                Language = x.Item1.ToString(),
 1091                Expression = x.Item3.ToString().Trim()
 1092            });
 93
 294        var elsaExpressionWithoutLang = arrow
 295            .And(rawExpressionText)
 296            .Then<ExpressionNode>(x => new ElsaExpressionNode
 297            {
 298                Language = null,
 299                Expression = x.Item2.ToString().Trim()
 2100            });
 101
 2102        var elsaExpression = elsaExpressionWithLang.Or(elsaExpressionWithoutLang);
 103
 104        // Expression priority: try most specific first
 2105        expression.Parser = elsaExpression
 2106            .Or(arrayLiteral)
 2107            .Or(booleanLiteral)
 2108            .Or(numberLiteral)
 2109            .Or(intLiteral)
 2110            .Or(stringExpr)
 2111            .Or(identifierExpr);
 112
 113        // Argument parser: name: value or just value
 2114        var namedArgument = identifier
 2115            .And(colon)
 2116            .And(expression)
 3117            .Then(x => new ArgumentNode { Name = x.Item1.ToString(), Value = x.Item3 });
 118
 2119        var positionalArgument = expression
 28120            .Then(x => new ArgumentNode { Value = x });
 121
 2122        var argument = namedArgument.Or(positionalArgument);
 123
 29124        var commaSeparatedArgument = argument.And(ZeroOrOne(comma)).Then(x => x.Item1);
 2125        var arguments = ZeroOrMany(commaSeparatedArgument);
 126
 127        // Activity invocation: ActivityName(args) - with or without arguments
 2128        var activityInvocationWithArgs = identifier
 2129            .And(leftParen)
 2130            .And(arguments)
 2131            .And(rightParen)
 33132            .Then(x => new ActivityInvocationNode
 33133            {
 33134                ActivityName = x.Item1.ToString(),
 33135                Arguments = x.Item3.ToList()
 33136            });
 137
 2138        var activityInvocationNoArgs = identifier
 2139            .And(leftParen)
 2140            .And(rightParen)
 2141            .Then(x => new ActivityInvocationNode
 2142            {
 2143                ActivityName = x.Item1.ToString(),
 2144                Arguments = []
 2145            });
 146
 2147        var activityInvocation = activityInvocationWithArgs.Or(activityInvocationNoArgs);
 148
 149        // Variable declaration: var/const name = expr
 2150        var variableKindParser = varKeyword.Or(constKeyword);
 151
 2152        var variableDeclaration = variableKindParser
 2153            .And(identifier)
 2154            .And(equals)
 2155            .And(expression)
 2156            .Then<StatementNode>(x =>
 2157            {
 2158                // x is a flat tuple (kind, identifier, equals, expression)
 8159                var kind = x.Item1.ToString() switch
 8160                {
 6161                    "var" => VariableKind.Var,
 2162                    "const" => VariableKind.Const,
 0163                    _ => VariableKind.Var
 8164                };
 2165
 8166                return new VariableDeclarationNode
 8167                {
 8168                    Kind = kind,
 8169                    Name = x.Item2.ToString(),
 8170                    Value = x.Item4
 8171                };
 2172            });
 173
 174        // Listen statement: listen ActivityName(args)
 2175        var listenStatement = listenKeyword
 2176            .And(activityInvocation)
 5177            .Then<StatementNode>(x => new ListenNode { Activity = x.Item2 });
 178
 179        // Statement: variable declaration, listen, or activity invocation
 2180        var activityStatement = activityInvocation
 26181            .Then<StatementNode>(x => x);
 182
 183        // Declare deferred for loop, foreach, and flowchart parsers
 2184        var forStatement = Deferred<StatementNode>();
 2185        var foreachStatement = Deferred<StatementNode>();
 2186        var flowchartStatement = Deferred<StatementNode>();
 187
 2188        statement.Parser = variableDeclaration
 2189            .Or(listenStatement)
 2190            .Or(forStatement)
 2191            .Or(foreachStatement)
 2192            .Or(flowchartStatement)
 2193            .Or(activityStatement);
 194
 195        // Statement with optional semicolon
 47196        var statementWithSemicolon = statement.And(ZeroOrOne(semicolon)).Then(x => x.Item1);
 197
 198        // For loop statement: for (var i = 0 to 10 step 1) { body } or for (i = 0 to 10) statement
 199        // Must be defined after statementWithSemicolon
 2200        var rangeOperator = toKeyword.Or(throughKeyword);
 201
 202        // For body can be either a block or a single statement
 2203        var forBlockBody = Between(leftBrace, ZeroOrMany(statementWithSemicolon), rightBrace)
 6204            .Then(statements => (StatementNode)(statements.Count == 1
 6205                ? statements.First()
 6206                : new BlockNode { Statements = statements.ToList() }));
 2207        var forSingleStatementBody = statement;
 2208        var forBody = forBlockBody.Or(forSingleStatementBody);
 209
 210        // For header with optional var: (var i = start to/through end step stepValue)
 211        // or (i = start to/through end step stepValue)
 212        // Step clause is optional
 2213        var optionalVarKeyword = ZeroOrOne(varKeyword);
 6214        var optionalStepClause = ZeroOrOne(stepKeyword.And(expression).Then(x => x.Item2));
 215
 2216        var forHeader = Between(leftParen,
 2217            optionalVarKeyword
 2218                .And(identifier)
 2219                .And(equals)
 2220                .And(expression)
 2221                .And(rangeOperator)
 2222                .And(expression)
 2223                .And(optionalStepClause)
 4224                .Then(x => (
 4225                    HasVar: x.Item1 != null,
 4226                    VarName: x.Item2.ToString(),
 4227                    Start: x.Item4,
 4228                    RangeOp: x.Item5.ToString(),
 4229                    End: x.Item6,
 4230                    Step: x.Item7
 4231                )),
 2232            rightParen);
 233
 2234        var forStatementParser = forKeyword
 2235            .And(forHeader)
 2236            .And(forBody)
 2237            .Then<StatementNode>(result =>
 2238            {
 4239                var header = result.Item2;
 4240                var body = result.Item3;
 2241
 2242                // Default step to 1 if not specified
 4243                var stepExpr = header.Step ?? new LiteralNode { Value = 1 };
 2244
 4245                return new ForNode
 4246                {
 4247                    DeclaresVariable = header.HasVar,
 4248                    VariableName = header.VarName,
 4249                    Start = header.Start,
 4250                    End = header.End,
 4251                    Step = stepExpr,
 4252                    IsInclusive = header.RangeOp == "through",
 4253                    Body = body
 4254                };
 2255            });
 256
 2257        forStatement.Parser = forStatementParser;
 258
 259        // ForEach statement: foreach (var item in collection) { body } or foreach (item in collection) statement
 260        // Must be defined after statementWithSemicolon
 261        // ForEach body can be either a block or a single statement
 2262        var foreachBlockBody = Between(leftBrace, ZeroOrMany(statementWithSemicolon), rightBrace)
 2263            .Then(statements => (StatementNode)(statements.Count == 1
 2264                ? statements.First()
 2265                : new BlockNode { Statements = statements.ToList() }));
 2266        var foreachSingleStatementBody = statement;
 2267        var foreachBody = foreachBlockBody.Or(foreachSingleStatementBody);
 268
 269        // ForEach header with optional var: (var item in collection) or (item in collection)
 2270        var foreachOptionalVarKeyword = ZeroOrOne(varKeyword);
 271
 2272        var foreachHeader = Between(leftParen,
 2273            foreachOptionalVarKeyword
 2274                .And(identifier)
 2275                .And(inKeyword)
 2276                .And(expression)
 0277                .Then(x => (
 0278                    HasVar: x.Item1 != null,
 0279                    VarName: x.Item2.ToString(),
 0280                    Collection: x.Item4
 0281                )),
 2282            rightParen);
 283
 2284        var foreachStatementParser = foreachKeyword
 2285            .And(foreachHeader)
 2286            .And(foreachBody)
 2287            .Then<StatementNode>(result =>
 2288            {
 0289                var header = result.Item2;
 0290                var body = result.Item3;
 2291
 0292                return new ForEachNode
 0293                {
 0294                    DeclaresVariable = header.HasVar,
 0295                    VariableName = header.VarName,
 0296                    Collection = header.Collection,
 0297                    Body = body
 0298                };
 2299            });
 300
 2301        foreachStatement.Parser = foreachStatementParser;
 302
 303        // Flowchart statement: flowchart { [variables] [nodes] [connections] [entry] }
 304        // Node declaration: label: statement;
 305        // Entry declaration: entry label;
 306        // Connection declaration: source -> target; or source.Outcome -> target;
 307
 308        // Flowchart body element can be:
 309        // 1. Variable declaration
 310        // 2. Node declaration (label: statement)
 311        // 3. Entry declaration (entry label)
 312        // 4. Connection declaration (source -> target or source.Outcome -> target)
 313
 314        // Node declaration: label: activityInvocation; or label: { block }
 315        // Note: We use activityInvocation directly (not statement) to avoid circular dependency
 316        // since statement includes flowchart which would include node declarations
 2317        var nodeBlock = Between(leftBrace, ZeroOrMany(statementWithSemicolon), rightBrace)
 4318            .Then<StatementNode>(statements => statements.Count == 1
 4319                ? statements.First()
 4320                : new BlockNode { Statements = statements.ToList() });
 321
 6322        var nodeActivityStatement = activityInvocation.Then<StatementNode>(s => s);
 323
 2324        var nodeDeclaration = identifier
 2325            .And(colon)
 2326            .And(nodeBlock.Or(nodeActivityStatement))
 2327            .And(ZeroOrOne(semicolon))
 8328            .Then(x => new LabeledActivityNode
 8329            {
 8330                Label = x.Item1.ToString(),
 8331                Activity = x.Item3
 8332            });
 333
 334        // Entry declaration: entry label;
 2335        var entryDeclaration = entryKeyword
 2336            .And(identifier)
 2337            .And(ZeroOrOne(semicolon))
 6338            .Then(x => x.Item2.ToString());
 339
 340        // Connection declaration: source -> target; or source.Outcome -> target;
 341        // Source can be: identifier or identifier.identifier (with outcome)
 2342        var optionalOutcome = ZeroOrOne(dot.And(identifier).Then(x => x.Item2.ToString()));
 343
 2344        var connectionSource = identifier
 2345            .And(optionalOutcome)
 4346            .Then(x => (
 4347                SourceLabel: x.Item1.ToString(),
 4348                Outcome: x.Item2
 4349            ));
 350
 2351        var connectionTarget = identifier;
 352
 2353        var connectionDeclaration = connectionSource
 2354            .And(rightArrow)
 2355            .And(connectionTarget)
 2356            .And(ZeroOrOne(semicolon))
 4357            .Then(x => new ConnectionNode
 4358            {
 4359                Source = x.Item1.SourceLabel,
 4360                Outcome = x.Item1.Outcome,
 4361                Target = x.Item3.ToString()
 4362            });
 363
 364        // Flowchart body element type - try each parser in order
 2365        var flowchartBodyElement = variableDeclaration.Then<object>(v => v)
 4366            .Or(entryDeclaration.Then<object>(e => e))
 6367            .Or(nodeDeclaration.Then<object>(n => n))
 4368            .Or(connectionDeclaration.Then<object>(c => c));
 369
 2370        var flowchartBody = Between(leftBrace, ZeroOrMany(flowchartBodyElement), rightBrace);
 371
 2372        var flowchartStatementParser = flowchartKeyword
 2373            .And(flowchartBody)
 2374            .Then<StatementNode>(result =>
 2375            {
 6376                var bodyElements = result.Item2;
 2377
 6378                var variables = new List<VariableDeclarationNode>();
 6379                var nodes = new List<LabeledActivityNode>();
 6380                var connections = new List<ConnectionNode>();
 6381                string? entryPoint = null;
 2382
 36383                foreach (var element in bodyElements)
 2384                {
 12385                    if (element is VariableDeclarationNode varDecl)
 0386                        variables.Add(varDecl);
 12387                    else if (element is LabeledActivityNode node)
 6388                        nodes.Add(node);
 6389                    else if (element is ConnectionNode conn)
 2390                        connections.Add(conn);
 4391                    else if (element is string entry)
 4392                        entryPoint = entry;
 2393                }
 2394
 6395                return new FlowchartNode
 6396                {
 6397                    Variables = variables,
 6398                    Activities = nodes,
 6399                    Connections = connections,
 6400                    EntryPoint = entryPoint
 6401                };
 2402            });
 403
 2404        flowchartStatement.Parser = flowchartStatementParser;
 405
 406        // Use statement: use Namespace; or use expressions lang;
 2407        var namespaceUse = identifier
 2408            .And(ZeroOrMany(dot.And(identifier)))
 2409            .Then(x =>
 2410            {
 4411                var ns = x.Item1.ToString();
 24412                foreach (var part in x.Item2)
 2413                {
 8414                    ns += "." + part.Item2.ToString();
 2415                }
 4416                return new UseNode { Type = UseType.Namespace, Value = ns };
 2417            });
 418
 2419        var expressionUse = expressionsKeyword
 2420            .And(identifier)
 17421            .Then(x => new UseNode { Type = UseType.Expressions, Value = x.Item2.ToString() });
 422
 2423        var useStatement = useKeyword
 2424            .And(expressionUse.Or(namespaceUse))
 2425            .And(ZeroOrOne(semicolon))
 21426            .Then(x => x.Item2);
 427
 428        // Workflow metadata: name: value
 2429        var metadataEntry = identifier
 2430            .And(colon)
 2431            .And(expression)
 14432            .Then(x => (Name: x.Item1.ToString(), Value: EvaluateConstantExpressionStatic(x.Item3)));
 433
 14434        var commaSeparatedMetadata = metadataEntry.And(ZeroOrOne(comma)).Then(x => x.Item1);
 2435        var metadataList = ZeroOrMany(commaSeparatedMetadata);
 436
 437        // Workflow declaration: workflow Identifier [(metadata)] { [use statements] [statements] }
 2438        var workflowMetadata = Between(leftParen, metadataList, rightParen);
 439
 440        // Workflow body can contain use statements and regular statements
 2441        var workflowUseStatement = useStatement;
 442
 2443        var workflowBodyElement = Deferred<object>();
 2444        workflowBodyElement.Parser = workflowUseStatement
 2445            .Then<object>(u => u)
 36446            .Or(statementWithSemicolon.Then<object>(s => s));
 447
 2448        var workflowBody = Between(leftBrace, ZeroOrMany(workflowBodyElement), rightBrace);
 449
 2450        var workflowWithMetadata = workflowKeyword
 2451            .And(identifier)
 2452            .And(workflowMetadata)
 4453            .Then(x => (WorkflowId: x.Item2.ToString(), Metadata: x.Item3));
 454
 2455        var workflowWithoutMetadata = workflowKeyword
 2456            .And(identifier)
 22457            .Then(x => (WorkflowId: x.Item2.ToString(), Metadata: (IReadOnlyList<(string Name, object Value)>?)null));
 458
 2459        var workflowHeader = workflowWithMetadata.Or(workflowWithoutMetadata);
 460
 2461        var workflowDeclaration = workflowHeader
 2462            .And(workflowBody)
 2463            .Then(x =>
 2464            {
 22465                var header = x.Item1;
 22466                var bodyElements = x.Item2;
 2467
 22468                var metadataDict = new Dictionary<string, object>();
 22469                if (header.Metadata != null)
 2470                {
 28471                    foreach (var entry in header.Metadata)
 2472                    {
 12473                        metadataDict[entry.Name] = entry.Value;
 2474                    }
 2475                }
 2476
 2477                // Separate use statements from regular statements in body
 22478                var workflowUses = new List<UseNode>();
 22479                var statements = new List<StatementNode>();
 2480
 116481                foreach (var element in bodyElements)
 2482                {
 36483                    if (element is UseNode useNode)
 2484                        workflowUses.Add(useNode);
 34485                    else if (element is StatementNode stmt)
 34486                        statements.Add(stmt);
 2487                }
 2488
 22489                return new WorkflowNode
 22490                {
 22491                    Id = header.WorkflowId,
 22492                    Metadata = metadataDict,
 22493                    UseStatements = workflowUses,
 22494                    Body = statements
 22495                };
 2496            });
 497
 498        // Program with single workflow: [global use statements] [workflow declaration]
 2499        var programWithWorkflow = ZeroOrMany(useStatement)
 2500            .And(workflowDeclaration)
 2501            .Then(x =>
 2502            {
 39503                var globalUses = x.Item1.Select(u => (UseNode)u).ToList();
 22504                var workflow = x.Item2;
 2505
 22506                return new ProgramNode
 22507                {
 22508                    GlobalUseStatements = globalUses,
 22509                    Workflows = new List<WorkflowNode> { workflow }
 22510                };
 2511            });
 512
 513        // Fallback: raw statements without workflow keyword (backward compatibility)
 514        // Only match if there are actual statements (OneOrMany)
 2515        var programWithStatements = ZeroOrMany(useStatement)
 2516            .And(OneOrMany(statementWithSemicolon))
 2517            .Then(x =>
 2518            {
 2519                var globalUses = x.Item1.Select(u => (UseNode)u).ToList();
 2520                var statements = x.Item2.ToList();
 2521
 2522                return new ProgramNode
 2523                {
 2524                    GlobalUseStatements = globalUses,
 2525                    Workflows = new List<WorkflowNode>
 2526                    {
 2527                        new WorkflowNode
 2528                        {
 2529                            Id = "DefaultWorkflow",
 2530                            UseStatements = new List<UseNode>(),
 2531                            Body = statements
 2532                        }
 2533                    }
 2534                };
 2535            });
 536
 2537        var programParser = programWithWorkflow.Or(programWithStatements);
 538
 2539        ProgramParser = programParser;
 2540    }
 541
 542    /// <inheritdoc />
 543    public ProgramNode Parse(string source)
 544    {
 24545        if (!ProgramParser.TryParse(source, out var result, out var error))
 546        {
 0547            var errorMessage = error != null
 0548                ? $"{error.Message} at {error.Position}"
 0549                : "Unknown parse error";
 0550            throw new ParseException($"Failed to parse ElsaScript: {errorMessage}");
 551        }
 24552        return result;
 553    }
 554
 555    /// <summary>
 556    /// Static helper to evaluate constant expressions during parsing.
 557    /// </summary>
 558    private static object EvaluateConstantExpressionStatic(ExpressionNode exprNode)
 559    {
 12560        return exprNode switch
 12561        {
 12562            LiteralNode literal => literal.Value ?? string.Empty,
 0563            IdentifierNode identifier => identifier.Name,
 0564            _ => string.Empty
 12565        };
 566    }
 567}
 568
 569/// <summary>
 570/// Exception thrown when parsing fails.
 571/// </summary>
 572public class ParseException : Exception
 573{
 574    public ParseException(string message) : base(message)
 575    {
 576    }
 577}
 578
 579/// <summary>
 580/// Custom parser that captures raw text after => until the matching closing parenthesis.
 581/// Supports nested parentheses.
 582/// </summary>
 583internal sealed class RawExpressionParser : Parser<TextSpan>
 584{
 585    public override bool Parse(ParseContext context, ref ParseResult<TextSpan> result)
 586    {
 587        context.EnterParser(this);
 588
 589        var scanner = context.Scanner;
 590        var start = scanner.Cursor.Offset;
 591        var depth = 0;
 592
 593        while (!scanner.Cursor.Eof)
 594        {
 595            var ch = scanner.Cursor.Current;
 596
 597            if (ch == '(')
 598            {
 599                depth++;
 600                scanner.Cursor.Advance();
 601            }
 602            else if (ch == ')')
 603            {
 604                if (depth == 0)
 605                {
 606                    // This is the closing paren for the activity invocation
 607                    break;
 608                }
 609                depth--;
 610                scanner.Cursor.Advance();
 611            }
 612            else
 613            {
 614                scanner.Cursor.Advance();
 615            }
 616        }
 617
 618        var length = scanner.Cursor.Offset - start;
 619        if (length == 0)
 620        {
 621            context.ExitParser(this);
 622            return false;
 623        }
 624
 625        var text = new TextSpan(scanner.Buffer, start, length);
 626        result.Set(start, scanner.Cursor.Offset, text);
 627        context.ExitParser(this);
 628        return true;
 629    }
 630}