UniversalExpressionParser

Summary

UniversalExpressionParser is a library for parsing expressions like the one demonstrated below into expression items for functions, literals, operators, etc.

 1var z = x1*y1+x2*y2;
 2
 3var matrixMultiplicationResult = [[x1_1, x1_2, x1_3], [x2_1, x2_2, x2_3]]*[[y1_1, x1_2], [y2_1, x2_2], [y3_1, x3_2]];
 4
 5println(matrixMultiplicationResult);
 6
 7[NotNull] [PublicName("Calculate")]
 8public F1(x, y) : int =>
 9{
10
11    /*This demos multiline
12    comments that can be placed anywhere*/
13    ++y /*another multiline comment*/;
14    return 1.3EXP-2.7+ ++x+y*z; // Line comments.
15}
16
17public abstract class ::metadata{description: "Demo prefix"} ::types[T1, T2, T3] Animal
18   where T1: IType1 where T2: T1, IType2 whereend
19   where T3: IType3 whereend
20{
21    public abstract Move() : void;
22}
23
24public class Dog : (Animal)
25{
26    public override Move() : void => println("Jump");
27}
  • Below is the demo code used to parse this expression:

 1using TextParser;
 2using UniversalExpressionParser.DemoExpressionLanguageProviders;
 3
 4namespace UniversalExpressionParser.Tests.Demos
 5{
 6    public class SummaryDemo
 7    {
 8        private readonly IExpressionParser _expressionParser;
 9        private readonly IExpressionLanguageProvider _nonVerboseLanguageProvider = new NonVerboseCaseSensitiveExpressionLanguageProvider();
10        private readonly IExpressionLanguageProvider _verboseLanguageProvider = new VerboseCaseInsensitiveExpressionLanguageProvider();
11        private readonly IParseExpressionOptions _parseExpressionOptions = new ParseExpressionOptions();
12
13        public SummaryDemo()
14        {
15            IExpressionLanguageProviderCache expressionLanguageProviderCache =
16                new ExpressionLanguageProviderCache(new DefaultExpressionLanguageProviderValidator());
17
18            _expressionParser = new ExpressionParser(new TextSymbolsParserFactory(), expressionLanguageProviderCache);
19
20            expressionLanguageProviderCache.RegisterExpressionLanguageProvider(_nonVerboseLanguageProvider);
21            expressionLanguageProviderCache.RegisterExpressionLanguageProvider(_verboseLanguageProvider);
22        }
23
24        public IParseExpressionResult ParseNonVerboseExpression(string expression)
25        {
26            /*
27            The same instance _expressionParser of UniversalExpressionParser.IExpressionParser can be used
28            to parse multiple expressions using different instances of UniversalExpressionParser.IExpressionLanguageProvider
29            Example:
30
31            var parsedExpression1 = _expressionParser.ParseExpression(_nonVerboseLanguageProvider.LanguageName, "var x=2*y; f1() {++x;} f1();");
32            var parsedExpression2 = _expressionParser.ParseExpression(_verboseLanguageProvider.LanguageName, "var x=2*y; f1() BEGIN ++x;END f1();");
33            */
34            return _expressionParser.ParseExpression(_nonVerboseLanguageProvider.LanguageName, expression, _parseExpressionOptions);
35        }
36    }
37}
  • Expression is parsed to an instance of UniversalExpressionParser.IParseExpressionResult by calling the method IParseExpressionResult ParseExpression(string expressionLanguageProviderName, string expressionText, IParseExpressionOptions parseExpressionOptions) in UniversalExpressionParser.IExpressionParser.

  • The interface UniversalExpressionParser.IParseExpressionResult (i.e., result of parsing the expression) has a property UniversalExpressionParser.ExpressionItems.IRooxExpressionItem RootExpressionItem { get; } that stores the root expression item of a tree structure of parsed expression items.

  • The code that evaluates the parsed expression can use the following properties in UniversalExpressionParser.ExpressionItems.IRootExpressionItem to iterate through all parsed expression items:

    • IEnumerable<IExpressionItemBase> AllItems { get; }

    • IReadOnlyList<IExpressionItemBase> Prefixes { get; }

    • IReadOnlyList<IKeywordExpressionItem> AppliedKeywords { get; }

    • IReadOnlyList<IExpressionItemBase> RegularItems { get; }

    • IReadOnlyList<IExpressionItemBase> Children { get; }

    • IReadOnlyList<IExpressionItemBase> Postfixes { get; }

    • IReadOnlyList<IExpressionItemBase> Prefixes { get; }

  • All expressions are parsed either to expressions items of type UniversalExpressionParser.ExpressionItems.IExpressionItemBase or one of its subclasses for simple expressions or to expressions items of type UniversalExpressionParser.ExpressionItems.IComplexExpressionItem (which is a sub-interface of UniversalExpressionParser.ExpressionItems.IExpressionItemBase) or one of its subclasses for expression items that consists of other expression items.

  • Some examples simple expression items are: UniversalExpressionParser.ExpressionItems.ICommaExpressionItem for commas, UniversalExpressionParser.ExpressionItems.IOpeningBraceExpressionItem and UniversalExpressionParser.ExpressionItems.IClosingBraceExpressionItem for opening and closing braces “(” and “)”

  • Some complex expression items are: UniversalExpressionParser.ExpressionItems.IBracesExpressionItem for functions like “f1 (x1, x2)”, UniversalExpressionParser.ExpressionItems.IOperatorExpressionItem for operators like the binary operator with operands “f1(x)” and “y” in “f1(x) + y”.

  • All expressions are currently parsed to one of the following expression items (or intances of other sub-interfaces of these interfaces) in namespaces UniversalExpressionParser.ExpressionItems and UniversalExpressionParser.ExpressionItems.Custom: ILiteralExpressionItem, ILiteralNameExpressionItem, IConstantTextExpressionItem, IConstantTextValueExpressionItem, INumericExpressionItem, INumericExpressionValueItem, IBracesExpressionItem, IOpeningBraceExpressionItem, IClosingBraceExpressionItem, ICommaExpressionItem, ICodeBlockExpressionItem, ICustomExpressionItem, IKeywordExpressionItem, ICodeBlockStartMarkerExpressionItem, ICodeBlockEndMarkerExpressionItem, ISeparatorCharacterExpressionItem, IOperatorExpressionItem, IOperatorInfoExpressionItem, IKeywordExpressionItem, UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItem, IRootExpressionItem, IComplexExpressionItem, ITextExpressionItem, IExpressionItemBase. The state of this expression items can be analyzed when evaluating the parsed expression.

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

  • The format of valid expressions is defined by properties and methods in interface UniversalExpressionParser.IExpressionLanguageProvider. The expression language name UniversalExpressionParser.IExpressionLanguageProvider.LanguageName of some instance UniversalExpressionParser.IExpressionLanguageProvider is passed as a parameter to method ParseExpression(…) in UniversalExpressionParser.IExpressionParser, as demonstrated in example above. Most of the properties and methods of this interface are demonstrated in examples in sections below.

  • The default abstract implementation of this interface in this package is UniversalExpressionParser.ExpressionLanguageProviderBase. In most cases, this abstract class can be extended and abstract methods and properties can be implemented, rather than providing a brand new implementation of UniversalExpressionParser.IExpressionLanguageProvider.

  • The test project UniversalExpressionParser.Tests in git repository has a number of tests for testing successful parsing, as well as tests for testing expressions that result in errors (see section Error Reporting below). These tests generate random expressions as well as generate randomly configured instances of UniversalExpressionParser.IExpressionLanguageProvider to validate parsing of thousands of all possible languages and expressions (see the test classes UniversalExpressionParser.Tests.SuccessfulParseTests.ExpressionParserSuccessfulTests and UniversalExpressionParser.Tests.ExpressionParseErrorTests.ExpressionParseErrorTests).

  • The demo expressions and tests used to parse the demo expressions in this documentation are in folder Demos in test project UniversalExpressionParser.Tests. This documentation uses implementations of UniversalExpressionParser.IExpressionLanguageProvider in project UniversalExpressionParser.DemoExpressionLanguageProviders in git repository.

  • The parsed expressions in this documentation (i.e., instances of UniversalExpressionParser.ExpressionItems.IParseExpressionResult) are visualized into xml texts, that contain values of most properties of the parsed expression. However, to make the files shorter, the visualized xml files do not include all the property values.

