diff --git a/README.md b/README.md index dc043ee..9122943 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,11 @@ Returns VERITAS if a strict majority of the arguments are VERITAS, FALSITAS othe Returns the type of `value` as a string: `NVMERVS` (integer), `LITTERA` (string), `VERAX` (boolean), `CATALOGVS` (list), `FRACTIO` (fraction), `TABVLA` (dict), `FVNCTIO` (function), or `NVLLVS` (null). +### DORMI +`DORMI(n)` + +Sleeps for `n` seconds, where `n` is an integer, fraction, or NVLLVS (treated as 0). Returns nothing meaningful. + ## Modules Modules are additions to the base `CENTVRION` syntax. They add or change certain features. Modules are included in your code by having diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index ef926e7..2bd341b 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -1,5 +1,6 @@ import re import random +import time from fractions import Fraction from rply.token import BaseBox @@ -1128,6 +1129,18 @@ class BuiltIn(Node): ValFunc: "FVNCTIO", ValNul: "NVLLVS", } return vtable, ValStr(type_map[type(params[0])]) + case "DORMI": + v = params[0] + if isinstance(v, ValNul): + seconds = 0 + elif isinstance(v, ValInt): + seconds = v.value() + elif isinstance(v, ValFrac): + seconds = float(v.value()) + else: + raise CentvrionError("DORMI requires a number or NVLLVS") + time.sleep(seconds) + return vtable, ValNul() case _: raise NotImplementedError(self.builtin) diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index 33dda29..105e2b6 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -258,6 +258,10 @@ def _emit_builtin(node, ctx): case "TYPVS": lines.append(f"CentValue {tmp} = cent_typvs({param_vars[0]});") + case "DORMI": + lines.append(f"cent_dormi({param_vars[0]});") + lines.append(f"CentValue {tmp} = cent_null();") + case _: raise NotImplementedError(node.builtin) diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index 9b602fa..2e44ca0 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -559,6 +559,23 @@ CentValue cent_typvs(CentValue v) { return cent_str("IGNOTA"); /* unreachable */ } +void cent_dormi(CentValue n) { + struct timespec ts; + if (n.type == CENT_NULL) { + ts.tv_sec = 0; ts.tv_nsec = 0; + } else if (n.type == CENT_INT) { + ts.tv_sec = n.ival; ts.tv_nsec = 0; + } else if (n.type == CENT_FRAC) { + long sec = n.fval.num / n.fval.den; + long rem = n.fval.num % n.fval.den; + ts.tv_sec = sec; + ts.tv_nsec = rem * 1000000000L / n.fval.den; + } else { + cent_type_error("'DORMI' requires a number or NVLLVS"); + } + nanosleep(&ts, NULL); +} + CentValue cent_fortis_numerus(CentValue lo, CentValue hi) { if (lo.type != CENT_INT || hi.type != CENT_INT) cent_type_error("'FORTIS_NVMERVS' requires two integers"); diff --git a/centvrion/compiler/runtime/cent_runtime.h b/centvrion/compiler/runtime/cent_runtime.h index db1ae68..b0d9453 100644 --- a/centvrion/compiler/runtime/cent_runtime.h +++ b/centvrion/compiler/runtime/cent_runtime.h @@ -221,6 +221,7 @@ void cent_semen(CentValue seed); /* SEMEN */ void cent_everro(void); /* EVERRO */ CentValue cent_senatus(CentValue *args, int n); /* SENATVS */ CentValue cent_typvs(CentValue v); /* TYPVS */ +void cent_dormi(CentValue n); /* DORMI */ /* ------------------------------------------------------------------ */ /* Array helpers */ diff --git a/centvrion/lexer.py b/centvrion/lexer.py index e55f22e..73b6fec 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -44,6 +44,7 @@ builtin_tokens = [("BUILTIN", i) for i in [ "CLAVES", "DECIMATIO", "DICE", + "DORMI", "EVERRO", "FORTIS_NVMERVS", "FORTIS_ELECTIONIS", diff --git a/tests.py b/tests.py index e11437f..bfa245b 100644 --- a/tests.py +++ b/tests.py @@ -2,6 +2,7 @@ import os import random import subprocess import tempfile +import time import unittest from io import StringIO from unittest.mock import patch @@ -2217,5 +2218,93 @@ class TestFvnctio(unittest.TestCase): run_test(self, source, nodes, value, output) +# --- DORMI --- + +dormi_tests = [ + ("DORMI(NVLLVS)", + Program([], [ExpressionStatement(BuiltIn("DORMI", [Nullus()]))]), + ValNul()), +] + +class TestDormi(unittest.TestCase): + @parameterized.expand(dormi_tests) + def test_dormi(self, source, nodes, value, output=""): + run_test(self, source, nodes, value, output) + + def test_dormi_timing_int(self): + source = "DORMI(I)\n" + lexer = Lexer().get_lexer() + tokens = lexer.lex(source) + program = Parser().parse(tokens) + + start = time.time() + program.eval() + elapsed = time.time() - start + self.assertAlmostEqual(elapsed, 1.0, delta=0.5) + + def test_dormi_timing_int_compiled(self): + source = "DORMI(I)\n" + lexer = Lexer().get_lexer() + tokens = lexer.lex(source) + program = Parser().parse(tokens) + + 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, + ) + start = time.time() + proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) + elapsed = time.time() - start + self.assertEqual(proc.returncode, 0) + self.assertAlmostEqual(elapsed, 1.0, delta=0.5) + finally: + os.unlink(tmp_c_path) + os.unlink(tmp_bin_path) + + def test_dormi_timing_frac(self): + source = "CVM FRACTIO\nDORMI(S)\n" + lexer = Lexer().get_lexer() + tokens = lexer.lex(source) + program = Parser().parse(tokens) + + start = time.time() + program.eval() + elapsed = time.time() - start + self.assertAlmostEqual(elapsed, 0.5, delta=0.5) + + def test_dormi_timing_frac_compiled(self): + source = "CVM FRACTIO\nDORMI(S)\n" + lexer = Lexer().get_lexer() + tokens = lexer.lex(source) + program = Parser().parse(tokens) + + 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, + ) + start = time.time() + proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) + elapsed = time.time() - start + self.assertEqual(proc.returncode, 0) + self.assertAlmostEqual(elapsed, 0.5, delta=0.5) + finally: + os.unlink(tmp_c_path) + os.unlink(tmp_bin_path) + + if __name__ == "__main__": unittest.main()