Creating an interpreter – Python

I am creating an interpreter which calculates sqrt of a number. When user types “sqrt(number)”, it must print the sqrt of that number. proccall_statement is ID LPAREN (expr) RPAREN. I am getting an error ‘str’ object has no attribute ‘type’. Am I missing something here?

I cannot seem to find the factors that resulted into an error.

Thank you!

import math
# Token types
#
# EOF (end-of-file) token is used to indicate that
# there is no more input left for lexical analysis
class TokenType():
    # single-character token types
    PLUS          = '+'
    MINUS         = '-'
    MUL           = '*'
    DIV           = "https://stackoverflow.com/"
    LPAREN        = '('
    RPAREN        = ')'
    COMMA         = ','
    # block of reserved words
    INTEGER       = 'INTEGER'
    SQRT          = 'SQRT'
    FLOAT         = 'FLOAT'
    ID            = 'ID'
    EOF           = 'EOF'


class Token(object):
    
    def __init__(self, type, value):
        # token token_type: INTEGER, PLUS, MINUS, or EOF
        self.type = type
        # token value: non-negative integer value, '+', '-', or None
        self.value = value

    def __str__(self):
        """String representation of the class instance.

        Examples:
            Token(TokenType.INTEGER, 3)
            Token(TokenType.PLUS, '+')
        """
        return 'Token({type}, {value})'.format(
            type=self.type,
            value=repr(self.value)
        )

    def __repr__(self):
        return self.__str__()