Literals

  • Literals are names that can be used either alone (say in operators) or can be part of more complex expressions. For example literals can precede opening square or round braces (e.g., f1 in f1(x), or m1 in m1[1, 2]), or code blocks (e.g., Dog in expression public class Dog {}).

  • Literals are parsed into expression items of type UniversalExpressionParser.ExpressionItems.ILiteralExpressionItem objects.

  • If literal precedes round or square braces, it will be stored in value of property ILiteralExpressionItem Name { get; } of UniversalExpressionParser.ExpressionItems.IBracesExpressionItem.

  • If literal precedes a code block staring marker (e.g., Dog in expression public class Dog {}), then the code block is added to the list UniversalExpressionParser.ExpressionItems.IComplexExpressionItem.Postfixes in expression item for the literal (see section Postfixes for more details on postfixes).

  • Literals are texts that cannot have space and can only contain characters validated by method UniversalExpressionParser.IExpressionLanguageProvider.IsValidLiteralCharacter(char character, int positionInLiteral, ITextSymbolsParserState textSymbolsParserState) in interface UniversalExpressionParser.IExpressionLanguageProvider. In other words, a literal can contain any character (including numeric or operator characters, a dot ‘.’, ‘_’, etc.), that is considered a valid literal character by method IExpressionLanguageProvider.IsValidLiteralCharacter(…).

Examples of literals

 1// In example below _x, f$, x1, x2, m1, and x3, Dog, Color, string, println, and Dog.Color are literals.
 2var _x = f$(x1, x2) + m1[1, x3];
 3
 4public class Dog
 5{
 6    public Color : string => "brown";
 7}
 8
 9// println is a literal and it is part of "println(Dog.Color)" braces expression item. In this example,
10// println will be parsed to an expression item UniversalExpressionParser.ExpressionItems.INamedComplexExpressionItem
11// and will be the value of property NamedExpressionItem in UniversalExpressionParser.ExpressionItems.IBracesExpressionItem
12println(Dog.Color);

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Functions and Braces

  • Functions are round or square braces preceded with a literal (e.g., F1(x), F1(x) {}, m1[i,j], m1[i,j]{}). Functions are parsed to expression items of type UniversalExpressionParser.ExpressionItems.IBracesExpressionItem with the value of property ILiteralExpressionItem NameLiteral { get; } equal to a literal that precedes the braces.

  • Braces are a pair of round or square braces ((e.g., (x), (x) {}, [i,j], [i,j]{})). Braces are parsed to expression items of type UniversalExpressionParser.ExpressionItems.IBracesExpressionItem with the value of property ILiteralExpressionItem NameLiteral { get; } equal to null.

Examples of functions

 1// The statements below do not make much sense.
 2// They just demonstrate different ways the square and round braces can be used
 3// in expressions.
 4var x = x1+f1(x2, x3+x4*5+x[1])+
 5         matrix1[[y1+3, f1(x4)], x3, f2(x3, m2[x+5])];
 6
 7f1(x, y) => x+y;
 8
 9f2[x, y]
10{
11   // Normally matrixes do not have bodies like functions doo. This is just to demo that
12   // the parser allows this.
13}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Examples of braces

1// The statements below do not make much sense.
2// They just demonstrate different ways the square and round braces can be used
3// in expressions.
4var x = ((x1, x2, x3), [x4, x5+1, x6], y);
5x += (x2, x4) + 2*[x3, x4];

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Operators

  • Operators that the valid expressions can use are defined in property System.Collections.Generic.IReadOnlyList<UniversalExpressionParser.IOperatorInfo> Operators { get; } in interface UniversalExpressionParser.IExpressionLanguageProvider (an instance of this interface is passed to the parser).

  • The interface UniversalExpressionParser.IOperatorInfo has properties for operator name (i.e., a collection of texts that operator consists of, such as [“IS”, “NOT”, “NUL”] or [“+=”]), priority, unique Id, operator type (i.e., binary, unary prefix or unary postfix).

  • Two different operators can have similar names, as long as they have different operator. For example “++” can be used both as unary prefix as well as unary postfix operator.

Example of defining operators in an implementation of UniversalExpressionParser.IExpressionLanguageProvider

 1public class TestExpressionLanguageProviderBase : ExpressionLanguageProviderBase
 2{
 3    //...
 4    // Some other method and property implementations here
 5    // ...
 6    public override IReadOnlyList<IOperatorInfo> Operators { get; } = new IOperatorInfo[]
 7    {
 8           // The third parameter (e.g., 0) is the priority.
 9           new OperatorInfo(1, new [] {"!"}, OperatorType.PrefixUnaryOperator, 0),
10           new OperatorInfo(2, new [] {"IS", "NOT", "NULL"}, OperatorType.PostfixUnaryOperator, 0),
11
12           new OperatorInfo(3, new [] {"*"}, OperatorType.BinaryOperator, 10),
13           new OperatorInfo(4, new [] {"/"}, OperatorType.BinaryOperator, 10),
14
15           new OperatorInfo(5, new [] {"+"}, OperatorType.BinaryOperator, 30),
16       new OperatorInfo(6, new [] {"-"}, OperatorType.BinaryOperator, 30),
17    }
18}
  • Operator expression (e.g., “a * b + c * d”) is parsed to an expression item of type UniversalExpressionParser.ExpressionItems.IOperatorExpressionItem (a subclass of UniversalExpressionParser.ExpressionItems.IComplexExpressionItem).

Click here to see the definition of UniversalExpressionParser.ExpressionItems.IOperatorExpressionItem

For example the expression “a * b + c * d”, will be parsed to an expression logically similar to “(+(a, b), +(x,d))”. This is so since the binary operator “+” has lower priority (the value of **IOperatorInfo.Priority* is larger), than the binary operator “*”.

In other words this expression will be parsed to a binary operator expression item for “+” (i.e., an instance of IOperatorExpressionItem) with Operand1 and Operand2 also being binary operator expression items of type UniversalExpressionParser.ExpressionItems.IOperatorExpressionItem for expression items “a * b” and “c * d”.

Example of considering priorities when parsing operators

1// The binary operator + has priority 30 and * has priority 20. Therefore,
2// in expression below,  * is applied first and + is applied next.
3// The following expression is parsed to an expression equivalent to
4// "=(var y, +(x1, *(f1(x2, +(x3, 1)), x4)))"
5var y = x1 + f1(x2,x3+1)*x4;

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Example of using braces to change order of application of operators

1// Without the braces, the expression below would be equivalent to x1+(x2*x3)-x4.
2var y1 = [x1+x2]*(x3-x4);

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Example of operators with multiple parts in operator names

