9090 Terminal ,
9191)
9292from .grammar_utils import is_epsilon , rhs_elements
93+ from .proto_ast import ProtoMessage
9394from .target import (
9495 Assign ,
9596 BaseType ,
100101 ListExpr ,
101102 ListType ,
102103 Lit ,
104+ MessageType ,
103105 ParseNonterminal ,
104106 ParseNonterminalDef ,
105107 Seq ,
@@ -132,28 +134,55 @@ class AmbiguousGrammarError(Exception):
132134
133135
134136def generate_parse_functions (
135- grammar : Grammar , indent : str = ""
137+ grammar : Grammar ,
138+ indent : str = "" ,
139+ proto_messages : dict [tuple [str , str ], ProtoMessage ] | None = None ,
136140) -> list [ParseNonterminalDef ]:
137141 parser_methods = []
138142 reachable , _ = grammar .analysis .partition_nonterminals_by_reachability ()
139143 for nt in reachable :
140144 rules = grammar .rules [nt ]
141- method_code = _generate_parse_method (nt , rules , grammar , indent )
145+ method_code = _generate_parse_method (nt , rules , grammar , indent , proto_messages )
142146 parser_methods .append (method_code )
143147 return parser_methods
144148
145149
150+ def _wrap_with_span (body : TargetExpr , return_type ) -> TargetExpr :
151+ """Wrap a nonterminal body with span_start/record_span."""
152+ span_var = Var (gensym ("span_start" ), BaseType ("Int64" ))
153+ result_var = Var (gensym ("result" ), return_type )
154+ type_name = return_type .name if isinstance (return_type , MessageType ) else ""
155+ return Let (
156+ span_var ,
157+ Call (make_builtin ("span_start" ), []),
158+ Let (
159+ result_var ,
160+ body ,
161+ Seq (
162+ [
163+ Call (make_builtin ("record_span" ), [span_var , Lit (type_name )]),
164+ result_var ,
165+ ]
166+ ),
167+ ),
168+ )
169+
170+
146171def _generate_parse_method (
147- lhs : Nonterminal , rules : list [Rule ], grammar : Grammar , indent : str = ""
172+ lhs : Nonterminal ,
173+ rules : list [Rule ],
174+ grammar : Grammar ,
175+ indent : str = "" ,
176+ proto_messages : dict [tuple [str , str ], ProtoMessage ] | None = None ,
148177) -> ParseNonterminalDef :
149- """Generate parse method code as string (preserving existing logic) ."""
178+ """Generate parse method for a nonterminal with provenance tracking ."""
150179 return_type = None
151180 rhs = None
152181 follow_set = FollowSet (grammar , lhs )
153182 if len (rules ) == 1 :
154183 rule = rules [0 ]
155184 rhs = _generate_parse_rhs_ir (
156- rule .rhs , grammar , follow_set , True , rule .constructor
185+ rule .rhs , grammar , follow_set , True , rule .constructor , proto_messages
157186 )
158187 return_type = rule .constructor .return_type
159188 else :
@@ -171,7 +200,6 @@ def _generate_parse_method(
171200 ],
172201 )
173202 for i , rule in enumerate (rules ):
174- # Ensure the return type is the same for all actions for this nonterminal.
175203 assert return_type is None or return_type == rule .constructor .return_type , (
176204 f"Return type mismatch at rule { i } : { return_type } != { rule .constructor .return_type } "
177205 )
@@ -183,12 +211,19 @@ def _generate_parse_method(
183211 make_builtin ("equal" ), [Var (prediction , BaseType ("Int64" )), Lit (i )]
184212 ),
185213 _generate_parse_rhs_ir (
186- rule .rhs , grammar , follow_set , True , rule .constructor
214+ rule .rhs ,
215+ grammar ,
216+ follow_set ,
217+ True ,
218+ rule .constructor ,
219+ proto_messages ,
187220 ),
188221 tail ,
189222 )
190223 rhs = Let (Var (prediction , BaseType ("Int64" )), predictor , tail )
191224 assert return_type is not None
225+ if isinstance (return_type , MessageType ):
226+ rhs = _wrap_with_span (rhs , return_type )
192227 return ParseNonterminalDef (lhs , [], return_type , rhs , indent )
193228
194229
@@ -377,30 +412,19 @@ def _generate_parse_rhs_ir(
377412 follow_set : TerminalSequenceSet ,
378413 apply_action : bool = False ,
379414 action : Lambda | None = None ,
415+ proto_messages : dict [tuple [str , str ], ProtoMessage ] | None = None ,
380416) -> TargetExpr :
381- """Generate IR for parsing an RHS.
382-
383- Args:
384- rhs: The RHS to parse
385- grammar: The grammar
386- follow_set: TerminalSequenceSet for computing follow lazily
387- apply_action: Whether to apply the semantic action
388- action: The semantic action to apply (required if apply_action is True)
389-
390- Returns IR expression for leaf nodes (Literal, Terminal, Nonterminal).
391- Returns None for complex cases that still use string generation.
392- """
417+ """Generate IR for parsing an RHS with provenance tracking."""
393418 if isinstance (rhs , Sequence ):
394419 return _generate_parse_rhs_ir_sequence (
395- rhs , grammar , follow_set , apply_action , action
420+ rhs , grammar , follow_set , apply_action , action , proto_messages
396421 )
397422 elif isinstance (rhs , LitTerminal ):
398423 parse_expr = Call (make_builtin ("consume_literal" ), [Lit (rhs .name )])
399424 if apply_action and action :
400425 return Seq ([parse_expr , apply_lambda (action , [])])
401426 return parse_expr
402427 elif isinstance (rhs , NamedTerminal ):
403- # Use terminal's actual type for consume_terminal instead of generic Token
404428 from .target import FunctionType
405429
406430 terminal_type = rhs .target_type ()
@@ -427,14 +451,18 @@ def _generate_parse_rhs_ir(
427451 elif isinstance (rhs , Option ):
428452 assert grammar is not None
429453 predictor = _build_option_predictor (grammar , rhs .rhs , follow_set )
430- parse_result = _generate_parse_rhs_ir (rhs .rhs , grammar , follow_set , False , None )
454+ parse_result = _generate_parse_rhs_ir (
455+ rhs .rhs , grammar , follow_set , False , None , proto_messages
456+ )
431457 return IfElse (predictor , Call (make_builtin ("some" ), [parse_result ]), Lit (None ))
432458 elif isinstance (rhs , Star ):
433459 assert grammar is not None
434460 xs = Var (gensym ("xs" ), ListType (rhs .rhs .target_type ()))
435461 cond = Var (gensym ("cond" ), BaseType ("Boolean" ))
436462 predictor = _build_option_predictor (grammar , rhs .rhs , follow_set )
437- parse_item = _generate_parse_rhs_ir (rhs .rhs , grammar , follow_set , False , None )
463+ parse_item = _generate_parse_rhs_ir (
464+ rhs .rhs , grammar , follow_set , False , None , proto_messages
465+ )
438466 item = Var (gensym ("item" ), rhs .rhs .target_type ())
439467 loop_body = Seq (
440468 [
@@ -458,6 +486,7 @@ def _generate_parse_rhs_ir_sequence(
458486 follow_set : TerminalSequenceSet ,
459487 apply_action : bool = False ,
460488 action : Lambda | None = None ,
489+ proto_messages : dict [tuple [str , str ], ProtoMessage ] | None = None ,
461490) -> TargetExpr :
462491 if is_epsilon (rhs ):
463492 return Lit (None )
@@ -473,7 +502,9 @@ def _generate_parse_rhs_ir_sequence(
473502 follow_set_i = ConcatSet (first_following , follow_set )
474503 else :
475504 follow_set_i = follow_set
476- elem_ir = _generate_parse_rhs_ir (elem , grammar , follow_set_i , False , None )
505+ elem_ir = _generate_parse_rhs_ir (
506+ elem , grammar , follow_set_i , False , None , proto_messages
507+ )
477508 if isinstance (elem , LitTerminal ):
478509 exprs .append (elem_ir )
479510 else :
@@ -494,13 +525,10 @@ def _generate_parse_rhs_ir_sequence(
494525 lambda_call = apply_lambda (action , arg_vars )
495526 exprs .append (lambda_call )
496527 elif len (arg_vars ) > 1 :
497- # Multiple values - wrap in tuple
498528 exprs .append (Call (make_builtin ("tuple" ), arg_vars ))
499529 elif len (arg_vars ) == 1 :
500- # Single value - return the variable
501530 exprs .append (arg_vars [0 ])
502531 else :
503- # no non-literal elements, return None
504532 return Lit (None )
505533
506534 if len (exprs ) == 1 :
0 commit comments