< 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: 390
Uncovered lines: 22
Coverable lines: 412
Total lines: 627
Line coverage: 94.6%
Branch coverage
66%
Covered branches: 28
Total branches: 42
Branch coverage: 66.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()78.12%323296%
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)>)[]));
 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>();
 68469                foreach (var entry in header.Metadata)
 2470                {
 12471                    metadataDict[entry.Name] = entry.Value;
 2472                }
 2473
 2474                // Separate use statements from regular statements in body
 22475                var workflowUses = new List<UseNode>();
 22476                var statements = new List<StatementNode>();
 2477
 116478                foreach (var element in bodyElements)
 2479                {
 36480                    if (element is UseNode useNode)
 2481                        workflowUses.Add(useNode);
 34482                    else if (element is StatementNode stmt)
 34483                        statements.Add(stmt);
 2484                }
 2485
 22486                return new WorkflowNode
 22487                {
 22488                    Id = header.WorkflowId,
 22489                    Metadata = metadataDict,
 22490                    UseStatements = workflowUses,
 22491                    Body = statements
 22492                };
 2493            });
 494
 495        // Program with single workflow: [global use statements] [workflow declaration]
 2496        var programWithWorkflow = ZeroOrMany(useStatement)
 2497            .And(workflowDeclaration)
 2498            .Then(x =>
 2499            {
 39500                var globalUses = x.Item1.Select(u => (UseNode)u).ToList();
 22501                var workflow = x.Item2;
 2502
 22503                return new ProgramNode
 22504                {
 22505                    GlobalUseStatements = globalUses,
 22506                    Workflows = new List<WorkflowNode> { workflow }
 22507                };
 2508            });
 509
 510        // Fallback: raw statements without workflow keyword (backward compatibility)
 511        // Only match if there are actual statements (OneOrMany)
 2512        var programWithStatements = ZeroOrMany(useStatement)
 2513            .And(OneOrMany(statementWithSemicolon))
 2514            .Then(x =>
 2515            {
 2516                var globalUses = x.Item1.Select(u => (UseNode)u).ToList();
 2517                var statements = x.Item2.ToList();
 2518
 2519                return new ProgramNode
 2520                {
 2521                    GlobalUseStatements = globalUses,
 2522                    Workflows = new List<WorkflowNode>
 2523                    {
 2524                        new WorkflowNode
 2525                        {
 2526                            Id = "DefaultWorkflow",
 2527                            UseStatements = new List<UseNode>(),
 2528                            Body = statements
 2529                        }
 2530                    }
 2531                };
 2532            });
 533
 2534        var programParser = programWithWorkflow.Or(programWithStatements);
 535
 2536        ProgramParser = programParser;
 2537    }
 538
 539    /// <inheritdoc />
 540    public ProgramNode Parse(string source)
 541    {
 24542        if (!ProgramParser.TryParse(source, out var result, out var error))
 543        {
 0544            var errorMessage = error != null
 0545                ? $"{error.Message} at {error.Position}"
 0546                : "Unknown parse error";
 0547            throw new ParseException($"Failed to parse ElsaScript: {errorMessage}");
 548        }
 24549        return result;
 550    }
 551
 552    /// <summary>
 553    /// Static helper to evaluate constant expressions during parsing.
 554    /// </summary>
 555    private static object EvaluateConstantExpressionStatic(ExpressionNode exprNode)
 556    {
 12557        return exprNode switch
 12558        {
 12559            LiteralNode literal => literal.Value ?? string.Empty,
 0560            IdentifierNode identifier => identifier.Name,
 0561            _ => string.Empty
 12562        };
 563    }
 564}
 565
 566/// <summary>
 567/// Exception thrown when parsing fails.
 568/// </summary>
 569public class ParseException : Exception
 570{
 571    public ParseException(string message) : base(message)
 572    {
 573    }
 574}
 575
 576/// <summary>
 577/// Custom parser that captures raw text after => until the matching closing parenthesis.
 578/// Supports nested parentheses.
 579/// </summary>
 580internal sealed class RawExpressionParser : Parser<TextSpan>
 581{
 582    public override bool Parse(ParseContext context, ref ParseResult<TextSpan> result)
 583    {
 584        context.EnterParser(this);
 585
 586        var scanner = context.Scanner;
 587        var start = scanner.Cursor.Offset;
 588        var depth = 0;
 589
 590        while (!scanner.Cursor.Eof)
 591        {
 592            var ch = scanner.Cursor.Current;
 593
 594            if (ch == '(')
 595            {
 596                depth++;
 597                scanner.Cursor.Advance();
 598            }
 599            else if (ch == ')')
 600            {
 601                if (depth == 0)
 602                {
 603                    // This is the closing paren for the activity invocation
 604                    break;
 605                }
 606                depth--;
 607                scanner.Cursor.Advance();
 608            }
 609            else
 610            {
 611                scanner.Cursor.Advance();
 612            }
 613        }
 614
 615        var length = scanner.Cursor.Offset - start;
 616        if (length == 0)
 617        {
 618            context.ExitParser(this);
 619            return false;
 620        }
 621
 622        var text = new TextSpan(scanner.Buffer, start, length);
 623        result.Set(start, scanner.Cursor.Offset, text);
 624        context.ExitParser(this);
 625        return true;
 626    }
 627}