1// The expression below is similar to
2// z = !((x1 IS NOT NULL) && (x2 IS NULL);
3z = !(x1 IS NOT NULL && x2 IS NULL);

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Example of two operators (e.g., postfix operators, then a binary operator) used next to each other without spaces in between

1// The spaces between two ++ operators, and + was omitted intentionally to show that the parser will parse the expression
2// correctly even without the space.
3// The expression below is similar to  println(((x1++)++)+x2). To avoid confusion, in some cases it is better to use braces.
4println(x1+++++x2)

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Example of unary prefix operator used to implement “return” statement

 1// return has priority int.MaxValue which is greater then any other operator priority, therefore
 2// the expression below is equivalent to "return (x+(2.5*x))";
 3return x+2.5*y;
 4
 5// another example within function body
 6f1(x:int, y:int) : bool
 7{
 8   // return has priority int.MaxValue which is greater then any other operator priority, therefore
 9   // the expression below is equivalent to "return (x+(2.5*x))";
10   return f(x)+y > 10;
11}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Numeric Values

  • Universal Expression Parser parses expression items that have numeric format to expression items of type UniversalExpressionParser.ExpressionItems.INumericExpressionItem. The format of expression items that will be parsed to UniversalExpressionParser.ExpressionItems.INumericExpressionItem is determined by property IReadOnlyList<NumericTypeDescriptor> NumericTypeDescriptors { get; } in interface UniversalExpressionParser.IExpressionLanguageProvider, an instance of which is passed to the parser.

Click here to see the definition of UniversalExpressionParser.NumericTypeDescriptor

Click here to see the definition of UniversalExpressionParser.ExpressionItems.INumericExpressionItem

  • The parser scans the regular expressions in list in property IReadOnlyList<string> RegularExpressions { get; } in type NumericTypeDescriptor for each provided instance of UniversalExpressionParser.NumericTypeDescriptor to try to parse the expression to numeric expression item of type UniversalExpressionParser.ExpressionItems.INumericExpressionItem.

  • The abstract class UniversalExpressionParser.ExpressionLanguageProviderBase that can be used as a base class for implementations of UniversalExpressionParser.IExpressionLanguageProvider in most cases, implements the property NumericTypeDescriptors as a virtual property. The implementation of property NumericTypeDescriptors in UniversalExpressionParser.ExpressionLanguageProviderBase is demonstrated below, and it can be overridden to provide different format for numeric values:

Note

The regular expressions used in implementation of property NumericTypeDescriptors should always start with ‘^’ and should never end with ‘$’.

 1public virtual IReadOnlyList<NumericTypeDescriptor> NumericTypeDescriptors { get; } = new List<NumericTypeDescriptor>
 2{
 3    new NumericTypeDescriptor(KnownNumericTypeDescriptorIds.ExponentFormatValueId,
 4    new[] { @"^(\d+\.\d+|\d+\.|\.\d+|\d+)(EXP|exp|E|e)[+|-]?(\d+\.\d+|\d+\.|\.\d+|\d+)"}),
 5
 6    new NumericTypeDescriptor(KnownNumericTypeDescriptorIds.FloatingPointValueId,
 7    new[] { @"^(\d+\.\d+|\d+\.|\.\d+)"}),
 8
 9    new NumericTypeDescriptor(KnownNumericTypeDescriptorIds.IntegerValueId, new[] { @"^\d+" })
10}
  • The first regular expression that matches the expression, is stored in properties SucceededNumericTypeDescriptor of type UniversalExpressionParser.NumericTypeDescriptor and IndexOfSucceededRegularExpression in parsed expression item of type UniversalExpressionParser.ExpressionItems.INumericExpressionItem.

  • The numeric value is stored as text in property INameExpressionItem Value in text format. Therefore, there is no limit on numeric value digits.

  • The expression evaluator that uses the Universal Expression Parser can convert the textual value in property Value of type INameExpressionItem in UniversalExpressionParser.ExpressionItems.INumericExpressionItem to a value of numeric type (int, long, double, etc.).

  • Examples of numeric value expression items are demonstrated below:

1// By default exponent notation can be used.
2println(-0.5e-3+.2exp3.4+3.E2.7+2.1EXP.3);
3println(.5e15*x);
4
5// Numeric values can have no limitations on the number of digits. The value is stored as text in
6// UniversalExpressionParser.ExpressionItems.INumericExpressionItem.
7// The text can be validated farther and converted to numeric values by the expression evaluator that
8// uses the parser.
9var x = 2.3*x+123456789123456789123456789123456789;

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Texts

The interface UniversalExpressionParser.IExpressionLanguageProvider has a property IReadOnlyList&lt;char&gt; ConstantTextStartEndMarkerCharacters { get; } that are used by Universal Expression Parser to parse expression items that start or end with some character in ConstantTextStartEndMarkerCharacters to parse the expression item to UniversalExpressionParser.ExpressionItems.IConstantTextExpressionItem.

  • The default implementation UniversalExpressionParser.ExpressionLanguageProviderBase of UniversalExpressionParser.IExpressionLanguageProvider has the following characters in property UniversalExpressionParser.IExpressionLanguageProvider.ConstantTextStartEndMarkerCharacters and the examples below will use these text marker characters.

  • If an expression starts with some character listed in property UniversalExpressionParser.IExpressionLanguageProvider.ConstantTextStartEndMarkerCharacters, then the text expression should end with the same character. In other words, if text starts with character it should end with . Similarly, if text starts with character it should end with .

  • The text in expression parsed to UniversalExpressionParser.ExpressionItems.IConstantTextExpressionItem can contain any of the text marker characters in UniversalExpressionParser.IExpressionLanguageProvider.ConstantTextStartEndMarkerCharacters. if the text contains a text marker character that marks the start of the text expression, it should be typed twice as displayed in examples below:

  • Texts can span multiple lines (see the example below).

 1// Example of texts using text markers ',", and ` in IExpressionLanguageProvider.ConstantTextStartEndMarkerCharacters property.
 2// The text marker that starts the specific text expression can be used in text as well when it is
 3// typed twice (e.g., '', "", ``, etc).
 4
 5// Example of using all text markers together in operators
 6x = "We can use this text expression marker "" if we type it twice. However, other text marker chars do not need to be typed twice such as ' and `."
 7    +'We can use this text expression marker '' if we type it twice. However, other text marker chars do not need to be typed twice such as " and `.'
 8    +`We can use this text expression marker `` if we type it twice. However, other text marker chars do not need to be typed twice such as " and '.`;
 9
