


from runtime.ast.tokenizer import Tokenizer


class Parser:
    
    def __init__(self) -> None:
        self.script = ""
        self.tokenizer = Tokenizer()
        self.current_token = None
    
    def parse(self, script: str):
        self.script = script
        self.tokenizer.init(script)
        self.current_token = self.tokenizer.get_next_token()
        return self.program()

    def program(self):
        return {
            "type": 'Program',
            "body": {
                "type": "BlockStatement",
                "body": self.statement_list()
            },
        }
    
    def return_statement(self):
        self.eat('return')
        return {
            "type": 'ReturnStatement',
            "value": self.expression_statement(),
        }

    def statement_list(self):
        statment_list = [self.statement()]
        while self.current_token != None:
            statment_list.append(self.statement())
        return statment_list

    def statement(self):
        if self.token_type() == "let":
            return self.variable_statement()
        return self.expression_statement()
    
    def block_statement(self):
        if self.token_type() != "{":    
            self.eat('{')
        body = self.statement_list()
        if self.token_type() != "}":
            self.eat('}')
        return {
            "type": 'BlockStatement',
            "body": body,
        }

    def if_statement(self):
        self.eat('if')
        return {
            "type": 'IfStatement',
        }

    def expression_statement(self):
        if self.token_type() == "if":
            return self.if_statement()
        if self.token_type() == "return":
            return self.return_statement()
        if self._is_literal():
            return self.literal()
        if self.token_type() == "IDENTIFIER":
            identifier = self.identifier()
            if self.token_type() == "(":
                return self.call_expression(identifier)
            if self.token_type() == "SIMPLE_ASSIGN":
                return self.assignment_expression(identifier)
            return identifier
        raise Exception("Unexpected token: " + self.token_type())

    def assignment_expression(self,identifier):
        self.eat('SIMPLE_ASSIGN')
        return {
            "type": 'AssignmentExpression',
            "identifier": identifier,
            "value": self.statement(),
        }
    
    def call_expression(self, identifier):
        self.eat('(')
        arguments = self.argument_list()
        self.eat(')')
        return {
            "type": 'CallExpression',
            "arguments": arguments,
            "callee": identifier,
        }
    
    def token_type(self):
        if self.current_token == None:
            return None
        return self.current_token["type"]
    
    def _is_literal(self):
        return self.current_token["type"] in ["NUMBER", "STRING", "FLOAT", "true", "false"]

    # variable
    def variable_statement(self):
        self.eat('let')
        identifier = self.identifier()
        self.eat('SIMPLE_ASSIGN')
        return {
            "type": 'VariableDeclaration',
            "identifier": identifier,
            "value": self.statement(),
        }

    def eat(self, tokenType):
        token = self.current_token
        if token == None:
            raise Exception("Unexpected EOF")
        
        if token["type"] != tokenType:
            raise Exception("Unexpected token: " + token["type"])

        self.current_token = self.tokenizer.get_next_token()
        return token
    
    def identifier(self):
        name = self.eat('IDENTIFIER')
        return {
            "type": 'Identifier',
            "name": name["value"],
        }

    def literal(self):
        token_type = self.current_token["type"]
        if token_type == "true" or token_type == "false":
            return self.boolean_literal()
        if token_type == "NUMBER":
            return self.numberic_literal()
        if token_type == "STRING":
            return self.string_literal()
        if token_type == "FLOAT":
            return self.float_literal()
        raise Exception("Unexpected token: " + token_type)

    def boolean_literal(self):
        if self.token_type() == "true":
            self.eat('true')
            value = "True"
        else:
            self.eat("false")
            value = "False"
        return {
            "type": 'BooleanLiteral',
            "value": value,
        } 

    def numberic_literal(self):
        token = self.eat('NUMBER')
        return {
            "type": 'NumericLiteral',
            "value": token["value"],
        }
    
    def string_literal(self):
        token = self.eat('STRING')
        return {
            "type": 'StringLiteral',
            "value": token["value"][1:-1],
        }
    
    def float_literal(self):
        token = self.eat('FLOAT')
        return {
            "type": 'FloatLiteral',
            "value": token["value"],
        }
    
    def argument_list(self):
        args = []
        while self.token_type() != ")":
            args.append(self.statement())
            if self.token_type() == ",":
                self.eat(',')
        return args