Skip to content

Commit 1e433c5

Browse files
committed
SampleScanner updates
1 parent 524a18a commit 1e433c5

File tree

3 files changed

+124
-48
lines changed

3 files changed

+124
-48
lines changed

Docs/CustomScanner.md

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ This tutorial describes how to construct a custom Scanner for use with Flow Expr
77
The simple Scanner below takes a source string and then provides a few scanning services:
88

99
```csharp
10-
public class SimpleScanner {
10+
public class SimpleScanner
11+
{
1112
private string _source;
12-
public char Delim { get; private set; } // Last Delimiter logged
13-
private int _index = 0; // Current scan position
14-
protected static char _Eos = '0'; // End of source character
13+
public char Delim { get; private set; } // Last Delimiter logged
14+
private int _index = 0; // Scan pointer / index
15+
protected static char _Eos = '0'; // End of source character
1516
1617
public SimpleScanner(string source) => _source = source;
1718

@@ -28,7 +29,8 @@ public class SimpleScanner {
2829
return false;
2930
}
3031

31-
// Return true if char at index matches chars, log char in Delim and advance index
32+
// Return true if char at index matches any of the chars and advance index
33+
// Also log the char matched in Delim for later access
3234
// Else return false and index unchanged
3335
public bool IsAnyCh(string chars) {
3436
if (!chars.Contains(PeekCh())) return false;
@@ -38,7 +40,7 @@ public class SimpleScanner {
3840
}
3941

4042
// Skip spaces
41-
public void SkipSp() { while (IsCh(' ')); }
43+
public void SkipSp() { while (IsCh(' ')) ; }
4244

4345
// Build and error message string showing error position
4446
public string ErrorMsg(string msg)
@@ -53,10 +55,12 @@ public static void DemoSimpleScanner1() {
5355
var scn = new SimpleScanner(" N3 N1N2-abc");
5456
var fex = new FlowExpression<SimpleScanner>();
5557

58+
// Grammar: (space* 'N' ('1' | '2' | '3'))+ '-' 'ab'? 'c'
59+
5660
var validNumber = fex.Seq(s => s
5761
.Op(c => c.IsAnyCh("123"))
5862
.OnFail(c => Console.WriteLine(c.ErrorMsg("1 2 or 3 expected")))
59-
.Act(c => Console.WriteLine($"Number = {c.Delim}"))
63+
.Act(c => Console.WriteLine($"N value = N{c.Delim}"))
6064
);
6165

6266
var after = fex.Seq(s => s
@@ -66,36 +70,44 @@ public static void DemoSimpleScanner1() {
6670

6771
var startRep = fex.Rep1N(r => r.Op(c => c.IsCh('N')).PreOp(p => p.SkipSp()).Fex(validNumber));
6872

69-
var test = fex.Seq(s => s.Fex(startRep).Op(c => c.IsCh('-')).Fex(after));
73+
var axiom = fex.Seq(s => s.Fex(startRep).Op(c => c.IsCh('-')).Fex(after));
7074

71-
if (test.Run(scn)) Console.WriteLine("Passed");
75+
if (axiom.Run(scn)) Console.WriteLine("Passed");
7276
else Console.WriteLine("Failed");
7377
}
7478
```
7579

7680
## Context Operator Extensions
77-
One can extend FexBuilder with operators (Op) specific to the context:
81+
One can extend FexBuilder with operators (Op) bound to the context:
7882
- FexBuilder\<T> (where T is the context) implements the *fluid* API for building flow expressions.
79-
- The few extension below can be defined for our SimpleScanner.
83+
- The few operator extension below can be defined for our SimpleScanner.
8084
- We can also defined extensions for *OnFail* and *Fail* to simplify things.
8185

