@@ -16,6 +16,7 @@ public sealed class Then<T, U> : Parser<U>, ICompilable, ISeekable
1616{
1717 private readonly Func < T , U > ? _action1 ;
1818 private readonly Func < ParseContext , T , U > ? _action2 ;
19+ private readonly Func < ParseContext , int , int , T , U > ? _action3 ;
1920 private readonly U ? _value ;
2021 private readonly Parser < T > _parser ;
2122
@@ -41,6 +42,11 @@ public Then(Parser<T> parser, Func<ParseContext, T, U> action) : this(parser)
4142 _action2 = action ?? throw new ArgumentNullException ( nameof ( action ) ) ;
4243 }
4344
45+ public Then ( Parser < T > parser , Func < ParseContext , int , int , T , U > action ) : this ( parser )
46+ {
47+ _action3 = action ?? throw new ArgumentNullException ( nameof ( action ) ) ;
48+ }
49+
4450 public Then ( Parser < T > parser , U value ) : this ( parser )
4551 {
4652 _value = value ;
@@ -68,9 +74,13 @@ public override bool Parse(ParseContext context, ref ParseResult<U> result)
6874 {
6975 result . Set ( parsed . Start , parsed . End , _action2 . Invoke ( context , parsed . Value ) ) ;
7076 }
77+ else if ( _action3 != null )
78+ {
79+ result . Set ( parsed . Start , parsed . End , _action3 . Invoke ( context , parsed . Start , parsed . End , parsed . Value ) ) ;
80+ }
7181 else
7282 {
73- // _value can't be null if action1 and action2 are null
83+ // _value can't be null if action1, action2, and action3 are null
7484 result . Set ( parsed . Start , parsed . End , _value ! ) ;
7585 }
7686
@@ -88,12 +98,27 @@ public CompilationResult Compile(CompilationContext context)
8898
8999 // parse1 instructions
90100 //
101+ // var startOffset = context.Scanner.Cursor.Offset; // Only for _action3
102+ // parser1 body (which may include whitespace skipping for Terms)
91103 // if (parser1.Success)
92104 // {
105+ // var endOffset = context.Scanner.Cursor.Offset; // Only for _action3
93106 // success = true;
94- // value = action(parse1.Value);
107+ // value = action(parse1.Value) // or action(context, start, end, parse1.Value) for _action3
95108 // }
96109
110+ ParameterExpression ? startOffset = null ;
111+ ParameterExpression ? endOffset = null ;
112+
113+ if ( _action3 != null )
114+ {
115+ // Capture the start offset before the parser runs
116+ // Note: For Terms parsers (which skip whitespace), this will be before whitespace is skipped
117+ // This differs from non-compiled mode where parsed.Start is after whitespace skipping
118+ startOffset = result . DeclareVariable < int > ( $ "startOffset{ context . NextNumber } ", context . Offset ( ) ) ;
119+ endOffset = result . DeclareVariable < int > ( $ "endOffset{ context . NextNumber } ") ;
120+ }
121+
97122 var parserCompileResult = _parser . Build ( context , requireResult : true ) ;
98123
99124 Expression assignValue ;
@@ -110,6 +135,16 @@ public CompilationResult Compile(CompilationContext context)
110135 ? Expression . Invoke ( Expression . Constant ( _action2 ) , [ context . ParseContext , parserCompileResult . Value ] )
111136 : Expression . Assign ( result . Value , Expression . Invoke ( Expression . Constant ( _action2 ) , [ context . ParseContext , parserCompileResult . Value ] ) ) ;
112137 }
138+ else if ( _action3 != null )
139+ {
140+ // Capture end offset when parser succeeds, then invoke the action
141+ assignValue = Expression . Block (
142+ Expression . Assign ( endOffset ! , context . Offset ( ) ) ,
143+ context . DiscardResult
144+ ? Expression . Invoke ( Expression . Constant ( _action3 ) , [ context . ParseContext , startOffset ! , endOffset ! , parserCompileResult . Value ] )
145+ : Expression . Assign ( result . Value , Expression . Invoke ( Expression . Constant ( _action3 ) , [ context . ParseContext , startOffset ! , endOffset ! , parserCompileResult . Value ] ) )
146+ ) ;
147+ }
113148 else
114149 {
115150 assignValue = context . DiscardResult
0 commit comments