Update app.py
Browse files
app.py
CHANGED
@@ -165,74 +165,65 @@ def createParseTable():
|
|
165 |
return parse_table, grammar_is_LL
|
166 |
|
167 |
def validateStringUsingStackBuffer(parse_table, input_string, start_sym):
|
168 |
-
|
169 |
-
|
170 |
# Initialize stack and input buffer
|
171 |
stack = [start_sym, '$']
|
172 |
input_tokens = input_string.split()
|
173 |
input_tokens.append('$')
|
174 |
buffer = input_tokens
|
175 |
|
176 |
-
|
177 |
-
|
178 |
|
179 |
-
parsing_steps = []
|
180 |
-
parsing_steps.append(f"Stack: {stack}, Input: {buffer}")
|
181 |
-
|
182 |
-
# Processing loop
|
183 |
while stack and buffer:
|
184 |
-
print(f"\nCurrent stack: {stack}") # Debug print
|
185 |
-
print(f"Current buffer: {buffer}") # Debug print
|
186 |
-
|
187 |
-
# Get top of stack and current input
|
188 |
top_stack = stack[0]
|
189 |
current_input = buffer[0]
|
190 |
|
191 |
-
|
192 |
|
193 |
-
# Case 1: Match found
|
194 |
if top_stack == current_input:
|
195 |
stack.pop(0)
|
196 |
buffer.pop(0)
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
# Case 2: Non-terminal on top
|
201 |
-
if top_stack in parse_table:
|
202 |
if current_input in parse_table[top_stack]:
|
203 |
production = parse_table[top_stack][current_input]
|
204 |
-
|
205 |
if production:
|
206 |
# Pop the non-terminal
|
207 |
stack.pop(0)
|
208 |
# Push the production in reverse (if it's not epsilon)
|
209 |
if production != ['#']:
|
210 |
stack = production + stack
|
211 |
-
|
|
|
212 |
else:
|
213 |
-
return False, f"No production for {top_stack} with input {current_input}"
|
214 |
else:
|
215 |
-
return False, f"Input symbol {current_input} not in parse table for {top_stack}"
|
216 |
else:
|
217 |
-
|
218 |
-
return False, f"Unexpected symbol {top_stack} on stack", parsing_steps
|
219 |
-
|
220 |
-
print(f"Updated stack: {stack}") # Debug print
|
221 |
|
222 |
-
# Check if both stack and buffer are empty (except for $)
|
223 |
if len(stack) <= 1 and len(buffer) <= 1:
|
224 |
-
return True, "String accepted"
|
225 |
else:
|
226 |
-
return False, "String rejected - incomplete parse"
|
227 |
|
228 |
# Streamlit UI
|
229 |
st.title("LL(1) Grammar Analyzer")
|
230 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
231 |
# Input section
|
232 |
st.header("Grammar Input")
|
233 |
start_symbol = st.text_input("Enter Start Symbol:", "S")
|
234 |
|
235 |
-
with st.expander("Enter Grammar Rules"):
|
236 |
num_rules = st.number_input("Number of Rules:", min_value=1, value=4)
|
237 |
rules = []
|
238 |
for i in range(num_rules):
|
@@ -243,14 +234,14 @@ with st.expander("Enter Grammar Rules"):
|
|
243 |
nonterm_input = st.text_input("Enter Non-terminals (comma-separated):", "S,A,B,C")
|
244 |
term_input = st.text_input("Enter Terminals (comma-separated):", "a,b,c,d,k,r,O")
|
245 |
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
if st.button("Analyze Grammar"):
|
252 |
# Clear previous data
|
253 |
diction.clear()
|
|
|
|
|
254 |
|
255 |
# Process rules
|
256 |
for rule in rules:
|
@@ -260,9 +251,10 @@ if st.button("Analyze Grammar"):
|
|
260 |
rhs_parts = [x.strip().split() for x in rhs.split("|")]
|
261 |
diction[lhs] = rhs_parts
|
262 |
|
263 |
-
#
|
264 |
-
st.subheader("Grammar
|
265 |
-
|
|
|
266 |
st.write("After removing left recursion:")
|
267 |
diction = removeLeftRecursion(diction)
|
268 |
st.write(diction)
|
@@ -275,20 +267,21 @@ if st.button("Analyze Grammar"):
|
|
275 |
computeAllFirsts()
|
276 |
computeAllFollows()
|
277 |
|
278 |
-
with st.expander("
|
279 |
st.write("FIRST Sets:", {k: list(v) for k, v in firsts.items()})
|
280 |
st.write("FOLLOW Sets:", {k: list(v) for k, v in follows.items()})
|
281 |
|
282 |
-
# Create
|
283 |
parse_table, grammar_is_LL = createParseTable()
|
|
|
284 |
|
|
|
285 |
st.subheader("Parse Table")
|
286 |
-
# Convert parse table to pandas DataFrame for better display
|
287 |
df_data = []
|
288 |
terminals = term_userdef + ['$']
|
289 |
|
290 |
for non_term in parse_table:
|
291 |
-
row = [non_term]
|
292 |
for term in terminals:
|
293 |
production = parse_table[non_term].get(term, "")
|
294 |
if production:
|
@@ -305,19 +298,79 @@ if st.button("Analyze Grammar"):
|
|
305 |
else:
|
306 |
st.error("This grammar is not LL(1)!")
|
307 |
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
313 |
|
314 |
-
#
|
315 |
-
if
|
316 |
-
st.
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
return parse_table, grammar_is_LL
|
166 |
|
167 |
def validateStringUsingStackBuffer(parse_table, input_string, start_sym):
|
168 |
+
"""Validates a string using the parsing table"""
|
|
|
169 |
# Initialize stack and input buffer
|
170 |
stack = [start_sym, '$']
|
171 |
input_tokens = input_string.split()
|
172 |
input_tokens.append('$')
|
173 |
buffer = input_tokens
|
174 |
|
175 |
+
steps = []
|
176 |
+
steps.append(("Initial Configuration:", f"Stack: {stack}", f"Input: {buffer}"))
|
177 |
|
|
|
|
|
|
|
|
|
178 |
while stack and buffer:
|
|
|
|
|
|
|
|
|
179 |
top_stack = stack[0]
|
180 |
current_input = buffer[0]
|
181 |
|
182 |
+
steps.append(("Current Step:", f"Stack Top: {top_stack}", f"Current Input: {current_input}"))
|
183 |
|
|
|
184 |
if top_stack == current_input:
|
185 |
stack.pop(0)
|
186 |
buffer.pop(0)
|
187 |
+
steps.append(("Action:", "Matched and consumed terminal", f"Remaining Input: {buffer}"))
|
188 |
+
elif top_stack in parse_table:
|
|
|
|
|
|
|
189 |
if current_input in parse_table[top_stack]:
|
190 |
production = parse_table[top_stack][current_input]
|
|
|
191 |
if production:
|
192 |
# Pop the non-terminal
|
193 |
stack.pop(0)
|
194 |
# Push the production in reverse (if it's not epsilon)
|
195 |
if production != ['#']:
|
196 |
stack = production + stack
|
197 |
+
steps.append(("Production Applied:", f"{top_stack} -> {' '.join(production) if production else '#'}",
|
198 |
+
f"New Stack: {stack}"))
|
199 |
else:
|
200 |
+
return False, steps, f"No production for {top_stack} with input {current_input}"
|
201 |
else:
|
202 |
+
return False, steps, f"Input symbol {current_input} not in parse table for {top_stack}"
|
203 |
else:
|
204 |
+
return False, steps, f"Unexpected symbol {top_stack} on stack"
|
|
|
|
|
|
|
205 |
|
|
|
206 |
if len(stack) <= 1 and len(buffer) <= 1:
|
207 |
+
return True, steps, "String accepted!"
|
208 |
else:
|
209 |
+
return False, steps, "String rejected - incomplete parse"
|
210 |
|
211 |
# Streamlit UI
|
212 |
st.title("LL(1) Grammar Analyzer")
|
213 |
|
214 |
+
# Session state initialization
|
215 |
+
if 'test_strings' not in st.session_state:
|
216 |
+
st.session_state.test_strings = []
|
217 |
+
if 'parse_table' not in st.session_state:
|
218 |
+
st.session_state.parse_table = None
|
219 |
+
if 'grammar_processed' not in st.session_state:
|
220 |
+
st.session_state.grammar_processed = False
|
221 |
+
|
222 |
# Input section
|
223 |
st.header("Grammar Input")
|
224 |
start_symbol = st.text_input("Enter Start Symbol:", "S")
|
225 |
|
226 |
+
with st.expander("Enter Grammar Rules", expanded=True):
|
227 |
num_rules = st.number_input("Number of Rules:", min_value=1, value=4)
|
228 |
rules = []
|
229 |
for i in range(num_rules):
|
|
|
234 |
nonterm_input = st.text_input("Enter Non-terminals (comma-separated):", "S,A,B,C")
|
235 |
term_input = st.text_input("Enter Terminals (comma-separated):", "a,b,c,d,k,r,O")
|
236 |
|
237 |
+
# Process Grammar Button
|
238 |
+
if st.button("Process Grammar"):
|
239 |
+
st.session_state.grammar_processed = False
|
240 |
+
|
|
|
|
|
241 |
# Clear previous data
|
242 |
diction.clear()
|
243 |
+
nonterm_userdef = [x.strip() for x in nonterm_input.split(',') if x.strip()]
|
244 |
+
term_userdef = [x.strip() for x in term_input.split(',') if x.strip()]
|
245 |
|
246 |
# Process rules
|
247 |
for rule in rules:
|
|
|
251 |
rhs_parts = [x.strip().split() for x in rhs.split("|")]
|
252 |
diction[lhs] = rhs_parts
|
253 |
|
254 |
+
# Grammar Processing
|
255 |
+
st.subheader("Grammar Analysis")
|
256 |
+
|
257 |
+
with st.expander("Grammar Transformations", expanded=True):
|
258 |
st.write("After removing left recursion:")
|
259 |
diction = removeLeftRecursion(diction)
|
260 |
st.write(diction)
|
|
|
267 |
computeAllFirsts()
|
268 |
computeAllFollows()
|
269 |
|
270 |
+
with st.expander("FIRST and FOLLOW Sets", expanded=True):
|
271 |
st.write("FIRST Sets:", {k: list(v) for k, v in firsts.items()})
|
272 |
st.write("FOLLOW Sets:", {k: list(v) for k, v in follows.items()})
|
273 |
|
274 |
+
# Create parse table
|
275 |
parse_table, grammar_is_LL = createParseTable()
|
276 |
+
st.session_state.parse_table = parse_table
|
277 |
|
278 |
+
# Display parse table
|
279 |
st.subheader("Parse Table")
|
|
|
280 |
df_data = []
|
281 |
terminals = term_userdef + ['$']
|
282 |
|
283 |
for non_term in parse_table:
|
284 |
+
row = [non_term]
|
285 |
for term in terminals:
|
286 |
production = parse_table[non_term].get(term, "")
|
287 |
if production:
|
|
|
298 |
else:
|
299 |
st.error("This grammar is not LL(1)!")
|
300 |
|
301 |
+
st.session_state.grammar_processed = True
|
302 |
+
|
303 |
+
# String Validation Section
|
304 |
+
if st.session_state.grammar_processed:
|
305 |
+
st.header("String Validation")
|
306 |
+
|
307 |
+
# Input for new test string
|
308 |
+
col1, col2 = st.columns([3, 1])
|
309 |
+
with col1:
|
310 |
+
new_string = st.text_input("Enter a string to test (space-separated):")
|
311 |
+
with col2:
|
312 |
+
if st.button("Add String"):
|
313 |
+
if new_string and new_string not in st.session_state.test_strings:
|
314 |
+
st.session_state.test_strings.append(new_string)
|
315 |
+
|
316 |
+
# Display and validate all test strings
|
317 |
+
if st.session_state.test_strings:
|
318 |
+
st.subheader("Test Results")
|
319 |
+
for test_string in st.session_state.test_strings:
|
320 |
+
with st.expander(f"String: {test_string}", expanded=True):
|
321 |
+
is_valid, steps, message = validateStringUsingStackBuffer(
|
322 |
+
st.session_state.parse_table, test_string, start_symbol)
|
323 |
+
|
324 |
+
# Display result
|
325 |
+
if is_valid:
|
326 |
+
st.success(message)
|
327 |
+
else:
|
328 |
+
st.error(message)
|
329 |
+
|
330 |
+
# Display parsing steps
|
331 |
+
st.write("Parsing Steps:")
|
332 |
+
for i, (step_type, *step_details) in enumerate(steps, 1):
|
333 |
+
st.text(f"Step {i}:")
|
334 |
+
st.text(f" {step_type}")
|
335 |
+
for detail in step_details:
|
336 |
+
st.text(f" {detail}")
|
337 |
|
338 |
+
# Option to clear test strings
|
339 |
+
if st.button("Clear All Test Strings"):
|
340 |
+
st.session_state.test_strings = []
|
341 |
+
st.experimental_rerun()
|
342 |
+
else:
|
343 |
+
st.info("Please process the grammar first before testing strings.")
|
344 |
+
|
345 |
+
# Help section
|
346 |
+
with st.expander("Help & Instructions"):
|
347 |
+
st.markdown("""
|
348 |
+
### How to use this LL(1) Grammar Analyzer:
|
349 |
+
|
350 |
+
1. **Enter the Grammar**:
|
351 |
+
- Specify the start symbol
|
352 |
+
- Enter the grammar rules in the format: A -> B c | d
|
353 |
+
- List all non-terminals and terminals
|
354 |
+
|
355 |
+
2. **Process the Grammar**:
|
356 |
+
- Click "Process Grammar" to analyze the grammar
|
357 |
+
- View the transformed grammar, FIRST/FOLLOW sets, and parse table
|
358 |
+
|
359 |
+
3. **Test Strings**:
|
360 |
+
- Enter strings to test in the validation section
|
361 |
+
- Add multiple strings to test
|
362 |
+
- View detailed parsing steps for each string
|
363 |
+
|
364 |
+
### Example Grammar:
|
365 |
+
```
|
366 |
+
S -> A k O
|
367 |
+
A -> A d | a B | a C
|
368 |
+
C -> c
|
369 |
+
B -> b B C | r
|
370 |
+
```
|
371 |
+
|
372 |
+
### Example Test Strings:
|
373 |
+
- a r k O
|
374 |
+
- a c k O
|
375 |
+
- a b r c k O
|
376 |
+
""")
|