global error_det
error_det = ''

import string
from strings_with_arrows import *

from errors import *
from nodes import *

DIGITS = '0123456789'
LETTERS = string.ascii_letters
LETTERS_DIGITS = LETTERS + DIGITS

class Position:
    def __init__(self,idx,ln,col,fn,ftxt):
        self.fn = fn
        self.ftxt = ftxt
        self.idx = idx
        self.ln = ln
        self.col = col

    def advance(self,current_char = None):
        self.idx += 1
        self.col += 1

        if current_char == '\n':
            self.ln += 1
            self.col = 0

        return self
    def regress(self,current_char = None):
        self.idx -= 1
        self.col -= 1

    def copy(self):
        return Position(self.idx,self.ln,self.col,self.fn,self.ftxt)

TT_INT = 'Int'
TT_REAL = 'Real'
TT_STRING = 'String'
TT_IDENTIFIER = 'Identifier'
TT_KEYWORD = 'Keyword'
TT_EQ = 'Eq'
TT_PLUS = 'Plus'
TT_MINUS = 'Minus'
TT_MUL = 'Mul'
TT_DIV = 'Div'
TT_POWER = 'Power'
TT_LPAREN = 'LParen'
TT_RPAREN = "RParen"
TT_LSQUAREBRACKET = 'LSquareBracket'
TT_RSQUAREBRACKET = 'RSquareBracket'
TT_LT = 'NE'
TT_EE = 'EE'
TT_LT = 'LT'
TT_GT = 'GT'
TT_LTE = 'LTE'
TT_GTE = 'GTE'
TT_NE = 'NE'
TT_Comma = 'COMMA'
TT_Arrow = "ARROW"
TT_Newline = 'Newline'
TT_EOF = "Eof"

KEYWORDS = [
'AND',"OR","NOT","IF","ELSE","THEN","FOR","WHILE","DO","STEP","TO","REPEAT","UNTIL","FUNCTION",'RETURNS','ENDIF','ENDFUNCTION','RETURNS','ENDWHILE',"NEXT",'RETURN', 'PROCEDURE', 'ARRAY'
]

class Token:
    def __init__(self,type_,value = None, pos_start = None, pos_end = None):
        self.type = type_
        self.value = value
        if pos_start:
            self.pos_start = pos_start
            self.pos_end = self.pos_start.copy()
            self.pos_end.advance()
        if pos_end:
            self.pos_end = pos_end
    def __repr__(self):
        if self.value: return f'{self.type}:{self.value}'
        return f'{self.type}'

    def matches(self,type_,value):
        return self.type == type_ and self.value == value
    
class Lexer:
    def __init__(self,fn, text):
        self.fn = fn
        self.text = text
        self.pos = Position(-1,0,-1,fn,text)
        self.current_char = None
        self.advance()
    def advance(self):
        self.pos.advance(self.current_char)
        self.current_char = self.text[self.pos.idx] if self.pos.idx < len(self.text) else None
    def regress(self):
        self.pos.regress(self.current_char)
        self.current_char = self.text[self.pos.idx] if self.pos.idx < len(self.text) else None
    def make_tokens(self):
        tokens = []

        while self.current_char != None:

            if self.current_char in ' \t':
                self.advance()

            elif self.current_char in ['\n',';']:
                tokens.append(Token(TT_Newline,pos_start=self.pos))
                self.advance()

            elif self.current_char in LETTERS:
                tokens.append(self.make_identifier())

            elif self.current_char in DIGITS:
                tokens.append(self.make_number())


            elif self.current_char in ['"',"'"]:
                tokens.append(self.make_string(self.current_char))

            elif self.current_char == '[':
                tokens.append(Token(TT_LSQUAREBRACKET,pos_start = self.pos))
                self.advance()

            elif self.current_char == ']':
                tokens.append(Token(TT_RSQUAREBRACKET,pos_start = self.pos))
                self.advance()

            elif self.current_char == '+' or self.current_char == '&':
                tokens.append(Token(TT_PLUS, pos_start=self.pos))
                self.advance()

            elif self.current_char == '-':
                tokens.append(Token(TT_MINUS, pos_start=self.pos))
                self.advance()
            elif self.current_char == ':':
                tokens.append(Token(TT_Arrow,pos_start=self.pos))
                self.advance()
            elif self.current_char == '*':
                tokens.append(Token(TT_MUL, pos_start=self.pos))
                self.advance()

            elif self.current_char == '/':
                tokens.append(Token(TT_DIV, pos_start=self.pos))
                self.advance()
            elif self.current_char == '(':
                tokens.append(Token(TT_LPAREN, pos_start=self.pos))
                self.advance()
            elif self.current_char == ')':
                tokens.append(Token(TT_RPAREN, pos_start=self.pos))
                self.advance()
            elif self.current_char == '^':
                tokens.append(Token(TT_POWER,pos_start = self.pos))
                self.advance()
            elif self.current_char == '<':
                tok = self.make_not_lessThan_assign()
                tokens.append(tok)
            elif self.current_char == '=':
                tokens.append(Token(TT_EE,pos_start=self.pos))
                self.advance()

            elif self.current_char == '>':
                tokens.append(self.make_greater_than())

            elif self.current_char == ',':
                tokens.append(Token(TT_Comma, pos_start=self.pos))
                self.advance()
            else:
                pos_start = self.pos.copy()
                char = self.current_char
                self.advance()
                return [],IllegalCharError(pos_start,self.pos,"'" + char + "'")

        tokens.append(Token(TT_EOF, pos_start=self.pos))

        return tokens, None

    def make_string(self,end):
        string = ''
        pos_start = self.pos.copy()
        self.advance()
        while self.current_char != None and self.current_char != end:
            string += self.current_char
            self.advance()

        self.advance()
        return Token(TT_STRING,string,pos_start,self.pos)


    def make_number(self):
        num_str = ''
        dotCount = 0
        pos_start = self.pos.copy()
        while self.current_char != None and self.current_char in DIGITS + '.':
            if self.current_char == '.':
                if dotCount == 1: break
                num_str += '.'
                dotCount += 1
            else:
                num_str += self.current_char
            self.advance()
        if dotCount == 0:
            return Token(TT_INT,int(num_str),pos_start,self.pos)
        else:
            return Token(TT_REAL,float(num_str),pos_start,self.pos)

    def make_identifier(self):
        id_str = ''
        pos_start = self.pos.copy()

        while self.current_char != None and self.current_char in LETTERS_DIGITS + '_':
            id_str += self.current_char
            self.advance()
        tok_type = TT_KEYWORD if id_str in KEYWORDS else TT_IDENTIFIER

        return Token(tok_type,id_str,pos_start,self.pos)

    def make_greater_than(self):
        pos_start = self.pos.copy()
        self.advance()
        if self.current_char == '=':
            self.advance()
            return Token(TT_GTE,pos_start=pos_start,pos_end=self.pos)
        return Token(TT_GT,pos_start=pos_start,pos_end=self.pos)

    def make_not_lessThan_assign(self):
        pos_start = self.pos.copy()
        self.advance()
        if self.current_char == '-':
            self.advance()
            return Token(TT_EQ,pos_start=pos_start,pos_end=self.pos)
        elif self.current_char == '>':
            self.advance()
            return Token(TT_NE,pos_start=pos_start,pos_end=self.pos)
        elif self.current_char == '=':
            self.advance()
            return Token(TT_LTE, pos_start=pos_start, pos_end=self.pos)
        else:
            return Token(TT_LT, pos_start=pos_start, pos_end=self.pos)


