diff --git a/serve.bat b/serve.bat new file mode 100644 index 0000000..4b2b6bf --- /dev/null +++ b/serve.bat @@ -0,0 +1,3 @@ +@echo off +cd ./wasm +npx serve \ No newline at end of file diff --git a/src/built-ins.go b/src/built-ins.go index 9cc09bd..3e28fa9 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -2,9 +2,12 @@ package main import "fmt" -func makeGlobal() ArObject { +func makeGlobal(allowDocument bool) ArObject { var vars = Map(anymap{}) vars.obj["global"] = vars + if allowDocument { + vars.obj["document"] = ArDocument + } vars.obj["term"] = ArTerm vars.obj["number"] = builtinFunc{"number", ArgonNumber} vars.obj["string"] = builtinFunc{"string", ArgonString} diff --git a/src/document.go b/src/document.go new file mode 100644 index 0000000..c498bf0 --- /dev/null +++ b/src/document.go @@ -0,0 +1,210 @@ +package main + +import ( + "syscall/js" +) + +func windowElement(element js.Value) ArObject { + return ArObject{ + TYPE: "map", + obj: anymap{ + "innerHTML": builtinFunc{"innerHTML", func(args ...any) (any, ArErr) { + if len(args) > 0 { + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "innerHTML only accepts strings", 0, "", "", true} + } + element.Set("innerHTML", args[0].(string)) + } + return element.Get("innerHTML").String(), ArErr{} + }}, + "innerText": builtinFunc{"innerText", func(args ...any) (any, ArErr) { + if len(args) > 0 { + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "innerText only accepts strings", 0, "", "", true} + } + element.Set("innerText", args[0].(string)) + } + return element.Get("innerText").String(), ArErr{} + }}, + "addEventListener": builtinFunc{"addEventListener", func(args ...any) (any, ArErr) { + if len(args) < 2 { + return nil, ArErr{"Argument Error", "Not enough arguments for addEventListener", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "addEventListener's first argument must be a string", 0, "", "", true} + } + event := args[0].(string) + if typeof(args[1]) != "function" { + return nil, ArErr{"Argument Error", "addEventListener's second argument must be a function", 0, "", "", true} + } + callable := args[1] + element.Call("addEventListener", event, js.FuncOf(func(this js.Value, args []js.Value) interface{} { + runCall(call{ + callable: callable, + args: []any{}, + }, stack{}, 0) + return nil + })) + return nil, ArErr{} + }}, + "removeEventListener": builtinFunc{"removeEventListener", func(args ...any) (any, ArErr) { + if len(args) < 2 { + return nil, ArErr{"Argument Error", "Not enough arguments for removeEventListener", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "removeEventListener's first argument must be a string", 0, "", "", true} + } + event := args[0].(string) + if typeof(args[1]) != "function" { + return nil, ArErr{"Argument Error", "removeEventListener's second argument must be a function", 0, "", "", true} + } + callable := args[1] + element.Call("removeEventListener", event, js.FuncOf(func(this js.Value, args []js.Value) interface{} { + runCall(call{ + callable: callable, + args: []any{}, + }, stack{}, 0) + return nil + })) + return nil, ArErr{} + }}, + "appendChild": builtinFunc{"appendChild", func(args ...any) (any, ArErr) { + if len(args) < 1 { + return nil, ArErr{"Argument Error", "Not enough arguments for appendChild", 0, "", "", true} + } + if typeof(args[0]) != "map" { + return nil, ArErr{"Argument Error", "appendChild's first argument must be a map", 0, "", "", true} + } + child := args[0].(anymap) + if child["__TYPE__"] != "windowElement" { + return nil, ArErr{"Argument Error", "appendChild's first argument must be an element", 0, "", "", true} + } + element.Call("appendChild", child["__element__"]) + return nil, ArErr{} + }}, + "removeChild": builtinFunc{"removeChild", func(args ...any) (any, ArErr) { + if len(args) < 1 { + return nil, ArErr{"Argument Error", "Not enough arguments for removeChild", 0, "", "", true} + } + if typeof(args[0]) != "map" { + return nil, ArErr{"Argument Error", "removeChild's first argument must be a map", 0, "", "", true} + } + child := args[0].(anymap) + if child["__TYPE__"] != "windowElement" { + return nil, ArErr{"Argument Error", "removeChild's first argument must be an element", 0, "", "", true} + } + element.Call("removeChild", child["__element__"]) + return nil, ArErr{} + }}, + "setAttribute": builtinFunc{"setAttribute", func(args ...any) (any, ArErr) { + if len(args) < 2 { + return nil, ArErr{"Argument Error", "Not enough arguments for setAttribute", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "setAttribute's first argument must be a string", 0, "", "", true} + } + element.Call("setAttribute", args[0].(string), anyToArgon(args[1], false, false, 3, 0, false, 0)) + return nil, ArErr{} + }}, + "__element__": element, + "__TYPE__": "windowElement", + }, + } +} + +var ArDocument = Map( + anymap{ + "body": windowElement(js.Global().Get("document").Get("body")), + "head": windowElement(js.Global().Get("document").Get("head")), + "getElementById": builtinFunc{"getElementById", func(args ...any) (any, ArErr) { + if len(args) < 1 { + return nil, ArErr{"Argument Error", "Not enough arguments for getElementById", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "getElementById's first argument must be a string", 0, "", "", true} + } + id := args[0].(string) + result := js.Global().Get("document").Call("getElementById", id) + if js.Null().Equal(result) { + return nil, ArErr{} + } + return windowElement(result), ArErr{} + }}, + "createElement": builtinFunc{"createElement", func(args ...any) (any, ArErr) { + if len(args) < 1 { + return nil, ArErr{"Argument Error", "Not enough arguments for createElement", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "createElement's first argument must be a string", 0, "", "", true} + } + tag := args[0].(string) + return windowElement(js.Global().Get("document").Call("createElement", tag)), ArErr{} + }}, + "createTextNode": builtinFunc{"createTextNode", func(args ...any) (any, ArErr) { + if len(args) < 1 { + return nil, ArErr{"Argument Error", "Not enough arguments for createTextNode", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "createTextNode's first argument must be a string", 0, "", "", true} + } + text := args[0].(string) + return windowElement(js.Global().Get("document").Call("createTextNode", text)), ArErr{} + }}, + "createComment": builtinFunc{"createComment", func(args ...any) (any, ArErr) { + if len(args) < 1 { + return nil, ArErr{"Argument Error", "Not enough arguments for createComment", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "createComment's first argument must be a string", 0, "", "", true} + } + text := args[0].(string) + return windowElement(js.Global().Get("document").Call("createComment", text)), ArErr{} + }}, + "createDocumentFragment": builtinFunc{"createDocumentFragment", func(args ...any) (any, ArErr) { + return windowElement(js.Global().Get("document").Call("createDocumentFragment")), ArErr{} + }}, + "addEventListener": builtinFunc{"addEventListener", func(args ...any) (any, ArErr) { + if len(args) < 2 { + return nil, ArErr{"Argument Error", "Not enough arguments for addEventListener", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "addEventListener's first argument must be a string", 0, "", "", true} + } + event := args[0].(string) + if typeof(args[1]) != "function" { + return nil, ArErr{"Argument Error", "addEventListener's second argument must be a function", 0, "", "", true} + } + callable := args[1] + js.Global().Get("document").Call("addEventListener", event, js.FuncOf(func(this js.Value, args []js.Value) interface{} { + runCall(call{ + callable: callable, + args: []any{}, + }, stack{}, 0) + return nil + })) + return nil, ArErr{} + }}, + "removeEventListener": builtinFunc{"removeEventListener", func(args ...any) (any, ArErr) { + if len(args) < 2 { + return nil, ArErr{"Argument Error", "Not enough arguments for removeEventListener", 0, "", "", true} + } + if typeof(args[0]) != "string" { + return nil, ArErr{"Argument Error", "removeEventListener's first argument must be a string", 0, "", "", true} + } + event := args[0].(string) + if typeof(args[1]) != "function" { + return nil, ArErr{"Argument Error", "removeEventListener's second argument must be a function", 0, "", "", true} + } + callable := args[1] + js.Global().Get("document").Call("removeEventListener", event, js.FuncOf(func(this js.Value, args []js.Value) interface{} { + runCall(call{ + callable: callable, + args: []any{}, + }, stack{}, 0) + return nil + })) + return nil, ArErr{} + }}, + "__TYPE__": "document", + }, +) diff --git a/src/main.go b/src/main.go index 6fe355f..9ee9e7e 100644 --- a/src/main.go +++ b/src/main.go @@ -23,10 +23,14 @@ func main() { obj := js.Global().Get("Object").New() obj.Set("eval", js.FuncOf(func(this js.Value, args []js.Value) interface{} { code := "" - if len(args) > 0 { + allowDocument := false + if len(args) >= 1 { code = args[0].String() } - val, err := wasmRun(code) + if len(args) >= 2 { + allowDocument = args[1].Bool() + } + val, err := wasmRun(code, allowDocument) if err.EXISTS { panicErr(err) return js.Null() diff --git a/src/wasm.go b/src/wasm.go index 0e14842..0d59bdd 100644 --- a/src/wasm.go +++ b/src/wasm.go @@ -33,7 +33,9 @@ func argonToJsValid(argon any) any { } } -func wasmRun(code string) (any, ArErr) { +func wasmRun(code string, allowDocument bool) (any, ArErr) { + initRandom() + global := makeGlobal(allowDocument) lines := strings.Split(code, "\n") codelines := []UNPARSEcode{} for i := 0; i < len(lines); i++ { @@ -49,7 +51,7 @@ func wasmRun(code string) (any, ArErr) { if translationerr.EXISTS { return nil, translationerr } - global := newscope() + local := newscope() localvars := Map(anymap{ "program": Map(anymap{ "args": []any{}, @@ -67,5 +69,5 @@ func wasmRun(code string) (any, ArErr) { "scope": global, }), }) - return ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) + return ThrowOnNonLoop(run(translated, stack{global, localvars, local})) } diff --git a/wasm/argon_wasm.js b/wasm/argon_wasm.js index 0db04ac..0edaeb3 100644 --- a/wasm/argon_wasm.js +++ b/wasm/argon_wasm.js @@ -1,5 +1,6 @@ window.ArgonWASMRuntime = async (config = {}) => { const term = config.console || console; + const path = config.path || "/bin/argon.wasm"; if (typeof global !== "undefined") { } else if (typeof window !== "undefined") { window.global = window; @@ -309,6 +310,7 @@ window.ArgonWASMRuntime = async (config = {}) => { }; const timeOrigin = Date.now() - performance.now(); this.importObject = { + env: {}, go: { // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported @@ -340,7 +342,7 @@ window.ArgonWASMRuntime = async (config = {}) => { // func resetMemoryDataView() "runtime.resetMemoryDataView": (sp) => { sp >>>= 0; - this.mem = new DataView(this._inst.exports.mem.buffer); + this.mem = memory; }, // func nanotime1() int64 "runtime.nanotime1": (sp) => { @@ -646,11 +648,7 @@ window.ArgonWASMRuntime = async (config = {}) => { } _resume() { if (this.exited) { - (async () => { - await run(); - this._inst.exports.resume(); - })(); - return; + throw new Error("Go program has already exited"); } this._inst.exports.resume(); if (this.exited) { @@ -668,13 +666,11 @@ window.ArgonWASMRuntime = async (config = {}) => { } }; const go = new Go(); - const file = await fetch("bin/argon.wasm"); - const run = async () => { - const result = await WebAssembly.instantiateStreaming( - file.clone(), - go.importObject - ); - go.run(result.instance); - }; - await run(); + const file = fetch(path); + const result = await WebAssembly.instantiateStreaming( + (await file).clone(), + go.importObject + ); + + go.run(result.instance); }; diff --git a/wasm/index.html b/wasm/index.html index e77a0c9..06beca2 100644 --- a/wasm/index.html +++ b/wasm/index.html @@ -2,6 +2,7 @@