-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
126 lines (104 loc) · 3.21 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//go:build js && wasm
package main
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"syscall/js"
"github.com/jasoncabot/fabled-story-book/internal/jabl"
)
var interpreter *jabl.Interpreter
var state jabl.State
func main() {
// Keep this 'program' running and ready to receive function calls from Javascript
c := make(chan struct{}, 0)
state = &localStorageMapper{}
loader := &jsLoader{fn: "loadSection"}
// Create an instance of the interpreter we will use to execute the code
// There is nothing shared between executions of the interpreter, so we can use a single instance
interpreter = jabl.NewInterpreter(loader)
registerCallbacks()
<-c
}
// A loader that delegates back to a Javascript function `loadSection` for loading the next block of code to execute
type jsLoader struct {
fn string
}
func (l *jsLoader) LoadSection(identifier jabl.SectionId, onLoaded func(code string, err error)) {
var cb js.Func
cb = js.FuncOf(func(this js.Value, args []js.Value) any {
result := args[0].String()
err := args[1]
if !err.IsUndefined() {
onLoaded("", errors.New(err.String()))
} else {
onLoaded(result, nil)
}
cb.Release()
return nil
})
js.Global().Get("window").Call(l.fn, string(identifier), cb)
}
func execSection(this js.Value, inputs []js.Value) any {
section := jabl.SectionId(inputs[0].String())
callback := inputs[1]
// The interpreter delegates back to the loader for getting the code to execute from an identifier
interpreter.Execute(section, state, func(section *jabl.Result, err error) {
if err != nil {
callback.Invoke(js.Undefined(), err.Error())
} else {
jsonValueOfRes, err := json.Marshal(section)
if err != nil {
callback.Invoke(js.Undefined(), err.Error())
} else {
callback.Invoke(string(jsonValueOfRes), js.Undefined())
}
}
})
return nil
}
func evalCode(this js.Value, inputs []js.Value) any {
callback := inputs[2]
interpreter.Evaluate(inputs[0].String(), inputs[1].String(), state, func(section *jabl.Result, err error) {
jsonValueOfRes, err := json.Marshal(section)
if err != nil {
callback.Invoke(js.Undefined(), err.Error())
} else {
callback.Invoke(string(jsonValueOfRes), js.Undefined())
}
})
return nil
}
func registerCallbacks() {
js.Global().Set("execSection", js.FuncOf(execSection))
js.Global().Set("evalCode", js.FuncOf(evalCode))
}
// A state mapper that delegates back to Javascript for getting and setting state
type localStorageMapper struct{}
func (s *localStorageMapper) Get(key string) (any, error) {
value := js.Global().Get("bookStorage").Call("getItem", key)
if value.IsUndefined() {
return 0, nil
}
switch value.Type() {
case js.TypeNumber:
return value.Float(), nil
case js.TypeString:
return value.String(), nil
case js.TypeBoolean:
return value.Bool(), nil
}
return nil, fmt.Errorf("unknown type %s", value.Type())
}
func (s *localStorageMapper) Set(key string, value any) error {
switch v := value.(type) {
case float64:
js.Global().Get("bookStorage").Call("setItem", key, "n", strconv.FormatFloat(v, 'f', -1, 64))
case string:
js.Global().Get("bookStorage").Call("setItem", key, "s", v)
case bool:
js.Global().Get("bookStorage").Call("setItem", key, "b", strconv.FormatBool(v))
}
return nil
}