#Parser
#---------------------------------------

class Parser:
    def __init__(self,tokens):
        self.tokens = tokens
        self.hasEq = 0
        for q in tokens:
            if q.type == TT_EQ:
                self.hasEq += 1
        self.tok_idx = -1
        self.advance()



    def advance(self):
        self.tok_idx += 1
        if self.tok_idx < len(self.tokens):
            self.current_tok = self.tokens[self.tok_idx]

        return self.current_tok

    def regress(self):
        self.tok_idx -= 1
        self.current_tok = self.tokens[self.tok_idx]
        return self.current_tok

    def reverse(self,amount = 1):
        self.tok_idx -= amount
        self.update_current_tok()
        return self.current_tok

    def update_current_tok(self):
        if self.tok_idx >= 0 and self.tok_idx < len(self.tokens):
            self.current_tok = self.tokens[self.tok_idx]

    def parse(self):
        res = self.statements()
        if not res.error and self.current_tok.type != TT_EOF:
            return res.failure(InvalidSyntaxError(
                self.current_tok.pos_start, self.current_tok.pos_end,
                "Expected '+', '-', '*' or '/'"
            ))
        return res



    def statements(self):
        res = ParseResult()
        statements = []
        pos_start = self.current_tok.pos_start.copy()
        n = 0
        while self.current_tok.type == TT_Newline:
            res.register(self.advance())

        statement = res.register(self.expr())
        if res.error: return res
        statements.append(statement)

        more_statements = True

        while True:
            newline_count = 0
            while self.current_tok.type == TT_Newline:
                res.register(self.advance())
                newline_count +=1
            if newline_count == 0: more_statements = False

            if not more_statements: break
            statement = res.try_register(self.expr())
            if statement == None:
                self.reverse(1)
                more_statements = False
                continue
            statements.append(statement)

        return res.success(ListNode(
            statements,pos_start,self.current_tok.pos_end.copy()
        ))

    def for_expr(self):
        res = ParseResult()
        if not self.current_tok.matches(TT_KEYWORD,'FOR'):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end,f"Expected 'FOR'"))
        res.register(self.advance())
        if self.current_tok.type != TT_IDENTIFIER:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected identifier"))
        var_name = self.current_tok
        res.register(self.advance())
        if self.current_tok.type != TT_EQ:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, "Expected '<-'"))
        res.register(self.advance())
        start_value = res.register(self.expr())
        if res.error: return res

        if not self.current_tok.matches(TT_KEYWORD,'TO'):

            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'TO'"))
        res.register(self.advance())
        end_value = res.register(self.expr())
        if res.error: return res

        if self.current_tok.matches(TT_KEYWORD,'STEP'):
            res.register(self.advance())
            step_value = res.register(self.expr())
            if res.error:return res
        else:
            step_value = None
        if not self.current_tok.type == TT_Newline:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected Newline"))
        res.register(self.advance())
        body = res.register(self.statements())
        if res.error:
            return res

        if not self.current_tok.matches(TT_KEYWORD,'NEXT'):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'NEXT'"))
        res.register(self.advance())

        if not self.current_tok.type == TT_IDENTIFIER:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected Identifier"))
        res.register(self.advance())

        if not self.current_tok.type in (TT_Newline,TT_EOF):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected EOF or Newline"))


        return res.success(ForNode(var_name,start_value,end_value,step_value,body))

    def while_expr(self):
        res = ParseResult()
        if not self.current_tok.matches(TT_KEYWORD,"WHILE"):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'WHILE'"))
        res.register(self.advance())

        condition = res.register(self.expr())
        if res.error: return res
        if self.current_tok.type != TT_Newline:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                           self.current_tok.pos_end, f"Expected Newline"))
        res.register(self.advance())

        if not self.current_tok.matches(TT_KEYWORD,'DO'):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'DO'"))
        res.register(self.advance())

        if self.current_tok.type != TT_Newline:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                           self.current_tok.pos_end, f"Expected Newline"))
        res.register(self.advance())
        body = res.register(self.statements())
        if res.error: return res

        if not self.current_tok.matches(TT_KEYWORD,'ENDWHILE'):
                return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                      self.current_tok.pos_end, f"Expected 'ENDWHILE'"))
        res.register(self.advance())
        if not self.current_tok.type in (TT_Newline,TT_EOF):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected Newline or EOF"))
        return res.success(WhileNode(condition,body))

    def repeat_expr(self):
        res = ParseResult()
        if not self.current_tok.matches(TT_KEYWORD,"REPEAT"):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'REPEAT'"))
        res.register(self.advance())
        if not self.current_tok.type == TT_Newline:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected Newline"))
        body = res.register(self.statements())
        if res.error: return res

        if not self.current_tok.matches(TT_KEYWORD,'UNTIL'):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'UNTIL'"))
        res.register(self.advance())
        condition = res.register(self.expr())
        res.register(self.advance())
        if not self.current_tok.type in (TT_Newline,TT_EOF):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected newline or EOF"))
        return res.success(RepeatNode(body,condition))


    def if_expr(self):
        res = ParseResult()
        cases = []
        else_case = None
        if not self.current_tok.matches(TT_KEYWORD,'IF'):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'IF'"))

        res.register(self.advance())
        cond = res.register(self.expr())
        if res.error: return res

        if self.current_tok.type != TT_Newline:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'Newline'"))
        res.register(self.advance())

        if not self.current_tok.matches(TT_KEYWORD,'THEN'):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'THEN'"))

        res.register(self.advance())
        if self.current_tok.type != TT_Newline:
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'Newline'"))
        res.register(self.advance())

        statements = res.register(self.statements())
        if res.error: return res
        cases.append((cond,statements))

        if self.current_tok.matches(TT_KEYWORD,'ELSE'): #instead of making a separate else-expr I added it here
            res.register(self.advance())
            if not self.current_tok.type == TT_Newline:
                return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                      self.current_tok.pos_end, f"Expected 'Newline'"))
            else_case = res.register(self.statements())
            if res.error: return res


        if not self.current_tok.matches(TT_KEYWORD,'ENDIF'):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected 'ENDIF'"))

        res.register(self.advance())
        if not self.current_tok.type in (TT_Newline,TT_EOF):
            return res.failure(InvalidSyntaxError(self.current_tok.pos_start,
                                                  self.current_tok.pos_end, f"Expected EOF or Newline"))

        return res.success(IfNode(cases,else_case))



    def call(self):
        res = ParseResult()
        atom = res.register(self.atom())
        if res.error:
            return res

        if self.current_tok.type == TT_LPAREN:
            res.register(self.advance())
            arg_nodes = []

            if self.current_tok.type  == TT_RPAREN:
                res.register(self.advance())

            else:
                tok = self.current_tok
                arg_nodes.append(res.register(self.expr()))
                if res.error: res.failure(InvalidSyntaxError(tok.pos_start,tok.pos_end,""))

                while self.current_tok.type == TT_Comma:
                 res.register(self.advance())
                 arg_nodes.append(res.register(self.expr()))
                 if res.error: return res

                if self.current_tok.type != TT_RPAREN:
                    res.failure(
                        InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected ')' or ','"))
                res.register(self.advance())
            return res.success(CallNode(atom,arg_nodes))
        return res.success(atom)

    def atom(self):
        res = ParseResult()
        tok = self.current_tok

        if tok.type in [TT_INT,TT_REAL]:
            res.register(self.advance())
            return res.success(NumberNode(tok))
        if tok.type == TT_STRING:
            res.register(self.advance())
            return res.success(StringNode(tok))

        elif tok.type == TT_IDENTIFIER:
            res.register(self.advance())
            return res.success(VarAccessNode(tok))
        elif tok.type == TT_LPAREN:
            res.register(self.advance())
            expr = res.register(self.expr())
            if res.error: return res
            if self.current_tok.type == TT_RPAREN:
                res.register(self.advance())
                return res.success(expr)
            else:
                return res.failure(InvalidSyntaxError(
                    self.current_tok.pos_start, self.current_tok.pos_end,
                    "Expected ')'"
                ))
        elif tok.type == TT_LSQUAREBRACKET:
            list_expr = res.register(self.list_expr())
            if res.error:
                return res
            return res.success(list_expr)

        elif tok.matches(TT_KEYWORD,'IF'):
            if_expr = res.register(self.if_expr())
            if res.error: return res
            return res.success(if_expr)

        elif tok.matches(TT_KEYWORD,'FOR'):
            for_expr = res.register(self.for_expr())
            if res.error:
                return res
            return res.success(for_expr)

        elif tok.matches(TT_KEYWORD,'WHILE'):
            while_expr = res.register(self.while_expr())
            if res.error:return res
            return res.success(while_expr)
        elif tok.matches(TT_KEYWORD,"REPEAT"):
            repeat_expr = res.register(self.repeat_expr())
            if res.error:
                return res
            return res.success(repeat_expr)
        elif tok.matches(TT_KEYWORD,"FUNCTION"):
            function_def = res.register(self.func_def())
            if res.error:
                return res

            return res.success(function_def)

        else:

            return res.failure(InvalidSyntaxError(tok.pos_start,tok.pos_end,"Expected int,real, '+', '-','(','IF','FOR','WHILE','REPEAT', or 'FUNCTION'"))

    def power(self):
        return self.bin_op(self.call,(TT_POWER,),self.factor)

    def factor(self):
        res = ParseResult()
        tok = self.current_tok

        if tok.type in (TT_PLUS,TT_MINUS):
            res.register(self.advance())
            factor = res.register(self.factor())
            if res.error:
                return res
            return res.success(UnaryOpNode(tok,factor))

        return self.power()


    def term(self):
        return self.bin_op(self.factor,(TT_DIV,TT_MUL))
    def arith_expr(self):
        return self.bin_op(self.term,(TT_PLUS,TT_MINUS))
    def comp_expr(self):
        res = ParseResult()
        if self.current_tok.matches(TT_KEYWORD,'NOT'):
            op_tok = self.current_tok
            res.register(self.advance())
            if res.error: return res
            node = res.register(self.comp_expr())
            if res.error:return res
            return res.success(UnaryOpNode(op_tok,node))
        node = res.register(self.bin_op(self.arith_expr,(TT_EE,TT_NE,TT_LT,TT_LTE,TT_GT,TT_GTE)))
        tok = self.current_tok
        if res.error:
            return res
        return res.success(node)

    def expr(self):
        res = ParseResult()
        if self.current_tok.type == TT_IDENTIFIER:
            var_name = self.current_tok
            res.register(self.current_tok)
            res.register(self.advance())
            if self.current_tok.type == TT_EQ:
                res.register(self.advance())
                expr = res.register(self.expr())
                if res.error: return res
                return res.success(VarAssignNode(var_name,expr))
            self.regress()

        return self.bin_op(self.comp_expr,((TT_KEYWORD,'AND'),(TT_KEYWORD,'OR')))

    def bin_op(self,func_a,ops,func_b = None):
        if func_b == None:
            func_b = func_a
        res = ParseResult()
        left = res.register(func_a())

        while self.current_tok.type in ops or (self.current_tok.type, self.current_tok.value) in ops:
            op_tok = self.current_tok
            res.register(self.advance())
            right = res.register(func_b())
            if res.error:
                return res
            left = BinOpNode(left, op_tok, right)

        return res.success(left)

    def list_expr(self):
        res = ParseResult()
        element_nodes = []
        pos_start = self.current_tok.pos_start.copy()

        res.register(self.advance())
        if self.current_tok.type == TT_RSQUAREBRACKET:
            res.register(self.advance())
        else:
            tok = self.current_tok
            element_nodes.append(res.register(self.expr()))
            if res.error: res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected ']', 'IF','FOR','WHILE','FUNCTION',variable,datatypoe"))

            while self.current_tok.type == TT_Comma:
                res.register(self.advance())
                element_nodes.append(res.register(self.expr()))
                if res.error: return res

            if self.current_tok.type != TT_RSQUAREBRACKET:
                res.failure(
                    InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected ']' or ','"))
            res.register(self.advance())
        return ListNode(element_nodes,pos_start,self.current_tok.pos_end.copy())


    def func_def(self):
        res = ParseResult()
        tok = self.current_tok
        if not self.current_tok.matches(TT_KEYWORD,'FUNCTION'):
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected 'FUNCTION'"))

        res.register(self.advance())
        if self.current_tok.type != TT_IDENTIFIER:
            tok = self.current_tok
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected indentifier"))
        var_name_tok = self.current_tok
        res.register(self.advance())

        if self.current_tok.type != TT_LPAREN:
            tok = self.current_tok
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected '('"))

        res.register(self.advance())
        arg_name_toks = []
        if self.current_tok.type == TT_IDENTIFIER:
            arg_name_toks.append(self.current_tok)
            res.register(self.advance())
            if not self.current_tok.type == TT_Arrow:
                return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected ':'"))
            res.register(self.advance())
            if not self.current_tok.type == TT_IDENTIFIER or self.current_tok.value not in ['REAL', 'INTEGER', 'CHAR','BOOLEAN', 'STRING']:
                return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected datatype"))
            res.register(self.advance())
            while self.current_tok.type == TT_Comma:
                res.register(self.advance())

                if self.current_tok.type != TT_IDENTIFIER:
                    tok = self.current_tok
                    return res.failure(
                        InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected identifier"))
                name = self.current_tok
                res.register(self.advance())
                if not self.current_tok.type == TT_Arrow:
                    return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected ':'"))
                res.register(self.advance())
                if not self.current_tok.type == TT_IDENTIFIER or self.current_tok.value not in ['REAL', 'INTEGER', 'CHAR', 'BOOLEAN', 'STRING']:
                    return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected datatype"))
                arg_name_toks.append(name)
                res.register(self.advance())


            if self.current_tok.type != TT_RPAREN:
                tok = self.current_tok
                return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected ',' or ')'"))

        else:
            if self.current_tok.type != TT_RPAREN:
                tok = self.current_tok
                return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected identifier or ')'"))
        res.register(self.advance())

        if not self.current_tok.matches(TT_KEYWORD,'RETURNS'):
            tok = self.current_tok
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected 'RETURNS'"))

        res.register(self.advance())
        if self.current_tok.value not in ['INTEGER','REAL','BOOLEAN','STRING','CHAR']:
            tok = self.current_tok
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected 'INTEGER','REAL','BOOLEAN','STRING' or 'CHAR'"))
        res.register(self.advance())
        if not self.current_tok.type == TT_Newline:
            tok = self.current_tok
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected Newline'"))

        node_to_return = res.register(self.statements())
        if res.error: return res
        if not self.current_tok.matches(TT_KEYWORD,'RETURN'):
            tok = self.current_tok
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected 'RETURN'"))
        res.register(self.advance())
        expr = res.register(self.expr())
        if res.error: return res
        res.register(self.advance())

        if not self.current_tok.matches(TT_KEYWORD,'ENDFUNCTION'):
            tok = self.current_tok
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected 'ENDFUNCTION'"))
        res.register(self.advance())
        if not self.current_tok.type in (TT_Newline,TT_EOF):
            tok = self.current_tok
            return res.failure(InvalidSyntaxError(tok.pos_start, tok.pos_end, "Expected Newline or EOF'"))

        return res.success(FuncDefNode(
            var_name_tok,arg_name_toks,node_to_return,expr

        ))





#runtime result
#-------------------------------------------------

class RTResult:
    def __init__(self):
        self.value = None
        self.error = None

    def register(self,res):
        if res.error: self.error = res.error
        return res.value

    def success(self,value):
        self.value = value

        return self

    def failure(self,error):
        self.error = error

        return self

#values
#-------------------------------------------------
class Value:
    def __init__(self):
        self.set_context()
        self.set_pos()
    def set_context(self,context = None):
        self.context = context
        return self

    def set_pos(self,pos_start = None,pos_end = None):
        self.pos_start = pos_start
        self.pos_end = pos_end
        return self

    def added_to(self,other):
        return None,self.illegal_operation(other)

    def subtracted_by(self,other):
        return None,self.illegal_operation(other)

    def multiplied_by(self,other):
        return None,self.illegal_operation(other)

    def divided_by(self,other):
        return None, self.illegal_operation(other)

    def powered_by(self,other):
        return None,self.illegal_operation(other)

    def get_comparison_eq(self, other):
        return None,self.illegal_operation(other)

    def get_comparison_ne(self, other):
        return None,self.illegal_operation(other)

    def get_comparison_lt(self, other):
        return None,self.illegal_operation(other)

    def get_comparison_gt(self, other):
        return None,self.illegal_operation(other)

    def get_comparison_lte(self, other):
        return None,self.illegal_operation(other)

    def get_comparison_gte(self, other):
        return None,self.illegal_operation(other)

    def anded_by(self, other):
        return None,self.illegal_operation(other)

    def ored_by(self, other):
        return None,self.illegal_operation(other)

    def notted(self):
        return None,self.illegal_operation(self)

    def execute(self, args):
        return RTResult().failure(self.illegal_operation())


    def copy(self):
        raise Exception('No copy method defined')
    def is_true(self):
        return False

    def illegal_operation(self, other=None):
        if not other: other = self
        return RTError(
            self.pos_start, other.pos_end,
            'Illegal operation',
            self.context
        )

class Number(Value):

    def __init__(self,value):
        self.value = value
        super().__init__()

    def set_context(self,context = None):
        self.context = context
        return self

    def set_pos(self,pos_start = None,pos_end = None):
        self.pos_start = pos_start
        self.pos_end = pos_end
        return self

    def added_to(self,other):
        if isinstance(other,Number):
            return Number(self.value + other.value).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def subtracted_by(self,other):
        if isinstance(other,Number):
            return Number(self.value - other.value).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def multiplied_by(self,other):
        if isinstance(other,Number):
            return Number(self.value * other.value).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def divided_by(self,other):
        if isinstance(other,Number):
            if other.value == 0:
                return None,RTError(
                    other.pos_start
                    ,other.pos_end,
                    'Divison by Zero',
                    self.context
                )
            return Number(self.value / other.value).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def powered_by(self,other):
        if isinstance(other,Number):
            return Number(self.value ** other.value).set_context(self.context),None
        else:
            return None, Value.illegal_operation(self, other)

    def get_comparison_eq(self, other):
        if isinstance(other, Number):

            return Number(int(self.value == other.value)).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def get_comparison_ne(self, other):
        if isinstance(other, Number):
            return Number(int(self.value != other.value)).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def get_comparison_lt(self, other):
        if isinstance(other, Number):
            return Number(int(self.value < other.value)).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def get_comparison_gt(self, other):
        if isinstance(other, Number):
            return Number(int(self.value > other.value)).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def get_comparison_lte(self, other):
        if isinstance(other, Number):
            return Number(int(self.value <= other.value)).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def get_comparison_gte(self, other):
        if isinstance(other, Number):
            return Number(int(self.value >= other.value)).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def anded_by(self, other):
        if isinstance(other, Number):
            return Number(int(self.value and other.value)).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def ored_by(self, other):
        if isinstance(other, Number):
            return Number(int(self.value or other.value)).set_context(self.context), None
        else:
            return None, Value.illegal_operation(self, other)

    def notted(self):
        return Number(1 if self.value == 0 else 0).set_context(self.context), None


    def copy(self):
        copy = Number(self.value)
        copy.set_pos(self.pos_start,self.pos_end)
        copy.set_context(self.context)
        return copy

    def is_true(self):
        return self.value != 0
    def __repr__(self):
        return f'{self.value}'

class String(Value):
    def __init__(self,value):
        super().__init__()
        self.value = value

    def added_to(self,other):
        if isinstance(other,String):
            return String(self.value + other.value).set_context(self.context),None
        else:
            return None,Value.illegal_operation(self,other)

    def copy(self):
        copy = String(self.value)
        copy.set_pos(self.pos_start,self.pos_end)
        copy.set_context(self.context)
        return copy

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

class List(Value):
    def __init__(self,elements):
        super().__init__()
        self.elements = elements

    def added_to(self,other):
        new_list = self.copy()
        new_list.elements.append(other)
        return new_list,None
    def subtracted_by(self,other):
        if isinstance(other,Number):
            new_list = self.copy()
            try:
               new_list.elements.pop(other)
               return new_list,None
            except:
                return None,RTError(other.pos_start,other.pos_end,'Element at this indewx is out of bound',self.context)

        else:
            return None,Value.illegal_operation(self,other)
    def multiplied_by(self,other):
        if isinstance(other,List):
            new_list = self.copy()
            new_list.elements.extend(other.elements)
        else:
            return None,Value.illegal_operation(self,other)

    def divided_by(self,other):
        if isinstance(other, Number):
            try:
                return self.elements[other.value],None
            except:
                return None, RTError(other.pos_start, other.pos_end, 'Element at this indewx is out of bound',
                                     self.context)

        else:
            return None, Value.illegal_operation(self, other)

    def copy(self):
        copy = List(self.elements)
        copy.set_pos(self.pos_start,self.pos_end)
        copy.set_pos(self.context)
        return copy

    def __repr__(self):
        return f"[{','.join([str(x) for x in self.elements])}]"

class BaseFunction(Value):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def generateNewContext(self):
        new_context = Context(self.name, self.context, self.pos_start)
        new_context.symbol_table = SymbolTable(new_context.parent.symbol_table)
        return new_context

    def check_args(self,arg_names,args):
        res = RTResult()
        if len(args) > len(arg_names):
            return res.failure(RTError(
                self.pos_start, self.pos_end,
                f"{len(args) - len(arg_names)} too many args passed into '{self.name}'",
                self.context
            ))

        if len(args) < len(arg_names):
            return res.failure(RTError(
                self.pos_start, self.pos_end,
                f"{len(arg_names) - len(args)} too few args passed into '{self.name}'",
                self.context
            ))
        return res.success(None)

    def populate_args(self,arg_names,args,exec_ctx):
        for i in range(len(args)):
            new_context = exec_ctx
            arg_name = arg_names[i]
            arg_value = args[i]
            arg_value.set_context(new_context)
            new_context.symbol_table.set(arg_name, arg_value)


    def check_and_populate_args(self,arg_names,args,exec_ctx):
        res = RTResult()

        res.register(self.check_args(arg_names,args))
        if res.error: return res
        self.populate_args(arg_names,args,exec_ctx)
        return res.success(None)


class Function(BaseFunction):
    def __init__(self,name,body_node,arg_names,ret_node):
        super().__init__(name)
        self.body_node = body_node
        self.arg_names = arg_names
        self.ret_node = ret_node
    def execute(self, args):
        res = RTResult()
        interpreter = Interpreter()
        new_context = self.generateNewContext()

        res.register(self.check_and_populate_args(self.arg_names,args,new_context))
        if res.error: return res
        value = res.register(interpreter.visit(self.body_node, new_context))
        if res.error: return res
        q = res.register(interpreter.visit(self.ret_node,new_context))
        return res.success(q)

    def copy(self):
        copy = Function(self.name, self.body_node, self.arg_names,self.ret_node)
        copy.set_context(self.context)
        copy.set_pos(self.pos_start, self.pos_end)
        return copy

    def __repr__(self):
        return f"<function {self.name}>"


class BuiltInFunction(BaseFunction):
    def __init__(self,name):
        super().__init__(name)


    def execute(self, args):
        res = RTResult()
        exec_ctx = self.generateNewContext()

        method_name = f'execute_{self.name}'
        method = getattr(self,method_name,self.no_visit_method)
        res.register(self.check_and_populate_args(method.arg_names,args,exec_ctx))
        if res.error: return res

        return_value = res.register(method(exec_ctx))
        if res.error: return res

        return res.success(return_value)

    def no_visit_method(self,node,context):
        raise Exception(f'No execute_{self.name} method defined')

    def copy(self):
        copy = BuiltInFunction(self.name)
        copy.set_context(self.context)
        copy.set_pos(self.pos_start,self.pos_end)
        return copy

    def __repr__(self):
        return f'<built-in function {self.name}>'

    #BuiltIn functions

    def execute_output(self,exec_ctx):
        print(str(exec_ctx.symbol_table.get('value')))
        return RTResult().success(Number(0)) #return null equivalent

    execute_output.arg_names = ['value']

    def execute_mid(self,exec_ctx):

        s = exec_ctx.symbol_table.get('s')
        a = exec_ctx.symbol_table.get('a')
        b = exec_ctx.symbol_table.get('b')
        f = str(s)[a.value - 1 :a.value - 1 + b.value ]
        return RTResult().success(Number(String(f)))

    execute_mid.arg_names = ['s','a','b']

BuiltInFunction.output = BuiltInFunction('output')
BuiltInFunction.mid = BuiltInFunction('mid')
#context
#--------------------------------------------------

class Context:
    def __init__(self,display_name, parent = None, parent_entry_pos = None):
        self.display_name = display_name
        self.parent = parent
        self.parent_entry_pos = parent_entry_pos
        self.symbol_table = None


#symbol stable
#--------------------------------------------------

class SymbolTable:
    def __init__(self,parent = None):
        self.symbols = dict()
        self.parent = parent

    def get(self,name):
        value = self.symbols.get(name, None)
        if value == None and self.parent:
            return self.parent.get(name)
        return value

    def set(self, name, value):
        self.symbols[name] = value

    def remove(self,name):
        del self.symbols[name]


#interpreter
#--------------------------------------------------

class Interpreter:
    def visit(self,node,context):
        method_name = f'visit_{type(node).__name__}'
        method = getattr(self,method_name,self.no_visit_method)
        return method(node,context)



    def no_visit_method(self,node,context):
        raise Exception(f'No visit_{type(node).__name__} defined')

    def visit_VarAccessNode(self, node, context):
        res = RTResult()
        var_name = node.var_name_tok.value
        value = context.symbol_table.get(var_name)

        if not value:
            return res.failure(RTError(node.pos_start,
                                       node.pos_end,f"'{var_name} is not defined'",context))
        value = value.copy().set_pos(node.pos_start,node.pos_end).set_context(context)
        return res.success(value)

    def visit_VarAssignNode(self, node, context):
        res = RTResult()
        var_name = node.var_name_tok.value
        value = res.register(self.visit(node.value_node,context))
        if res.error: return res

        context.symbol_table.set(var_name,value)
        return res.success(value)

    def visit_NumberNode(self,node,context):
        return RTResult().success(
            Number(node.tok.value).set_context(context).set_pos(node.pos_start,node.pos_end)
        )

    def visit_StringNode(self,node,context):
        return RTResult().success(
            String(node.tok.value).set_context(context).set_pos(node.pos_start,node.pos_end)
        )
    def visit_ListNode(self,node,context):
        res = RTResult()
        elements = []
        for element_node in node.element_nodes:
            elements.append(res.register(self.visit(element_node,context)))
            if res.error:return res

        return res.success(
            List(elements).set_context(context).set_pos(node.pos_start,node.pos_end)
        )

    def visit_BinOpNode(self,node,context):
        res = RTResult()
        left = res.register(self.visit(node.left_node,context))
        if res.error: return res
        right = res.register(self.visit(node.right_node,context))
        if res.error: return res

        if node.op_tok.type == TT_PLUS or node.op_tok.type:
            result,error = left.added_to(right)

        if node.op_tok.type == TT_MINUS:
            result,error = left.subtracted_by(right)

        if node.op_tok.type == TT_MUL:
            result,error = left.multiplied_by(right)

        if node.op_tok.type == TT_DIV:
            result,error = left.divided_by(right)

        if node.op_tok.type == TT_POWER:
            result,error = left.powered_by(right)
        elif node.op_tok.type == TT_EE:
            result, error = left.get_comparison_eq(right)
        elif node.op_tok.type == TT_NE:
            result, error = left.get_comparison_ne(right)
        elif node.op_tok.type == TT_LT:
            result, error = left.get_comparison_lt(right)
        elif node.op_tok.type == TT_GT:
            result, error = left.get_comparison_gt(right)
        elif node.op_tok.type == TT_LTE:
            result, error = left.get_comparison_lte(right)
        elif node.op_tok.type == TT_GTE:
            result, error = left.get_comparison_gte(right)
        elif node.op_tok.matches(TT_KEYWORD, 'AND'):
            result, error = left.anded_by(right)
        elif node.op_tok.matches(TT_KEYWORD, 'OR'):
            result, error = left.ored_by(right)


        if error: return res.failure(error)
        return res.success(result.set_pos(node.pos_start,node.pos_end))

    def visit_UnaryOpNode(self,node,context):
        res = RTResult()
        number = res.register(self.visit(node.node,context))
        if res.error: return res

        error = None

        if node.op_tok.type == TT_MINUS:
            number,error = number.multiplied_by(Number(-1))
        elif node.op_tok.matches(TT_KEYWORD, 'NOT'):
            number, error = number.notted()

        if error: return res.failure(error)
        return res.success(number.set_pos(node.pos_start,node.pos_end))

    def visit_IfNode(self,node,context):
        res = RTResult()
        for condition,expr in node.cases:
            condition_value = res.register(self.visit(condition,context))
            if res.error: return res

            if condition_value.is_true():
                expr_value = res.register(self.visit(expr,context))
                if res.error: return res
                return res.success(Number(0)) #returns null eq
            if node.else_case:
                else_value = res.register(self.visit(node.else_case,context))
                if res.error:return res
                return res.success(Number(0))
            return res.success(Number(0))

    def visit_ForNode(self,node,context):
        res = RTResult()
        elements = []

        start_value = res.register(self.visit(node.start_value_node,context))
        if res.error:return res

        end_value = res.register(self.visit(node.end_value_node,context))
        if res.error: return res

        if node.step_value_node:
            step_value = res.register(self.visit(node.step_value_node,context))
            if res.error: return res

        else:
            step_value = Number(1)
        i = start_value.value

        if step_value.value >= 0:
            condition = lambda: i <= end_value.value
        else:
            condition = lambda: i >= end_value.value

        while condition():
            context.symbol_table.set(node.var_name_tok.value,Number(i))
            i += step_value.value
            elements.append(res.register(self.visit(node.body_node,context)))
            if res.error: return res

        return res.success(
            Number(0)
        )
    def visit_WhileNode(self,node,context):
        res = RTResult()
        elements = []
        while True:
            condition = res.register(self.visit(node.condition_node,context))
            if res.error: return res
            if not condition.is_true(): break
            elements.append(res.register(self.visit(node.body_node,context)))
            if res.error: return res

        return res.success(
            Number(0)
        )

    def visit_RepeatNode(self,node,context):
        res = RTResult()
        elements = []
        while True:
            elements.append(res.register(self.visit(node.body_node,context)))
            if res.error: return res
            condition = res.register(self.visit(node.condition_node,context))
            if res.error: return res
            if condition.is_true():break
        return res.success(
            Number(0)
        )

    def visit_FuncDefNode(self,node,context):
        res = RTResult()

        func_name = node.var_name_tok.value if node.var_name_tok else None
        body_node = node.body_node
        arg_names = [arg_name.value for arg_name in node.arg_name_toks]
        func_value = Function(func_name, body_node, arg_names,node.ret_node).set_context(context).set_pos(node.pos_start,
                                                                                            node.pos_end)

        if node.var_name_tok:
            context.symbol_table.set(func_name, func_value)

        return res.success(func_value)

    def visit_CallNode(self, node, context):
        res = RTResult()
        args = []

        value_to_call = res.register(self.visit(node.node_to_call, context))
        if res.error: return res
        value_to_call = value_to_call.copy().set_pos(node.pos_start, node.pos_end)

        for arg_node in node.arg_nodes:
            args.append(res.register(self.visit(arg_node, context)))
            if res.error: return res

        return_value = res.register(value_to_call.execute(args))
        if res.error: return res
        return_value = return_value.copy().set_pos(node.pos_start, node.pos_end).set_context(context)
        return res.success(return_value)
#run
#--------------------------------------------------

global_symbol_table = SymbolTable()
global_symbol_table.set('TRUE',Number(1))
global_symbol_table.set('FALSE',Number(0))

global_symbol_table.set('OUTPUT',BuiltInFunction.output)
global_symbol_table.set('MID',BuiltInFunction.mid)

def run(fn,text):
    lexer = Lexer(fn,text)
    tokens,error = lexer.make_tokens()

    if error: return None,error

    #generate ast
    parser = Parser(tokens)
    ast = parser.parse()
    if ast.error: return None,ast.error

    #runs
    interpreter = Interpreter()
    context = Context('<program>')
    context.symbol_table = global_symbol_table
    result = interpreter.visit(ast.node,context)

    return result.value,result.error


kindly add the functionality for procedures and arrays, as this is CAIE level psuedocode.