8286
```csharp
83-
public static class FexSimpleScannerExt {
84-
85-
public static FexBuilder<T> Ch<T>(this FexBuilder<T> exp, char ch) where T : SimpleScanner
87+
public static class FexSimpleScannerExt
88+
{
89+
// Operator extension bound to scanner.IsCh(...)
90+
public static FexBuilder<T> Ch<T>(this FexBuilder<T> exp, char ch) where T : SimpleScanner
8691
=> exp.Op(c => c.IsCh(ch));
8792

88-
// Operator extension that records a value
89-
// Check for a char match and provide a valueAction Delegate on the value
93+
// Operator extension bound to scanner.IsAnyCh(...):
94+
// - IsAnyCh records the char found in scanner.Delim:
95+
// - So we record this value in the Op for access via ActValue<T>(Action<T> valueAction)
96+
// - Or, as below, we can directly provide an Action<char> delegate to operate on the value.
97+
// E.g. AnyCh("123", c => Console.WriteLine($"Number = {c}"))
98+
// rather than: AnyCh("123").ActValue<char>(c => Console.WriteLine($"Number = {c}"))
9099
public static FexBuilder<T> AnyCh<T>(this FexBuilder<T> exp, string matchChars, Action<char> valueAction = null) where T : SimpleScanner
91-
=> exp.Op((c, v) => v.SetValue(c.IsAnyCh(matchChars), c.Delim)).Value(valueAction);
100+
=> exp.Op((c, v) => v.SetValue(c.IsAnyCh(matchChars), c.Delim)).ActValue(valueAction);
92101

93-
public static FexBuilder<T> Sp<T>(this FexBuilder<T> exp) where T : SimpleScanner
102+
// Operator extension to skip spaces without ever failing.
103+
public static FexBuilder<T> Sp<T>(this FexBuilder<T> exp) where T : SimpleScanner
94104
=> exp.Op(c => { c.SkipSp(); return true; });
95-
105+
106+
// Override OnFail to produce console output
96107
public static FexBuilder<T> OnFail<T>(this FexBuilder<T> exp, string errorMsg) where T : SimpleScanner
97108
=> exp.OnFail(c => Console.WriteLine(c.ErrorMsg(errorMsg)));
98109

110+
// Override Fail to produce console output
99111
public static FexBuilder<T> Fail<T>(this FexBuilder<T> exp, string errorMsg) where T : SimpleScanner
100112
=> exp.Fail(c => Console.WriteLine(c.ErrorMsg(errorMsg)));
101113
}
@@ -104,25 +116,30 @@ public static class FexSimpleScannerExt {
104116
And now it may be used as follows (which is much easier to read and work with)
105117

106118
```csharp
107-
public static void DemoSimpleScanner2() {
108-
var scn = new SimpleScanner(" N3 N1N2-abc");
119+
public static void DemoSimpleScanner2(string source = " N3 N1N2-abc") {
120+
121+
// Grammar: (space* 'N' ('1' | '2' | '3'))+ '-' 'ab'? 'c'
122+
123+
Console.WriteLine($"Source = \"{source}\"");
124+
125+
var scn = new SimpleScanner(source);
109126
var fex = new FlowExpression<SimpleScanner>();
110127

111128
var validNumber = fex.Seq(s => s
112-
.AnyCh("123", v => Console.WriteLine($"Number = {v}"))
129+
.AnyCh("123", v => Console.WriteLine($"N value = N{v}"))
113130
.OnFail("1, 2 or 3 expected")
114131
);
115132

116133
var after = fex.Seq(s => s
117-
.Opt(o => o.Ch('a').Ch('b').OnFail("b expected"))
134+
.Opt(o => o.Ch('a').Ch('b').OnFail("b expected")) // If we have a then b must follow
118135
.Ch('c').OnFail("c expected")
119136
);
120137

121138
var startRep = fex.Rep1N(r => r.Ch('N').PreOp(p => p.SkipSp()).Fex(validNumber));
122139

123-
var test = fex.Seq(s => s.Fex(startRep).Ch('-').Fex(after));
140+
var axiom = fex.Seq(s => s.Fex(startRep).Ch('-').Fex(after));
124141

125-
if (test.Run(scn)) Console.WriteLine("Passed");
142+
if (axiom.Run(scn)) Console.WriteLine("Passed");
126143
else Console.WriteLine("Failed");
127144
}
128145
```

FexSampleSet/Program.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ void RunSamples() {
1313

1414
var samples = new List<Sample> {
1515
new Sample("Quick Start", () => QuickStart()),
16-
new Sample("Use Simple Scanner", () => DemoSimpleScanner()),
16+
new Sample("Simple Scanner (valid)", () => SSDemo.DemoSimpleScanner2(" N3 N1N2-abc")),
17+
new Sample("Simple Scanner (invalid)", () => SSDemo.DemoSimpleScanner2(" N3 N1N2-ac")),
1718
new Sample("Expression Eval", () => ExpressionEval()),
1819
new Sample("Expression REPL", () => ExpressionREPL()),
1920
};
@@ -48,7 +49,9 @@ void RunSamplesFex() {
4849

4950
var samples = new List<Sample> {
5051
new Sample("Quick Start", () => QuickStart()),
51-
new Sample("Use Simple Scanner", () => DemoSimpleScanner()),
52+
new Sample("Simple Scanner 1", () => SSDemo.DemoSimpleScanner1()),
53+
new Sample("Simple Scanner 2 (valid)", () => SSDemo.DemoSimpleScanner2(" N3 N1N3-abc")),
54+
new Sample("Simple Scanner 2 (invalid)", () => SSDemo.DemoSimpleScanner2(" N3 N1N2-ac")),
5255
new Sample("Expression Eval", () => ExpressionEval()),
5356
new Sample("Expression REPL", () => ExpressionREPL()),
5457
};
@@ -98,11 +101,13 @@ void RunSamplesFex() {
98101
void QuickStart() {
99102
/* Parse demo telephone number of the form:
100103
* (dialing_code) area_code-number: E.g (011) 734-9571
101-
* - dialing code: 3 or more digits enclosed in (..)
102-
* - Followed by optional spaces
103-
* - area_code: 3 digits
104-
* - Single space or -
105-
* - number: 4 digits
104+
*
105+
* Grammar: '(' (digit)3+ ')' space* (digit)3 (space | '-') (digit)4
106+
* - dialing code: 3 or more digits enclosed in (..)
107+
* - Followed by optional spaces
108+
* - area_code: 3 digits
109+
* - Single space or -
110+
* - number: 4 digits
106111
*/
107112

108113
var fex = new FlowExpression<FexScanner>(); // Flow Expression using FexScanner

FexSampleSet/SimpleScanner.cs

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ namespace FexExampleSet
1010
public class SimpleScanner
1111
{
1212
private string _source;
13-
public char Delim { get; private set; } // Last Delimiter logged
14-
private int _index = 0;
15-
protected static char _Eos = '0'; // End of source character
13+
public char Delim { get; private set; } // Last Delimiter logged
14+
private int _index = 0; // Scan pointer / index
15+
protected static char _Eos = '0'; // End of source character
1616

1717
public SimpleScanner(string source) => _source = source;
1818

@@ -29,7 +29,8 @@ public bool IsCh(char ch) {
2929
return false;
3030
}
3131

32-
// Return true if char at index matches chars, log char in Delim and advance index
32+
// Return true if char at index matches any of the chars and advance index
33+
// Also log the char matched in Delim for later access
3334
// Else return false and index unchanged
3435
public bool IsAnyCh(string chars) {
3536
if (!chars.Contains(PeekCh())) return false;
@@ -49,32 +50,85 @@ public string ErrorMsg(string msg)
4950
public static class FexSimpleScannerExt
5051
{
5152

53+
// Operator extension bound to scanner.IsCh(...)
5254
public static FexBuilder<T> Ch<T>(this FexBuilder<T> exp, char ch) where T : SimpleScanner
5355
=> exp.Op(c => c.IsCh(ch));
5456

55-
// Operator extension that records a value
56-
// Check for a char match and provide a valueAction Delegate on the value
57+
// Operator extension bound to scanner.IsAnyCh(...):
58+
// - IsAnyCh records the char found in scanner.Delim:
59+
// - So we record this value in the Op for access via ActValue<T>(Action<T> valueAction)
60+
// - Or, as below, we can directly provide an Action<char> delegate to operate on the value.
61+
// E.g. AnyCh("123", c => Console.WriteLine($"Number = {c}"))
62+
// rather than: AnyCh("123").ActValue<char>(c => Console.WriteLine($"Number = {c}"))
5763
public static FexBuilder<T> AnyCh<T>(this FexBuilder<T> exp, string matchChars, Action<char> valueAction = null) where T : SimpleScanner
5864
=> exp.Op((c, v) => v.SetValue(c.IsAnyCh(matchChars), c.Delim)).ActValue(valueAction);
5965

66+
// Operator extension to skip spaces without ever failing.
6067
public static FexBuilder<T> Sp<T>(this FexBuilder<T> exp) where T : SimpleScanner
6168
=> exp.Op(c => { c.SkipSp(); return true; });
6269

63-
public static FexBuilder<T> OptSp<T>(this FexBuilder<T> exp) where T : SimpleScanner
64-
=> exp.Opt(o => o.Op(c => {
65-
if (c.IsCh(' ')) {
66-
//while (c.IsCh(' ')) ;
67-
c.SkipSp();
68-
return true;
69-
}
70-
return false;
71-
}));
72-
70+
// Override OnFail to produce console output
7371
public static FexBuilder<T> OnFail<T>(this FexBuilder<T> exp, string errorMsg) where T : SimpleScanner
7472
=> exp.OnFail(c => Console.WriteLine(c.ErrorMsg(errorMsg)));
7573

74+
// Override Fail to produce console output
7675
public static FexBuilder<T> Fail<T>(this FexBuilder<T> exp, string errorMsg) where T : SimpleScanner
7776
=> exp.Fail(c => Console.WriteLine(c.ErrorMsg(errorMsg)));
7877
}
7978

79+
public static class SSDemo {
80+
81+
public static void DemoSimpleScanner1() {
82+
var scn = new SimpleScanner(" N3 N1N2-abc");
83+
var fex = new FlowExpression<SimpleScanner>();
84+
85+
// Grammar: (space* 'N' ('1' | '2' | '3'))+ '-' 'ab'? 'c'
86+
87+
var validNumber = fex.Seq(s => s
88+
.Op(c => c.IsAnyCh("123"))
89+
.OnFail(c => Console.WriteLine(c.ErrorMsg("1 2 or 3 expected")))
90+
.Act(c => Console.WriteLine($"N value = N{c.Delim}"))
91+
);
92+
93+
var after = fex.Seq(s => s
94+
.Opt(o => o.Op(c => c.IsCh('a')).Op(c => c.IsCh('b')).OnFail(c => Console.WriteLine(c.ErrorMsg("b expected"))))
95+
.Op(c => c.IsCh('c')).OnFail(c => Console.WriteLine(c.ErrorMsg("c expected")))
96+
);
97+
98+
var startRep = fex.Rep1N(r => r.Op(c => c.IsCh('N')).PreOp(p => p.SkipSp()).Fex(validNumber));
99+
100+
var axiom = fex.Seq(s => s.Fex(startRep).Op(c => c.IsCh('-')).Fex(after));
101+
102+
if (axiom.Run(scn)) Console.WriteLine("Passed");
103+
else Console.WriteLine("Failed");
104+
}
105+
106+
public static void DemoSimpleScanner2(string source = " N3 N1N2-abc") {
107+
108+
// Grammar: (space* 'N' ('1' | '2' | '3'))+ '-' 'ab'? 'c'
109+
110+
Console.WriteLine($"Source = \"{source}\"");
111+
112+
var scn = new SimpleScanner(source);
113+
var fex = new FlowExpression<SimpleScanner>();
114+
115+
var validNumber = fex.Seq(s => s
116+
.AnyCh("123", v => Console.WriteLine($"N value = N{v}"))
117+
.OnFail("1, 2 or 3 expected")
118+
);
119+
120+
var after = fex.Seq(s => s
121+
.Opt(o => o.Ch('a').Ch('b').OnFail("b expected")) // If we have a then b must follow
122+
.Ch('c').OnFail("c expected")
123+
);
124+
125+
var startRep = fex.Rep1N(r => r.Ch('N').PreOp(p => p.SkipSp()).Fex(validNumber));
126+
127+
var axiom = fex.Seq(s => s.Fex(startRep).Ch('-').Fex(after));
128+
129+
if (axiom.Run(scn)) Console.WriteLine("Passed");
130+
else Console.WriteLine("Failed");
131+
}
132+
}
133+
80134
}

0 commit comments

Comments
 (0)