10println("This is a text that spans
11 multiple
12 lines.
13" + x + ' Some other text.');

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Code Separators and Code Blocks

  • If the value of property char ExpressionSeparatorCharacter { get; } in UniversalExpressionParser.IExpressionLanguageProvider is not equal to character ‘0’, multiple expressions can be used in a single expression.

For example if the value of property ExpressionSeparatorCharacter is ‘;’ the expression “var x=f1(y);println(x)” will be parsed to a list of two expression items for “x=f1(y)” and “println(x)”. Otherwise, the parser will report an error for this expression (see section on Error Reporting for more details on errors).

  • If both values of properties char CodeBlockStartMarker { get; } and string CodeBlockEndMarker { get; } in UniversalExpressionParser.IExpressionLanguageProvider are not equal to character ‘0’, code block expressions can be used to combine multiple expressions into code block expression items of type UniversalExpressionParser.ExpressionItems.ICodeBlockExpressionItem.

  • For example if the values of properties CodeBlockStartMarker and CodeBlockEndMarker are ‘{’ and ‘}’, the expression below will be parsed to two code block expressions of type UniversalExpressionParser.ExpressionItems.ICodeBlockExpressionItem. Otherwise, the parser will report errors.

1{
2    var x = y^2;
3    println(x);
4}
5
6{
7    fl(x1, x2);
8    println(x) // No need for ';' after the last expression
9}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

More complex examples of code blocks and code separators

 1var y = x1 + 2.5 * x2;
 2
 3f1()
 4{
 5    // Code block used as function body (see examples in Postfixes folder)
 6}
 7
 8var z = e ^ 2.3;
 9{
10    var x = 5 * y;
11    println("x=" + x);
12
13    {
14        var y1 = 10 * x;
15        println(getExp(y1));
16    }
17
18    {
19        var y2 = 20 * x;
20        println(getExp(y2));
21    }
22
23    getExp(x) : double
24    {
25        // another code block used as function body (see examples in Postfixes folder)
26        return e^x;
27    }
28}
29
30f2()
31{
32    // Another code block used as function body (see examples in Postfixes folder)
33}
34
35{
36    // Another code block
37}
38
39public class Dog
40{
41    public Bark()
42    {
43        // Note, code separator ';' is not necessary, if the expression is followed by code block end marker '}'.
44        println("bark")
45    }
46}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Keywords

  • Keywords are special names (e.g., var, public, class, where) that can be specified in property IReadOnlyList&lt;ILanguageKeywordInfo&gt; Keywords { get; } in interface UniversalExpressionParser.IExpressionLanguageProvider, as shown in example below.

Note

Keywords are supported only if the value of property SupportsKeywords in UniversalExpressionParser.IExpressionLanguageProvider is true.

 1public class DemoExpressionLanguageProvider : IExpressionLanguageProvider
 2{
 3    ...
 4    public override IReadOnlyList<ILanguageKeywordInfo> Keywords { get; } = new []
 5    {
 6        new UniversalExpressionParser.UniversalExpressionParser(1, "where"),
 7        new UniversalExpressionParser.UniversalExpressionParser(2, "var"),
 8        ...
 9    };
10}
  • Keywords are parsed to expression items of type UniversalExpressionParser.ExpressionItems.IKeywordExpressionItem.

  • Keywords have the following two applications.

  1. One or more keywords can be placed in front of any literal (e.g., variable name), round or square braces expression, function or matrix expression, a code block. In this type of usage of keywords the parser parses the keywords and adds the list of parsed keyword expression items (i.e., list of UniversalExpressionParser.ExpressionItems.IKeywordExpressionItem objects) to list in property IReadOnlyList&lt;IKeywordExpressionItem&gt; AppliedKeywords { get; } in UniversalExpressionParser.ExpressionItems.IComplexExpressionItem for the expression item that follows the list of keywords.

  2. Custom expression parser evaluates the list of parsed keywords to determine if the expression that follows the keywords should be parsed to a custom expression item. See section Custom Expression Item Parsers for more details on custom expression parsers.

Examples of keywords

 1// Keywords "public" and "class" will be added to list in property "AppliedKeywords" in class
 2// "UniversalExpressionParser.ExpressionItems.Custom.IComplexExpressionItem" for the parsed expression "Dog".
 3public class Dog;
 4
 5// Keywords "public" and "static will be added to list in property "AppliedKeywords" in class
 6// "UniversalExpressionParser.ExpressionItems.Custom.IComplexExpressionItem" for the parsed expression "F1()".
 7public static F1();
 8
 9// Keywords "public" and "static" will be added to list in property "AppliedKeywords" in class
10// "UniversalExpressionParser.ExpressionItems.Custom.IComplexExpressionItem" for the parsed expression "F1()".
11public static F1() {return 1; }
12
13// Keyword "::codeMarker" will be added to list in property "AppliedKeywords" in class
14// "UniversalExpressionParser.ExpressionItems.Custom.IComplexExpressionItem" for the parsed expression "(x1, x2)".
15::codeMarker (x1, x2);
16
17// Keyword "::codeMarker" will be added to list in property "AppliedKeywords" in class
18// "UniversalExpressionParser.ExpressionItems.Custom.IComplexExpressionItem" for the parsed expression "m1[2, x1]".
19::codeMarker m1[2, x1];
20
21// Keyword "::codeMarker" will be added to list in property "AppliedKeywords" in class
22// "UniversalExpressionParser.ExpressionItems.Custom.IComplexExpressionItem" for the parsed expression "[x1, x2]".
23::codeMarker[x1, x2];
24
25// Keyword "static" will be added to list in property "AppliedKeywords" in class
26// "UniversalExpressionParser.ExpressionItems.Custom.IComplexExpressionItem" for the code block parsed expression "{}".
27static
28{
29    var x;
30}
31
32// Keyword "::pragma" will be used by custom expression parser "UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.PragmaCustomExpressionItemParser" to
33// parse expressions "::pragma x2" and "::pragma x3" to custom expression items of type
34// "UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.PragmaCustomExpressionItem".
35var y = x1 +::pragma x2+3*::pragma x3 +y;

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Prefixes

Prefixes are one or more expression items that precede some other expression item, and are added to the list in property Prefixes in interface UniversalExpressionParser.ExpressionItems.IComplexExpressionItem for the expression item that follows the list of prefix expression items.

Note

Prefixes are supported only if the value of property SupportsPrefixes in interface UniversalExpressionParser.IExpressionLanguageProvider is true.

Currently Universal Expression Parser supports two types of prefixes:

1) Nameless brace as prefixes

Square or round braces expressions items without names (i.e. expression items that are parsed to UniversalExpressionParser.ExpressionItems.IBracesExpressionItem with property NamedExpressionItem equal to null) that precede another expression item (e.g., another braces expression, a literal, a code block, text expression item, numeric value expression item, etc) are parsed as prefixes and are added to expression item they precede.

  • In the example below the braces expression items parsed from “[NotNull, ItemNotNull]” and “(Attribute(“MarkedFunction”))” will be added as prefixes to expression item parsed from “F1(x, x2)”.

1[NotNull, ItemNotNull](Attribute("MarkedFunction")) F1(x, x2)
2{
3    // This code block will be added to expression item parsed from F1(x:T1, y:T2, z: T3) as a postfix.
4    retuens [x1, x2, x3];
5}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

2) Custom expressions as prefixes

If custom expression items of type UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItem with property CustomExpressionItemCategory equal to UniversalExpressionParser.ExpressionItems.Custom.CustomExpressionItemCategory.Prefix are added as prefixes to expression item they precede.

Note

List of prefixes can include both nameless brace expression items as well as custom expression items, placed in any order.

  • In the example below, the expression items “::types[T1,T2]” and “::types[T3]” are parsed to custom expression items of type UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.IGenericTypeDataExpressionItem, and are added as prefixes to braces expression item parsed from “F1(x:T1, y:T2, z: T3)”.

Note

For more details on custom expression items see section Custom Expression Item Parsers.

1::types[T1,T2] ::types[T3] F1(x:T1, y:T2, z: T3)
2{
3    // This code block will be added to expression item parsed from F1(x:T1, y:T2, z: T3) as a postfix.
4}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Note

