-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgrammar.py
More file actions
118 lines (100 loc) · 4.11 KB
/
Copy pathgrammar.py
File metadata and controls
118 lines (100 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from ai_client import GeminiClient
_ai_client = GeminiClient()
class Grammar:
def __init__(self):
self.productions = {}
self.non_terminals = set()
self.terminals = set()
self.start_symbol = None
def add_production(self, lhs, rhs_list):
if self.start_symbol is None:
self.start_symbol = lhs
self.non_terminals.add(lhs)
if lhs not in self.productions:
self.productions[lhs] = []
for rhs in rhs_list:
symbols = rhs.split()
self.productions[lhs].append(symbols)
for symbol in symbols:
if symbol == "^":
continue
if symbol[0].isupper():
self.non_terminals.add(symbol)
else:
self.terminals.add(symbol)
def validate(self):
errors = []
warnings = []
if not self.productions:
errors.append("Grammar has no productions.")
return errors, warnings
# 1. Undefined non-terminals
defined_nts = set(self.productions.keys())
undefined = self.non_terminals - defined_nts
if undefined:
errors.append(
f"Undefined non-terminals (used in RHS but no production): {', '.join(undefined)}"
)
# 2. Reachability from start symbol
if self.start_symbol:
reachable = {self.start_symbol}
queue = [self.start_symbol]
while queue:
current = queue.pop(0)
if current in self.productions:
for rhs in self.productions[current]:
for symbol in rhs:
if symbol in self.non_terminals and symbol not in reachable:
reachable.add(symbol)
queue.append(symbol)
unreachable = defined_nts - reachable
if unreachable:
warnings.append(
f"Unreachable non-terminals from start symbol '{self.start_symbol}': "
f"{', '.join(unreachable)}"
)
else:
errors.append("Start symbol not defined.")
# 3. Non-terminating non-terminals (productivity)
productive = set()
changed = True
while changed:
changed = False
for lhs, rhs_list in self.productions.items():
if lhs in productive:
continue
for rhs in rhs_list:
if all(
symbol in self.terminals or symbol in productive or symbol == "^"
for symbol in rhs
):
productive.add(lhs)
changed = True
break
non_productive = defined_nts - productive
if non_productive:
errors.append(
f"Non-terminating non-terminals (cannot derive a string of terminals): "
f"{', '.join(non_productive)}"
)
return errors, warnings
def aivalidate(self) -> str:
"""Send grammar to Claude for AI-assisted analysis."""
# Represent grammar as text (convert ^ → ε for AI readability)
grammar_text = "\n".join(
f"{lhs} -> {' | '.join(' '.join(sym if sym != '^' else 'ε' for sym in rhs) for rhs in rhss)}"
for lhs, rhss in self.productions.items()
)
return _ai_client.analyze_grammar(grammar_text)
def aiask(self, previous_analysis: str, question: str) -> str:
"""Ask a follow-up question about this grammar."""
grammar_text = "\n".join(
f"{lhs} -> {' | '.join(' '.join(sym if sym != '^' else 'ε' for sym in rhs) for rhs in rhss)}"
for lhs, rhss in self.productions.items()
)
return _ai_client.ask_followup(grammar_text, previous_analysis, question)
def display(self):
print("\nGrammar:")
for lhs in self.productions:
right = [" ".join(prod) for prod in self.productions[lhs]]
print(f"{lhs} -> {' | '.join(right)}")