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 |
+
""")
|