The list of prefixes can include both types of prefixes at the same time (i.e., braces and custom expression items).

  • Here is an example of prefixes used to model c# like attributes for classes and methods:

 1// [TestFixture] and [Attribute("IntegrationTest")] are added as prefixes to literal MyTests.
 2[TestFixture]
 3[Attribute("IntegrationTest")]
 4// public and class are added as keywords to MyTests
 5public class MyTests
 6{
 7    // Brace expression items [SetupMyTests], [Attribute("This is a demo of multiple prefixes")]
 8    // and custom expression item starting with ::metadata and ending with } are added as prefixes to
 9    // expression SetupMyTests()
10    [TestSetup]
11    [Attribute("This is a demo of multiple prefixes")]
12    ::metadata {
13        Description: "Demo of custom expression item parsed to
14                        UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.IMetadataCustomExpressionItem
15                        used in prefixes list of expression parsed from 'SetupMyTests()'";
16        SomeMetadata: 1
17    }
18    // public and static are added as keywords to expression SetupMyTests().
19    public static SetupMyTests() : void
20    {
21        // Do some test setup here
22    }
23}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Note

The list of prefixes can include both types of prefixes at the same time (i.e., braces and custom expression items).

  • Below is an example of using prefixes with different expression item types:

 1// Prefixes added to a literal "x".
 2[NotNull] [Attribute("Marker")] x;
 3
 4// Prefixes added to named round braces. [NotNull] [Attribute("Marker")] will be added
 5// to prefixes in braces expression item parsed from "f1(x1)"
 6[NotNull] [Attribute("Marker")] f1(x1);
 7
 8// Prefixes added to unnamed round braces. [NotNull] [Attribute("Marker")] will be added
 9// to prefixes in braces expression item parsed from "(x1)"
10[NotNull] [Attribute("Marker")] (x1);
11
12// Prefixes added to named square braces. [NotNull] [Attribute("Marker")] will be added
13// to prefixes in named braces expression item parsed from "m1[x1]"
14[NotNull] [Attribute("Marker")] m1[x1];
15
16// Prefixes added to unnamed square braces. [NotNull] [Attribute("Marker")] will be added
17// to prefixes in braces expression item parsed from "[x1]".
18[NotNull] [Attribute("Marker")] [x1];
19
20// Prefixes added to code block.
21// Custom prefix expression item "::types[T1,T2]" will be added to list of prefixes in code block expression item
22// parsed from "{var i = 12;}".
23// Note, if we replace "::types[T1,T2]" to unnamed braces, then the unnamed braces will be used as a postfix for
24// code block.
25::types[T1,T2] {var i = 12;};
26
27// Prefixes added to custom expression item parsed from "::pragma x".
28// [Attribute("Marker")] will be added to list of prefixes in custom expression item
29// parsed from "::pragma x".
30[Attribute("Marker")] ::pragma x;
31
32// Prefixes added text expression item.
33// [Attribute("Marker")] will be added to list of prefixes in text expression item
34// parsed from "Some text".
35[Attribute("Marker")] "Some text";
36
37// Prefixes added to numeric value item.
38// [Attribute("Marker")] will be added to list of prefixes in numeric value expression item
39// parsed from "0.5e-3.4".
40[Attribute("Marker")] 0.5e-3.4;

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Postfixes

Postfixes are one or more expression items that are placed after some other expression item, and are added to the list in property Postfixes in interface UniversalExpressionParser.ExpressionItems.IComplexExpressionItem for the expression item that the postfixes are placed after.

Currently Universal Expression Parser supports two types of postfixes:

1) Code block expression items

Code block expression items that are parsed to UniversalExpressionParser.ExpressionItems.ICodeBlockExpressionItem that succeed another expression item are added as postfixes to the expression item they succeed.

Note

The following are expression types that can have postfixes: Literals, such a x1 or Dog, braces expression items, such as f(x1), (y), m1[x1], [x2], or custom expression items for which property CustomExpressionItemCategory in interface UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItem is equal to UniversalExpressionParser.ExpressionItems.Custom.CustomExpressionItemCategory.Regular.

  • In the example below the code block expression item of type UniversalExpressionParser.ExpressionItems.ICodeBlockExpressionItem parsed from expression that starts with ‘{’ and ends with ‘}’” will be added to the list Postfixes in UniversalExpressionParser.ExpressionItems.IComplexExpressionItem for the literal expression item parsed from expression Dog.

1public class Dog
2{
3   // This code block will be added as a postfix to literal expression item parsed from "Dog"
4}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

2) Custom postfix expression items

Custom expression items of type UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItem with property CustomExpressionItemCategory equal to UniversalExpressionParser.ExpressionItems.Custom.CustomExpressionItemCategory.Postfix that succeed another expression item are added as postfixes to the expression item they succeed.

  • In the example below the two custom expression items of type UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.IWhereCustomExpressionItem parsed from expressions that start with “where” and end with “whereend”” as well as the code block will be added as postfixes to literal expression item parsed from “Dog”.

Note

For more details on custom expression items see section Custom Expression Item Parsers.

1::types[T1,T2, T3] F1(x:T1, y:T2, z: T3)
2// The where below will be added as a postfix to expression item parsed from "F1(x:T1, y:T2, z: T3)
3where T1:int where T2:double whereend
4// The where below will be added as a postfix to expression item parsed from "F1(x:T1, y:T2, z: T3)
5where T3:T1  whereend
6{
7    // This code block will be added as a postfix to expression item parsed from "F1(x:T1, y:T2, z: T3).
8}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Note

The list of postfixes can include both types of postfixes at the same time (i.e., custom expression items as well as a code block postfix).

  • Example of a code block postfix used to model function body:

 1// More complicated cases
 2// In the example below the parser will apply operator ':' to 'f2(x1:int, x2:int)' and 'int'
 3// and will add the code block after 'int' as a postfix to 'int'.
 4// The evaluator that processes the parsed expression can do farther transformation so that the code block is assigned to
 5// some new property in some wrapper for an expression for 'f2(x1:int, x2:int)', so that the code block belongs to the function, rather than
 6// to the returned type 'int' of function f2.
 7f2(x1:int, x2:int) : int
 8{
 9   f3() : int
10   {
11       var result = x1+x2;
12           println("result='"+result+"'");
13           return result;
14   }
15
16   return f3();
17}
18
19var myFunc = f2(x1:int, x2:int) =>
20{
21    println(exp ^ (x1 + x2));
22}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

  • Example of code block postfix used to model class definition:

 1// In the example below the parser will apply operator ':' to literal 'Dog' (with keywords public and class) and
 2// braces '(Anymal, IDog)' and will add the code block after '(Anymal, IDog)' as a postfix to '(Anymal, IDog)'.
 3// The evaluator that processes the parsed expression can do farther transformation so that the code block is assigned to
 4// some new property in some wrapper for an expression for 'Dog', so that the code block belongs to the 'Dog' class, rather than
 5// to the braces for public classes in '(Anymal, IDog)'.
 6public class Dog : (Anymal, IDog)
 7{
 8    public Bark() : void
 9    {
10        println("Bark.");
11    }
12}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

  • Below are some more examples of postfixes with different expression items:

 1f1(x1)
 2{
 3    // Code block added to postfixes list for braces expression "f1(x1)"
 4    return x2*y1;
 5}
 6
 7m1[x2]
 8{
 9    // Code block added to postfixes list for braces expression "m1[x2]"
10    x:2*3
11}
12
13(x3)
14{
15    // Code block added to postfixes list for braces expression "(x3)"
16    return x3*2;
17}
18
19[x4]
20{
21    // Code block added to postfixes list for braces expression "[x4]"
22    x4:2*3
23}
24
25class Dog
26{
27    // Code block added to postfixes list for literal expression "Dog"
28}
29
30::pragma x
31{
32    // Code block added to custom expression item IPragmaCustomExpressionItem parsed from "::pragma x"
33}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Custom Expression Item Parsers

Custom expression parsers allow to plugin into parsing process and provide special parsing of some portion of the parsed expression.

