Skip to content

Commit 5fd5cfc

Browse files
committed
🍱 all: Implement output UI initialisation (#106)
Implement output UI initialisation from evy source code analysis: * show/hide canvas if there are some/no drawing functions in evy code. * show/hide read input text field * show/hide sliders Update the parser.Program ast node to expose `CalledBuiltinFuncs` which determine UI changes. Include eventhandler implementation in code analysis. Note: there is a race condition when navigating back/forwards between stopping the program and reinstantiating wasmInst for formatting / ui initialisation of new code. wasmInst is asynchronously set to undefined after the wasm execution has finished and called afterStop function. This merges the following commits: * frontend: Expose jsActions as evy command * all: Implement output UI initialisation * wasm: Add forceYield utility frontend/index.js | 71 +++++++++++++++++++++++++++-------- pkg/evaluator/evaluator.go | 6 +-- pkg/parser/ast.go | 7 +++- pkg/parser/expression.go | 4 +- pkg/parser/parser.go | 22 ++++++++++- pkg/parser/parser_test.go | 12 ++++++ pkg/wasm/imports.go | 7 +++- pkg/wasm/main.go | 77 ++++++++++++++++++++++++++------------ 8 files changed, 157 insertions(+), 49 deletions(-) Pull-Request: #106
2 parents 03b33b9 + 4954f9a commit 5fd5cfc

File tree

8 files changed

+157
-49
lines changed

8 files changed

+157
-49
lines changed

frontend/index.js

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ let jsReadInitialised = false
1010
let stopped = true
1111
let animationStart
1212
let courses
13+
let actions = "fmt,ui,eval"
1314

1415
// --- Initialise ------------------------------------------------------
1516

@@ -37,6 +38,8 @@ function newEvyGo() {
3738
const evyEnv = {
3839
jsPrint,
3940
jsRead,
41+
jsActions,
42+
jsPrepareUI,
4043
evySource,
4144
setEvySource,
4245
move,
@@ -52,6 +55,25 @@ function newEvyGo() {
5255
go.importObject.env = Object.assign(go.importObject.env, evyEnv)
5356
return go
5457
}
58+
// jsActions returns the comma separated evy actions to executed, e.g.
59+
// fmt,ui,eval. The result string is written to wasm memory
60+
// bytes. jsActions return the pointer and length of these bytes
61+
// encoded into a single 64 bit number
62+
function jsActions() {
63+
return stringToMemAddr(actions)
64+
}
65+
66+
function jsPrepareUI(ptr, len) {
67+
const arr = memToString(ptr, len).split(",")
68+
const names = Object.fromEntries(arr.map((k) => [k, true]))
69+
names["read"] ? showElements(".read") : hideElements(".read")
70+
names["input"] ? showElements(".input") : hideElements(".input")
71+
needsCanvas(names) ? showElements(".canvas") : hideElements(".canvas")
72+
}
73+
74+
function needsCanvas(f) {
75+
return f.move || f.line || f.width || f.circle || f.rect || f.color || f.colour
76+
}
5577

5678
// jsPrint converts wasmInst memory bytes from ptr to ptr+len to string and
5779
// writes it to the output textarea.
@@ -125,18 +147,6 @@ function ptrLenToBigInt({ ptr, len }) {
125147
return ptrLenNum
126148
}
127149

128-
function getElements(q) {
129-
if (!q) {
130-
return []
131-
}
132-
try {
133-
return Array.from(document.querySelectorAll(q))
134-
} catch (error) {
135-
consol.error("getElements", error)
136-
return []
137-
}
138-
}
139-
140150
// --- UI: handle run --------------------------------------------------
141151

142152
async function handleRun() {
@@ -163,8 +173,8 @@ async function handleMobRun() {
163173
stop()
164174
}
165175

166-
// start calls the evy main() wasm/go code parsing, formatting and
167-
// evaluating evy code.
176+
// start calls evy wasm/go main(). It parses, formats and evaluates evy
177+
// code and initialises the output ui.
168178
async function start() {
169179
stopped = false
170180
wasmInst = await WebAssembly.instantiate(wasmModule, go.importObject)
@@ -176,6 +186,14 @@ async function start() {
176186
runButton.classList.add("running")
177187
runButtonMob.innerText = "Stop"
178188
runButtonMob.classList.add("running")
189+
actions = "fmt,ui,eval"
190+
go.run(wasmInst)
191+
}
192+
193+
// format calls evy wasm/go main() but doesn't evaluate.
194+
async function format() {
195+
wasmInst = await WebAssembly.instantiate(wasmModule, go.importObject)
196+
actions = "fmt,ui"
179197
go.run(wasmInst)
180198
}
181199

@@ -264,6 +282,7 @@ function ctrlEnterListener(e) {
264282

265283
async function handleHashChange() {
266284
hideModal()
285+
await stopAndSlide() // go to code screen for new code
267286
let opts = parseHash()
268287
if (!opts.source && !opts.unit) {
269288
opts = { unit: "welcome" }
@@ -283,7 +302,7 @@ async function handleHashChange() {
283302
document.querySelector("#code").value = source
284303
updateBreadcrumbs(crumbs)
285304
clearOutput()
286-
await stopAndSlide() // go to code screen for new code
305+
format()
287306
} catch (err) {
288307
console.error(err)
289308
}
@@ -593,3 +612,25 @@ function showConfetti() {
593612
confettiDivs.forEach((div) => div.classList.add("fadeout"))
594613
}, 8500)
595614
}
615+
616+
// --- Utilities -------------------------------------------------------
617+
618+
function getElements(q) {
619+
if (!q) {
620+
return []
621+
}
622+
try {
623+
return Array.from(document.querySelectorAll(q))
624+
} catch (error) {
625+
consol.error("getElements", error)
626+
return []
627+
}
628+
}
629+
630+
function showElements(q) {
631+
getElements(q).map((el) => el.classList.remove("hidden"))
632+
}
633+
634+
function hideElements(q) {
635+
getElements(q).map((el) => el.classList.add("hidden"))
636+
}

pkg/evaluator/evaluator.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,7 @@ func (e *Evaluator) Eval(node parser.Node) (Value, error) {
139139
}
140140

141141
func (e *Evaluator) EventHandlerNames() []string {
142-
names := make([]string, 0, len(e.eventHandlers))
143-
for name := range e.eventHandlers {
144-
names = append(names, name)
145-
}
146-
return names
142+
return parser.EventHandlerNames(e.eventHandlers)
147143
}
148144

149145
func (e *Evaluator) HandleEvent(ev Event) error {

pkg/parser/ast.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ type Node interface {
1313
}
1414

1515
type Program struct {
16-
Statements []Node
17-
EventHandlers map[string]*EventHandlerStmt
16+
Statements []Node
17+
EventHandlers map[string]*EventHandlerStmt
18+
CalledBuiltinFuncs []string
1819

1920
alwaysTerminates bool
2021
formatting *formatting
@@ -116,6 +117,8 @@ type FuncDeclStmt struct {
116117
VariadicParam *Var
117118
ReturnType *Type
118119
Body *BlockStatement
120+
121+
isCalled bool
119122
}
120123

121124
type IfStmt struct {

pkg/parser/expression.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ func (p *parser) parseTopLevelExpr(scope *scope) Node {
5757
func (p *parser) parseFuncCall(scope *scope) Node {
5858
fc := &FuncCall{Token: p.cur, Name: p.cur.Literal}
5959
p.advance() // advance past function name IDENT
60-
fc.FuncDecl = p.funcs[fc.Name]
60+
funcDecl := p.funcs[fc.Name]
61+
funcDecl.isCalled = true
62+
fc.FuncDecl = funcDecl
6163
fc.Arguments = p.parseExprList(scope)
6264
p.assertArgTypes(fc.FuncDecl, fc.Arguments)
6365
return fc

pkg/parser/parser.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ func newParser(input string, builtins Builtins) *parser {
9494
formatting: newFormatting(),
9595
}
9696
for name, funcDecl := range builtins.Funcs {
97-
p.funcs[name] = funcDecl
97+
fd := *funcDecl
98+
p.funcs[name] = &fd
9899
}
99100

100101
// Read all tokens, collect function declaration tokens by index
@@ -178,6 +179,7 @@ func (p *parser) parseProgram() *Program {
178179
}
179180
p.validateScope(scope)
180181
program.EventHandlers = p.eventHandlers
182+
program.CalledBuiltinFuncs = p.calledBuiltinFuncs()
181183
return program
182184
}
183185

@@ -973,3 +975,21 @@ func (p *parser) curComment() string {
973975
}
974976
return ""
975977
}
978+
979+
func (p *parser) calledBuiltinFuncs() []string {
980+
var funcs []string
981+
for name, funcDecl := range p.funcs {
982+
if _, ok := p.builtins.Funcs[name]; ok && funcDecl.isCalled {
983+
funcs = append(funcs, name)
984+
}
985+
}
986+
return funcs
987+
}
988+
989+
func EventHandlerNames(eventHandlers map[string]*EventHandlerStmt) []string {
990+
names := make([]string, 0, len(eventHandlers))
991+
for name := range eventHandlers {
992+
names = append(names, name)
993+
}
994+
return names
995+
}

pkg/parser/parser_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package parser
22

33
import (
4+
"sort"
45
"strings"
56
"testing"
67

@@ -1178,6 +1179,17 @@ end
11781179
}
11791180
}
11801181

1182+
func TestCalledBuiltinFuncs(t *testing.T) {
1183+
input := `print (len "ABC")`
1184+
parser := newParser(input, testBuiltins())
1185+
prog := parser.Parse()
1186+
assertNoParseError(t, parser, input)
1187+
got := prog.CalledBuiltinFuncs
1188+
sort.Strings(got)
1189+
want := []string{"len", "print"}
1190+
assert.Equal(t, want, got)
1191+
}
1192+
11811193
func TestDemo(t *testing.T) {
11821194
input := `
11831195
move 10 10

pkg/wasm/imports.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,14 @@ func newSleepingYielder() *sleepingYielder {
5252
func (y *sleepingYielder) Yield() {
5353
y.count++
5454
if y.count > 1000 && time.Since(y.start) > 100*time.Millisecond {
55-
time.Sleep(minSleepDur)
56-
y.Reset()
55+
y.ForceYield()
5756
}
5857
}
5958

59+
func (y *sleepingYielder) ForceYield() {
60+
y.Sleep(minSleepDur)
61+
}
62+
6063
func (y *sleepingYielder) Sleep(dur time.Duration) {
6164
time.Sleep(dur)
6265
y.Reset()

pkg/wasm/main.go

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
package main
44

55
import (
6+
"strings"
7+
68
"foxygo.at/evy/pkg/evaluator"
79
"foxygo.at/evy/pkg/parser"
810
)
@@ -14,43 +16,72 @@ var (
1416
)
1517

1618
func main() {
17-
rt := newJSRuntime()
18-
builtins := evaluator.DefaultBuiltins(rt)
19-
2019
defer afterStop()
21-
source, err := format(builtins)
20+
actions := getActions()
21+
22+
rt := newJSRuntime()
23+
input := getEvySource()
24+
ast, err := parse(input, rt)
2225
if err != nil {
2326
rt.Print(err.Error())
2427
return
2528
}
26-
evaluate(source, builtins, rt.yielder)
29+
if actions["fmt"] {
30+
formattedInput := ast.Format()
31+
if formattedInput != input {
32+
setEvySource(formattedInput)
33+
}
34+
}
35+
if actions["ui"] {
36+
prepareUI(ast)
37+
}
38+
if actions["eval"] {
39+
// The ast does not correspond to the formatted source code. For
40+
// now this is acceptable because evaluator errors don't output
41+
// source code locations.
42+
evaluate(ast, rt)
43+
}
2744
}
2845

29-
func format(evalBuiltins evaluator.Builtins) (string, error) {
30-
input := getEvySource()
46+
func getActions() map[string]bool {
47+
m := map[string]bool{}
48+
addr := jsActions()
49+
s := getStringFromAddr(addr)
50+
actions := strings.Split(s, ",")
51+
for _, action := range actions {
52+
if action != "" {
53+
m[action] = true
54+
}
55+
}
56+
return m
57+
}
3158

32-
builtins := evalBuiltins.ParserBuiltins()
59+
func getEvySource() string {
60+
addr := evySource()
61+
return getStringFromAddr(addr)
62+
}
63+
64+
func parse(input string, rt evaluator.Runtime) (*parser.Program, error) {
65+
builtins := evaluator.DefaultBuiltins(rt).ParserBuiltins()
3366
prog, err := parser.Parse(input, builtins)
3467
if err != nil {
35-
return "", parser.TruncateError(err, 8)
36-
}
37-
formattedInput := prog.Format()
38-
if formattedInput != input {
39-
setEvySource(formattedInput)
68+
return nil, parser.TruncateError(err, 8)
4069
}
41-
return formattedInput, nil
70+
return prog, nil
4271
}
4372

44-
func evaluate(input string, builtins evaluator.Builtins, yielder *sleepingYielder) {
45-
eval = evaluator.NewEvaluator(builtins)
46-
47-
eval.Run(input)
48-
handleEvents(yielder)
73+
func prepareUI(prog *parser.Program) {
74+
funcNames := prog.CalledBuiltinFuncs
75+
eventHandlerNames := parser.EventHandlerNames(prog.EventHandlers)
76+
names := append(funcNames, eventHandlerNames...)
77+
jsPrepareUI(strings.Join(names, ","))
4978
}
5079

51-
func getEvySource() string {
52-
addr := evySource()
53-
return getStringFromAddr(addr)
80+
func evaluate(prog *parser.Program, rt *jsRuntime) {
81+
builtins := evaluator.DefaultBuiltins(rt)
82+
eval = evaluator.NewEvaluator(builtins)
83+
eval.Eval(prog)
84+
handleEvents(rt.yielder)
5485
}
5586

5687
func handleEvents(yielder *sleepingYielder) {
@@ -71,7 +102,7 @@ func handleEvents(yielder *sleepingYielder) {
71102
yielder.Reset()
72103
eval.HandleEvent(event)
73104
} else {
74-
yielder.Sleep(minSleepDur)
105+
yielder.ForceYield()
75106
}
76107
}
77108
}

0 commit comments

Comments
 (0)