class Interpreter(object): #Lexer
    def __init__(self, text):
        # client string input, e.g. "3 + 5", "12 - 5 + 3", etc
        self.text = text
        # self.pos is an index into self.text
        self.pos = 0
        # current token instance
        self.current_token = None
        self.current_char = self.text[self.pos]

    ##########################################################
    # Lexer code                                             #
    ##########################################################
    def error(self, msg=None):
        if msg != None:
            raise Exception(msg)
        if self.pos > len(self.text) - 1:
            raise Exception('Unexpected end of file at position %d.' %
                            self.pos)
        raise Exception('Unexpected character '%c' at position %d.' %
                        (self.text[self.pos], self.pos))

    def _id(self):
        """Handle identifiers and reserved keywords"""

        # Create a new token with current line and column number
        token = Token(type=None, value=None)

        value=""
        # alnum - letter or number
        while self.current_char is not None and self.current_char.isalnum():
            value += self.current_char
            self.advance()

        token_type = TokenType
        if token_type is None:
            token.type = TokenType.ID
            token.value = value
        else:
            # reserved keyword
            token.type = token_type
            token.value = value.upper()

        return token

    def advance(self):
        """Advance the `pos` pointer and set the `current_char` variable."""
        self.pos += 1
        if self.pos > len(self.text) - 1:
            self.current_char = None  # Indicates end of input
        else:
            self.current_char = self.text[self.pos]

    def skip_whitespace(self):
        while self.current_char is not None and self.current_char.isspace():
            self.advance()
    

    def number(self):
        '''return a (multidigit) int or float consumed from the input '''
        is_int = True
        result=""
        while self.current_char is not None and self.current_char.isdigit():
            result += self.current_char
            self.advance()
        if self.current_char == '.':
            is_int = False
            result += self.current_char
            self.advance()
        while self.current_char is not None and self.current_char.isdigit():
            result += self.current_char
            self.advance()
            

        return int(result) if is_int else float(result)
        

    def get_next_token(self):
        """Lexical analyzer (also known as scanner or tokenizer)

        This method is responsible for breaking a sentence
        apart into tokens. One token at a time.
        """
        while self.current_char is not None:

            if self.current_char.isspace():
                self.skip_whitespace()
                continue

            if self.current_char.isdigit() or self.current_char == '.':
                return self.number()      

            if self.current_char.isalpha():
                return self._id()

            if self.current_char == '+':
                self.advance()
                return TokenType.PLUS

            if self.current_char == '-':
                self.advance()
                return TokenType.MINUS
            

            if self.current_char == '*':
                self.advance()
                return TokenType.MUL

            if self.current_char == "https://stackoverflow.com/":
                self.advance()
                return TokenType.DIV
            
            if self.current_char == '(':
                self.advance()
                return TokenType.LPAREN

            if self.current_char == ')':
                self.advance()
                return TokenType.RPAREN

            self.error()

        return TokenType.EOF
    ##########################################################
    # Parser / Interpreter code                              #
    ##########################################################
    def eat(self, token_type):
        # compare the current token type with the passed token
        # type and if they match then "eat" the current token
        # and assign the next token to the self.current_token,
        # otherwise raise an exception.
        if self.current_token.type == token_type:
            self.current_token = self.get_next_token()
        else:
            self.error()

    def term(self):
        """Return a numeric(INTEGER or FLOAT) token value."""
        token = self.current_token
        self.eat(token.type)
        return token.value

    def expr(self):
        """Arithmetic expression parser / interpreter."""
        # set current token to the first token taken from the input
        self.current_token = self.get_next_token()
        result = self.term()
        while self.current_token.type in (TokenType.PLUS, TokenType.MINUS, TokenType.SQRT):
            token = self.current_token
            if token.type == TokenType.PLUS:
                self.eat(TokenType.PLUS)
                result = result + self.term()
            elif token.type == TokenType.MINUS:
                self.eat(TokenType.MINUS)
                result = result - self.term()
            elif token.type == TokenType.SQRT:
                self.eat(TokenType.SQRT)
                result = math.sqrt(result) 

    def factor(self):
        """factor : PLUS factor
                  | MINUS factor
                  | INTEGER_CONST
                  | REAL_CONST
                  | LPAREN expr RPAREN
                  | variable
        """
        token = self.current_token
        
        if token.type == TokenType.ID:
            node = self.proccall_statement(token)
            return node
        
        if token.type == TokenType.PLUS:
            self.eat(TokenType.PLUS)
            node = UnaryOp(token, self.factor())
            return node
        
        elif token.type == TokenType.MINUS:
            self.eat(TokenType.MINUS)
            node = UnaryOp(token, self.factor())
            return node
        
        elif token.type == TokenType.LPAREN:
            self.eat(TokenType.LPAREN)
            node = self.expr()
            self.eat(TokenType.RPAREN)
            return node
        
        else:
            node = self.variable()
            return node
    

    def proccall_statement(self):
        """proccall_statement : ID LPAREN (expr (COMMA expr)*)? RPAREN"""
        token = self.current_token

        proc_name = self.current_token.value
        self.eat(TokenType.ID)
        self.eat(TokenType.LPAREN)
        actual_params = []
        if self.current_token.type != TokenType.RPAREN:
            node = self.expr()
            actual_params.append(node)

        while self.current_token.type == TokenType.COMMA:
            self.eat(TokenType.COMMA)
            node = self.expr()
            actual_params.append(node)

        self.eat(TokenType.RPAREN)

        node = ProcedureCall(
            proc_name = proc_name,
            actual_params=actual_params,
            token= TokenType.SQRT
        )
        return node
    

class AST:
    pass

class UnaryOp(AST):
    def __init__(self, op, expr):
        self.token = self.op = op
        self.expr = expr
   

class ProcedureCall(AST):
    def __init__(self, proc_name, actual_params, token):
        self.proc_name="sqrt"
        self.actual_params = actual_params  # a list of AST nodes
        self.token = token
        # a reference to procedure declaration symbol
        self.proc_symbol = None


def main():
    while True:
        try:
            text = input('calc> ')
            if text == 'exit':
                raise EOFError
        except EOFError:
            # If user types CTRL+D
            print('Bye.')
            break
        if not text:
            continue
        interpreter = Interpreter(text)
        try:
            result = interpreter.expr()
            print(result)
        except Exception as error:
            print('Error:', error)


if __name__ == '__main__':
    main()

Leave a Comment