The expression parser (i.e., UniversalExpressionParser.IExpressionParser) iteratively parses keywords (see the section above on keywords), before parsing any other symbols.

Then the expression parser loops through all the custom expression parsers of type UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItemParser in property CustomExpressionItemParsers in interface UniversalExpressionParser.IExpressionLanguageProvider passed to the parser, and for each custom expression parser executes the method

ICustomExpressionItem ICustomExpressionItemParser.TryParseCustomExpressionItem(IParseExpressionItemContext context, IReadOnlyList&lt;IExpressionItemBase&gt; parsedPrefixExpressionItems, IReadOnlyList&lt;IKeywordExpressionItem&gt; keywordExpressionItems).

If method call TryParseCustomExpressionItem(…) returns non-null value of type UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItem, the parser uses the parsed custom expression item.

Otherwise, if TryParseCustomExpressionItem(…) returns null, the parser tries to parse a non custom expression item at current position (i.e., operators, a literal, function, code block, etc.).

Interface ICustomExpressionItem has a property UniversalExpressionParser.ExpressionItems.Custom.CustomExpressionItemCategory which is used by the parser to determine if the parsed custom expression item should be used as
  • a prefix for subsequently parsed regular expression (i.e., literal, function, braces, etc.).

  • should be treated as regular expression (which can be part of operators, function parameter, etc.).

  • or should be used as a postfix for the previously parsed expression item.

In the example below the parser parses “::pragma x” to a regular custom expression item of type UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.PragmaCustomExpressionItem (i.e., the value of CustomExpressionItemCategory property in UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItem is equal to UniversalExpressionParser.ExpressionItems.Custom.CustomExpressionItemCategory.Regular). As a result, the expression “::pragma x+y;” below is logically similar to “(::pragma x)+y;”

In a similar manner the expression “::types[T1, T2]” is parsed to a prefix custom expression item of type UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.GenericTypesCustomExpressionItem by custom expression item parser UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.GenericTypesExpressionItemParser.

The custom expression item parsed from “::types[T1, T2]” is added as a prefix to an expression item parsed from F1(x:T1, y:T2).

Also, the expression “where T1:int where T2:double whereend” is parsed to postfix custom expression item of type UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.WhereCustomExpressionItem by custom expression parser UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.WhereCustomExpressionItemParserForTests.

The parser adds the parsed custom expression as a postfix to the preceding regular expression item parsed from text “F1(x:T1, y:T2)”.

In this example, the code block after “whereend” (the expression “{…}”) is parsed as a postfix expression item of type UniversalExpressionParser.ExpressionItems.ICodeBlockExpressionItem and is added as a postfix to regular expression item parsed from “F1(x:T1, y:T2)” as well, since the parser adds all the prefixes/postfixes to regular expression item it finds after/before the prefixes/postfixes.

1::pragma x+y;
2::types[T1,T2] F1(x:T1, y:T2) where T1:int where T2:double whereend
3{
4   // This code block will be added as a postfix to expression item parsed from "F1(x:T1, y:T2)".
5}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

  • This is another example demonstrating that the parsed expression can have multiple prefix and postfix custom expressions items applied to the same regular expression item parsed from “F1(x:T1, y:T2, z:T3)”.

 1// The expression below ("::metadata {...}") is parsed to a prefix custom expression item and added to list of prefixes of regular
 2// expression item parsed from F1(x:T1, y:T2, z:T3)
 3::metadata {description: "F1 demoes regular function expression item to which multiple prefix and postfix custom expression items are added."}
 4
 5// ::types[T1,T2] is also parsed to a prefix custom expression item and added to list of prefixes of regular
 6// expression item parsed from F1(x:T1, y:T2, z:T3)
 7::types[T1,T2]
 8F1(x:T1, y:T2, z:T3)
 9
10// The postfix custom expression item parsed from "where T1:int where T2:double whereend" is added to list of postfixes of regular expression
11// parsed from "F1(x:T1, y:T2, z:T3)".
12where T1:int,class where T2:double whereend
13
14// The postfix custom expression item parsed from "where T3 : T1 whereend " is also added to list of postfixes of regular expression
15// parsed from "F1(x:T1, y:T2, z:T3)".
16where T3 : T1 whereend
17{
18   // This code block will be added as a postfix to expression item parsed from "F1(x:T1, y:T2, z:T3)".
19}

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Implementing Custom Expression Parsers

For examples of custom expression item parsers look at some examples in demo project UniversalExpressionParser.DemoExpressionLanguageProviders.

The following demo implementations of UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItemParserByKeywordId might be useful when implementing custom expression parses:

  • UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.WhereCustomExpressionItemParserBase

  • UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.PragmaCustomExpressionItemParser

  • UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions.MetadataCustomExpressionItemParser

Also, these custom expression parser implementations demonstrate how to use the helper class UniversalExpressionParser.IParseExpressionItemContext that is passed as a parameter to method DoParseCustomExpressionItem(IParseExpressionItemContext context,…) in UniversalExpressionParser.ExpressionItems.Custom.CustomExpressionItemParserByKeywordId to parse the text at current position, as well as how to report errors, if any.

  • To add a new custom expression parser, one needs to implement an interface UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItemParser and make sure the property CustomExpressionItemParsers in interface UniversalExpressionParser.IExpressionLanguageProvider includes an instance of the implemented parser class.

  • In most cases the default implementation UniversalExpressionParser.ExpressionItems.Custom.AggregateCustomExpressionItemParser of UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItemParser can be used to initialize the list of all custom expression parers that will be used by Universal Expression Parser.

UniversalExpressionParser.ExpressionItems.Custom.AggregateCustomExpressionItemParser has a dependency on IEnumerable&lt;ICustomExpressionItemParserByKeywordId&gt; (injected into constructor).

  • Using a single instance of AggregateCustomExpressionItemParser in property CustomExpressionItemParsers in interface UniversalExpressionParser.IExpressionLanguageProvider instead of multiple custom expression parsers in this property improves the performance.

