@@ -80,104 +80,108 @@ def _ast_to_railroad(ast_node):
8080 OneOrMore , zero_or_more , optional
8181 )
8282
83- # Handle different AST node types
84- if isinstance (ast_node , Literal ):
85- # Handle literal characters
86- if ast_node .value in ['^' , '$' , '@' , '.' , '*' , '+' , '?' , '|' , '(' , ')' , '[' , ']' , '{' , '}' , '\\ ' ]:
87- return Terminal (ast_node .value )
88- else :
89- return Terminal (f"'{ ast_node .value } '" )
90-
91- elif isinstance (ast_node , CharClass ):
92- return Terminal (f"[{ ast_node .value } ]" )
93-
94- elif isinstance (ast_node , Quantifier ):
95- child = _ast_to_railroad (ast_node .child )
96- if ast_node .quant == '*' :
97- return zero_or_more (child )
98- elif ast_node .quant == '+' :
99- return OneOrMore (child )
100- elif ast_node .quant == '?' :
101- return optional (child )
102- else :
103- # Handle other quantifiers like {n}, {n,m}, etc.
104- return Terminal (f"{ child } { ast_node .quant } " )
105-
106- elif isinstance (ast_node , ParserSequence ):
107- # Convert each element in the sequence
108- elements = [_ast_to_railroad (elem ) for elem in ast_node .elements ]
109- if len (elements ) == 1 :
110- return elements [0 ]
111- else :
112- return Sequence (* elements )
113-
114- elif isinstance (ast_node , Alternation ):
115- alternatives = [_ast_to_railroad (alt ) for alt in ast_node .options ]
116- if len (alternatives ) == 1 :
117- return alternatives [0 ]
118- else :
119- return Choice (0 , * alternatives )
120-
121- elif isinstance (ast_node , Anchor ):
122- return Terminal (ast_node .value )
123-
124- elif isinstance (ast_node , Escape ):
125- # Handle escape sequences with meaningful labels
126- escape_mapping = {
127- '\\ w' : 'word char' ,
128- '\\ d' : 'digit' ,
129- '\\ s' : 'whitespace' ,
130- '\\ W' : 'non-word char' ,
131- '\\ D' : 'non-digit' ,
132- '\\ S' : 'non-whitespace' ,
133- '\\ b' : 'word boundary' ,
134- '\\ B' : 'non-word boundary' ,
135- '\\ A' : 'start of string' ,
136- '\\ Z' : 'end of string' ,
137- '\\ z' : 'end of string' ,
138- '\\ G' : 'end of prev match' ,
139- '\\ n' : 'newline' ,
140- '\\ r' : 'carriage return' ,
141- '\\ t' : 'tab' ,
142- '\\ f' : 'form feed' ,
143- '\\ v' : 'vertical tab' ,
144- '\\ u' : 'unicode char' ,
145- '\\ x' : 'hex char' ,
146- '\\ N' : 'named char' ,
147- }
83+ try :
84+ # Handle different AST node types
85+ if isinstance (ast_node , Literal ):
86+ # Handle literal characters
87+ if ast_node .value in ['^' , '$' , '@' , '.' , '*' , '+' , '?' , '|' , '(' , ')' , '[' , ']' , '{' , '}' , '\\ ' ]:
88+ return Terminal (ast_node .value )
89+ else :
90+ return Terminal (f"'{ ast_node .value } '" )
14891
149- # Check if it's a known escape sequence
150- if ast_node .value in escape_mapping :
151- return Terminal (escape_mapping [ast_node .value ])
152- else :
153- # For unknown escape sequences, show the raw value
92+ elif isinstance (ast_node , CharClass ):
93+ return Terminal (f"[{ ast_node .value } ]" )
94+
95+ elif isinstance (ast_node , Quantifier ):
96+ child = _ast_to_railroad (ast_node .child )
97+ if ast_node .quant == '*' :
98+ return zero_or_more (child )
99+ elif ast_node .quant == '+' :
100+ return OneOrMore (child )
101+ elif ast_node .quant == '?' :
102+ return optional (child )
103+ else :
104+ # Handle other quantifiers like {n}, {n,m}, etc.
105+ return Terminal (f"{ child } { ast_node .quant } " )
106+
107+ elif isinstance (ast_node , ParserSequence ):
108+ # Convert each element in the sequence
109+ elements = [_ast_to_railroad (elem ) for elem in ast_node .elements ]
110+ if len (elements ) == 1 :
111+ return elements [0 ]
112+ else :
113+ return Sequence (* elements )
114+
115+ elif isinstance (ast_node , Alternation ):
116+ alternatives = [_ast_to_railroad (alt ) for alt in ast_node .options ]
117+ if len (alternatives ) == 1 :
118+ return alternatives [0 ]
119+ else :
120+ return Choice (0 , * alternatives )
121+
122+ elif isinstance (ast_node , Anchor ):
154123 return Terminal (ast_node .value )
155-
156- elif isinstance (ast_node , Group ):
157- # Handle groups - for now, just show the group type
158- if ast_node .children :
159- children = [_ast_to_railroad (child ) for child in ast_node .children ]
160- if len (children ) == 1 :
161- return children [0 ]
124+
125+ elif isinstance (ast_node , Escape ):
126+ # Handle escape sequences with meaningful labels
127+ escape_mapping = {
128+ '\\ w' : 'word char' ,
129+ '\\ d' : 'digit' ,
130+ '\\ s' : 'whitespace' ,
131+ '\\ W' : 'non-word char' ,
132+ '\\ D' : 'non-digit' ,
133+ '\\ S' : 'non-whitespace' ,
134+ '\\ b' : 'word boundary' ,
135+ '\\ B' : 'non-word boundary' ,
136+ '\\ A' : 'start of string' ,
137+ '\\ Z' : 'end of string' ,
138+ '\\ z' : 'end of string' ,
139+ '\\ G' : 'end of prev match' ,
140+ '\\ n' : 'newline' ,
141+ '\\ r' : 'carriage return' ,
142+ '\\ t' : 'tab' ,
143+ '\\ f' : 'form feed' ,
144+ '\\ v' : 'vertical tab' ,
145+ '\\ u' : 'unicode char' ,
146+ '\\ x' : 'hex char' ,
147+ '\\ N' : 'named char' ,
148+ }
149+
150+ # Check if it's a known escape sequence
151+ if ast_node .value in escape_mapping :
152+ return Terminal (escape_mapping [ast_node .value ])
162153 else :
163- return Sequence (* children )
164- else :
165- # Empty group
166- if ast_node .group_type == 'GROUP_NONCAP' :
167- return Terminal ('(?:)' )
168- elif ast_node .group_type == 'GROUP_NAMED' :
169- return Terminal (f'(?P<{ ast_node .name } >)' )
170- elif ast_node .group_type == 'GROUP_LOOKAHEAD' :
171- return Terminal ('(?=)' )
172- elif ast_node .group_type == 'GROUP_NEG_LOOKAHEAD' :
173- return Terminal ('(?!)' )
174- elif ast_node .group_type == 'GROUP_LOOKBEHIND' :
175- return Terminal ('(?<=)' )
176- elif ast_node .group_type == 'GROUP_NEG_LOOKBEHIND' :
177- return Terminal ('(?<!)' )
154+ # For unknown escape sequences, show the raw value
155+ return Terminal (ast_node .value )
156+
157+ elif isinstance (ast_node , Group ):
158+ # Handle groups - for now, just show the group type
159+ if ast_node .children :
160+ children = [_ast_to_railroad (child ) for child in ast_node .children ]
161+ if len (children ) == 1 :
162+ return children [0 ]
163+ else :
164+ return Sequence (* children )
178165 else :
179- return Terminal ('()' )
166+ # Empty group
167+ if ast_node .group_type == 'GROUP_NONCAP' :
168+ return Terminal ('(?:)' )
169+ elif ast_node .group_type == 'GROUP_NAMED' :
170+ return Terminal (f'(?P<{ ast_node .name } >)' )
171+ elif ast_node .group_type == 'GROUP_LOOKAHEAD' :
172+ return Terminal ('(?=)' )
173+ elif ast_node .group_type == 'GROUP_NEG_LOOKAHEAD' :
174+ return Terminal ('(?!)' )
175+ elif ast_node .group_type == 'GROUP_LOOKBEHIND' :
176+ return Terminal ('(?<=)' )
177+ elif ast_node .group_type == 'GROUP_NEG_LOOKBEHIND' :
178+ return Terminal ('(?<!)' )
179+ else :
180+ return Terminal ('()' )
181+
182+ else :
183+ # Fallback for unknown types
184+ return Terminal (str (ast_node ))
180185
181- else :
182- # Fallback for unknown types
186+ except Exception as e :
183187 return Terminal (str (ast_node ))
0 commit comments