2222 "constant" : "PyBaseObject_Type" ,
2323}
2424
25+ AST_EVENT_HELPER_C = r"""
26+ #include <stdio.h>
27+ #include <string.h>
28+ #include <stdlib.h>
29+ #include "firmament2.h"
30+
31+ /* Emit one AST event line, gated by FIRMAMENT2_ENABLE. */
32+ static void
33+ emit_ast_event_json(const char *kind,
34+ int lineno, int col_offset,
35+ int end_lineno, int end_col_offset)
36+ {
37+ if (!_firm2_enabled()) {
38+ return;
39+ }
40+
41+ /* Envelope */
42+ unsigned long long eid = _firm2_next_eid();
43+ unsigned long pid = _firm2_pid();
44+ unsigned long long tid = _firm2_tid();
45+ long long ts = _firm2_now_ns();
46+
47+ /* Source scope */
48+ const char *filename = _firm2_current_filename();
49+ const char *source_id = _firm2_current_source_id_hex();
50+ if (!filename) filename = "<unknown>";
51+ if (!source_id) source_id = "";
52+
53+ char json_buf[640];
54+ (void)snprintf(
55+ json_buf,
56+ sizeof(json_buf),
57+ "{"
58+ "\"type\":\"ast\","
59+ "\"envelope\":{"
60+ "\"event_id\":%llu,"
61+ "\"pid\":%lu,"
62+ "\"tid\":%llu,"
63+ "\"ts_ns\":%lld"
64+ "},"
65+ "\"payload\":{"
66+ "\"kind\":\"%s\","
67+ "\"lineno\":%d,"
68+ "\"col_offset\":%d,"
69+ "\"end_lineno\":%d,"
70+ "\"end_col_offset\":%d,"
71+ "\"filename\":\"%s\","
72+ "\"source_id\":\"%s\""
73+ "}"
74+ "}",
75+ eid, pid, tid, ts,
76+ kind,
77+ lineno, col_offset, end_lineno, end_col_offset,
78+ filename, source_id
79+ );
80+ printf("%s\n", json_buf);
81+ fflush(stdout);
82+ }
83+ """
84+
2585def get_c_type (name ):
2686 """Return a string for the C name of the type.
2787
@@ -407,61 +467,78 @@ def visitProduct(self, prod, name):
407467 self .get_args (prod .attributes ),
408468 union = False )
409469
410-
411470class FunctionVisitor (PrototypeVisitor ):
412471 """Visitor to generate constructor functions for AST."""
413472
414473 def emit_function (self , name , ctype , args , attrs , union = True ):
415474 def emit (s , depth = 0 , reflow = True ):
416475 self .emit (s , depth , reflow )
417- argstr = ", " .join (["%s %s" % (atype , aname )
418- for atype , aname , opt in args + attrs ])
476+
477+ # Build full C argument list (fields + attributes)
478+ all_args = args + attrs
479+ argstr = ", " .join (f"{ atype } { aname } " for atype , aname , opt in all_args )
419480 if argstr :
420481 argstr += ", PyArena *arena"
421482 else :
422483 argstr = "PyArena *arena"
423- self .emit ("%s" % ctype , 0 )
424- emit ("%s(%s)" % (ast_func_name (name ), argstr ))
484+
485+ # Function signature
486+ self .emit (f"{ ctype } " , 0 )
487+ emit (f"{ ast_func_name (name )} ({ argstr } )" )
425488 emit ("{" )
426- emit ("%s p;" % ctype , 1 )
489+ emit (f"{ ctype } p;" , 1 )
490+
491+ # Required argument checks (non-optional, non-int)
427492 for argtype , argname , opt in args :
428493 if not opt and argtype != "int" :
429- emit ("if (!%s ) {" % argname , 1 )
494+ emit (f "if (!{ argname } ) {{" , 1 )
430495 emit ("PyErr_SetString(PyExc_ValueError," , 2 )
431- msg = "field '%s' is required for %s" % (argname , name )
432- emit (' "%s");' % msg ,
433- 2 , reflow = False )
434- emit ('return NULL;' , 2 )
435- emit ('}' , 1 )
496+ msg = f"field '{ argname } ' is required for { name } "
497+ emit (f' "{ msg } ");' , 2 , reflow = False )
498+ emit ("return NULL;" , 2 )
499+ emit ("}" , 1 )
436500
437- emit ("p = (%s)_PyArena_Malloc(arena, sizeof(*p));" % ctype , 1 );
501+ # Allocate node
502+ emit (f"p = ({ ctype } )_PyArena_Malloc(arena, sizeof(*p));" , 1 )
438503 emit ("if (!p)" , 1 )
439- emit ("return NULL;" , 2 )
504+ emit (" return NULL;" , 2 )
505+
506+ # Initialize node fields and attributes
440507 if union :
441508 self .emit_body_union (name , args , attrs )
442509 else :
443510 self .emit_body_struct (name , args , attrs )
511+
512+ # Emit JSON event for nodes with location info
513+ attr_names = {aname for _ , aname , _ in attrs }
514+ if "lineno" in attr_names and "col_offset" in attr_names :
515+ end_lineno_expr = "end_lineno" if "end_lineno" in attr_names else "lineno"
516+ end_col_expr = "end_col_offset" if "end_col_offset" in attr_names else "col_offset"
517+ emit (
518+ f'emit_ast_event_json("{ name } ", lineno, col_offset, { end_lineno_expr } , { end_col_expr } );' ,
519+ 1 , reflow = False
520+ )
521+
444522 emit ("return p;" , 1 )
445523 emit ("}" )
446524 emit ("" )
447525
448526 def emit_body_union (self , name , args , attrs ):
449527 def emit (s , depth = 0 , reflow = True ):
450528 self .emit (s , depth , reflow )
451- emit ("p->kind = %s_kind;" % name , 1 )
529+ emit (f "p->kind = { name } _kind;" , 1 )
452530 for argtype , argname , opt in args :
453- emit ("p->v.%s.%s = %s;" % ( name , argname , argname ) , 1 )
531+ emit (f "p->v.{ name } . { argname } = { argname } ;" , 1 )
454532 for argtype , argname , opt in attrs :
455- emit ("p->%s = %s;" % ( argname , argname ) , 1 )
533+ emit (f "p->{ argname } = { argname } ;" , 1 )
456534
457535 def emit_body_struct (self , name , args , attrs ):
458536 def emit (s , depth = 0 , reflow = True ):
459537 self .emit (s , depth , reflow )
460538 for argtype , argname , opt in args :
461- emit ("p->%s = %s;" % ( argname , argname ) , 1 )
539+ emit (f "p->{ argname } = { argname } ;" , 1 )
462540 for argtype , argname , opt in attrs :
463- emit ("p->%s = %s;" % (argname , argname ), 1 )
464-
541+ emit (f"p->{ argname } = { argname } ;" , 1 )
465542
466543class PickleVisitor (EmitVisitor ):
467544
@@ -1009,7 +1086,7 @@ def visitModule(self, mod):
10091086 else {
10101087 if (PyErr_WarnFormat(
10111088 PyExc_DeprecationWarning, 1,
1012- "Field %R is missing from %.400s._field_types. "
1089+ "Field '%U' is missing from %.400s._field_types. "
10131090 "This will become an error in Python 3.15.",
10141091 name, Py_TYPE(self)->tp_name
10151092 ) < 0) {
@@ -1044,7 +1121,7 @@ def visitModule(self, mod):
10441121 // simple field (e.g., identifier)
10451122 if (PyErr_WarnFormat(
10461123 PyExc_DeprecationWarning, 1,
1047- "%.400s.__init__ missing 1 required positional argument: %R . "
1124+ "%.400s.__init__ missing 1 required positional argument: '%U' . "
10481125 "This will become an error in Python 3.15.",
10491126 Py_TYPE(self)->tp_name, name
10501127 ) < 0) {
@@ -2249,7 +2326,6 @@ def generate_ast_state(module_state, f):
22492326 f .write (' PyObject *' + s + ';\n ' )
22502327 f .write ('};' )
22512328
2252-
22532329def generate_ast_fini (module_state , f ):
22542330 f .write (textwrap .dedent ("""
22552331 void _PyAST_Fini(PyInterpreterState *interp)
@@ -2266,7 +2342,6 @@ def generate_ast_fini(module_state, f):
22662342
22672343 """ ))
22682344
2269-
22702345def generate_module_def (mod , metadata , f , internal_h ):
22712346 # Gather all the data needed for ModuleSpec
22722347 state_strings = {
@@ -2326,6 +2401,9 @@ def generate_module_def(mod, metadata, f, internal_h):
23262401 }
23272402 """ ).strip (), file = f )
23282403
2404+ # Firmament2: helper used by generated _PyAST_* constructors.
2405+ f .write (AST_EVENT_HELPER_C )
2406+
23292407 generate_ast_fini (module_state , f )
23302408
23312409 f .write ('static int init_identifiers(struct ast_state *state)\n ' )
@@ -2337,6 +2415,7 @@ def generate_module_def(mod, metadata, f, internal_h):
23372415 f .write (' return 0;\n ' )
23382416 f .write ('};\n \n ' )
23392417
2418+
23402419def write_header (mod , metadata , f ):
23412420 f .write (textwrap .dedent ("""
23422421 #ifndef Py_INTERNAL_AST_H
0 commit comments