AggregateCustomExpressionItemParser keeps internally a mapping from keyword Id to all the instances of UniversalExpressionParser.ExpressionItems.Custom.ICustomExpressionItemParserByKeywordId injected in constructor. When the parser executes the method TryParseCustomExpressionItem(…,IReadOnlyList<IKeywordExpressionItem> parsedKeywordExpressionItems,…) in interface UniversalExpressionParser.ExpressionItems.Custom, the custom expression item parser of type AggregateCustomExpressionItemParser evaluates the last keyword in list in parameter parsedKeywordExpressionItems to retrieve all the parsers mapped to this keyword Id, to try to parse a custom expression item using only those custom expression item parsers.

  • Below is some of the code from classes AggregateCustomExpressionItemParser and ICustomExpressionItemParserByKeywordId.

 1namespace UniversalExpressionParser.ExpressionItems.Custom;
 2
 3public class AggregateCustomExpressionItemParser : ICustomExpressionItemParser
 4{
 5    public AggregateCustomExpressionItemParser(
 6        IEnumerable<ICustomExpressionItemParserByKeywordId> customExpressionItemParsers)
 7    {
 8        ...
 9    }
10
11    public ICustomExpressionItem TryParseCustomExpressionItem(IParseExpressionItemContext context,
12            IReadOnlyList<IExpressionItemBase> parsedPrefixExpressionItems,
13            IReadOnlyList<IKeywordExpressionItem> parsedKeywordExpressionItems)
14    {
15        ...
16    }
17}
18
19public interface ICustomExpressionItemParserByKeywordId
20{
21    long ParsedKeywordId { get; }
22
23    ICustomExpressionItem TryParseCustomExpressionItem(IParseExpressionItemContext context,
24            IReadOnlyList<IExpressionItemBase> parsedPrefixExpressionItems,
25            IReadOnlyList<IKeywordExpressionItem> parsedKeywordExpressionItemsWithoutLastKeyword,
26            IKeywordExpressionItem lastKeywordExpressionItem);
27}
  • Here is the code from demo custom expression item parser PragmaCustomExpressionItemParser

 1using System.Collections.Generic;
 2using UniversalExpressionParser.ExpressionItems;
 3using UniversalExpressionParser.ExpressionItems.Custom;
 4
 5namespace UniversalExpressionParser.DemoExpressionLanguageProviders.CustomExpressions
 6{
 7    /// <summary>
 8    ///  Example: ::pragma x
 9    /// </summary>
10    public class PragmaCustomExpressionItemParser : CustomExpressionItemParserByKeywordId
11    {
12        public PragmaCustomExpressionItemParser() : base(KeywordIds.Pragma)
13        {
14        }
15
16        /// <inheritdoc />
17        protected override ICustomExpressionItem DoParseCustomExpressionItem(IParseExpressionItemContext context, IReadOnlyList<IExpressionItemBase> parsedPrefixExpressionItems,
18                                                                           IReadOnlyList<IKeywordExpressionItem> parsedKeywordExpressionItemsWithoutLastKeyword,
19                                                                           IKeywordExpressionItem pragmaKeywordExpressionItem)
20        {
21            var pragmaKeywordInfo = pragmaKeywordExpressionItem.LanguageKeywordInfo;
22
23            var textSymbolsParser = context.TextSymbolsParser;
24
25            if (!context.SkipSpacesAndComments() || !context.TryParseSymbol(out var literalExpressionItem))
26            {
27                if (!context.ParseErrorData.HasCriticalErrors)
28                {
29                    // Example: print("Is in debug mode=" + ::pragma IsDebugMode)
30                    context.AddParseErrorItem(new ParseErrorItem(textSymbolsParser.PositionInText,
31                        () => $"Pragma keyword '{pragmaKeywordInfo.Keyword}' should be followed with pragma symbol. Example: println(\"Is in debug mode = \" + {pragmaKeywordInfo.Keyword} IsDebug);",
32                        CustomExpressionParseErrorCodes.PragmaKeywordShouldBeFollowedByValidSymbol));
33                }
34
35                return null;
36            }
37
38            return new PragmaCustomExpressionItem(parsedPrefixExpressionItems, parsedKeywordExpressionItemsWithoutLastKeyword,
39                pragmaKeywordExpressionItem,
40                new NameExpressionItem(literalExpressionItem, textSymbolsParser.PositionInText - literalExpressionItem.Length));
41        }
42    }
43}

Click here to see the definition of interface UniversalExpressionParser.IParseExpressionItemContext

Comments

The interface UniversalExpressionParser.IExpressionLanguageProvider has properties string LineCommentMarker { get; }, string MultilineCommentStartMarker { get; }, and string MultilineCommentEndMarker { get; } for specifying comment markers.

If the values of these properties are not null, line and code block comments can be used.

The abstract implementation UniversalExpressionParser.ExpressionLanguageProviderBase of UniversalExpressionParser.IExpressionLanguageProvider overrides these properties to return “//”, “/”, and “/” (the values of these properties can be overridden in subclasses).

The on commented out code data is stored in property IReadOnlyList&lt;UniversalExpressionParser.ICommentedTextData&gt; SortedCommentedTextData { get; } in UniversalExpressionParser.IParsedExpressionResult, an instance of which is returned by the call to method UniversalExpressionParser.IExpressionParser.ParseExpression(…).

  • Below are some examples of line and code block comments:

 1// Line comment
 2var x = 5*y; // another line comments
 3
 4println(x +/*Code block
 5comments
 6can span multiple lines and can be placed anywhere.
 7*/y+10*z);
 8
 9/*
10Another code block comments
11var y=5*x;
12var z = 3*y;
13*/

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

  • Below is the definition of interface UniversalExpressionParser.ICommentedTextData that stores data on comments.

 1// Copyright (c) UniversalExpressionParser Project. All rights reserved.
 2// Licensed under the MIT License. See LICENSE in the solution root for license information.
 3
 4using UniversalExpressionParser.ExpressionItems;
 5
 6namespace UniversalExpressionParser
 7{
 8    /// <summary>
 9    /// Info on commented out code block.
10    /// </summary>
11    public interface ICommentedTextData: ITextItem
12    {
13        /// <summary>
14        /// If true, the comment is a line comment. Otherwise, it is a block comment.
15        /// </summary>
16        bool IsLineComment { get; }
17    }
18}

Error Reporting

Parse error data is stored in property UniversalExpressionParser.IParseErrorData ParseErrorData { get; }. The class UniversalExpressionParser.IParseErrorData has a property IReadOnlyList<UniversalExpressionParser.IParseErrorItem> AllParseErrorItems { get; } that stores data on all parse errors.

Click here to see the definition of UniversalExpressionParser.IParseErrorItem

The extensions class UniversalExpressionParser.ParseExpressionResultExtensionMethods has number of helper methods, among which is string GetErrorTextWithContextualInformation(this IParsedExpressionResult parsedExpressionResult, int parsedTextStartPosition, int parsedTextEnd, int maxNumberOfCharactersToShowBeforeOrAfterErrorPosition = 50) for returning a string with error details and contextual data (i.e., text before and after the position where error happened, along with arrow pointing to the error).

Click here to see the definition of UniversalExpressionParser.ParseExpressionResultExtensionMethods

  • Below is an expression which has several errors:

 1var x = y /*operator is missing here*/x;
 2
 3{ // This code block is not closed
 4    f1(x, y, /*function parameter is missing here*/)
 5    {
 6
 7        var z = ++x + y + /*' +' is not a postfix and operand is missing */;
 8        return + /*' +' is not a postfix and operand is missing */ z + y;
 9    }
10// Closing curly brace is missing here

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Click to see the the error text generated by using the helper extension method UniversalExpressionParser.ParseExpressionResultExtensionMethods.GetErrorTextWithContextualInformation(...) for the errors reported by the parser for the expression above

Parsing Section in Text

  • Sometimes we want to parse a single braces expression at specific location in text (i.e., an expression starting with “(” or “[” and ending in “)” or “]” correspondingly) or single code block expression (i.e., an expression starting with UniversalExpressionParser.IExpressionLanguageProvider.CodeBlockStartMarker and ending in UniversalExpressionParser.IExpressionLanguageProvider.CodeBlockEndMarker). In these scenarios, we want the parser to stop right after fully parsing the braces or code block expression.

  • The interface UniversalExpressionParser.IExpressionParser has two methods for doing just that.

  • The methods for parsing single braces or code block expression are UniversalExpressionParser.IParseExpressionResult ParseBracesExpression(string expressionLanguageProviderName, string expressionText, IParseExpressionOptions parseExpressionOptions) and UniversalExpressionParser.IParseExpressionResult ParseCodeBlockExpression(string expressionLanguageProviderName, string expressionText, IParseExpressionOptions parseExpressionOptions), and are demonstrated in sub-sections below.

  • The parsed expression of type UniversalExpressionParser.IParseExpressionResult returned by these methods has a property int PositionInTextOnCompletion { get; } that stores the position in text, after the parsing is complete (i.e., the position after closing brace or code block end marker).

Example of parsing single braces expression

  • Below is an an SQLite table definition in which we want to parse only the braces expression (SALARY > 0 AND SALARY > MAX_SALARY/2 AND f1(SALARY) < f2(MAX_SALARY)), and stop parsing right after the closing brace ‘)’.

 1CREATE TABLE COMPANY(
 2   ID INT PRIMARY KEY     NOT NULL,
 3   MAX_SALARY     REAL,
 4   /* The parser will only parse expression
 5   (SALARY > 0 AND SALARY > MAX_SALARY/2 AND f1(SALARY)<f2(MAX_SALARY)) and will stop right after the
 6   closing round brace ')' of in this expression. */
 7   AVG_SALARY     REAL
 8                   CHECK(SALARY > 0 AND
 9                             SALARY > MAX_SALARY/2 AND
10                             f1(SALARY) < f2(MAX_SALARY)),
11   ADDRESS        CHAR(50)
12);

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

  • The method ParseBracesAtCurrentPosition(string expression, int positionInText) in class UniversalExpressionParser.Tests.Demos.ParseSingleBracesExpressionAtPositionDemo (shown below) demonstrates how to parse the braces expression (SALARY > 0 AND SALARY > MAX_SALARY/2 AND f1(SALARY) < f2(MAX_SALARY)), by passing the position of opening brace in parameter positionInText.

