2 years ago

#26810

test-img

Irodi

How to make recursive descent parsing recognise expressions

I am making a graphing calculator and the issue I am having is finding a way to take in inputs like sin(90) and run my function for sin that I coded into another file.

I am using a recursive descent parser to go through each input and assign it to a token but the issue I am having is that when the user enters something like sin(90), the program checks each letter individually to try assign it to a token but since it doesn't recognise any token that is just an 's' or just an 'i', it outputs an error.

    def generate_tokens(self):  # Generate tokens from input
    while self.current_char is not None:
        if self.current_char in WHITESPACE:
            self.advance()  # If character is whitespace, skip it
        elif self.current_char == '.' or self.current_char in DIGITS:
            yield self.generate_number()  # If character is digit or decimal point, run next method
        elif self.current_char == '+':  # Specific token is generated if specific input 
            self.advance()
            yield Token(TokenType.PLUS)
        elif self.current_char == '-':
            self.advance()
            yield Token(TokenType.MINUS)
        elif self.current_char == '*' or self.current_char == 'x':
            self.advance()
            yield Token(TokenType.MULTIPLY)
        elif self.current_char == '/':
            self.advance()
            yield Token(TokenType.DIVIDE)
        elif self.current_char == '(':
            self.advance()
            yield Token(TokenType.LPAREN)
        elif self.current_char == ')':
            self.advance()
            yield Token(TokenType.RPAREN)
        elif self.current_char == '^':
            self.advance()
            yield Token(TokenType.INDICES)
        elif self.current_char == 's' and next(self.text) == 'i' and next(self.text) == 'n':
            self.advance()
            yield Token(TokenType.SIN)
        else:
            raise Exception(f'Illegal Character {self.current_char}')

Here is the code that generates a token for each input

I use a expression, term, factor rule and I put the sin function in the factor rule. Here is the code for the factor rule` def factor(self): # Highest level rule, checks for numbers token = self.current_token # Checks for numbers

    if token.type == TokenType.LPAREN:
        self.advance()
        result = self.expr()

        if self.current_token.type != TokenType.RPAREN:
            self.raise_error()

        self.advance()
        return result

    elif token.type == TokenType.NUMBER:
        self.advance()
        return NumberNode(token.value)

    elif token.type == TokenType.SIN:
        self.advance()
        return SinNode(self.factor())

    elif token.type == TokenType.PLUS:
        self.advance()
        return PlusNode(self.factor())

    elif token.type == TokenType.MINUS:  # Check if number is negative and then output it as negative
        self.advance()
        return MinusNode(self.factor())

    self.raise_error()`

And this goes to the interpreter which has this code.

`class Interpreter: def visit(self, node): # Pass in the root node here method_name = f'visit_{type(node).name}' # This tells us the type of node the node we are on is method = getattr(self, method_name) return method(node)

def visit_NumberNode(self, node):
    return Number(node.value)

def visit_AddNode(self, node):
    return Number(self.visit(node.node_a).value + self.visit(node.node_b).value)

def visit_SubtractNode(self, node):
    return Number(self.visit(node.node_a).value - self.visit(node.node_b).value)

def visit_MultiplyNode(self, node):
    return Number(self.visit(node.node_a).value * self.visit(node.node_b).value)

def visit_DivideNode(self, node):
    try:
        return Number(self.visit(node.node_a).value / self.visit(node.node_b).value)
    except:
        raise Exception('Runtime math error')

def visit_PlusNode(self, node):
    return self.visit(node.node)

def visit_MinusNode(self, node):
    return Number(-self.visit(node.node).value)

def visit_IndiceNode(self, node):
    return Number(self.visit(node.node_a).value ** self.visit(node.node_b).value)

def visit_SinNode(self, node):
    return Number(function.sin(self.visit(node.node.value)))

` This is the code for the nodes

@dataclass

class SinNode: node: any

def __repr__(self):
    return f'(sin({self.node}))'

And this is the code for the values:

@dataclass

class Number: value: float

def __repr__(self):
    return f'{self.value}'

I would appreciate any help because this project is really stressing me out. I can't figure out why it won't work and I have no idea how to make it work. I just need to read an expression from the user (something like 'x^2*tan(2x)') and apply it to np.linspace and graph that but I can't figure out a way to read the expression and keep order of operations correct. I also want to minimise use of libraries since there are libraries that do this but I need to do this without them

python

recursive-descent

0 Answers

Your Answer

Accepted video resources