def processAssign(tokens1: List[Token]) -> (Node, List[Error]): """Retrieve tokens and return a node according to the assign layout. """ error = [] tokens = copy.copy(tokens1) index = getIndexToken(lambda x: x.instance == TokenValues.ASSIGN, tokens) # get index of assignment if (index != 1): error.append( Error(Errornr.SYNTAX_ERROR, "too many arguments for assignment")) if (tokens[0].instance != TokenValues.VAR): error.append( Error( Errornr.SYNTAX_ERROR, "on line " + str(tokens[0].linenr) + " -> \"" + tokens[0].type + "\" cannot be an variable")) lhs = value_node(tokens[0].linenr, tokens[0].type, TokenValues.VAR_ASSIGN) # check if after assignment there are enough parameters (value and terminator) if (index + 1 >= len(tokens) - 1): error.append( Error(Errornr.SYNTAX_ERROR, "On line " + str(tokens[index].linenr) + ", invalid syntax")) return None, error rhs, pv1 = processTokens(tokens[index + 1:]) pv = copy.copy(pv1) pv.unprocessedTokens = [] if (pv.state == State.ERROR): error += pv.error_list if (len(rhs) > 1): error += pv.error_list current_node = operator_node(tokens[index].linenr, tokens[index].type, lhs, tokens[index].instance, rhs[0]) return current_node, error
def processComparison(tokens1: List[Token]) -> (Node, List[Error]): """Retrieve tokens and return a node according to the comparison rules""" error = [] tokens = copy.copy(tokens1) # get first element that meets the condition func = lambda currentToken: currentToken.instance == TokenValues.EQUAL or currentToken.instance == TokenValues.NOTEQUAL or currentToken.instance == TokenValues.GE \ or currentToken.instance == TokenValues.SE or currentToken.instance == TokenValues.GREATER or currentToken.instance == TokenValues.SMALLER index = _getFirst(list(map(func, tokens))) lhs, pv_lhs = processTokens( tokens[:index]) # process the tokens till the comparison token if (len(pv_lhs.error_list) > 0): error += (pv_lhs.error_list) if (pv_lhs.state == State.ERROR or len(lhs) > 1): error += (Error( Errornr.SYNTAX_ERROR, "On line " + str(tokens[0].linenr) + " ^" + str(tokens[0].type) + " invalid syntax: too many arguments for comparison")) rhs, pv_rhs = processTokens( tokens[index + 1:]) # process the tokens after the comparison token if (len(pv_rhs.error_list) > 0): error += (pv_rhs.error_list) if ((pv_rhs.state == State.ERROR or len(rhs) > 1) and len(error) == 0): error.append( Error( Errornr.SYNTAX_ERROR, "On line " + str(tokens[0].linenr) + " ^" + str(tokens[index + 1].type) + " invalid syntax: too many arguments for comparison")) current_node = operator_node(tokens[index].linenr, tokens[index].type, lhs[0], tokens[index].instance, rhs[0]) return current_node, error
def processMath(currentToken1: Token, current_node1: Node) -> (Node, [Error]): """Retrieve node and append the token in this node return this Node and the Errorlist. Errorlist is empty if no errors occur. Append Node according to the math rules""" error = [] currentToken = copy.copy(currentToken1) current_node = copy.copy(current_node1) if (currentToken.instance == TokenValues.PLUS or currentToken.instance == TokenValues.MIN): new_node = operator_node(currentToken.linenr, currentToken.type, current_node, currentToken.instance) return new_node, error elif (currentToken.instance == TokenValues.MULTIPLY or currentToken.instance == TokenValues.DIVIDED_BY): # find the node that is not filled yet node_ = findNode(current_node) if (node_ != None): lhs = node_.rhs new_node = operator_node(currentToken.linenr, currentToken.type, lhs, currentToken.instance) node_.rhs = new_node else: # if there are only values in the nodetree new_node = operator_node(currentToken.linenr, currentToken.type, current_node, currentToken.instance) current_node = new_node else: new_node = value_node(currentToken.linenr, currentToken.type, currentToken.instance) # check if list is empty if (current_node.value == None): current_node = new_node else: # get empty operator_node node_ = findNode(current_node) if (node_ != None): if (node_.lhs == None): node_.lhs = new_node elif (node_.rhs == None): node_.rhs = new_node else: error.append( Error( Errornr.MATH_ERROR, "On line: " + str(currentToken.linenr) + ", too many numbers versus operators")) else: error.append( Error( Errornr.MATH_ERROR, "On line: " + str(currentToken.linenr) + ", cannot calculate " + currentToken.type)) pass return current_node, error
def lexer( filename: str) -> Union[Tuple[List[Token], Error], Tuple[None, Error]]: """Reads from file the content. If None is returned, show error else retrieve the tokens matching to the inputted lines. If a Token could not be parsed, create error and return result""" fileContainer = readFromFile(filename) if (fileContainer != None): tokenlist = lex_rec(lex_func, fileContainer) errorlist = list( filter(lambda x: x.instance == TokenValues.ERROR, tokenlist)) error = Error(Errornr.NO_ERROR) if (len(errorlist) > 0): error = (Error( Errornr.SYNTAX_ERROR, "On line " + str(errorlist[0].linenr) + ", cannot define " + " \"" + errorlist[0].type + "\" " + " ")) return tokenlist, error return None, Error(Errornr.FileNotFoundError, "Cannot open " + filename)
def processIf_While( tokens1: List[Token] ) -> Union[Tuple[Node, List[Error]], Tuple[None, List[Error]]]: """Retrieve tokens and return a node according to the if or while layout. Since if and while have the same rules, these commands can both be processed in this funcion""" error = [] tokens = copy.copy(tokens1) # get index of all important tokens index_parL = getIndexToken(lambda x: x.instance == TokenValues.LPAREN, tokens) index_parR = getIndexToken(lambda x: x.instance == TokenValues.RPAREN, tokens) index_bracL = getIndexToken(lambda x: x.instance == TokenValues.LBRACE, tokens) # check for syntax errors if (index_parL == -1 or index_parR == -1): error.append( Error(Errornr.SYNTAX_ERROR, "Syntax Error on line " + str(tokens[0].linenr))) return None, error if ((index_bracL - index_parR) != 1): error.append( Error(Errornr.SYNTAX_ERROR, "Syntax Error on line " + str(tokens[index_parR].linenr))) return None, error elif (index_bracL == -1): error.append( Error(Errornr.SYNTAX_ERROR, "Syntax Error on line " + str(tokens[index_parR].linenr))) return None, error new_node = operator_node(tokens[0].linenr, tokens[0].type, None, tokens[0].instance) compare_node, error = processComparison(tokens[index_parL + 1:index_parR]) node_list_rhs, pv1 = processTokens(tokens[index_bracL + 1:-1]) pv = copy.copy(pv1) pv.unprocessedTokens = [] if (len(error) > 0): error += pv.error_list new_node.lhs = compare_node new_node.rhs = node_list_rhs return new_node, error
def if_func(enum: ProgramActions, a: bool, b: List[Node]) -> Error: '''if the condition is true, execute the nodes in list b Return the first error''' error = [] if (a): # execute the codeblock in the ifstatement result, _error = AST_to_actions(enum, b) error.append(_error) #check if an error has occured. If so, return the error. Else, return NO-Error errors = list(filter(lambda x: x.nr != Errornr.NO_ERROR, error)) if (len(errors) > 0): return errors[0] return Error()
def AST_to_actions(enum: ProgramActions, nodes: List[Node]) -> Tuple[Union[None, float], Error]: '''Loop throught the list of nodes and give the AST to the _loopNodeFunction. If an error occurs, stop the loop and return None and the error''' if (len(nodes) == 0): return None, Error() result, error_ast = AST_to_actions(enum, nodes[0:-1]) #check for errors if (error_ast.nr != Errornr.NO_ERROR): return None, error_ast # unpack the AST result, error = _loopNode(enum, nodes[-1]) if (error.nr != Errornr.NO_ERROR): return None, error #useful for whileloop return result, error
def _loopNode(enum: ProgramActions, node: Node) -> Tuple[Union[None, float, str], Error]: '''Loop through the node. If an error occurs, return the error''' if (isinstance(node, value_node)): if (node.type == TokenValues.NUMBER): return float(node.value), Error() if (node.type == TokenValues.VAR): # check if the value is in the dictionary if (node.value in enum.variables): return enum.variables[node.value], Error() else: return (None, Error( Errornr.NameError, "On line " + str(node.linenr) + ", name \"" + node.value + "\" is not defined")) if (node.type == TokenValues.VAR_ASSIGN): return node.value, Error() if (isinstance(node, operator_node)): # use different call for whileloop if (node.operator == TokenValues.WHILE): error = execute(enum, node.operator, ([node.lhs], node.rhs)) return None, error # Check if the nodes are filled, this is obligated if (node.lhs == None or node.rhs == None): return None, Error(Errornr.SYNTAX_ERROR, "on line: " + str(node.linenr)) # get lhs left, error = _loopNode(enum, node.lhs) if (error.nr != Errornr.NO_ERROR): return None, error #check if node is a list (for the if-statement function) if ((not isinstance(node.rhs, list))): right, error = _loopNode(enum, node.rhs) if (error.nr != Errornr.NO_ERROR): return None, error else: right = node.rhs result = execute(enum, node.operator, (left, right)) # check if the result is an error if (isinstance(result, Error)): return None, result # return result and No-error return result, Error() # if empty node occurs, no big deal return None, Error()
def while_func(enum: ProgramActions, while_condition: List[Node], codeBlock: List[Node]) -> Error: '''check if the condition is true, then execute the codeblock check the condition_nodes every time, since the condition can be updated due to the codeblock''' condition, error = AST_to_actions(enum, while_condition) if (error.nr != Errornr.NO_ERROR): return error while (condition): #execute the code in the codeblok of the whileloop result, error = AST_to_actions(enum, codeBlock) if (error.nr != Errornr.NO_ERROR): return error # check if the condition is still true condition, error = AST_to_actions(enum, while_condition) # check for errors if (error.nr != Errornr.NO_ERROR): return error return Error()
def processTokens(tokens1: List[Token]) -> (List[Node], ProgramValues): """Retrieve the tokens that needs to be processed. Check in the token the instance and act accordingly It returns the nodes of the processed tokens and the ProgramValues""" tokens = copy.copy(tokens1) if (len(tokens) == 0): return [], ProgramValues(unprocessed=[]) # default values nodes, pv_old = processTokens(tokens[0:-1]) pv = copy.copy(pv_old) currentToken = tokens[-1] if (pv.state == State.ERROR): return nodes, pv if (pv.state == State.PRINT): if (currentToken.instance == TokenValues.PRINT_END): new_node = operator_node(pv.unprocessedTokens[0].linenr, pv.unprocessedTokens[0].type, None, pv.unprocessedTokens[0].instance) rhs, pv = copy.copy(processTokens(pv.unprocessedTokens[1:])) pv.state = State.Idle if (len(rhs) > 0): new_node.lhs = rhs[0] new_node.rhs = rhs nodes.append(new_node) return nodes, pv pv.unprocessedTokens.append(currentToken) return nodes, pv if (pv.state == State.Math): # program rules, assignment before math if (currentToken.instance == TokenValues.ASSIGN): pv.state = State.ASSIGN # remove incorrect math equation nodes = nodes[:-1] pv.unprocessedTokens.append(currentToken) elif (currentToken.instance != TokenValues.NUMBER and currentToken.instance != TokenValues.VAR and currentToken.instance != TokenValues.PLUS and currentToken.instance != TokenValues.MIN and currentToken.instance != TokenValues.MULTIPLY and currentToken.instance != TokenValues.DIVIDED_BY): # math state is over, remove processed tokens pv.unprocessedTokens = [] pv.state = State.Idle else: pv.unprocessedTokens.append(currentToken) nodes[-1], error = processMath(currentToken, nodes[-1]) if (len(error) > 0): pv.state = State.ERROR pv.error_list += (error) return nodes, pv elif (pv.state == State.ASSIGN): if (currentToken.instance == TokenValues.SEMICOLON): pv.unprocessedTokens.append(currentToken) new_node, error_list = processAssign(pv.unprocessedTokens) if (len(error_list) > 0): pv.state = State.ERROR pv.error_list += error_list pv.unprocessedTokens = [] return nodes, pv pv.unprocessedTokens = [] nodes.append(new_node) pv.state = State.Idle else: pv.unprocessedTokens.append(currentToken) return nodes, pv elif (pv.state == State.IF_WHILE): pv.unprocessedTokens.append(currentToken) if (currentToken.instance == TokenValues.RBRACE): amountBraces = amountOpenBraces(pv.unprocessedTokens) if (amountBraces == 0): new_node, error_list = processIf_While(pv.unprocessedTokens) if (new_node == None): pv.state = State.ERROR pv.error_list += error_list return nodes, pv nodes.append(new_node) pv.unprocessedTokens = [] pv.state = State.Idle nodes.append(Node()) elif (amountBraces < 0): pv.error_list.append( Error( Errornr.SYNTAX_ERROR, "on line " + str(pv.unprocessedTokens[0].linenr) + ", missing opening brace")) return nodes, pv elif (currentToken.instance == TokenValues.PLUS or currentToken.instance == TokenValues.MIN or currentToken.instance == TokenValues.MULTIPLY or currentToken.instance == TokenValues.DIVIDED_BY or currentToken.instance == TokenValues.NUMBER or currentToken.instance == TokenValues.VAR): if (pv.state == State.Idle): pv.state = State.Math nodes.append(Node()) nodes[-1], error = processMath(currentToken, nodes[-1]) if (len(error) > 0): pv.state = State.ERROR pv.error_list += (error) return nodes, pv elif (currentToken.instance == TokenValues.ASSIGN): if (pv.state == State.Idle): pv.state = State.ASSIGN elif (currentToken.instance == TokenValues.IF): pv.state = State.IF_WHILE elif (currentToken.instance == TokenValues.WHILE): pv.state = State.IF_WHILE elif (currentToken.instance == TokenValues.PRINT): pv.state = State.PRINT pv.unprocessedTokens.append(currentToken) return nodes, pv
def inner(a: A, b: B) -> Union[A, B, Error]: '''Check if b is not 0: if not, return the devision, else return a zeroDivisionError''' if (b == 0): return Error(Errornr.ZeroDivisionError, "cannot divide " + str(a) + " with " + str(b)) return f(a, b)