remote: [tester::#FI3] [test-4] Running test case: 4
remote: [tester::#FI3] [test-4] Writing contents to ./test.lox:
remote: [tester::#FI3] [test-4] [test.lox] print "62" + "foo";
remote: [tester::#FI3] [test-4] [test.lox] print true * (49 + 72);
remote: [tester::#FI3] [test-4] $ ./your_program.sh run test.lox
remote: [your_program] 62foo
remote: [your_program] Traceback (most recent call last):
remote: [your_program]   File "<frozen runpy>", line 198, in _run_module_as_main
remote: [your_program]   File "<frozen runpy>", line 88, in _run_code
remote: [your_program]   File "/app/app/main.py", line 974, in <module>
remote: [your_program]     main()
remote: [your_program]   File "/app/app/main.py", line 894, in main
remote: [your_program]     runner.run(ast)
remote: [your_program]   File "/app/app/main.py", line 870, in run
remote: [your_program]     self.evaluator.execute(statement)
remote: [your_program]   File "/app/app/main.py", line 625, in execute
remote: [your_program]     value = self.evaluate(statement[1])
remote: [your_program]             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
remote: [your_program]   File "/app/app/main.py", line 761, in evaluate
remote: [your_program]     self.check_number_operands(left, right)
remote: [your_program]   File "/app/app/main.py", line 825, in check_number_operands
remote: [your_program]     raise LoxRuntimeError("Operands must be numbers.")
remote: [your_program]           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
remote: [your_program] TypeError: LoxRuntimeError.__init__() missing 1 required positional argument: 'line'
remote: [tester::#FI3] [test-4] expected exit code 70, got 1
remote: [tester::#FI3] [test-4] Test failed
remote: 
remote: Try our CLI to run tests faster without Git: https://codecrafters.io/cli
remote:
remote: View our article on debugging test failures: https://codecrafters.io/debug
remote:
To https://git.codecrafters.io/03303a39c3a06311
   044f135..5a02bd2  master -> master : import sys
import time

class Lox:
    had_error = False
    def error(line, message):
        print(f"[line {line}] Error: {message}", file=sys.stderr)
        Lox.had_error = True

class Token:
    def __init__(self, token_type, lexeme, literal, line):
        self.type = token_type
        self.lexeme = lexeme
        self.literal = literal
        self.line = line

    def __str__(self):
        literal_str = "null" if self.literal is None else str(self.literal)
        return f'{self.type} {self.lexeme} {literal_str}'
    
class Scanner:

    reserved_words = {
        "and": "AND",
        "class": "CLASS",
        "else": "ELSE",
        "false": "FALSE",
        "for": "FOR",
        "fun": "FUN",
        "if": "IF",
        "nil": "NIL",
        "or": "OR",
        "print": "PRINT",
        "return": "RETURN",
        "super": "SUPER",
        "this": "THIS",
        "true": "TRUE",
        "var": "VAR",
        "while": "WHILE",
        "else" : "ELSE",
        "fun" : "FUN",
        "return" : "RETURN"
    }

    def __init__(self, source):
        self.source = source
        self.tokens = []
        self.start = 0
        self.current = 0
        self.line = 1
        self.had_error = False

    def scan_tokens(self):
        while not self.is_at_end():
            self.start = self.current
            self.scan_token()

        self.tokens.append(Token('EOF', '', None, self.line))
        return self.tokens

    def is_at_end(self):
        return self.current >= len(self.source)

    def scan_token(self):
        c = self.advance()
        if c in (' ', '\r', '\t'):
            return
        elif c == '\n':
            self.line += 1
            return
        elif c == '"':
            self.string()
            return
        elif c.isdigit():
            self.number()
            return
        elif c.isalpha() or c == '_':
            self.identifier()
            return
        elif c == '(':
            self.add_token('LEFT_PAREN')
        elif c == ')':
            self.add_token('RIGHT_PAREN')
        elif c == '{':
            self.add_token('LEFT_BRACE')
        elif c == '}':
            self.add_token('RIGHT_BRACE')
        elif c == ',':
            self.add_token('COMMA')
        elif c == '.':
            self.add_token('DOT')
        elif c == '-':
            self.add_token('MINUS')
        elif c == '+':
            self.add_token('PLUS')
        elif c == ';':
            self.add_token('SEMICOLON')
        elif c == '*':
            self.add_token('STAR')
        elif c == '=':
            self.add_token('EQUAL_EQUAL' if self.match('=') else 'EQUAL')
        elif c == '!':
            self.add_token('BANG_EQUAL' if self.match('=') else 'BANG')
        elif c == '<':
            self.add_token('LESS_EQUAL' if self.match('=') else 'LESS')
        elif c == '>':
            self.add_token('GREATER_EQUAL' if self.match('=') else 'GREATER')
        elif c == '/':
            if self.match('/'):
                self.skip_line_comment()
            else:
                self.add_token('SLASH')
        else:
            self.error(c)

    def match(self, expected):
        if self.is_at_end():
            return False
        if self.source[self.current] != expected:
            return False
        self.current += 1
        return True
    
    def skip_line_comment(self):
        while not self.is_at_end() and self.source[self.current] != '\n':
            self.advance()

    def advance(self):
        char = self.source[self.current]
        self.current += 1
        return char

    def add_token(self, token_type, literal=None):
        text = self.source[self.start:self.current]
        self.tokens.append(Token(token_type, text, literal, self.line))

    def string(self):
        while self.peek() != '"' and not self.is_at_end():
            if self.peek() == '\n':
                self.line += 1
            self.advance()

        if self.is_at_end():
            Lox.error(self.line, "Unterminated string.")
            return

        self.advance()
        value = self.source[self.start + 1:self.current - 1]
        self.add_token('STRING', value)

    def number(self):
        while self.peek().isdigit():
            self.advance()

        if self.peek() == '.':
            self.advance()
            if not self.peek().isdigit():
                Lox.error(self.line, "Invalid number format.")
                return
            while self.peek().isdigit():
                self.advance()

        number_str = self.source[self.start:self.current]
        literal_value = float(number_str)
        self.add_token('NUMBER', literal_value)

    def identifier(self):
        while self.peek().isalpha() or self.peek() == '_' or self.peek().isdigit():
            self.advance()

        text = self.source[self.start:self.current]
        token_type = self.reserved_words.get(text, 'IDENTIFIER')
        self.add_token(token_type)

    def peek(self):
        if self.is_at_end():
            return '\0'
        return self.source[self.current]

    def error(self, c):
        self.had_error = True
        Lox.error(self.line, f"Unexpected character: {c}")

    def has_errors(self):
        return self.had_error



def tokenize(source):
    scanner = Scanner(source)
    tokens = scanner.scan_tokens()
    return tokens

class NativeFunction:
    def __init__(self, name, arity, function):
        self.name = name
        self._arity = arity
        self.function = function

    def call(self, interpreter, arguments):
        try:
            return self.function(*arguments)
        except Exception as e:
            raise LoxRuntimeError(f"Error in native function '{self.name}': {str(e)}", 0)

    def arity(self):
        return self._arity

    def __str__(self):
        return f"<native fn {self.name}>"
    
    
def native_clock():
    return time.time()

class Parser:
    def __init__(self, tokens):
        self.tokens = tokens
        self.current = 0

    def parse(self):
        statements = []
        while not self.is_at_end():
            if self.match('SEMICOLON'):
                continue  
            try:
                statements.append(self.declaration())
            except SyntaxError as error:
                self.synchronize()
        return statements

    def declaration(self):
        try:
            if self.match('FUN'):
                return self.function("function")
            elif self.match('VAR'):
                return self.var_declaration()
            else:
                return self.statement()
        except SyntaxError as error:
            self.synchronize()
            return None

    def var_declaration(self):
        var_name_token = self.consume('IDENTIFIER', "Expect variable name.")

        initializer = None
        if self.match('EQUAL'):
            initializer = self.expression()

        self.consume('SEMICOLON', "Expect ';' after variable declaration.")
        return ('var', var_name_token.lexeme, initializer)

    def function(self, kind):
        name_token = self.consume('IDENTIFIER', f"Expect {kind} name.")
        self.consume('LEFT_PAREN', f"Expect '(' after {kind} name.")
        parameters = []

        if not self.check('RIGHT_PAREN'):
            while True:
                if len(parameters) >= 255:
                    self.error(self.peek(), "Cannot have more than 255 parameters.")
                param_token = self.consume('IDENTIFIER', "Expect parameter name.")
                parameters.append(param_token.lexeme)
                if not self.match('COMMA'):
                    break
        self.consume('RIGHT_PAREN', "Expect ')' after parameters.")
        self.consume('LEFT_BRACE', f"Expect '{{' before {kind} body.")
        body = self.block()
        return ('function', name_token.lexeme, parameters, body)

    def return_statement(self):
        keyword_token = self.previous()
        value = None
        if not self.check('SEMICOLON'):
            value = self.expression()
        self.consume('SEMICOLON', "Expect ';' after return value.")
        return ('return', value, keyword_token)

    def print_statement(self):
        value = self.expression()
        self.consume('SEMICOLON', "Expect ';' after value.")
        return ('print', value)

    def expression_statement(self):
        expr = self.expression()
        if self.match('SEMICOLON') or self.is_at_end():
            return ('expr', expr)
        else:
            self.error(self.peek(), "Expect ';' after expression.")
            raise SyntaxError(self.peek(), "Expect ';' after expression.")

    def statement(self):
        if self.match('PRINT'):
            return self.print_statement()
        elif self.match('RETURN'):
            return self.return_statement()
        elif self.match('LEFT_BRACE'):
            return ('block', self.block())
        elif self.match('WHILE'):
            return self.while_statement()
        elif self.match('IF'):
            return self.if_statement()
        elif self.match('FOR'):
            return self.for_statement()
        else:
            return self.expression_statement()

    def if_statement(self):
        self.consume('LEFT_PAREN', "Expect '(' after 'if'.")
        condition = self.or_expr()
        self.consume('RIGHT_PAREN', "Expect ')' after if condition.")
        then_branch = self.statement()
        else_branch = None
        if self.match('ELSE'):
            if self.match('IF'):
                else_branch = self.if_statement()
            else:
                else_branch = self.statement()
        return ('if', condition, then_branch, else_branch)

    def expression(self):
        return self.or_expr()

    def or_expr(self):
        expr = self.and_expr()
        while self.match('OR'):
            right = self.and_expr()
            expr = ('or', expr, right)
        return expr

    def and_expr(self):
        expr = self.assignment()
        while self.match('AND'):
            right = self.assignment()
            expr = ('and', expr, right)
        return expr

    def while_statement(self):
        self.consume('LEFT_PAREN', "Expect '(' after 'while'.")
        condition = self.expression()
        self.consume('RIGHT_PAREN', "Expect ')' after while condition.")
        body = self.statement()
        return ('while', condition, body)

    def for_statement(self):
        self.consume('LEFT_PAREN', "Expect '(' after 'for'.")

        if self.match('SEMICOLON'):
            initializer = None
        elif self.match('VAR'):
            initializer = self.var_declaration()
        else:
            initializer = self.expression_statement()

        if not self.check('SEMICOLON'):
            condition = self.expression()
        else:
            condition = True
        self.consume('SEMICOLON', "Expect ';' after loop condition.")

        if not self.check('RIGHT_PAREN'):
            increment = self.expression()
        else:
            increment = None
        self.consume('RIGHT_PAREN', "Expect ')' after for clauses.")

        body = self.statement()

        if increment is not None:
            body = ('block', [body, ('expr', increment)])

        body = ('while', condition, body)

        if initializer is not None:
            body = ('block', [initializer, body])

        return body


    def block(self):
        statements = []

        while not self.check('RIGHT_BRACE') and not self.is_at_end():
            statements.append(self.declaration())

        self.consume('RIGHT_BRACE', "Expect '}' after block.")
        return statements

    def consume(self, token_type, message):
        if self.check(token_type):
            return self.advance()
        self.error(self.peek(), message)
        raise SyntaxError(self.peek(), message)

    def assignment(self):
        expr = self.equality()

        if not self.check('SEMICOLON') and self.match('EQUAL'):
            equals_token = self.previous()
            value = self.assignment()

            if isinstance(expr, tuple) and expr[0] == 'var_expr':
                var_name = expr[1]
                return ('assign', var_name, value)
            else:
                self.error(equals_token, "Invalid assignment target.")

        return expr

    def synchronize(self):
        self.advance()

        while not self.is_at_end():
            if self.previous().type == 'SEMICOLON':
                return

            next_token_type = self.peek().type
            if next_token_type in ('CLASS', 'FUN', 'VAR', 'FOR', 'IF', 'WHILE', 'PRINT', 'RETURN'):
                return

            self.advance()

    def equality(self):
        expr = self.comparison()
        while not self.check('SEMICOLON') and self.match('EQUAL_EQUAL', 'BANG_EQUAL'):
            operator = self.previous().lexeme
            right = self.comparison()
            expr = (operator, expr, right)
        return expr

    def comparison(self):
        expr = self.addition()
        while not self.check('SEMICOLON') and self.match('GREATER', 'GREATER_EQUAL', 'LESS', 'LESS_EQUAL'):
            operator = self.previous().lexeme
            right = self.addition()
            expr = (operator, expr, right)
        return expr

    def addition(self):
        expr = self.multiplication()
        while not self.check('SEMICOLON') and self.match('PLUS', 'MINUS'):
            operator = self.previous().lexeme
            right = self.multiplication()
            expr = (operator, expr, right)
        return expr

    def multiplication(self):
        expr = self.unary()
        while not self.check('SEMICOLON') and self.match('STAR', 'SLASH'):
            operator = self.previous().lexeme
            right = self.unary()
            expr = (operator, expr, right)
        return expr

    def unary(self):
        if not self.check('SEMICOLON') and self.match('BANG', 'MINUS'):
            operator = self.previous().lexeme
            right = self.unary()
            return (operator, right)
        return self.primary()

    def primary(self):
        if self.match('IDENTIFIER'):
            expr = ('var_expr', self.previous().lexeme, self.previous())
        elif self.match('FALSE'):
            expr = False
        elif self.match('TRUE'):
            expr = True
        elif self.match('NIL'):
            expr = None
        elif self.match('NUMBER'):
            expr = self.previous().literal
        elif self.match('STRING'):
            expr = self.previous().literal
        elif self.match('LEFT_PAREN'):
            expr = ('group', self.expression())
            self.consume('RIGHT_PAREN', "Expect ')' after expression.")
        else:
            self.error(self.peek(), "Expected expression.")
        
        return self.finish_call(expr)

    def finish_call(self, callee):
        while self.match('LEFT_PAREN'):
            callee = self.parse_call(callee)
        return callee

    def parse_call(self, callee):
        arguments = []
        if not self.check('RIGHT_PAREN'):
            while True:
                if len(arguments) >= 255:
                    self.error(self.peek(), "Cannot have more than 255 arguments.")
                arguments.append(self.expression())
                if not self.match('COMMA'):
                    break
        paren = self.consume('RIGHT_PAREN', "Expect ')' after arguments.")
        return ('call', callee, arguments, paren.line)

    def match(self, *types):
        for token_type in types:
            if self.check(token_type):
                self.advance()
                return True
        return False

    def check(self, token_type):
        if self.is_at_end():
            return False
        return self.peek().type == token_type

    def advance(self):
        if not self.is_at_end():
            self.current += 1
        return self.previous()

    def is_at_end(self):
        return self.peek().type == 'EOF'

    def peek(self):
        return self.tokens[self.current]

    def previous(self):
        return self.tokens[self.current - 1]

    def error(self, token, message):
        Lox.error(token.line, message)
        raise SyntaxError(token, message)

class SyntaxError(Exception):
    def __init__(self, token, message):
        self.token = token
        self.message = message
        self.line = token.line

class LoxFunction:
    def __init__(self, name, parameters, body, closure):
        self.name = name
        self.parameters = parameters
        self.body = body
        self.closure = closure

    def call(self, interpreter, arguments):
        environment = Environment(enclosing=self.closure)
        for param_name, arg_value in zip(self.parameters, arguments):
            environment.define(param_name, arg_value)
        try:
            interpreter.execute_block(self.body, environment)
        except Return as return_value:
            return return_value.value
        return None

    def arity(self):
        return len(self.parameters)

    def __str__(self):
        return f"<fn {self.name}>"

class LoxRuntimeError(Exception):
    def __init__(self, message, line):
        super().__init__(message)
        self.message = message
        self.line = line

class Return(Exception):
    def __init__(self, value):
        self.value = value

def parse(source):
    scanner = Scanner(source)
    tokens = scanner.scan_tokens()

    if scanner.has_errors():
        return None

    parser = Parser(tokens)
    ast = parser.parse()

    if ast is not None:
        return ast

class Environment:

    def __init__(self, enclosing=None):
        self.values = {}
        self.enclosing = enclosing

    def define(self, name, value):
        self.values[name] = value

    def get(self, name):
        if name in self.values:
            return self.values[name]
        elif self.enclosing is not None:
            return self.enclosing.get(name)
        else:
            raise LoxRuntimeError(f"Undefined variable '{name}'.", 0)

    def assign(self, name, value):
        if name in self.values:
            self.values[name] = value
        elif self.enclosing is not None:
            self.enclosing.assign(name, value)
        else:
            raise LoxRuntimeError(f"Undefined variable '{name}'.\n[line {self.get_line_of_variable(name)}]")

    def get_line_of_variable(self, name):
        return 0

class Evaluator:
    def __init__(self):
        self.environment = Environment()

        self.environment.define("clock", NativeFunction("clock", 0, native_clock))

    def execute(self, statement):
        if isinstance(statement, tuple):
            node_type = statement[0]
            if node_type == 'function':
                self.execute_function(statement)
            elif node_type == 'block':
                self.execute_block(statement[1], Environment(enclosing=self.environment))
            elif node_type == 'print':
                value = self.evaluate(statement[1])
                print_value(value)
            elif node_type == 'expr':
                self.evaluate(statement[1])
            elif node_type == 'var':
                self.evaluate(statement)
            elif node_type == 'while':
                self.execute_while(statement)
            elif node_type == 'if':
                self.execute_if(statement)
            elif node_type == 'return':
                value = None
                if statement[1] is not None:
                    value = self.evaluate(statement[1])
                raise Return(value)
            else:
                pass
        else:
            pass

    def execute_function(self, statement):
        name = statement[1]
        parameters = statement[2]
        body = statement[3]
        function = LoxFunction(name, parameters, body, self.environment)
        self.environment.define(name, function)

    def execute_block(self, statements, environment):
        previous_environment = self.environment
        self.environment = environment
        try:
            for statement in statements:
                self.execute(statement)
        finally:
            self.environment = previous_environment

    def execute_while(self, statement):
        condition = statement[1]
        body = statement[2]
        while self.is_truthy(self.evaluate(condition)):
            try:
                self.execute(body)
            except Return as return_value:
                raise return_value

    def execute_if(self, statement):
        condition = statement[1]
        then_branch = statement[2]
        else_branch = statement[3]

        if self.is_truthy(self.evaluate(condition)):
            self.execute(then_branch)
        elif else_branch is not None:
            self.execute(else_branch)

    def evaluate(self, expr):
        if isinstance(expr, bool):
            return expr
        if expr is None:
            return None
        if isinstance(expr, (int, float)):
            return expr
        if isinstance(expr, str):
            return expr
        if isinstance(expr, tuple):
            operator = expr[0]
            if operator == 'or':
                left = self.evaluate(expr[1])
                if self.is_truthy(left):
                    return left
                return self.evaluate(expr[2])
            elif operator == 'and':
                left = self.evaluate(expr[1])
                if not self.is_truthy(left):
                    return left
                return self.evaluate(expr[2])
            elif operator == 'group':
                return self.evaluate(expr[1])
            elif operator == 'var':
                var_name = expr[1]
                initializer = expr[2]
                value = self.evaluate(initializer) if initializer is not None else None
                self.environment.define(var_name, value)
                return None
            elif operator == 'var_expr':
                var_name = expr[1]
                token = expr[2]
                try:
                    return self.environment.get(var_name)
                except LoxRuntimeError as e:
                    raise LoxRuntimeError(f"{e.message}", e.line)
            elif operator == 'call':
                callee = self.evaluate(expr[1])
                arguments = [self.evaluate(arg) for arg in expr[2]]
                call_line = expr[3] if len(expr) >= 4 else 0  
                            
                if not isinstance(callee, (LoxFunction, NativeFunction)):
                    raise LoxRuntimeError("Can only call functions and classes.", call_line)
                            
                if len(arguments) != callee.arity():
                    raise LoxRuntimeError(
                        f"Expected {callee.arity()} arguments but got {len(arguments)}.",
                        call_line
                    )
                return callee.call(self, arguments)
            elif operator == 'assign':
                var_name = expr[1]
                value = self.evaluate(expr[2])
                self.environment.assign(var_name, value)
                return value
            elif operator == '-':
                if len(expr) == 2:
                    operand = self.evaluate(expr[1])
                    self.check_number_operand(operand)
                    return -operand
                else:
                    left = self.evaluate(expr[1])
                    right = self.evaluate(expr[2])
                    self.check_number_operands(left, right)
                    return left - right
            elif operator == '+':
                left = self.evaluate(expr[1])
                right = self.evaluate(expr[2])
                call_line = expr[3] if len(expr) >= 4 else 0  
                if isinstance(left, (int, float)) and isinstance(right, (int, float)):
                    return left + right
                elif isinstance(left, str) and isinstance(right, str):
                    return left + right
                else:
                    raise LoxRuntimeError("Operands must be two numbers or two strings.", call_line)
            elif operator == '!':
                operand = self.evaluate(expr[1])
                return not self.is_truthy(operand)
            elif operator in ('*', '/', '%'):
                left = self.evaluate(expr[1])
                right = self.evaluate(expr[2])
                self.check_number_operands(left, right)
                if operator == '*':
                    return left * right
                elif operator == '/':
                    return left / right
                elif operator == '%':
                    return left % right
            elif operator in ('>', '>=', '<', '<='):
                left = self.evaluate(expr[1])
                right = self.evaluate(expr[2])
                return self.compare(left, right, operator)
            elif operator in ('==', '!='):
                left = self.evaluate(expr[1])
                right = self.evaluate(expr[2])
                return left == right if operator == '==' else left != right
            else:
                raise LoxRuntimeError(f"Unknown operator: {operator}", 0)
        else:
            raise LoxRuntimeError("Unknown expression type.", 0)

    def compare(self, left, right, operator):
        if isinstance(left, (int, float)) and not isinstance(left, bool) and \
        isinstance(right, (int, float)) and not isinstance(right, bool):
            return self.compare_numbers(left, right, operator)
        if isinstance(left, str) and isinstance(right, str):
            return self.compare_strings(left, right, operator)
        raise LoxRuntimeError(f"Cannot compare types {type(left).__name__} and {type(right).__name__}.")

    def compare_numbers(self, left, right, operator):
        if operator == '>':
            return left > right
        elif operator == '>=':
            return left >= right
        elif operator == '<':
            return left < right
        elif operator == '<=':
            return left <= right

    def compare_strings(self, left, right, operator):
        if operator == '>':
            return left > right
        elif operator == '>=':
            return left >= right
        elif operator == '<':
            return left < right
        elif operator == '<=':
            return left <= right

    def is_truthy(self, value):
        if value is None:
            return False
        if isinstance(value, bool):
            return value
        return True

    def check_number_operand(self, operand):
        if isinstance(operand, bool):
            raise LoxRuntimeError("Operand must be a number.")
        if not isinstance(operand, (int, float)):
            raise LoxRuntimeError("Operand must be a number.")


    def check_number_operands(self, left, right):
        if isinstance(left, bool) or isinstance(right, bool):
            raise LoxRuntimeError("Operands must be numbers.")
        if not isinstance(left, (int, float)) or not isinstance(right, (int, float)):
            raise LoxRuntimeError("Operands must be numbers.")

    def visit_literal(self, literal):
        if literal == 'true':
            return True
        elif literal == 'false':
            return False
        elif literal == 'nil':
            return None
        elif isinstance(literal, str):
            return literal.strip('"')
        else:
            return literal

def ast_to_string(node):
    if isinstance(node, tuple):
        if node[0] == 'group':
            return f"(group {ast_to_string(node[1])})"
        elif node[0] == 'expr':
            return ast_to_string(node[1])  
        elif node[0] == 'print':
            return f"(print {ast_to_string(node[1])})"
        elif node[0] in ('-', '!', '+', '*', '/', '>', '>=', '<', '<=', '==', '!='):
            operator = node[0]
            if len(node) == 2:
                return f"({operator} {ast_to_string(node[1])})"
            elif len(node) == 3:
                return f"({operator} {ast_to_string(node[1])} {ast_to_string(node[2])})"
        else:
            return str(node)  
    elif isinstance(node, bool):
        return 'true' if node else 'false'
    elif node is None:
        return 'nil'
    else:
        return str(node)

class Runner:
    def __init__(self, evaluator):
        self.evaluator = evaluator

    def run(self, statements):
        for statement in statements:
            self.evaluator.execute(statement)

def main():

    if len(sys.argv) < 3:
        print("Usage: ./your_program.sh tokenize <filename>", file=sys.stderr)
        exit(1)

    command = sys.argv[1]
    filename = sys.argv[2]

    
    with open(filename) as file:
        file_contents = file.read()

    if command == "run":
        try:
            ast = parse(file_contents)
            if Lox.had_error:
                sys.exit(65)

            evaluator = Evaluator()
            runner = Runner(evaluator)

            runner.run(ast)
        except SyntaxError as e:
            print(f"[line {e.line}] Error at '{e.token.lexeme}': {e.message}", file=sys.stderr)
            sys.exit(65)
        except LoxRuntimeError as e:
            sys.exit(70)

    elif command == "parse":
        try:
            ast = parse(file_contents)
            if Lox.had_error:
                sys.exit(65)
            if ast:
                for stmt in ast:
                    ast_str = ast_to_string(stmt)
                    print(ast_str)
            else:
                print('nil')
        except SyntaxError as e:
            print(f"[line {e.line}] Error at '{e.token.lexeme}': {e.message}", file=sys.stderr)
            sys.exit(65)
    
    elif command == "tokenize":
        if file_contents:
            
            tokens = tokenize(file_contents)
            for token in tokens:
                print(token)
        else:
            print("EOF  null")  
        
        if Lox.had_error:
            sys.exit(65)

    elif command == "evaluate":
        try:
            ast = parse(file_contents)
            if Lox.had_error:
                sys.exit(65)

            evaluator = Evaluator()
            result = None

            for stmt in ast:
                if stmt[0] == 'expr':
                    result = evaluator.evaluate(stmt[1])
                elif stmt[0] == 'print':
                    result = evaluator.evaluate(stmt[1])
                    print_value(result)

            if result is not None:
                print_value(result)

            if result is None:
                print("nil")

        except SyntaxError as e:
            print(f"[line {e.token.line}] Error at '{e.token.lexeme}': {e.message}", file=sys.stderr)
            sys.exit(65)
        
        except LoxRuntimeError as e:
            print(e.message, file=sys.stderr)
            print(f"[line {e.line}]", file=sys.stderr)
            sys.exit(70)

    else:
        print(f"Unknown command: {command}", file=sys.stderr)
        exit(1)

def print_value(value):
    if isinstance(value, bool):
        print("true" if value else "false")
    elif isinstance(value, float) and value.is_integer():
        print(int(value))
    elif value is None:
        print("nil")
    else:
        print(value)

if __name__ == "__main__":
    main()