Files
CSM14204-Compilers/tests/interpreter_test.py
T
2026-06-24 17:24:04 +02:00

420 lines
9.5 KiB
Python

import pytest
from typing import Any
from compiler.parser import parse
from compiler.tokenizer import tokenize
from compiler.interpreter import interpret, SymTab, Unit
def test_interpreter_addition() -> None:
exp = '2 + 3'
tokens = tokenize(exp)
ast = parse(tokens)
assert interpret(ast.block) == 5
def test_interpreter_boolean_coercion() -> None:
exp = '1 + (2 < 3)'
tokens = tokenize(exp)
ast = parse(tokens)
assert interpret(ast.block) == 2
def test_interpreter_nested_blocks() -> None:
code = '''
{
var x = 5;
{
var y = 10;
x + y
}
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 15
def test_interpreter_variable_shadowing() -> None:
code = '''
var x = 1;
{
var x = 2;
x
}
x
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 1
def test_interpreter_assignment_in_outer_scope() -> None:
code = '''
var x = 5;
{
x = x + 1;
x
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 6
def test_interpreter_assignment_operator() -> None:
code = 'var x = 0; x = 5; x'
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 5
def test_interpreter_operator_precedence() -> None:
code = '10 - 4 - 3' # (10-4)-3 = 3
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 3
def test_interpreter_logical_operators() -> None:
code = 'true and false or true' # (true and false) or true = true
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is True
def test_interpreter_comparison_equality() -> None:
code = '5 > 3 == true' # (5>3) == true → true
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is True
def test_interpreter_unary_operators() -> None:
code = 'not (5 < 3)' # not(false) → true
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is True
def test_interpreter_block_scoping() -> None:
code = '''
{
var a = 10;
{
var b = 20;
a + b
}
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 30
def test_interpreter_variable_redeclaration() -> None:
code = '''
{
var x = 1;
var x = 2;
x
}
'''
tokens = tokenize(code)
ast = parse(tokens)
with pytest.raises(Exception):
interpret(ast.block)
def test_interpreter_while_loop() -> None:
code = '''
var counter = 3;
while counter > 0 do {
counter = counter - 1
};
counter
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 0
def test_interpreter_empty_block() -> None:
code = '{}'
tokens = tokenize(code)
ast = parse(tokens)
assert isinstance(interpret(ast.block), Unit)
def test_interpreter_block_last_semicolon() -> None:
code = '''
{
var x = 5;
x;
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert isinstance(interpret(ast.block), Unit)
def test_interpreter_if_without_else_returns_unit() -> None:
code = 'if false then 5'
tokens = tokenize(code)
ast = parse(tokens)
assert isinstance(interpret(ast.block), Unit)
def test_interpreter_if_else_true_branch() -> None:
code = 'if true then 10 else 20'
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 10
def test_interpreter_if_else_false_branch() -> None:
code = 'if false then 10 else 20'
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 20
def test_interpreter_function_call_raises_error(capsys: pytest.CaptureFixture) -> None:
code = 'print_int(5)'
tokens = tokenize(code)
ast = parse(tokens)
assert isinstance(interpret(ast.block), Unit)
captured = capsys.readouterr()
assert captured.out == '5\n'
def test_interpreter_division_modulo_operators() -> None:
code = '8 / 3 + 8 % 3' # 2 + 2 = 4
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 4
def test_interpreter_assignment_to_non_identifier_raises() -> None:
code = '5 = 10'
tokens = tokenize(code)
ast = parse(tokens)
with pytest.raises(Exception):
interpret(ast.block)
def test_interpreter_while_loop_zero_iterations() -> None:
code = '''
var x = 3;
while x > 5 do {
x = x - 1
};
x
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 3
def test_interpreter_nested_if_expression() -> None:
code = '(if true then 5 else 3) + 2'
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 7
def test_interpreter_unary_not_on_boolean() -> None:
code = 'not true'
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is False
def test_interpreter_unary_negate_non_int_raises() -> None:
code = '-true'
tokens = tokenize(code)
ast = parse(tokens)
with pytest.raises(Exception):
interpret(ast.block)
def test_interpreter_and_short_circuit() -> None:
code = '''
var evaluated = false;
false and { evaluated = true; true };
evaluated
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is False
def test_interpreter_and_evaluates_both() -> None:
code = '''
var evaluated = false;
true and { evaluated = true; true };
evaluated
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is True
def test_interpreter_or_short_circuit() -> None:
code = '''
var evaluated = false;
true or { evaluated = true; true };
evaluated
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is False
def test_interpreter_or_evaluates_both() -> None:
code = '''
var evaluated = false;
false or { evaluated = true; true };
evaluated
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is True
def test_interpreter_unary_negate_int() -> None:
code = '-5'
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == -5
def test_interpreter_unary_negate_bool_raises() -> None:
code = '-true'
tokens = tokenize(code)
ast = parse(tokens)
with pytest.raises(Exception):
interpret(ast.block)
def test_interpreter_unary_not_on_int_raises() -> None:
code = 'not 5'
tokens = tokenize(code)
ast = parse(tokens)
with pytest.raises(Exception):
interpret(ast.block)
def test_interpreter_binary_operator_precedence() -> None:
code = '1 + 2 * 3 == 7' # 1 + (2 * 3) == 7 → true
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is True
def test_interpreter_binary_operator_associativity() -> None:
code = '10 - 4 - 3' # (10 - 4) - 3 = 3
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 3
def test_interpreter_binary_operator_equality() -> None:
code = '5 == 5 and 3 != 4' # true and true → true
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is True
def test_interpreter_binary_operator_comparison() -> None:
code = '5 < 10 and 10 >= 10' # true and true → true
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) is True
def test_interpreter_binary_operator_division_by_zero_raises() -> None:
code = '5 / 0'
tokens = tokenize(code)
ast = parse(tokens)
with pytest.raises(Exception):
interpret(ast.block)
def test_interpreter_binary_operator_modulo_by_zero_raises() -> None:
code = '5 % 0'
tokens = tokenize(code)
ast = parse(tokens)
with pytest.raises(Exception):
interpret(ast.block)
def test_interpreter_while_loop_multiple_iterations() -> None:
code = '''
var x = 3;
while x > 0 do {
x = x - 1
};
x
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 0
def test_interpreter_block_with_multiple_statements() -> None:
code = '''
{
var x = 5;
var y = 10;
x + y
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 15
def test_interpreter_block_with_trailing_semicolon() -> None:
code = '''
{
var x = 5;
x;
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert isinstance(interpret(ast.block), Unit)
def test_interpreter_block_with_nested_blocks() -> None:
code = '''
{
var x = 5;
{
var y = 10;
x + y
}
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 15
def test_interpreter_variable_declaration_in_block() -> None:
code = '''
{
var x = 5;
x
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 5
def test_interpreter_variable_redeclaration_in_block_raises() -> None:
code = '''
{
var x = 5;
var x = 10;
x
}
'''
tokens = tokenize(code)
ast = parse(tokens)
with pytest.raises(Exception):
interpret(ast.block)
def test_interpreter_assignment_in_block() -> None:
code = '''
{
var x = 5;
x = x + 1;
x
}
'''
tokens = tokenize(code)
ast = parse(tokens)
assert interpret(ast.block) == 6
def test_interpreter_function_call_print_int() -> None:
code = 'print_int(5)'
tokens = tokenize(code)
ast = parse(tokens)
assert isinstance(interpret(ast.block), Unit)
def test_interpreter_function_call_print_bool() -> None:
code = 'print_bool(true)'
tokens = tokenize(code)
ast = parse(tokens)
assert isinstance(interpret(ast.block), Unit)
def test_interpreter_function_call_read_int() -> None:
code = 'read_int()'
tokens = tokenize(code)
ast = parse(tokens)
# Mock input for read_int
import builtins
original_input: Any = builtins.input
func: Any = lambda: '42'
builtins.input = func
assert interpret(ast.block) == 42
builtins.input = original_input