🐐 Compiler
This commit is contained in:
108
tests.py
108
tests.py
@@ -1,4 +1,7 @@
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from unittest.mock import patch
|
||||
@@ -14,11 +17,17 @@ from centvrion.ast_nodes import (
|
||||
Fractio, frac_to_fraction, fraction_to_frac,
|
||||
num_to_int, int_to_num, make_string,
|
||||
)
|
||||
from centvrion.compiler.emitter import compile_program
|
||||
from centvrion.errors import CentvrionError
|
||||
from centvrion.lexer import Lexer
|
||||
from centvrion.parser import Parser
|
||||
from centvrion.values import ValInt, ValStr, ValBool, ValList, ValNul, ValFunc, ValFrac
|
||||
|
||||
_RUNTIME_C = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"centvrion", "compiler", "runtime", "cent_runtime.c"
|
||||
)
|
||||
|
||||
def run_test(self, source, target_nodes, target_value, target_output="", input_lines=[]):
|
||||
random.seed(1)
|
||||
|
||||
@@ -72,11 +81,27 @@ def run_test(self, source, target_nodes, target_value, target_output="", input_l
|
||||
##########################
|
||||
###### Compiler Test #####
|
||||
##########################
|
||||
# try:
|
||||
# bytecode = program.compile()
|
||||
# ...
|
||||
# except Exception as e:
|
||||
# raise Exception("###Compiler test###") from e
|
||||
c_source = compile_program(program)
|
||||
with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c:
|
||||
tmp_c.write(c_source)
|
||||
tmp_c_path = tmp_c.name
|
||||
with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin:
|
||||
tmp_bin_path = tmp_bin.name
|
||||
try:
|
||||
subprocess.run(
|
||||
["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
stdin_data = "".join(f"{l}\n" for l in input_lines)
|
||||
proc = subprocess.run(
|
||||
[tmp_bin_path],
|
||||
input=stdin_data, capture_output=True, text=True,
|
||||
)
|
||||
self.assertEqual(proc.returncode, 0, f"Compiler binary exited non-zero:\n{proc.stderr}")
|
||||
self.assertEqual(proc.stdout, target_output, "Compiler output test")
|
||||
finally:
|
||||
os.unlink(tmp_c_path)
|
||||
os.unlink(tmp_bin_path)
|
||||
|
||||
|
||||
# --- Output ---
|
||||
@@ -384,7 +409,7 @@ error_tests = [
|
||||
("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", CentvrionError), # args to zero-param function
|
||||
("SI NVLLVS TVNC { DESIGNA r VT I }", CentvrionError), # NVLLVS cannot be used as boolean
|
||||
("NVLLVS AVT VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in AVT
|
||||
('"hello" + " world"', CentvrionError), # use : for string concatenation, not +
|
||||
('"hello" + " world"', CentvrionError), # use & for string concatenation, not +
|
||||
("[I, II][III]", CentvrionError), # index too high
|
||||
("CVM SVBNVLLA\n[I, II][-I]", CentvrionError), # negative index
|
||||
("[I, II][-I]", CentvrionError), # negative value
|
||||
@@ -402,10 +427,14 @@ error_tests = [
|
||||
("LONGITVDO(I)", CentvrionError), # LONGITVDO on non-array
|
||||
("DESIGNA x VT I\nINVOCA x ()", CentvrionError), # invoking a non-function
|
||||
("SI I TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: int
|
||||
("IIIS", CentvrionError), # fraction without FRACTIO module
|
||||
("DESIGNA z VT I - I\nSI z TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: zero int
|
||||
("SI [I] TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: non-empty list
|
||||
("SI [] TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: empty list
|
||||
("DESIGNA x VT I\nDVM x FACE {\nDESIGNA x VT x + I\n}", CentvrionError), # non-bool DVM condition: int
|
||||
("NON I", CentvrionError), # NON on integer
|
||||
("DESIGNA z VT I - I\nNON z", CentvrionError), # NON on zero integer
|
||||
('NON "hello"', CentvrionError), # NON on string
|
||||
]
|
||||
|
||||
class TestErrors(unittest.TestCase):
|
||||
@@ -415,6 +444,39 @@ class TestErrors(unittest.TestCase):
|
||||
run_test(self, source, None, None)
|
||||
|
||||
|
||||
def run_compiler_error_test(self, source):
|
||||
lexer = Lexer().get_lexer()
|
||||
tokens = lexer.lex(source + "\n")
|
||||
program = Parser().parse(tokens)
|
||||
try:
|
||||
c_source = compile_program(program)
|
||||
except CentvrionError:
|
||||
return # compile-time detection is valid
|
||||
with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c:
|
||||
tmp_c.write(c_source)
|
||||
tmp_c_path = tmp_c.name
|
||||
with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin:
|
||||
tmp_bin_path = tmp_bin.name
|
||||
try:
|
||||
subprocess.run(
|
||||
["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
proc = subprocess.run([tmp_bin_path], capture_output=True, text=True)
|
||||
self.assertNotEqual(proc.returncode, 0, "Expected non-zero exit for error program")
|
||||
self.assertTrue(proc.stderr.strip(), "Expected error message on stderr")
|
||||
finally:
|
||||
os.unlink(tmp_c_path)
|
||||
os.unlink(tmp_bin_path)
|
||||
|
||||
compiler_error_tests = [(s, e) for s, e in error_tests if e == CentvrionError]
|
||||
|
||||
class TestCompilerErrors(unittest.TestCase):
|
||||
@parameterized.expand(compiler_error_tests)
|
||||
def test_compiler_errors(self, source, error_type):
|
||||
run_compiler_error_test(self, source)
|
||||
|
||||
|
||||
# --- Repr ---
|
||||
|
||||
repr_tests = [
|
||||
@@ -1178,21 +1240,18 @@ non_tests = [
|
||||
("NON NON VERITAS",
|
||||
Program([], [ExpressionStatement(UnaryNot(UnaryNot(Bool(True))))]),
|
||||
ValBool(True)),
|
||||
("NON I",
|
||||
Program([], [ExpressionStatement(UnaryNot(Numeral("I")))]),
|
||||
ValBool(False)),
|
||||
# zero int is falsy, so NON gives True
|
||||
("DESIGNA z VT I - I\nNON z",
|
||||
Program([], [Designa(ID("z"), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")), ExpressionStatement(UnaryNot(ID("z")))]),
|
||||
("DESIGNA b VT I EST II\nNON b",
|
||||
Program([], [Designa(ID("b"), BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("b")))]),
|
||||
ValBool(True)),
|
||||
# NON binds tighter than AVT: (NON VERITAS) AVT FALSITAS → FALSITAS AVT FALSITAS → False
|
||||
("DESIGNA z VT I EST I\nNON z",
|
||||
Program([], [Designa(ID("z"), BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("z")))]),
|
||||
ValBool(False)),
|
||||
("NON VERITAS AVT FALSITAS",
|
||||
Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_AVT"))]),
|
||||
ValBool(False)),
|
||||
# NON binds tighter than EST: (NON I) EST I → FALSITAS EST I → False
|
||||
("NON I EST I",
|
||||
Program([], [ExpressionStatement(BinOp(UnaryNot(Numeral("I")), Numeral("I"), "KEYWORD_EST"))]),
|
||||
ValBool(False)),
|
||||
("NON VERITAS EST FALSITAS",
|
||||
Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_EST"))]),
|
||||
ValBool(True)),
|
||||
]
|
||||
|
||||
class TestNon(unittest.TestCase):
|
||||
@@ -1272,26 +1331,15 @@ class TestFractioComparisons(unittest.TestCase):
|
||||
run_test(self, source, nodes, value)
|
||||
|
||||
|
||||
class TestFractioErrors(unittest.TestCase):
|
||||
def test_fraction_without_module(self):
|
||||
source = "IIIS\n"
|
||||
lexer = Lexer().get_lexer()
|
||||
tokens = lexer.lex(source)
|
||||
program = Parser().parse(tokens)
|
||||
with self.assertRaises(CentvrionError):
|
||||
program.eval()
|
||||
|
||||
class TestFractioHelpers(unittest.TestCase):
|
||||
def test_frac_to_fraction_ordering(self):
|
||||
with self.assertRaises(CentvrionError):
|
||||
frac_to_fraction(".S") # . before S violates highest-to-lowest
|
||||
|
||||
def test_frac_to_fraction_level_overflow(self):
|
||||
with self.assertRaises(CentvrionError):
|
||||
frac_to_fraction("SSSSSS") # 6*S = 36/12 >= 1 per level... wait S can only appear once
|
||||
# Actually "SS" means S twice, which is 12/12 = 1, violating < 12/12 constraint
|
||||
frac_to_fraction("SSSSSS") # SS means S twice = 12/12 = 1, violating < 12/12 constraint
|
||||
|
||||
|
||||
class TestFractioHelpers(unittest.TestCase):
|
||||
def test_frac_to_fraction_iiis(self):
|
||||
self.assertEqual(frac_to_fraction("IIIS"), Fraction(7, 2))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user