From 78cdd8bad540f1cbc030f84129321c10312aeb9a Mon Sep 17 00:00:00 2001 From: Julia Ogris Date: Wed, 29 Mar 2023 09:45:44 +1100 Subject: [PATCH] wip --- frontend/courses/courses.json | 8 ++- frontend/courses/sample/animate/bounce.evy | 17 +++++ .../sample/{draw => animate}/ellipse.evy | 0 frontend/courses/sample/draw/curve.evy | 1 + frontend/index.js | 67 +++++++++++++++++++ frontend/module/yace-editor.js | 1 + main.go | 1 + pkg/evaluator/builtin.go | 29 ++++++++ pkg/parser/ast.go | 5 +- pkg/parser/parser.go | 2 +- pkg/wasm/imports.go | 33 ++++++--- 11 files changed, 149 insertions(+), 15 deletions(-) create mode 100644 frontend/courses/sample/animate/bounce.evy rename frontend/courses/sample/{draw => animate}/ellipse.evy (100%) create mode 100644 frontend/courses/sample/draw/curve.evy diff --git a/frontend/courses/courses.json b/frontend/courses/courses.json index 92f325e7..e4004e9f 100644 --- a/frontend/courses/courses.json +++ b/frontend/courses/courses.json @@ -24,10 +24,10 @@ { "id": "squares", "title": "Squares" }, { "id": "rainbow", "title": "Rainbow" }, { "id": "poly", "title": "Polygon and Polyline" }, - { "id": "ellipse", "title": "Ellipse" }, { "id": "fill", "title": "Stroke and Fill" }, { "id": "linestyle", "title": "Line Styles" }, - { "id": "text", "title": "Text" } + { "id": "text", "title": "Text" }, + { "id": "curve", "title": "Curve" } ] }, { @@ -36,9 +36,11 @@ "emoji": "🤹‍♀️", "units": [ { "id": "movingdot", "title": "Moving Red Dot" }, + { "id": "bounce", "title": "Bounce" }, + { "id": "draw", "title": "Draw" }, { "id": "juggle", "title": "Juggle" }, { "id": "splashtrig", "title": "Splash of Trig" }, - { "id": "draw", "title": "Draw" } + { "id": "ellipse", "title": "Interactive Ellipse" } ] }, { diff --git a/frontend/courses/sample/animate/bounce.evy b/frontend/courses/sample/animate/bounce.evy new file mode 100644 index 00000000..c53f33b3 --- /dev/null +++ b/frontend/courses/sample/animate/bounce.evy @@ -0,0 +1,17 @@ +background := "hsl(0deg 0% 0% / 10%)" +x := 10 +y := 50 +s := 1 +width 1 +fill background +stroke "red" + +on animate + clear background + move x y + circle 10 + x = x + s + if x < 10 or x > 90 + s = -s + end +end diff --git a/frontend/courses/sample/draw/ellipse.evy b/frontend/courses/sample/animate/ellipse.evy similarity index 100% rename from frontend/courses/sample/draw/ellipse.evy rename to frontend/courses/sample/animate/ellipse.evy diff --git a/frontend/courses/sample/draw/curve.evy b/frontend/courses/sample/draw/curve.evy new file mode 100644 index 00000000..062b5998 --- /dev/null +++ b/frontend/courses/sample/draw/curve.evy @@ -0,0 +1 @@ +print "todo" diff --git a/frontend/index.js b/frontend/index.js index 9840f2e8..ec4ea1d7 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -58,6 +58,7 @@ function newEvyGo() { // advanced canvas poly, ellipse, + curve, stroke, fill, dash, @@ -99,6 +100,7 @@ function needsCanvas(f) { f.clear || f.poly || f.ellipse || + f.curve || f.stroke || f.fill || f.dash || @@ -562,6 +564,71 @@ function ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle) { stroke && ctx.stroke() } +// curve is exported to evy go/wasm. +// curve draws connected curve segments encoded as string +// representing 2 dimensional array, e.g.: +// "1 2 3 4,5 6" => [[1,2,3,4], [5,6]] +// the curve segments here are 1 2 3 4 and 5 6 +// +// curve segments are interpreted differently depending on their +// **length**: +// 2: endX, endY - Line from current position end position x, y +// 4: controlX, controlY, endX, endY: quadratic bezier curve, see +// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/quadraticCurveTo +// 5: control1X, control1Y, control2X, control2Y, radius: arcTo, see +// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arcTo +// 6: control1X, control1Y, control2X, control2Y, endX, endY: bezierCurveTo, see +// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/bezierCurveTo +// +// This notation is very dense and is intended as escape hatch for advanced graphics. +function curve(ptr, len) { + const s = memToString(ptr, len) + // parse "1 2 3 4,5 6" into [[1,2,3,4], [5,6]]: + const curves = s.split(",").map((s) => s.split(" ").map(Number)) + + const { x, y, ctx, fill, stroke } = canvas + + ctx.beginPath() + ctx.moveTo(x, y) + + for (const curve of courves) { + switch (curve.length) { + case 2: + ctx.lineTo(transformX(curve[0]), transformY(curve[1])) + break + case 4: + ctx.quadraticCurveTo( + transformX(curve[0]), // controlPoint.x + transformY(curve[1]), // controlPoint.y + transformX(curve[2]), // endPoint.x + transformY(curve[3]) // endPoint.y + ) + break + case 5: + ctx.arcTo( + transformX(curve[0]), // controlPoint1.x + transformY(curve[1]), // controlPoint1.y + transformX(curve[2]), // controlPoint2.x + transformY(curve[3]), // controlPoint1.y + transformX(curve[4]) // radius + ) + break + case 6: + ctx.bezierCurveTo( + transformX(curve[0]), // controlPoint1.x + transformY(curve[1]), // controlPoint1.y + transformX(curve[2]), // controlPoint2.x + transformY(curve[3]), // controlPoint1.y + transformX(curve[4]), // endPoint.x + transformY(curve[5]) // endPoint.y + ) + break + } + } + fill && ctx.fill() + stroke && ctx.stroke() +} + // stroke is exported to evy go/wasm. function stroke(ptr, len) { const s = memToString(ptr, len) diff --git a/frontend/module/yace-editor.js b/frontend/module/yace-editor.js index 51391c1a..f84ec855 100644 --- a/frontend/module/yace-editor.js +++ b/frontend/module/yace-editor.js @@ -476,6 +476,7 @@ const builtins = new Set([ "clear", "poly", "ellipse", + "curve", "stroke", "fill", "dash", diff --git a/main.go b/main.go index 289c8d04..36f8cdcf 100644 --- a/main.go +++ b/main.go @@ -146,6 +146,7 @@ func format(r io.Reader, w io.StringWriter, checkOnly bool) error { parserBuiltins := evaluator.DefaultBuiltins(newCLIRuntime()).ParserBuiltins() prog, err := parser.Parse(in, parserBuiltins) if err != nil { + fmt.Println("parser error") return fmt.Errorf("%w: %w", errParse, parser.TruncateError(err, 8)) } out := prog.Format() diff --git a/pkg/evaluator/builtin.go b/pkg/evaluator/builtin.go index 5197dbc0..b3592c80 100644 --- a/pkg/evaluator/builtin.go +++ b/pkg/evaluator/builtin.go @@ -86,6 +86,7 @@ func DefaultBuiltins(rt Runtime) Builtins { "line": xyBuiltin("line", rt.Line), "rect": xyBuiltin("rect", rt.Rect), "circle": numBuiltin("circle", rt.Circle), + "width": numBuiltin("width", rt.Width), "color": stringBuiltin("color", rt.Color), "colour": stringBuiltin("colour", rt.Color), @@ -93,6 +94,7 @@ func DefaultBuiltins(rt Runtime) Builtins { "poly": {Func: polyFunc(rt.Poly), Decl: polyDecl}, "ellipse": {Func: ellipseFunc(rt.Ellipse), Decl: ellipseDecl}, + "curve": {Func: curveFunc(rt.Curve), Decl: curveDecl}, "stroke": stringBuiltin("stroke", rt.Stroke), "fill": stringBuiltin("fill", rt.Fill), @@ -154,6 +156,7 @@ type GraphicsRuntime interface { // advanced graphics functions Poly(vertices [][]float64) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64) + Curve(sements [][]float64) Stroke(s string) Fill(s string) Dash(segments []float64) @@ -191,6 +194,7 @@ func (rt *UnimplementedRuntime) Width(w float64) { rt.Unimplemented("w func (rt *UnimplementedRuntime) Color(s string) { rt.Unimplemented("color") } func (rt *UnimplementedRuntime) Clear(color string) { rt.Unimplemented("clear") } func (rt *UnimplementedRuntime) Poly(vertices [][]float64) { rt.Unimplemented("poly") } +func (rt *UnimplementedRuntime) Curve(sements [][]float64) { rt.Unimplemented("curve") } func (rt *UnimplementedRuntime) Stroke(s string) { rt.Unimplemented("stroke") } func (rt *UnimplementedRuntime) Fill(s string) { rt.Unimplemented("fill") } func (rt *UnimplementedRuntime) Dash(segments []float64) { rt.Unimplemented("dash") } @@ -646,6 +650,31 @@ func ellipseFunc(ellipseFn func(x, y, radiusX, radiusY, rotation, startAngle, en } } +var curveDecl = &parser.FuncDeclStmt{ + Name: "curve", + VariadicParam: &parser.Var{Name: "n", T: numArrayType}, + ReturnType: parser.NONE_TYPE, +} + +func curveFunc(curveFn func([][]float64)) BuiltinFunc { + return func(_ *scope, args []Value) (Value, error) { + segments := make([][]float64, len(args)) + for i, arg := range args { + vertex := arg.(*Array) + elements := *vertex.Elements + if len(elements) < 2 || len(elements) == 3 || len(elements) > 6 { + return nil, fmt.Errorf("%w: 'curve' argument %d has %d elements, expected 2, 4, 5 or 6", ErrBadArguments, i+1, len(elements)) + } + segments[i] = make([]float64, len(elements)) + for j, val := range elements { + segments[i][j] = val.(*Num).Val + } + } + curveFn(segments) + return nil, nil + } +} + var dashDecl = &parser.FuncDeclStmt{ Name: "dash", VariadicParam: &parser.Var{Name: "segments", T: parser.NUM_TYPE}, diff --git a/pkg/parser/ast.go b/pkg/parser/ast.go index ab506b5c..3e8a9987 100644 --- a/pkg/parser/ast.go +++ b/pkg/parser/ast.go @@ -544,7 +544,10 @@ func (i *IfStmt) AlwaysTerminates() bool { } func (e *EventHandlerStmt) String() string { - body := e.Body.String() + body := "" + if e.Body != nil { + body = e.Body.String() + } return "on " + e.Name + " {\n" + body + "}\n" } diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index de3d5387..0dcf8962 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -185,7 +185,6 @@ func (p *parser) parseFunc(scope *scope) Node { p.advance() // advance past FUNC tok := p.cur // function name funcName := p.cur.Literal - p.advancePastNL() // advance past signature, already parsed into p.funcs earlier fd := p.funcs[funcName] scope = newScopeWithReturnType(scope, fd, fd.ReturnType) @@ -202,6 +201,7 @@ func (p *parser) parseFunc(scope *scope) Node { if fd.ReturnType != NONE_TYPE && !block.AlwaysTerminates() { p.appendError("missing return") } + p.assertEnd() p.advance() p.recordComment(block) diff --git a/pkg/wasm/imports.go b/pkg/wasm/imports.go index 0f7d1181..6e7ea26d 100644 --- a/pkg/wasm/imports.go +++ b/pkg/wasm/imports.go @@ -57,6 +57,14 @@ func (rt *jsRuntime) Ellipse(x, y, rX, rY, rotation, startAngle, endAngle float6 ellipse(x, y, rX, rY, rotation, startAngle, endAngle) } +func (rt *jsRuntime) Curve(segments [][]float64) { + sStrings := make([]string, len(segments)) + for i, segment := range segments { + sStrings[i] = floatsToString(segment) + } + curve(strings.Join(sStrings, ",")) +} + func floatsToString(floats []float64) string { if len(floats) == 0 { return "" @@ -180,16 +188,6 @@ func rect(dx, dy float64) //export circle func circle(r float64) -// width is imported from JS, setting the lineWidth -// -//export width -func width(w float64) - -// color is imported from JS -// -//export color -func color(s string) - // clear is imported from JS // //export clear @@ -205,6 +203,21 @@ func poly(s string) //export ellipse func ellipse(x, y, rX, rY, rotation, startAngle, endAngle float64) +// curve is imported from JS +// +//export curve +func curve(s string) + +// width is imported from JS, setting the lineWidth +// +//export width +func width(w float64) + +// color is imported from JS +// +//export color +func color(s string) + // stroke is imported from JS // //export stroke