Click here to see definition of class UniversalExpressionParser.Tests.Demos.ParseSingleBracesExpressionAtPositionDemo

  • Here is square braces expression [f1()+m1[], f2{++i;}] between texts ‘any text before braces’ and ‘any text after braces…’, which can also be parsed using the code in class UniversalExpressionParser.Tests.Demos.ParseSingleBracesExpressionAtPositionDemo.

1any text before braces[f1()+m1[], f2
2{
3   ++i;
4}]any text after braces including more braces expressions that will not be parsed

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Example of parsing single code block expression

Below is a text with code block expression {f1(f2()+m1[], f2{++i;})} between texts ‘any text before code block’ and ‘any text after code block…’ that we want to parse.

1any text before braces[f1()+m1[], f2
2{
3   ++i;
4}]any text after braces including more braces expressions that will not be parsed

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

  • The method IParseExpressionResult ParseCodeBlockExpressionAtCurrentPosition(string expression, int positionInText) in class UniversalExpressionParser.Tests.Demos.ParseSingleCodeBlockExpressionAtPositionDemo demonstrates how to parse the single code block expression {f1(f2()+m1[], f2{++i;})}, by passing the position of code block start marker ‘{’ in parameter positionInText.

Click here to see definition of class UniversalExpressionParser.Tests.Demos.ParseSingleCodeBlockExpressionAtPositionDemo

Case Sensitivity and Non Standard Language Features

Case sensitivity

  • Case sensitivity is controlled by property bool IsLanguageCaseSensitive { get; } in interface UniversalExpressionParser.IExpressionLanguageProvider.

  • If the value of this property IsLanguageCaseSensitive is true, any two expressions are considered different, if the expressions are the same, except for capitalization of some of the text (say “public class Dog” vs “Public ClaSS DOg”). Otherwise, if the value of property IsLanguageCaseSensitive is false, the capitalization of any expression items does not matter (i.e., parsing will succeed regardless of capitalization in expression).

  • For example C# is considered a case sensitive language, and Visual Basic is considered case insensitive.

  • The value of property IsLanguageCaseSensitive in abstract implementation UniversalExpressionParser.ExpressionLanguageProviderBase of UniversalExpressionParser.IExpressionLanguageProvider returns true.

  • The expression below demonstrates parsing the expression by UniversalExpressionParser.IExpressionLanguageProvider with overridden IsLanguageCaseSensitive to return false.

Non standard comment markers

  • The properties string LineCommentMarker { get; }, string MultilineCommentStartMarker { get; }, and string MultilineCommentEndMarker { get; } in interface UniversalExpressionParser.IExpressionLanguageProvider determine the line comment marker as well as code block comment start and end markers.

  • The default implementation UniversalExpressionParser.ExpressionLanguageProviderBase of UniversalExpressionParser.IExpressionLanguageProvider returns “//”, “/”, and “/” for these properties to use C# like comments. However, other values can be used for these properties.

  • The expression below demonstrates parsing the expression by an instance of UniversalExpressionParser.IExpressionLanguageProvider with overridden LineCommentMarker, MultilineCommentStartMarker, and MultilineCommentEndMarker to return “rem”, “rem*”, “*rem”.

Non standard code separator character and code block markers

  • The properties char ExpressionSeparatorCharacter { get; }, string CodeBlockStartMarker { get; }, and string CodeBlockEndMarker { get; } in interface UniversalExpressionParser.IExpressionLanguageProvider determine the code separator character, as well as the code block start and end markers.

  • The default implementation UniversalExpressionParser.ExpressionLanguageProviderBase of UniversalExpressionParser.IExpressionLanguageProvider returns “;”, “{”, and “}” for these properties to use C# like code separator and code block markers. However, other values can be used for these properties.

  • The expression below demonstrates parsing the expression by an instance of UniversalExpressionParser.IExpressionLanguageProvider with overridden ExpressionSeparatorCharacter, CodeBlockStartMarker, and CodeBlockEndMarker to return “;”, “BEGIN”, and “END”.

Example demonstrating case insensitivity and non standard language features

  • The expression below is parsed using the expression language provider UniversalExpressionParser.DemoExpressionLanguageProviders.VerboseCaseInsensitiveExpressionLanguageProvider, which overrides IsLanguageCaseSensitive to return false. As can bee seen in this example, the keywords (e.g., var, public, class, ::pragma, etc), non standard code comment markers (i.e., “rem”, “rem*”, “rem”), code block markers (i.e., **BEGIN*, END) and operators IS NULL, IS NOT NULL can be used with any capitalization, and the expression is still parsed without errors.

 1rem This line commented out code with verbose line comment marker 'rem'
 2rem*this is a demo of verbose code block comment markers*rem
 3
 4rem#No space is required between line comment marker and the comment, if the first
 5rem character is a special character (such as operator, opening, closing round or squer braces, comma etc.)
 6
 7BEGIN
 8    println(x); println(x+y)
 9    rem* this is an example of code block
10    with verbose code block start and end markers 'BEGIN' and 'END'.
11    *rem
12END
13
14Rem Line comment marker can be used with any capitalization
15
16REm* Multi-line comment start/end markers can be used with
17any capitalization *ReM
18
19rem keywords public and class can be used with any capitalization.
20PUBLIC Class DOG
21BEGIN Rem Code block start marker 'BEGIN' can be used with any capitalization
22    PUBLIc static F1(); rem keywords (e.g., 'PUBLIC') can be used with any capitalization
23end
24
25REm keyword 'var' can be used with any capitalization.
26VaR x=::PRagma y;
27
28PRintLN("X IS NOT NULL=" + X Is noT Null && ::pRAGMA y is NULL);
29
30f1(x1, y1)
31BEGin Rem Code block start marker 'BEGIN'can be used with any capitalization.
32   Rem Line comment marker 'rem' can be used with any capitalization
33   rem Line comment marker 'rem' can be used with any capitalization
34
35   REm* Multi line comment start/end markers can be used with
36   any capitalization *rEM
37
38   RETurN X1+Y1; rem unary prefix operator 'return' (and any other) operator  can be used with any capitalization.
39enD rem Code block end marker 'END' can be used  with any capitalization.

Click here to see the visualized instance of UniversalExpressionParser.IParseExpressionResult

Indices and tables