|
| 1 | +# Flow Expressions |
| 2 | + |
| 3 | + |
| 4 | +Construct, ready to run, Parsers of any complexity using a declarative fluent syntax in C#. The system is lightweight, fast, and loosely coupled components provide complete implementation flexibility. |
| 5 | + |
| 6 | +## Building Flow Expressions |
| 7 | + |
| 8 | +Flow Expressions are defined by a structure of *FexElements* built via a Fluent API. This defines the logical flow and operations of the expression in a very readable format. |
| 9 | + |
| 10 | +Any logic than can be expressed as a Flow Expression (think flow chart) may be implemented. |
| 11 | + |
| 12 | + |
| 13 | +> A Flow Expression operates on a user supplied **Context**, which is any environment that manages and provides input/content/state. |
| 14 | +> |
| 15 | +> For a Parser, the context would be a **Scanner** that manages text scanning and provides <i>Tokens</i> to operate on. A comprehensive FexScanner is provided as the default - but you can roll your own if required. |
| 16 | +
|
| 17 | +<br> |
| 18 | + |
| 19 | +The following example is a complete **Expression Parser**, including evaluation and error reporting: |
| 20 | + |
| 21 | +```csharp |
| 22 | +void ExpressionEval(string calc = "9 - (5.5 + 3) * 6 - 4 / ( 9 - 1 )") |
| 23 | +{ |
| 24 | + // Expression Grammar: |
| 25 | + // expr => factor ( ( '-' | '+' ) factor )* ; |
| 26 | + // factor => unary ( ( '/' | '*' ) unary )* ; |
| 27 | + // unary => ( '-' unary ) | primary ; |
| 28 | + // primary => NUMBER | '(' expression ')' ; |
| 29 | +
|
| 30 | + // Number Stack for calculations: |
| 31 | + Stack<double> numStack = new Stack<double>(); |
| 32 | + |
| 33 | + Console.WriteLine($"Calculate: {calc}"); |
| 34 | + |
| 35 | + var fex = new FlowExpression<FexScanner>(); |
| 36 | + |
| 37 | + var expr = fex.Seq(s => s |
| 38 | + .Ref("factor") |
| 39 | + .RepOneOf(0, -1, r => r |
| 40 | + .Seq(s => s.Ch('+').Ref("factor").Act(c => numStack.Push(numStack.Pop() + numStack.Pop()))) |
| 41 | + .Seq(s => s.Ch('-').Ref("factor").Act(c => numStack.Push(-numStack.Pop() + numStack.Pop()))) |
| 42 | + )); |
| 43 | + |
| 44 | + var factor = fex.Seq(s => s.RefName("factor") |
| 45 | + .Ref("unary") |
| 46 | + .RepOneOf(0, -1, r => r |
| 47 | + .Seq(s => s.Ch('*').Ref("unary").Act(c => numStack.Push(numStack.Pop() * numStack.Pop()))) |
| 48 | + .Seq(s => s.Ch('/').Ref("unary") |
| 49 | + .Op(c => numStack.Peek() != 0).OnFail("Division by 0") // Trap division by 0 |
| 50 | + .Act(c => numStack.Push(1 / numStack.Pop() * numStack.Pop()))) |
| 51 | + )); |
| 52 | + |
| 53 | + var unary = fex.Seq(s => s.RefName("unary") |
| 54 | + .OneOf(o => o |
| 55 | + .Seq(s => s.Ch('-').Ref("unary").Act(a => numStack.Push(-numStack.Pop()))) |
| 56 | + .Ref("primary") |
| 57 | + )); |
| 58 | + |
| 59 | + var primary = fex.Seq(s => s.RefName("primary") |
| 60 | + .OneOf(o => o |
| 61 | + .Seq(e => e.Ch('(').Fex(expr).Ch(')').OnFail(") expected")) |
| 62 | + .Seq(s => s.NumDecimal(n => numStack.Push(n))) |
| 63 | + ).OnFail("Primary expected")); |
| 64 | + |
| 65 | + var exprEval = fex.Seq(s => s.GlobalPreOp(c => c.SkipSp()).Fex(expr).IsEos().OnFail("invalid expression")); |
| 66 | + |
| 67 | + var scn = new FexScanner(calc); |
| 68 | + |
| 69 | + Console.WriteLine(exprEval.Run(scn) |
| 70 | + ? $"Answer = {numStack.Pop():F4}" |
| 71 | + : scn.ErrorLog.AsConsoleError("Expression Error:")); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +<br> |
| 76 | + |
| 77 | +Example **error reporting** for the expression parser above: |
| 78 | + |
| 79 | +```dos |
| 80 | +Expression Error: |
| 81 | +9 - ( 5.5 ++ 3 ) * 6 - 4 / ( 9 - 1 ) |
| 82 | +-----------^ (Ln:1 Ch:12) |
| 83 | +Parse error: Primary expected |
| 84 | +``` |
0 commit comments