make wasm not require reinit

This commit is contained in:
2023-03-25 19:50:26 +00:00
parent 1e625a589a
commit a6c14b49a5
7 changed files with 250 additions and 29 deletions

3
serve.bat Normal file
View File

@@ -0,0 +1,3 @@
@echo off
cd ./wasm
npx serve

View File

@@ -2,9 +2,12 @@ package main
import "fmt" import "fmt"
func makeGlobal() ArObject { func makeGlobal(allowDocument bool) ArObject {
var vars = Map(anymap{}) var vars = Map(anymap{})
vars.obj["global"] = vars vars.obj["global"] = vars
if allowDocument {
vars.obj["document"] = ArDocument
}
vars.obj["term"] = ArTerm vars.obj["term"] = ArTerm
vars.obj["number"] = builtinFunc{"number", ArgonNumber} vars.obj["number"] = builtinFunc{"number", ArgonNumber}
vars.obj["string"] = builtinFunc{"string", ArgonString} vars.obj["string"] = builtinFunc{"string", ArgonString}

210
src/document.go Normal file
View File

@@ -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",
},
)

View File

@@ -23,10 +23,14 @@ func main() {
obj := js.Global().Get("Object").New() obj := js.Global().Get("Object").New()
obj.Set("eval", js.FuncOf(func(this js.Value, args []js.Value) interface{} { obj.Set("eval", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
code := "" code := ""
if len(args) > 0 { allowDocument := false
if len(args) >= 1 {
code = args[0].String() code = args[0].String()
} }
val, err := wasmRun(code) if len(args) >= 2 {
allowDocument = args[1].Bool()
}
val, err := wasmRun(code, allowDocument)
if err.EXISTS { if err.EXISTS {
panicErr(err) panicErr(err)
return js.Null() return js.Null()

View File

@@ -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") lines := strings.Split(code, "\n")
codelines := []UNPARSEcode{} codelines := []UNPARSEcode{}
for i := 0; i < len(lines); i++ { for i := 0; i < len(lines); i++ {
@@ -49,7 +51,7 @@ func wasmRun(code string) (any, ArErr) {
if translationerr.EXISTS { if translationerr.EXISTS {
return nil, translationerr return nil, translationerr
} }
global := newscope() local := newscope()
localvars := Map(anymap{ localvars := Map(anymap{
"program": Map(anymap{ "program": Map(anymap{
"args": []any{}, "args": []any{},
@@ -67,5 +69,5 @@ func wasmRun(code string) (any, ArErr) {
"scope": global, "scope": global,
}), }),
}) })
return ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) return ThrowOnNonLoop(run(translated, stack{global, localvars, local}))
} }

View File

@@ -1,5 +1,6 @@
window.ArgonWASMRuntime = async (config = {}) => { window.ArgonWASMRuntime = async (config = {}) => {
const term = config.console || console; const term = config.console || console;
const path = config.path || "/bin/argon.wasm";
if (typeof global !== "undefined") { if (typeof global !== "undefined") {
} else if (typeof window !== "undefined") { } else if (typeof window !== "undefined") {
window.global = window; window.global = window;
@@ -309,6 +310,7 @@ window.ArgonWASMRuntime = async (config = {}) => {
}; };
const timeOrigin = Date.now() - performance.now(); const timeOrigin = Date.now() - performance.now();
this.importObject = { this.importObject = {
env: {},
go: { go: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) // 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 // 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() // func resetMemoryDataView()
"runtime.resetMemoryDataView": (sp) => { "runtime.resetMemoryDataView": (sp) => {
sp >>>= 0; sp >>>= 0;
this.mem = new DataView(this._inst.exports.mem.buffer); this.mem = memory;
}, },
// func nanotime1() int64 // func nanotime1() int64
"runtime.nanotime1": (sp) => { "runtime.nanotime1": (sp) => {
@@ -646,11 +648,7 @@ window.ArgonWASMRuntime = async (config = {}) => {
} }
_resume() { _resume() {
if (this.exited) { if (this.exited) {
(async () => { throw new Error("Go program has already exited");
await run();
this._inst.exports.resume();
})();
return;
} }
this._inst.exports.resume(); this._inst.exports.resume();
if (this.exited) { if (this.exited) {
@@ -668,13 +666,11 @@ window.ArgonWASMRuntime = async (config = {}) => {
} }
}; };
const go = new Go(); const go = new Go();
const file = await fetch("bin/argon.wasm"); const file = fetch(path);
const run = async () => { const result = await WebAssembly.instantiateStreaming(
const result = await WebAssembly.instantiateStreaming( (await file).clone(),
file.clone(), go.importObject
go.importObject );
);
go.run(result.instance); go.run(result.instance);
};
await run();
}; };

View File

@@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<title>Argon WASM Runtime Example</title> <title>Argon WASM Runtime Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style> <style>
.terminal { .terminal {
font-family: monospace; font-family: monospace;
@@ -14,6 +15,7 @@
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
min-height: 250px; min-height: 250px;
max-width: 500px; max-width: 500px;
overflow-x: auto;
} }
.terminal * { .terminal * {
min-height: 1rem; min-height: 1rem;
@@ -60,27 +62,28 @@
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
} }
</style> </style>
<script src="argon_wasm.js"></script> <script src="/argon_wasm.js"></script>
</head> </head>
<body> <body>
<h1>Argon WASM Runtime Example</h1> <h1>Argon WASM Runtime Example</h1>
<button id="run" class="runButton">run</button> <button id="run" class="runButton">run</button>
<textarea id="editbox" class="editbox"> <textarea id="editbox" class="editbox" spellcheck="flase" autocapitalize="false">
term.log("hello world")</textarea term.log("hello world")</textarea
> >
<pre id="terminal" class="terminal"> <pre
id="terminal"
</pre> class="terminal"
><div> ____ </div><div> /\ |___ \ </div><div> / \ _ __ __ _ ___ _ __ __ ____) |</div><div> / /\ \ | '__/ _` |/ _ \| '_ \ \ \ / /__ &lt; </div><div> / ____ \| | | (_| | (_) | | | | \ V /___) |</div><div> /_/ \_\_| \__, |\___/|_| |_| \_/|____/ </div><div> __/ | </div><div> |___/ </div><div>----------------------------------------------</div><div>Welcome to ARGON for WASM!</div><div>write code above and click run to see it work like magic!</div></pre>
<script> <script>
const output = document.getElementById("terminal"); const output = document.getElementById("terminal");
const editbox = document.getElementById("editbox"); const editbox = document.getElementById("editbox");
const run = document.getElementById("run"); const run = document.getElementById("run");
run.addEventListener("click", () => { run.addEventListener("click", async () => {
output.innerHTML = ""; output.innerHTML = "";
setTimeout(()=>Ar.eval(editbox.value), 100) setTimeout(() => Ar.eval(editbox.value, true), 0);
}); });
ArgonWASMRuntime({ const runAr = ArgonWASMRuntime({
console: { console: {
log: (...msg) => { log: (...msg) => {
const p = document.createElement("div"); const p = document.createElement("div");