@@ -7,11 +7,12 @@ This tutorial describes how to construct a custom Scanner for use with Flow Expr
77The 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 {
104116And 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```
0 commit comments