103 lines
4.2 KiB
Python
103 lines
4.2 KiB
Python
from ParserOutput import ParserOutput, TreeNode
|
|
|
|
class RecursiveDescentParser:
|
|
def __init__(self, grammar, input_string):
|
|
self.grammar = grammar
|
|
self.input = input_string.split()
|
|
self.current_pos = 0
|
|
self.stack = [] # Stack for backtracking
|
|
self.parsed_stack = [] # Current sequence of symbols being parsed
|
|
self.production_sequence = [] # To track applied productions
|
|
self.parse_tree = ParserOutput(self.grammar.terminals, self.grammar.nonterminals) # Parse tree output
|
|
|
|
def expand(self, nonterminal, prod_index=0):
|
|
"""Expand a nonterminal using the specified production index."""
|
|
if nonterminal in self.grammar.productions:
|
|
production = self.grammar.productions[nonterminal][prod_index]
|
|
self.stack.append((nonterminal, prod_index, self.current_pos, len(self.parsed_stack))) # Push state
|
|
self.production_sequence.append((nonterminal, production)) # Track production
|
|
self.parsed_stack = production.split() + self.parsed_stack # Add production to parsed stack
|
|
else:
|
|
raise ValueError(f"No production for {nonterminal}")
|
|
|
|
def advance(self):
|
|
if self.current_pos < len(self.input) and self.parsed_stack:
|
|
next_symbol = self.parsed_stack.pop(0)
|
|
if next_symbol == self.input[self.current_pos]:
|
|
self.current_pos += 1
|
|
return True
|
|
else:
|
|
self.parsed_stack.insert(0, next_symbol) # Push it back
|
|
return False
|
|
return False
|
|
|
|
def back(self):
|
|
if self.stack:
|
|
# Pop the last saved state
|
|
nonterminal, prod_index, prev_pos, stack_size = self.stack.pop()
|
|
|
|
# Restore the previous position in the input
|
|
self.current_pos = prev_pos
|
|
|
|
# Restore the stack to the state it was before expanding the production
|
|
self.parsed_stack = self.parsed_stack[:stack_size]
|
|
self.production_sequence.pop()
|
|
|
|
# Try the next production for the same nonterminal
|
|
return self.another_try(nonterminal, prod_index)
|
|
|
|
# If the stack is empty, backtracking is not possible
|
|
return False
|
|
|
|
def another_try(self, nonterminal, prod_index):
|
|
"""Try the next production of the nonterminal."""
|
|
prod_list = self.grammar.productions.get(nonterminal, [])
|
|
if prod_index + 1 < len(prod_list):
|
|
self.expand(nonterminal, prod_index + 1) # Expand using the next production
|
|
return True
|
|
return False
|
|
|
|
def success(self):
|
|
"""Check if parsing succeeded."""
|
|
return self.current_pos == len(self.input) and not self.parsed_stack
|
|
|
|
def parse(self):
|
|
if not self.grammar.start_symbol:
|
|
raise ValueError("Grammar must have a start symbol defined.")
|
|
|
|
self.expand(self.grammar.start_symbol)
|
|
|
|
while True:
|
|
# If parsing succeeds
|
|
if self.success():
|
|
self.parse_tree.build_tree(self.production_sequence)
|
|
return True
|
|
# If there's something to parse
|
|
if self.parsed_stack:
|
|
next_symbol = self.parsed_stack[0]
|
|
|
|
# If next symbol is a terminal, try to match it
|
|
if next_symbol in self.grammar.terminals:
|
|
if not self.advance():
|
|
# If matching fails, backtrack
|
|
if not self.back() and len(self.stack) <= 1:
|
|
return False
|
|
elif next_symbol in self.grammar.nonterminals:
|
|
# Expand nonterminal
|
|
self.parsed_stack.pop(0)
|
|
self.expand(next_symbol)
|
|
else:
|
|
# Handle unexpected symbols
|
|
print(f"Error: Unexpected symbol {next_symbol}")
|
|
return False
|
|
else:
|
|
if not self.back() and len(self.stack) <= 1:
|
|
return False
|
|
|
|
def print_parse_tree(self):
|
|
"""Prints the parse tree to the screen."""
|
|
self.parse_tree.print_to_screen()
|
|
|
|
def save_parse_tree(self, filename):
|
|
"""Saves the parse tree representation to a file."""
|
|
self.parse_tree.save_to_file(filename) |