Skip to content

Commit 1cac330

Browse files
committed
Bugfix for Diagram Feature
1 parent 181e1e8 commit 1cac330

File tree

3 files changed

+112
-97
lines changed

3 files changed

+112
-97
lines changed

src/rexplain/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.3.2"
1+
__version__ = "0.3.3"
22

33
from .core.explainer import RegexExplainer
44
from .core.generator import ExampleGenerator

src/rexplain/cli/main.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@
1010
from rexplain.core.diagram import generate_railroad_diagram, generate_detailed_railroad_diagram
1111
from rexplain import __version__
1212
except ImportError as e:
13-
13+
# Try alternative import paths for when running as installed package
14+
try:
15+
# Add current directory to path for development
16+
import sys
17+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
18+
from rexplain.core.explainer import RegexExplainer
19+
from rexplain.core.generator import ExampleGenerator
20+
from rexplain.core.tester import RegexTester
21+
from rexplain.core.diagram import generate_railroad_diagram, generate_detailed_railroad_diagram
22+
from rexplain import __version__
23+
except ImportError as e2:
24+
print(f"IMPORT ERROR: {e2}", file=sys.stderr)
1425
# Stubs for development if core modules are missing
1526
class RegexExplainer:
1627
def explain(self, pattern):

src/rexplain/core/diagram.py

Lines changed: 99 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)