<filename>llgram/parsing.py
import re
from collections import deque

from llgram import llexceptions as lle
from llgram import constants as const
from llgram.rule import Rule

class LLParser:
    def __init__(self, parsingTable: dict, startingSymbol: str, actions: dict=None):
        """
            Parser based on an LL parsing table.

            Parameters
            ----------
            parsingTable: dict
                Parsing table (such as one generated by llgram.generation.TableGenerator)

            startingSymbol: str
                Starting symbol of the grammar

            actions: dict
                Dictionary of actions (python functions) to be performed when using a rule (Rule:fuct). If the action is None, nothing will happen.
                This will only be used with the parse method when the execute parameter was set to True, and only if an alternative set isn't provided.
        """
        self.table = parsingTable
        self.actions = actions
        self.startingSymbol = startingSymbol

    def parse(self, input: list, execute: bool=False, explicitActions: bool=False, actions: dict=None) -> list:
        """
            Parses the input and returns the leftmost derivation. Potentialy executes actions.

            Parameters
            ----------
            input: list
                List of input terminals
            
            execute: bool
                If true, will execute rule actions after derivation. Will use object actions if the actions parameter is None

            explicitActions: bool
                If this is true, actions from the actions parameter or actions from this object will be used in place of actions in rules.
            
            actions: dict
                If execute is False or if explicitActions is False, this is ignored.
                Dictionary of actions (python functions) to be performed when using a rule (Rule:fuct). If the action is None, nothing will happen.
                If this is None, instance (default) actions will be used.

            Returns
            -------
            list
                Leftmost derivation
        """
        string = input.copy()
        stack = deque()
        stack.append(self.startingSymbol)

        derivation = list() #leftmost derivation

        while True:
            #empty
            if len(string) == 0 or len(stack) == 0:
                if len(string) == 0 and len(stack) == 0:
                    break
                else:
                    raise lle.ParsingSyntaxException("")

            top = stack.pop()
            first = string[0]

            if top == first: # symbols match
                #consume them and carry on
                string = string[1:] #stack has already been popped
                continue
            else:
                #check for applicable rule
                try:
                    rule = self.table[top][first]
                except KeyError:
                    raise lle.ParsingLexicalException(f"Terminal \"{first}\" is unknown")
                if rule:
                    #rewrite stack
                    for symbol in reversed(rule.getRight()):
                        stack.append(symbol)

                    #print rule
                    derivation.append(rule)

        #potentialy execute actions
        if execute:
            for rule in derivation:
                if explicitActions:
                    if actions == None:
                        if self.actions[rule]:
                            self.actions[rule]()
                    else:
                        if actions[rule]:
                            actions[rule]()
                else:
                    rule.getAction()()
        
        return derivation