From 339801772bf43c6b80cc13b81d5fbc93c730495a Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Sat, 14 Sep 2024 19:53:02 -0700 Subject: [PATCH] Start of test macros and use + panic in images fix + `for i := num {` fix (#229) * Start of test macros and use * Add comments * fix error handling for images (panic) * Fix that := wouldn't work in simple for loop * Add mandelbrot example (will be good to profile that one as it's slow) * optimize/avoid allocs, better colors --- Makefile | 15 ++++++++++---- check_tests_double_format.sh | 17 +++++++++++++++ eval/eval.go | 2 +- eval/macro_expension.go | 20 ++++++++++++------ examples/mandelbrot.gr | 40 ++++++++++++++++++++++++++++++++++++ extensions/images.go | 2 +- grol_tests/_lib.gr | 36 ++++++++++++++++++++++++++++++++ grol_tests/constants.gr | 33 +++-------------------------- grol_tests/for.gr | 10 +++++++++ 9 files changed, 133 insertions(+), 42 deletions(-) create mode 100755 check_tests_double_format.sh create mode 100644 examples/mandelbrot.gr create mode 100644 grol_tests/_lib.gr create mode 100644 grol_tests/for.gr diff --git a/Makefile b/Makefile index 18a7a65b..0bd5e833 100644 --- a/Makefile +++ b/Makefile @@ -76,13 +76,20 @@ wasm/wasm_exec.js: Makefile wasm/wasm_exec.html: cp "$(shell $(WASM_GO) env GOROOT)/misc/wasm/wasm_exec.html" ./wasm/ -test: grol +test: grol unit-tests examples grol-tests + +unit-tests: CGO_ENABLED=0 go test -tags $(GO_BUILD_TAGS) ./... - GOMEMLIMIT=1GiB ./grol examples/*.gr - GOMEMLIMIT=1GiB ./grol -shared-state grol_tests/*.gr + +examples: grol + GOMEMLIMIT=1GiB ./grol -panic examples/*.gr + +grol-tests: grol + GOMEMLIMIT=1GiB ./grol -panic -shared-state grol_tests/*.gr check: grol ./check_samples_double_format.sh examples/*.gr + ./check_tests_double_format.sh generate: $(GEN) @@ -107,4 +114,4 @@ lint: .golangci.yml .golangci.yml: Makefile curl -fsS -o .golangci.yml https://raw.githubusercontent.com/fortio/workflows/main/golangci.yml -.PHONY: all lint generate test clean run build wasm tinygo wasm-release tiny_test tinygo-tests check install +.PHONY: all lint generate test clean run build wasm tinygo wasm-release tiny_test tinygo-tests check install unit-tests examples grol-tests diff --git a/check_tests_double_format.sh b/check_tests_double_format.sh new file mode 100755 index 00000000..324a5e3a --- /dev/null +++ b/check_tests_double_format.sh @@ -0,0 +1,17 @@ +#! /bin/bash +set -e +export GOMEMLIMIT=1GiB +BIN="./grol -panic -shared-state" +echo "---testing double format for grol_tests ---" +$BIN -format grol_tests/*.gr > /tmp/format1 +$BIN -format /tmp/format1 > /tmp/format2 +diff -u /tmp/format1 /tmp/format2 +$BIN -eval=false grol_tests/*.gr > /tmp/output1 +$BIN /tmp/format2 > /tmp/output2 +diff -u /tmp/output1 /tmp/output2 +$BIN -format -compact grol_tests/*.gr > /tmp/format3 +$BIN -format -compact /tmp/format3 > /tmp/format4 +diff -u /tmp/format3 /tmp/format4 +$BIN /tmp/format4 > /tmp/output3 +diff -u /tmp/output1 /tmp/output3 +echo "---done---" diff --git a/eval/eval.go b/eval/eval.go index 850a3432..8dfc5cda 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -803,7 +803,7 @@ func (s *State) evalForSpecialForms(fe *ast.ForExpression) (object.Object, bool) if !ok { return object.NULL, false } - if ie.Token.Type() != token.ASSIGN { + if ie.Token.Type() != token.ASSIGN && ie.Token.Type() != token.DEFINE { return object.NULL, false } if ie.Left.Value().Type() != token.IDENT { diff --git a/eval/macro_expension.go b/eval/macro_expension.go index b58bea9b..248b71b4 100644 --- a/eval/macro_expension.go +++ b/eval/macro_expension.go @@ -73,6 +73,15 @@ func isMacroCall(s *object.Environment, exp *ast.CallExpression) (*object.Macro, return macro, true } +func (s *State) MacroErrorf(fmtmsg string, args ...any) ast.Node { + res := ast.Builtin{} + res.Token = token.ByType(token.ERROR) + msgNode := ast.StringLiteral{} + msgNode.Token = token.Intern(token.STRING, fmt.Sprintf(fmtmsg, args...)) + res.Parameters = []ast.Node{&msgNode} + return &res +} + func (s *State) ExpandMacros(program ast.Node) ast.Node { return ast.Modify(program, func(node ast.Node) ast.Node { callExpression, ok := node.(*ast.CallExpression) @@ -86,6 +95,10 @@ func (s *State) ExpandMacros(program ast.Node) ast.Node { } args := quoteArgs(callExpression) + if len(args) != len(macro.Parameters) { + return s.MacroErrorf("wrong number of macro arguments, want=%d, got=%d", len(macro.Parameters), len(args)) + } + evalEnv := extendMacroEnv(macro, args) evaluated := evalEnv.Eval(macro.Body) @@ -94,12 +107,7 @@ func (s *State) ExpandMacros(program ast.Node) ast.Node { if !ok { estr := fmt.Sprintf("macro should return Quote. got=%T (%+v)", evaluated, evaluated) log.Critf("%s", estr) - res := ast.Builtin{} - res.Token = token.ByType(token.ERROR) - msg := ast.StringLiteral{} - msg.Token = token.Intern(token.STRING, estr) - res.Parameters = []ast.Node{&msg} - return &res + return s.MacroErrorf("%s", estr) } return quote.Node }) diff --git a/examples/mandelbrot.gr b/examples/mandelbrot.gr new file mode 100644 index 00000000..635f2485 --- /dev/null +++ b/examples/mandelbrot.gr @@ -0,0 +1,40 @@ + +maxIter = 32 + +func mandelbrot(x, y) { + m := maxIter // bind to global, prevents caching, save allocs/cache writes. + zx := 0.0 + zy := 0.0 + for n := m { + zx2 := zx * zx + zy2 := zy * zy + if zx2 + zy2 > 4.0 { + return n + } + zy = 2 * zx * zy + y + zx = zx2 - zy2 + x + } + m +} + +size:=256 +img:="img" +image.new(img, size, size) + +now := time.now() +for px := size { + for py := size { + x := (2.*px)/size - 1.5 + y := (2.*py)/size - 1. + m := mandelbrot(x, y) + color := [0,0,0] + if m != maxIter { + norm := ln(m)/ln(maxIter) + color = [ 0.5+cos(PI/3+norm*PI/4)/2., .9, .5] + } + image.set_hsl(img,px,py,color) + } +} +elapsed := time.now() - now +log("Elapsed time:", elapsed) +image.save(img) diff --git a/extensions/images.go b/extensions/images.go index 0f85c032..6f0c8aca 100644 --- a/extensions/images.go +++ b/extensions/images.go @@ -218,7 +218,7 @@ func createImageFunctions() { //nolint:funlen // this is a group of related func return object.Errorf("unknown image.set function %q", name) } if oerr != nil { - return oerr + return *oerr } img.Image.SetNRGBA(x, y, color) return args[0] diff --git a/grol_tests/_lib.gr b/grol_tests/_lib.gr new file mode 100644 index 00000000..6f6c8ec3 --- /dev/null +++ b/grol_tests/_lib.gr @@ -0,0 +1,36 @@ +/* + +Library of macros for testing + +*/ + +// Convert any grol object to a string. +func str(x) { + sprintf("%v", x) +} + +// Test for absence of error and that the expression's result matches a regexp. +NoErr = macro(msg, expr, expectedRegexp) { + quote(if (r = catch(unquote(expr))).err { + error("FAIL unexpected error:", r.value, "for", unquote(msg)) + } else { + if (regexp(unquote(expectedRegexp), str(r.value))) { + println("OK", unquote(msg), "is:", r.value) + } else { + error("FAIL", unquote(msg), "didn't match expected:", r.value) + } + }) +} + +// Test for expected error and that the message matches a regexp. +IsErr = macro(msg, expr, expectedRegexp) { + quote(if (r = catch(unquote(expr))).err { + if (regexp(unquote(expectedRegexp), str(r.value))) { + println("OK", unquote(msg), "get expected error:", r.value) + } else { + error("FAIL", unquote(msg), "didn't match expected:", r.value) + } + } else { + error("FAIL", unquote(msg),"didn't get expected error:",r.value) + }) +} diff --git a/grol_tests/constants.gr b/grol_tests/constants.gr index 8c6920ed..8bf5f24e 100644 --- a/grol_tests/constants.gr +++ b/grol_tests/constants.gr @@ -1,31 +1,4 @@ +// - Test constants -/* -NoErr = macro(expr,a,b) { - quote( - r = catch(unquote(expr)) - if r.err { - error("unexpected error:", r.value) - unquote(a) - } else { - unquote(b) - } - ) -} -*/ - -if (r=catch(PI)).err { - error("unexpected error:", r.value) -} else { - println("PI is:", r.value) -} - - -if !(r=catch(PI++)).err { - error("expected error, got ok", r) -} else { - if regexp("attempt to change constant PI", r.value) { - println("Got expected error:", r.value) - } else { - error("unexpected error:", r.value) - } -} +NoErr("PI should exist", PI, "3.141592") +IsErr("Can't change a constant", PI++, "attempt to change constant PI") diff --git a/grol_tests/for.gr b/grol_tests/for.gr new file mode 100644 index 00000000..9b43f094 --- /dev/null +++ b/grol_tests/for.gr @@ -0,0 +1,10 @@ + +s :=0 +// check that := works too in for loop. +for n := 4 { + s = s+ n +} +// 0 + 1 + 2 + 3 = 6 +if s != 6 { + error("Expected 6, got", s) // was 16 before the fix. +}