diff options
| author | user <user@clank> | 2026-08-16 16:16:19 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-16 16:16:19 +0200 |
| commit | a540b74dd3bb04d33a69219487cb2db32f2022f1 (patch) | |
| tree | 4e21d8c3cf739c0f9cd2e91983e23fd4e796cb63 /tests | |
| parent | sl0ppty: sin and cos (diff) | |
| download | slosh-a540b74dd3bb04d33a69219487cb2db32f2022f1.tar.gz slosh-a540b74dd3bb04d33a69219487cb2db32f2022f1.tar.xz slosh-a540b74dd3bb04d33a69219487cb2db32f2022f1.zip | |
sl0ppty: PI is 180
Angles here are degrees, and pi in an angle language is not a number -- it is a
half turn. So `PI` is 180, `TAU` is 360, and a formula written the radian way
ports across as written:
sin(TAU * x / cols) one cycle over the pane's width
sin(TAU * p * 3 / perim) three lobes fitted exactly to a border
PI / 2 a quarter turn
PI / 6 thirty degrees, exactly
That last line is the argument. A numeric pi would be 3 in this language, and
`3.14159 / 6` in integers is 0 -- so radians cannot express a sixth of a turn
here at all, while degrees give it as a whole number. The pun is not a
compromise; it is the more precise reading of the two.
Which is why there is no `deg2rad` or `rad2deg` beside them, and asking for one
says `unknown function`. There is one angle unit. A conversion could only lose
the angle (`deg2rad(90)` is 1, and `sin(1)` is nearly nothing) or invent a second
scale for the language to disagree with itself about.
Spelled-out numbers rather than variables: they read no environment, carry no
dependency, and fold into the program like the literals they are. The browser
preview learned the same two, from the same table.
Verified: 12 cases in `tests/expr_test.c`, including that `deg2rad` and
`rad2deg` are refused; the cross-check agrees with the compiler on 700
evaluations, now including `sin(TAU * x / cols)` and friends.
Two smaller things ride along, both from the repl. `PI`/`TAU` tab-complete, and
its drift test grew a third table to check -- it already caught `sin`/`cos`
missing before I did. And it now checks the script *parses* before starting a
pane that runs it: I broke the indentation of one line here, and the failure
arrived as `IndexError: list index out of range` from a pane that died before
its prompt, which is a long way from "line 212".
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/expr_test.c | 18 | ||||
| -rwxr-xr-x | tests/test_shader_repl.py | 22 | ||||
| -rw-r--r-- | tests/test_shadertoy.py | 5 |
3 files changed, 44 insertions, 1 deletions
diff --git a/tests/expr_test.c b/tests/expr_test.c index b84e355..f775065 100644 --- a/tests/expr_test.c +++ b/tests/expr_test.c @@ -132,6 +132,24 @@ int main(void) { eq("128 + sin(270) / 2", 1); /* -255/2 truncates toward zero, so 128 - 127 */ eq("abs(sin(270))", 255); + printf("\n-- PI and TAU are angles, not numbers\n"); + eq("PI", 180); + eq("TAU", 360); + eq("PI * 2 == TAU", 1); + /* The point of the pun: a formula written the radian way works as written. */ + eq("sin(PI)", 0); + eq("sin(PI / 2)", 255); + eq("cos(TAU)", 255); + eq("sin(TAU * 3 / 4)", -255); + /* ...and exactly, which real radians could not manage in integers: a sixth of + * a turn is 30 whole degrees, where 3.14159 / 6 would be 0. */ + eq("sin(PI / 6)", 127); + eq("sin(PI / 3)", 221); + eq("sin(PI / 4)", 180); + /* No conversions, because there is one unit and nothing to convert. */ + refused("deg2rad is not a thing", "deg2rad(90)"); + refused("nor rad2deg", "rad2deg(1)"); + printf("\n-- variables\n"); eq("x", 3); eq("y", 4); diff --git a/tests/test_shader_repl.py b/tests/test_shader_repl.py index c3c46cf..8736420 100755 --- a/tests/test_shader_repl.py +++ b/tests/test_shader_repl.py @@ -10,6 +10,7 @@ becomes a list of what was true once. The rest is the loop itself, driven the way a person drives it -- typed lines, history, a tab -- in a real session with a real pty. """ +import ast import os import re import subprocess @@ -34,7 +35,8 @@ def repl_lists(): body = re.search(r"^%s = (\[[^\]]*\])" % var, src, re.M).group(1) return [w.strip().strip('"\'').rstrip("=(").strip('"') for w in re.findall(r'["\'][^"\']+["\']', body)] - return {v: names(v) for v in ("COMMANDS", "SHADERS", "PROPS", "VARS", "FUNCS")} + return {v: names(v) + for v in ("COMMANDS", "SHADERS", "PROPS", "VARS", "CONSTS", "FUNCS")} def source_names(path, pattern): @@ -70,6 +72,19 @@ def session(cfg_path, data_home): cols=84, rows=16, config=cfg_path, env=env) +def test_the_script_is_a_script(): + """First, because everything below it starts a pane that runs this file: a + syntax error otherwise shows up as `IndexError: list index out of range` from + a pane that died before printing its prompt, which is a long way from saying + line 212 is indented wrong.""" + try: + ast.parse(open(REPL).read()) + ok, why = True, "" + except SyntaxError as exc: + ok, why = False, "%s line %s: %s" % (exc.__class__.__name__, exc.lineno, exc.msg) + check("contrib/shader-repl parses", ok, why) + + def test_the_completion_list_matches_the_code(): lists = repl_lists() @@ -90,6 +105,10 @@ def test_the_completion_list_matches_the_code(): variables <= set(lists["VARS"]), "missing: %s" % sorted(variables - set(lists["VARS"]))) + consts = source_names("expr.c", r'\{"([A-Z]+)", \d+\},?\s') + check("every constant can be completed", consts <= set(lists["CONSTS"]), + "missing: %s" % sorted(consts - set(lists["CONSTS"]))) + funcs = source_names("expr.c", r'\{"([a-z]+)", \d, OP_[A-Z]+\}') check("every expression function can be completed", funcs <= set(lists["FUNCS"]), @@ -185,6 +204,7 @@ def test_help_lists_what_there_is(): if __name__ == "__main__": + test_the_script_is_a_script() test_the_completion_list_matches_the_code() test_every_offered_shader_is_accepted_by_a_session() test_history_survives_the_next_run() diff --git a/tests/test_shadertoy.py b/tests/test_shadertoy.py index 2f88225..99e724a 100644 --- a/tests/test_shadertoy.py +++ b/tests/test_shadertoy.py @@ -123,6 +123,11 @@ EXPRS = [ "abs(sin(x * 9 + t / 6))", "clamp(sin(t / 5) + 128, 0, 255)", "sin(sin(t / 3))", + # pi as an angle: a half turn, in the degrees this language counts in + "PI", "TAU", "PI * 2 == TAU", + "sin(PI)", "sin(PI / 2)", "sin(PI / 6)", "cos(TAU)", + "sin(TAU * x / cols)", "sin(TAU * y / rows + t / 5)", + "128 + sin(TAU * t / 4000) / 2", ] |
