diff --git a/README.md b/README.md index 5f75c82..2fbf8a4 100644 --- a/README.md +++ b/README.md @@ -221,12 +221,14 @@ Vnlike many other programming languages with modules, the modules in `CENTVRION` ### FORS ![CVM FORS](snippets/fors.png) -The `FORS` module allows you to add randomness to your `CENTVRION` program. It adds 2 new built-in functions: `FORTIS_NVMERVS int int` and `FORTIS_ELECTIONIS ['a]`. +The `FORS` module allows you to add randomness to your `CENTVRION` program. It adds 3 new built-in functions: `FORTIS_NVMERVS int int`, `FORTIS_ELECTIONIS ['a]`, and `SEMEN int`. `FORTIS_NVMERVS int int` picks a random int in the (inclusive) range of the two given ints. `FORTIS_ELECTIONIS ['a]` picks a random element from the given array. `FORTIS_ELECTIONIS array` is identical to ```array[FORTIS_NVMERVS NVLLVS ((LONGITVDO array)-I)]```. +`SEMEN int` seeds the random number generator for reproducibility. + ### FRACTIO ![CVM FRACTIO](snippets/fractio.png) diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index f9ffb6d..c55fb0a 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -920,6 +920,14 @@ class BuiltIn(Node): if len(lst) == 0: raise CentvrionError("FORTIS_ELECTIONIS: cannot select from an empty array") return vtable, lst[random.randint(0, len(lst) - 1)] + case "SEMEN": + if "FORS" not in vtable["#modules"]: + raise CentvrionError("Cannot use 'SEMEN' without module 'FORS'") + seed = params[0].value() + if not isinstance(seed, int): + raise CentvrionError("SEMEN requires an integer seed") + random.seed(seed) + return vtable, ValNul() case "LONGITVDO": if isinstance(params[0], (ValList, ValStr)): return vtable, ValInt(len(params[0].value())) diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index fe2b433..84dc9a6 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -186,6 +186,14 @@ def _emit_builtin(node, ctx): else: lines.append(f"CentValue {tmp} = cent_fortis_electionis({param_vars[0]});") + case "SEMEN": + if not ctx.has_module("FORS"): + lines.append('cent_runtime_error("FORS module required for SEMEN");') + lines.append(f"CentValue {tmp} = cent_null();") + else: + lines.append(f"cent_semen({param_vars[0]});") + lines.append(f"CentValue {tmp} = cent_null();") + case "ERVMPE": # break as expression (side-effecting; result is unused) lines.append("break;") diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index 553bd10..2a28084 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -533,6 +533,12 @@ CentValue cent_fortis_electionis(CentValue lst) { return lst.lval.items[rand() % lst.lval.len]; } +void cent_semen(CentValue seed) { + if (seed.type != CENT_INT) + cent_type_error("'SEMEN' requires an integer seed"); + srand((unsigned)seed.ival); +} + /* ------------------------------------------------------------------ */ /* Array helpers */ /* ------------------------------------------------------------------ */ diff --git a/centvrion/compiler/runtime/cent_runtime.h b/centvrion/compiler/runtime/cent_runtime.h index 4612309..b37dbb4 100644 --- a/centvrion/compiler/runtime/cent_runtime.h +++ b/centvrion/compiler/runtime/cent_runtime.h @@ -177,6 +177,7 @@ CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */ CentValue cent_longitudo(CentValue v); /* LONGITVDO */ CentValue cent_fortis_numerus(CentValue lo, CentValue hi); /* FORTIS_NVMERVS */ CentValue cent_fortis_electionis(CentValue lst); /* FORTIS_ELECTIONIS */ +void cent_semen(CentValue seed); /* SEMEN */ void cent_everro(void); /* EVERRO */ /* ------------------------------------------------------------------ */ diff --git a/centvrion/lexer.py b/centvrion/lexer.py index 6745a48..b441ffd 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -41,7 +41,8 @@ builtin_tokens = [("BUILTIN", i) for i in [ "EVERRO", "FORTIS_NVMERVS", "FORTIS_ELECTIONIS", - "LONGITVDO" + "LONGITVDO", + "SEMEN" ]] data_tokens = [ diff --git a/snippets/syntaxes/centvrion.sublime-syntax b/snippets/syntaxes/centvrion.sublime-syntax index 566e83d..0afa2ba 100644 --- a/snippets/syntaxes/centvrion.sublime-syntax +++ b/snippets/syntaxes/centvrion.sublime-syntax @@ -57,7 +57,7 @@ contexts: scope: constant.language.centvrion builtins: - - match: '\b(AVDI_NVMERVS|AVDI|DICE|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO)\b' + - match: '\b(AVDI_NVMERVS|AVDI|DICE|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN)\b' scope: support.function.builtin.centvrion modules: diff --git a/tests.py b/tests.py index ed7322e..582cd5a 100644 --- a/tests.py +++ b/tests.py @@ -460,6 +460,7 @@ builtin_tests = [ ('LONGITVDO("salve")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("salve")]))]), ValInt(5)), ('LONGITVDO("")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("")]))]), ValInt(0)), ("CVM FORS\nFORTIS_ELECTIONIS([I, II, III])", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("FORTIS_ELECTIONIS", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValInt(1)), + ("CVM FORS\nSEMEN(XLII)\nFORTIS_NVMERVS(I, C)", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("FORTIS_NVMERVS", [Numeral("I"), Numeral("C")]))]), ValInt(82)), ] class TestBuiltins(unittest.TestCase): @@ -494,6 +495,8 @@ error_tests = [ ("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings ("I[I]", CentvrionError), # indexing a non-array ("DESIGNA x VT I\nDESIGNA x[I] VT II", CentvrionError), # index-assign to non-array + ("SEMEN(I)", CentvrionError), # requires FORS module + ('CVM FORS\nSEMEN("abc")', CentvrionError), # SEMEN requires integer seed ("FORTIS_ELECTIONIS([])", CentvrionError), # FORS required for FORTIS_ELECTIONIS ("CVM FORS\nFORTIS_ELECTIONIS([])", CentvrionError), # FORTIS_ELECTIONIS on empty array ("CVM FORS\nFORTIS_NVMERVS(X, I)", CentvrionError), # FORTIS_NVMERVS a > b