|
import streamlit as st |
|
import pandas as pd |
|
|
|
rules = [] |
|
nonterm_userdef = [] |
|
term_userdef = [] |
|
diction = {} |
|
firsts = {} |
|
follows = {} |
|
start_symbol = None |
|
|
|
def removeLeftRecursion(rulesDiction): |
|
store = {} |
|
for lhs in rulesDiction: |
|
alphaRules = [] |
|
betaRules = [] |
|
allrhs = rulesDiction[lhs] |
|
for subrhs in allrhs: |
|
if subrhs[0] == lhs: |
|
alphaRules.append(subrhs[1:]) |
|
else: |
|
betaRules.append(subrhs) |
|
if len(alphaRules) != 0: |
|
lhs_ = lhs + "'" |
|
while lhs_ in rulesDiction.keys() or lhs_ in store.keys(): |
|
lhs_ += "'" |
|
for b in range(len(betaRules)): |
|
betaRules[b].append(lhs_) |
|
rulesDiction[lhs] = betaRules |
|
for a in range(len(alphaRules)): |
|
alphaRules[a].append(lhs_) |
|
alphaRules.append(['#']) |
|
store[lhs_] = alphaRules |
|
for left in store: |
|
rulesDiction[left] = store[left] |
|
return rulesDiction |
|
|
|
def LeftFactoring(rulesDiction): |
|
newDict = {} |
|
for lhs in rulesDiction: |
|
allrhs = rulesDiction[lhs] |
|
temp = dict() |
|
for subrhs in allrhs: |
|
if subrhs[0] not in list(temp.keys()): |
|
temp[subrhs[0]] = [subrhs] |
|
else: |
|
temp[subrhs[0]].append(subrhs) |
|
new_rule = [] |
|
tempo_dict = {} |
|
for term_key in temp: |
|
allStartingWithTermKey = temp[term_key] |
|
if len(allStartingWithTermKey) > 1: |
|
lhs_ = lhs + "'" |
|
while lhs_ in rulesDiction.keys() or lhs_ in tempo_dict.keys(): |
|
lhs_ += "'" |
|
new_rule.append([term_key, lhs_]) |
|
ex_rules = [] |
|
for g in temp[term_key]: |
|
ex_rules.append(g[1:]) |
|
tempo_dict[lhs_] = ex_rules |
|
else: |
|
new_rule.append(allStartingWithTermKey[0]) |
|
newDict[lhs] = new_rule |
|
for key in tempo_dict: |
|
newDict[key] = tempo_dict[key] |
|
return newDict |
|
|
|
def first(rule): |
|
global term_userdef, diction |
|
if not rule: |
|
return ['#'] |
|
if rule[0] in term_userdef: |
|
return [rule[0]] |
|
elif rule[0] == '#': |
|
return ['#'] |
|
|
|
if rule[0] in diction: |
|
fres = [] |
|
rhs_rules = diction[rule[0]] |
|
for itr in rhs_rules: |
|
indivRes = first(itr) |
|
if indivRes: |
|
fres.extend(indivRes) |
|
|
|
if '#' in fres and len(rule) > 1: |
|
fres.remove('#') |
|
ansNew = first(rule[1:]) |
|
if ansNew: |
|
fres.extend(ansNew) |
|
return list(set(fres)) |
|
return [] |
|
|
|
def follow(nt): |
|
global start_symbol, diction |
|
solset = set() |
|
if nt == start_symbol: |
|
solset.add('$') |
|
|
|
for curNT in diction: |
|
rhs = diction[curNT] |
|
for subrule in rhs: |
|
if nt in subrule: |
|
while nt in subrule: |
|
index_nt = subrule.index(nt) |
|
subrule = subrule[index_nt + 1:] |
|
if subrule: |
|
res = first(subrule) |
|
if '#' in res: |
|
res.remove('#') |
|
if nt != curNT: |
|
follow_res = follow(curNT) |
|
if follow_res: |
|
res.extend(follow_res) |
|
solset.update(res) |
|
else: |
|
if nt != curNT: |
|
follow_res = follow(curNT) |
|
if follow_res: |
|
solset.update(follow_res) |
|
return list(solset) |
|
|
|
def computeAllFirsts(): |
|
global firsts, diction |
|
firsts.clear() |
|
for y in diction: |
|
firsts[y] = set() |
|
for sub in diction[y]: |
|
result = first(sub) |
|
if result: |
|
firsts[y].update(result) |
|
|
|
def computeAllFollows(): |
|
global follows, diction |
|
follows.clear() |
|
for NT in diction: |
|
follows[NT] = set(follow(NT)) |
|
|
|
def createParseTable(): |
|
global diction, term_userdef, firsts, follows |
|
|
|
parse_table = {} |
|
for non_term in diction: |
|
parse_table[non_term] = {} |
|
for term in term_userdef + ['$']: |
|
parse_table[non_term][term] = "" |
|
|
|
grammar_is_LL = True |
|
|
|
for non_term in diction: |
|
for production in diction[non_term]: |
|
first_set = first(production) |
|
|
|
if '#' in first_set: |
|
first_set.remove('#') |
|
follow_set = follows[non_term] |
|
first_set.extend(follow_set) |
|
|
|
for terminal in first_set: |
|
if terminal in term_userdef + ['$']: |
|
if parse_table[non_term][terminal] == "": |
|
parse_table[non_term][terminal] = production |
|
else: |
|
grammar_is_LL = False |
|
|
|
return parse_table, grammar_is_LL |
|
|
|
def validateStringUsingStackBuffer(parse_table, input_string, start_sym): |
|
"""Validates a string using the parsing table""" |
|
|
|
stack = [start_sym, '$'] |
|
input_tokens = input_string.split() |
|
input_tokens.append('$') |
|
buffer = input_tokens |
|
|
|
steps = [] |
|
steps.append(("Initial Configuration:", f"Stack: {stack}", f"Input: {buffer}")) |
|
|
|
while stack and buffer: |
|
top_stack = stack[0] |
|
current_input = buffer[0] |
|
|
|
steps.append(("Current Step:", f"Stack Top: {top_stack}", f"Current Input: {current_input}")) |
|
|
|
if top_stack == current_input: |
|
stack.pop(0) |
|
buffer.pop(0) |
|
steps.append(("Action:", "Matched and consumed terminal", f"Remaining Input: {buffer}")) |
|
elif top_stack in parse_table: |
|
if current_input in parse_table[top_stack]: |
|
production = parse_table[top_stack][current_input] |
|
if production: |
|
|
|
stack.pop(0) |
|
|
|
if production != ['#']: |
|
stack = production + stack |
|
steps.append(("Production Applied:", f"{top_stack} -> {' '.join(production) if production else '#'}", |
|
f"New Stack: {stack}")) |
|
else: |
|
return False, steps, f"No production for {top_stack} with input {current_input}" |
|
else: |
|
return False, steps, f"Input symbol {current_input} not in parse table for {top_stack}" |
|
else: |
|
return False, steps, f"Unexpected symbol {top_stack} on stack" |
|
|
|
if len(stack) <= 1 and len(buffer) <= 1: |
|
return True, steps, "String accepted!" |
|
else: |
|
return False, steps, "String rejected - incomplete parse" |
|
|
|
|
|
st.title("LL(1) Grammar Analyzer") |
|
|
|
|
|
if 'test_strings' not in st.session_state: |
|
st.session_state.test_strings = [] |
|
if 'parse_table' not in st.session_state: |
|
st.session_state.parse_table = None |
|
if 'grammar_processed' not in st.session_state: |
|
st.session_state.grammar_processed = False |
|
|
|
|
|
st.header("Grammar Input") |
|
start_symbol = st.text_input("Enter Start Symbol:", "S") |
|
|
|
with st.expander("Enter Grammar Rules", expanded=True): |
|
num_rules = st.number_input("Number of Rules:", min_value=1, value=4) |
|
rules = [] |
|
for i in range(num_rules): |
|
rule = st.text_input(f"Rule {i+1} (format: A -> B c | d)", key=f"rule_{i}") |
|
if rule: |
|
rules.append(rule) |
|
|
|
nonterm_input = st.text_input("Enter Non-terminals (comma-separated):", "S,A,B,C") |
|
term_input = st.text_input("Enter Terminals (comma-separated):", "a,b,c,d,k,r,O") |
|
|
|
|
|
if st.button("Process Grammar"): |
|
st.session_state.grammar_processed = False |
|
|
|
|
|
diction.clear() |
|
nonterm_userdef = [x.strip() for x in nonterm_input.split(',') if x.strip()] |
|
term_userdef = [x.strip() for x in term_input.split(',') if x.strip()] |
|
|
|
|
|
for rule in rules: |
|
if '->' in rule: |
|
lhs, rhs = rule.split("->") |
|
lhs = lhs.strip() |
|
rhs_parts = [x.strip().split() for x in rhs.split("|")] |
|
diction[lhs] = rhs_parts |
|
|
|
|
|
st.subheader("Grammar Analysis") |
|
|
|
with st.expander("Grammar Transformations", expanded=True): |
|
st.write("After removing left recursion:") |
|
diction = removeLeftRecursion(diction) |
|
st.write(diction) |
|
|
|
st.write("After left factoring:") |
|
diction = LeftFactoring(diction) |
|
st.write(diction) |
|
|
|
|
|
computeAllFirsts() |
|
computeAllFollows() |
|
|
|
with st.expander("FIRST and FOLLOW Sets", expanded=True): |
|
st.write("FIRST Sets:", {k: list(v) for k, v in firsts.items()}) |
|
st.write("FOLLOW Sets:", {k: list(v) for k, v in follows.items()}) |
|
|
|
|
|
parse_table, grammar_is_LL = createParseTable() |
|
st.session_state.parse_table = parse_table |
|
|
|
|
|
st.subheader("Parse Table") |
|
df_data = [] |
|
terminals = term_userdef + ['$'] |
|
|
|
for non_term in parse_table: |
|
row = [non_term] |
|
for term in terminals: |
|
production = parse_table[non_term].get(term, "") |
|
if production: |
|
row.append(' '.join(production)) |
|
else: |
|
row.append("") |
|
df_data.append(row) |
|
|
|
df = pd.DataFrame(df_data, columns=['Non-Terminal'] + terminals) |
|
st.dataframe(df) |
|
|
|
if grammar_is_LL: |
|
st.success("This grammar is LL(1)!") |
|
else: |
|
st.error("This grammar is not LL(1)!") |
|
|
|
st.session_state.grammar_processed = True |
|
|
|
|
|
if st.session_state.grammar_processed: |
|
st.header("String Validation") |
|
|
|
|
|
col1, col2 = st.columns([3, 1]) |
|
with col1: |
|
new_string = st.text_input("Enter a string to test (space-separated):") |
|
with col2: |
|
if st.button("Add String"): |
|
if new_string and new_string not in st.session_state.test_strings: |
|
st.session_state.test_strings.append(new_string) |
|
|
|
|
|
if st.session_state.test_strings: |
|
st.subheader("Test Results") |
|
for test_string in st.session_state.test_strings: |
|
with st.expander(f"String: {test_string}", expanded=True): |
|
is_valid, steps, message = validateStringUsingStackBuffer( |
|
st.session_state.parse_table, test_string, start_symbol) |
|
|
|
|
|
if is_valid: |
|
st.success(message) |
|
else: |
|
st.error(message) |
|
|
|
|
|
st.write("Parsing Steps:") |
|
for i, (step_type, *step_details) in enumerate(steps, 1): |
|
st.text(f"Step {i}:") |
|
st.text(f" {step_type}") |
|
for detail in step_details: |
|
st.text(f" {detail}") |
|
|
|
|
|
if st.button("Clear All Test Strings"): |
|
st.session_state.test_strings = [] |
|
st.experimental_rerun() |
|
else: |
|
st.info("Please process the grammar first before testing strings.") |
|
|
|
|
|
with st.expander("Help & Instructions"): |
|
st.markdown(""" |
|
### How to use this LL(1) Grammar Analyzer: |
|
|
|
1. **Enter the Grammar**: |
|
- Specify the start symbol |
|
- Enter the grammar rules in the format: A -> B c | d |
|
- List all non-terminals and terminals |
|
|
|
2. **Process the Grammar**: |
|
- Click "Process Grammar" to analyze the grammar |
|
- View the transformed grammar, FIRST/FOLLOW sets, and parse table |
|
|
|
3. **Test Strings**: |
|
- Enter strings to test in the validation section |
|
- Add multiple strings to test |
|
- View detailed parsing steps for each string |
|
|
|
### Example Grammar: |
|
``` |
|
S -> A k O |
|
A -> A d | a B | a C |
|
C -> c |
|
B -> b B C | r |
|
``` |
|
|
|
### Example Test Strings: |
|
- a r k O |
|
- a c k O |
|
- a b r c k O |
|
""") |