Skip to content
b3b00 edited this page Dec 24, 2024 · 2 revisions

Fluent parser builder

All following example will assume the following fluent lexer definition, and output type


public class ExprOutput {
    public string Name {get; set;}
}

public enum ExprToken {
    ID,
    INT    
}

public class FluentExprLexer
{
    public void ExprLexerTest()
    {

        var lexerBuilder = FluentLexerBuilder<ExprToken>.NewBuilder();

        BuildResult<ILexer<MyToken>> lexerResult = lexerBuilder
            .IgnoreEol(true)   // ignore end of lines
            .IgnoreWhiteSpace(true) // ignore white spaces
            .AlphaId(MyToken.ID)
            .Int(MyToken.INT)
            .Build("en");
        if (lexerResult.IsOk)
        {
            ILexer<MyToken> lexer = lexerResult.Result;
            lexer.Tokenize(@"
    identifier 42
# comment
$ money content € 
");
        }
    }

}

As the purpose of a fluent API is to allow fluent use, we will define every fluent method in detail.

The lexer fluent builder for a parser taking MyFluenttokens as input and returning a MyOutputis declared as follow :

var parserBuilder = FluentParserBuilder<MyToken,MyOutput>.NewBuilder(object parserInstance, string rootRule, string lang);

with parameters :

  • parserInstance ...
  • rootRule : the starting non terminal;
  • lang : language used for error messages.

Parser configuration

First we can configure the parser with some fluent methods :

  • UseMemoization(bool use = true) : if use is true then use the Memoization optimization (seed opt-in optimizations)
  • UseBroadenTokenWindow(bool use = true) : if use is true then use the broaden token window optimization (seed opt-in optimizations)
  • UseAutoCloseIndentations(bool use = true) : if use and parser is indentation aware then automatically close opened indents.
  • WithLexerbuilder(IFluentLexerBuilder<IN> lexerBuilder) : use the lexer built with FluentLexerBuilder. Note that the FluentLexerBuilder.Build()method must not have been called !

building the parser

To get the final `IParser<IN,OUT>` object you simply call the `BuildParser()`method

```csharp
var parserBuilder = FluentParserBuilder<ExprToken,int>.NewBuilder(new ExprParser(),"root","en");
// .... token definitions
BuildResult<IParser<ExprToken,int>> parserResult = lexerBuilder.BuildParser();
// check if parserResult is OK
if (parserResult.IsOk()) {
    IParser<ExprToken,int> parser = parserResult.Result;
}


### production rules

Production rules are defined with the `Production(string ruleString , Func<object[],OUT> callback)` (where OUT is the parser output type) : 
 - parserInstance : this parameter is only mandatory when using expression parser generator. TODO ....
 - ruleString : the rule definition as in 
 - a callback called to visit the syntax tree.

The visitor callback accepts a list of object as parameters. It's your reponsibility to cast the parameters according to [parser typing](defining-your-parser#parserbuilder-and-parser-generic-input-and-output-type-convention)

```csharp


public class ExprParser {
    // ....
}

// defines a rule that simply matches a identifier
var parserBuilder = FluentParserBuilder<ExprToken,ExprOutput>.NewBuilder(new ExprParser(), "root", "en");
parserBuilder.Production("root : ID")

expressions

  • Right(IN operation, int precedence, Func<object[], OUT> visitor) : an infix right associative operation (as an enum value) with precedence precedence and visitor callback
  • Right(string operation, int precedence, Func<object[], OUT> visitor) : an infix right associative operation (as a enum string value or explicit token) with precedence precedence and visitor callback
  • Left(IN operation, int precedence, Func<object[], OUT> visitor): : an infix left associative operation (as an enum value) with precedence precedence and visitor callback
  • Left(string operation, int precedence, Func<object[], OUT> visitor) : an infix left associative operation (as a enum string value or explicit token) with precedence precedence and visitor callback
  • Prefix(IN operation, int precedence, Func<object[], OUT> visitor) : a prefix operation (as an enum value) with precedence precedence and visitor callback
  • Prefix(string operation, int precedence, Func<object[], OUT> visitor) : a prefix operation (as a enum string value or explicit token) with precedence precedence and visitor callback
  • Postfix(IN operation, int precedence, Func<object[], OUT> visitor) : a postfix operation (as an enum value) with precedence precedence and visitor callback
  • Postfix(string operation, int precedence, Func<object[], OUT> visitor) : a postfix operation (as a enum string value or explicit token) with precedence precedence and visitor callback
  • Operand(string ruleString, Func<object[], OUT>) and Operand is the same as a Production() and is tagged as an operand so that it can be used as .... an operand for expression parsing.

Visitor callbacks. As for production rule you are responsible to correctly cast arguments :

  • infix operations : they are lambdas taking 3 parameters and returning an OUT value
    • first parameter is the left operand value
    • second parameter is the token of the operator
    • third is the right operand value
  • prefix : a lambda with 2 parameter taking 2 parameters and returning an OUT value
    • first parameter is the token of the operator
    • second parameter is the value of the operand
  • postfix : a lambda with 2 parameter taking 2 parameters and returning an OUT value
    • first parameter is the value of the operand
    • second parameter is the token of the operator

The type of the parser instance passed as the first argument of FluentParserBuilder<IN,OUT>.NewBuilder()will be used to generate the root expression rule, just as described in Expressions parsing

var parserBuilder = FluentParserBuilder<MyToken,int>.NewBuilder(new ExprParser(), "root", "en");
ParseResult<IParser<ExprToken,int> parserResult = parserBuilder
.Production("root : ExprParser_expressions",)
.Left("'-'",10(object[] args) => (int)args[0] - (int)args[2])
.Right("'+'",10 (object[] args) => (int)args[0] + (int)args[2])
.Left("'/'",50(object[] args) => (int)args[0] / (int)args[2])
.Right("'*'",50 (object[] args) => (int)args[0] * (int)args[2])
.Prefix("'-'",100 (Tobject[] args) => -(int)args[1])
.Operand("value : INT",(object[] args) => ((Token<ExprToken>)args[0]).IntValue)
.BuildParser();
if (parserResult.IsOk) {
    IParser<ExprToken, int> parser = parserResult.Result;
    var result = parser.Parse("-1 + 2 * 3");
    // result.IsOk should be true
    // result.Result should be equal to -1 + 2 * 3 = -1 + 6 = 5
}

node names