Skip to content

Commit fb04889

Browse files
committed
Doc and ScannerExt updates
1 parent e5ff980 commit fb04889

File tree

8 files changed

+442
-194
lines changed

8 files changed

+442
-194
lines changed

Docs/FexElementsRef.md

Lines changed: 81 additions & 74 deletions
Large diffs are not rendered by default.

Docs/FexOverview.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Flow Expressions Overview
2+
3+
A flow expression can implement just about any type of *flow logic*. Mainly for, but not limited to, **Parsers** and **DSL** (domain specific language) construction.
4+
5+
Flow expressions are constructed from the various **FexElements** (*building blocks*) via a fluent API. These FexElements define the logical flow and operation of the expression in a very readable and maintainable format.
6+
7+
> A running Flow Expression operates on a user supplied **Context**, which is any environment that manages and provides input/content/state.
8+
>
9+
> For a Parser, the context would be a **Scanner** that manages text scanning and provides <i>Tokens</i> to operate on.<br/>
10+
>
11+
> A comprehensive [FexScanner](Docs/FexScannerExt.md) is provided (derived from *ScriptScanner* in the [Scanners](https://github.com/PromicSW/scanners) repo/library) but you can roll your own if required.
12+
13+
## FexElements perform several types of functions:
14+
15+
> Please see the [Fex Element reference](Docs/FexElementsRef.md) section for full details.<br>
16+
> Also [FexScanner context extensions](Docs/FexScannerExt.md) for extensions specific to the FexScanner as context.
17+
18+
- **Operators (Op):** Perform a operation on the context, returning a success status (true/false).
19+
- An operator is implemented via a `Func<context, bool>` delegate which can either operate on the context and/or the *closure* environment.
20+
- For example: if the context is a scanner then the Op would typically perform one of the scanning methods/functions.
21+
- Certain Op's produce and record a **value** for later use via one of the Action elements.
22+
- An Op can operate on the context directly, but typically *Operator Extension methods* are defined to create re-usable (and more readable) operators specific to the context used.
23+
- There is also a facility to attach Pre-Operations to operators (e.g skip spaces when scanning etc.)
24+
- **Sequence(Seq):** A sequence is the primary construct used in flow expressions and defines a series of steps (1..n) to complete:
25+
- A step is any FexElement.
26+
- All steps is a sequence must complete else the sequence fails.
27+
- A step(s) may be optional and there are several rules governing this (see reference section).
28+
- **Flow Control:** These elements control the flow of an expression:
29+
- Opt: Optional sequence.
30+
- OneOf: One of a set of sequences must pass.
31+
- NotOneOf: Inverse of OneOf.
32+
- Rep...: Repeat sequences.
33+
34+
- **Actions:** Perform actions based on the current state of the production to check values or perform any other actions required on the context:
35+
- Actions don't affect the validity of a sequence, so may be included anywhere.
36+
- There are general actions and those that operate on a value recorded by and operator.
37+
- The `ActValue(...)` element is actually bound to the preceding operator that produced a value and as such must be placed directly after the operator.
38+
- An Action can operate on the context directly, but typically *Action Extension methods* are defined to create re-usable (and more readable) actions specific to the context used.
39+
- **Error reporting:** Elements for error handling and reporting.
40+
- **Tracing:** Tracing elements primarily for debugging purposes.
41+
42+
## Reference Example
43+
44+
The following is a fully commented expression evaluation example showing many of the features of a flow expressions:
45+
46+
```csharp
47+
using Psw.FlowExpressions;
48+
49+
void RefExpressionEval(string calc = "9 - (5.5 + 3) * 6 - 4 / ( 9 - 1 )")
50+
{
51+
/*
52+
* Expression Grammar:
53+
* expression => factor ( ( '-' | '+' ) factor )* ;
54+
* factor => unary ( ( '/' | '*' ) unary )* ;
55+
* unary => ( '-' unary ) | primary ;
56+
* primary => NUMBER | "(" expression ")" ;
57+
*/
58+
59+
// Number Stack for calculations:
60+
Stack<double> numStack = new Stack<double>();
61+
62+
// The FlowExpression object used to create FexElements with FexScanner as the context:
63+
var fex = new FlowExpression<FexScanner>();
64+
65+
// Define the main expression production, which returns a Sequence element:
66+
var expr = fex.Seq(s => s
67+
68+
// Forward reference to the factor element, which is only defined later.
69+
// The element is then included at this point in the sequence.
70+
.Ref("factor")
71+
72+
// Repeat one of the contained sequences zero or more times:
73+
.RepOneOf(0, -1, r => r
74+
75+
// If we have a '+' run factor and then add the top two values on the stack:
76+
.Seq(s => s.Ch('+').Ref("factor").Act(c => numStack.Push(numStack.Pop() + numStack.Pop())))
77+
78+
// If we have a '-' run factor and then subtract the top two values on the stack:
79+
// o We minus the first and add the second because the stack is in reverse order.
80+
.Seq(s => s.Ch('-').Ref("factor").Act(c => numStack.Push(-numStack.Pop() + numStack.Pop())))
81+
));
82+
83+
// Define the factor production:
84+
var factor = fex.Seq(s => s
85+
.RefName("factor") // Set the forward reference name.
86+
.Ref("unary") // Forward reference to unary
87+
88+
// Repeat one of the contained sequences zero or more times:
89+
.RepOneOf(0, -1, r => r
90+
91+
// If we have a '*' run unary and then multiply the top two values on the stack:
92+
.Seq(s => s.Ch('*').Ref("unary").Act(c => numStack.Push(numStack.Pop() * numStack.Pop())))
93+
94+
// If we have a '/' run unary, check for division by zero and then divide the top two values on the stack:
95+
// Note again the stack is in reverse order.
96+
.Seq(s => s.Ch('/').Ref("unary")
97+
.Op(c => numStack.Peek() != 0).OnFail("Division by 0") // Trap division by 0 and report as error.
98+
.Act(c => numStack.Push(1 / numStack.Pop() * numStack.Pop())))
99+
));
100+
101+
// Define the unary production:
102+
var unary = fex.Seq(s => s
103+
.RefName("unary") // Set the forward reference name.
104+
105+
// Now we either negate a unary or have a primary.
106+
.OneOf(o => o
107+
.Seq(s => s.Ch('-').Ref("unary").Act(a => numStack.Push(-numStack.Pop())))
108+
.Ref("primary")
109+
));
110+
111+
// Define the primary production:
112+
var primary = fex.Seq(s => s
113+
.RefName("primary") // Set the forward reference name.
114+
115+
// Now we either have a nested expression as (expr) or a numeric value:
116+
.OneOf(o => o
117+
118+
// Handle a nested expression in brackets and report an error for a missing closing bracket:
119+
// o Fex(expr) references/includes the expr element previously defined.
120+
// o We could have used the RefName() / Ref() combination but this is more efficient.
121+
// o Also Fex can take any number of elements Fex(e1, e2 ... en)
122+
.Seq(e => e.Ch('(').Fex(expr).Ch(')').OnFail(") expected"))
123+
124+
// Ultimately we have a number which is just pushed onto the stack.
125+
.Seq(s => s.NumDecimal(n => numStack.Push(n)))
126+
).OnFail("Primary expected")); // Fail with an error if not one of the above.
127+
128+
// Define the Axiom element that we will run later.
129+
var exprEval = fex.Seq(s => s
130+
131+
// Attach a pre-operation to all Op's to skip spaces before:
132+
// o Uses the Scanner.SkipSp() method for this.
133+
// o Pre-operations run efficiently only when needed.
134+
.GlobalPreOp(c => c.SkipSp())
135+
136+
// Reference/include the previously defined expr element
137+
.Fex(expr)
138+
139+
// Check that we ended at end-of-source else it's an error:
140+
.IsEos().OnFail("invalid expression"));
141+
142+
// Create the FexScanner with the calc string as source:
143+
var scn = new FexScanner(calc);
144+
145+
// Run the Axiom with the scanner which returns true/false:
146+
// o If valid display the answer = top value on the stack.
147+
// o Else display the error logged in the scanner's shared ErrorLog.
148+
Console.WriteLine(exprEval.Run(scn)
149+
? $"Answer = {numStack.Pop():F4}"
150+
: scn.ErrorLog.AsConsoleError("Expression Error:"));
151+
}
152+
```

0 commit comments

Comments
 (0)