make wasm work

This commit is contained in:
2023-03-25 00:45:13 +00:00
parent 47727a6d2f
commit 97ec428aca
12 changed files with 984 additions and 115 deletions

2
build
View File

@@ -1 +1 @@
go build -o bin/argon ./src
GOOS=js GOARCH=wasm go build -o wasm/bin/argon.wasm ./src

View File

@@ -1,2 +1,4 @@
@echo off
go build -o bin/argon.exe ./src
set GOOS=js
set GOARCH=wasm
go build -o wasm/bin/argon.wasm ./src

2
run
View File

@@ -1,2 +0,0 @@
# run the go run command passing the path to the main.go file, with the working directory set to the bin folder. pass in the arguments
go run ./src $@

View File

@@ -1,5 +0,0 @@
@echo off
:: run the go run command passing the path to the main.go file, with the working directory set to the bin folder. pass in the arguments
go run ./src %*

View File

@@ -2,6 +2,8 @@ package main
import (
"fmt"
"github.com/jwalton/go-supportscolor"
)
type ArErr struct {
@@ -19,5 +21,9 @@ func panicErr(err ArErr) {
fmt.Println(" " + err.code)
fmt.Println()
}
if supportscolor.Stdout().SupportsColor {
fmt.Printf("\x1b[%dm%s\x1b[0m", 91, fmt.Sprint(err.TYPE, ": ", err.message, "\n"))
} else {
fmt.Println(fmt.Sprint(err.TYPE, ": ", err.message))
}
}

View File

@@ -5,7 +5,6 @@ import (
"errors"
"log"
"os"
"path/filepath"
)
var imported = make(map[string]ArObject)
@@ -48,6 +47,12 @@ func readFile(path string) []UNPARSEcode {
}
func importMod(realpath string, origin string, main bool) (ArObject, ArErr) {
return ArObject{}, ArErr{
TYPE: "Import Error",
message: "importing in WASM is currently not supported",
EXISTS: true,
}
/*
extention := filepath.Ext(realpath)
path := realpath
if extention == "" {
@@ -145,4 +150,5 @@ func importMod(realpath string, origin string, main bool) (ArObject, ArErr) {
}
imported[p] = global
return global, ArErr{}
*/
}

View File

@@ -2,6 +2,7 @@ package main
import (
"os"
"syscall/js"
)
// args without the program path
@@ -17,17 +18,21 @@ func newscope() ArObject {
}
func main() {
ex, e := os.Getwd()
if e != nil {
panic(e)
c := make(chan ArObject)
obj := js.Global().Get("Object").New()
obj.Set("eval", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
code := ""
if len(args) > 0 {
code = args[0].String()
}
if len(Args) == 0 {
shell()
os.Exit(0)
}
_, err := importMod(Args[0], ex, true)
val, err := wasmRun(code)
if err.EXISTS {
panicErr(err)
os.Exit(1)
return js.Null()
}
return js.ValueOf(argonToJsValid(val))
}))
js.Global().Set("Ar", obj)
<-c
}

View File

@@ -44,6 +44,7 @@ func parseGenericImport(code UNPARSEcode, index int, codeline []UNPARSEcode) (Ar
}
func runImport(importOBJ ArImport, stack stack, stacklevel int) (any, ArErr) {
return nil, ArErr{"Import Error", "importing in WASM is currently not supported", importOBJ.line, importOBJ.path, importOBJ.code, true}
val, err := runVal(importOBJ.filePath, stack, stacklevel+1)
val = ArValidToAny(val)
if err.EXISTS {

View File

@@ -44,7 +44,6 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i
return parseGenericImport(code, index, codelines)
}
}
if isLine >= 1 {
if isDoWrap(code) {
return parseDoWrap(code, index, codelines)

71
src/wasm.go Normal file
View File

@@ -0,0 +1,71 @@
package main
import (
"strings"
"syscall/js"
)
func argonToJsValid(argon any) any {
switch x := argon.(type) {
case number:
f, _ := x.Float64()
return f
case ArObject:
if x.TYPE == "array" {
arr := js.Global().Get("Array").New()
for i, v := range x.obj["__value__"].([]any) {
arr.SetIndex(i, argonToJsValid(v))
}
return arr
} else if x.TYPE == "string" {
return x.obj["__value__"].(string)
}
obj := js.Global().Get("Object").New()
for k, v := range x.obj {
obj.Set(anyToArgon(k, false, false, 3, 0, false, 0), argonToJsValid(v))
}
return obj
case bool, string:
return x
default:
return nil
}
}
func wasmRun(code string) (any, ArErr) {
lines := strings.Split(code, "\n")
codelines := []UNPARSEcode{}
for i := 0; i < len(lines); i++ {
codelines = append(codelines, UNPARSEcode{
lines[i],
lines[i],
i + 1,
"<wasm>",
})
}
translated, translationerr := translate(codelines)
if translationerr.EXISTS {
return nil, translationerr
}
global := newscope()
localvars := Map(anymap{
"program": Map(anymap{
"args": []any{},
"origin": "",
"import": builtinFunc{"import", func(args ...any) (any, ArErr) {
return nil, ArErr{"Import Error", "Cannot Import in WASM", 0, "<wasm>", "", true}
}},
"cwd": "",
"exc": "",
"file": Map(anymap{
"name": "<wasm>",
"path": "",
}),
"main": true,
"scope": global,
}),
})
return ThrowOnNonLoop(run(translated, stack{vars, localvars, global}))
}

680
wasm/argon_wasm.js Normal file
View File

@@ -0,0 +1,680 @@
window.ArgonWASMRuntime = async (config = {}) => {
const term = config.console || console;
if (typeof global !== "undefined") {
} else if (typeof window !== "undefined") {
window.global = window;
} else if (typeof self !== "undefined") {
self.global = self;
} else {
throw new Error(
"cannot export Go (neither global, window nor self is defined)"
);
}
if (!global.require && typeof require !== "undefined") {
global.require = require;
}
if (!global.fs && global.require) {
const fs = require("fs");
if (
typeof fs === "object" &&
fs !== null &&
Object.keys(fs).length !== 0
) {
global.fs = fs;
}
}
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!global.fs) {
let outputBuf = "";
global.fs = {
constants: {
O_WRONLY: -1,
O_RDWR: -1,
O_CREAT: -1,
O_TRUNC: -1,
O_APPEND: -1,
O_EXCL: -1,
}, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
term.log(outputBuf.slice(0, nl));
outputBuf = outputBuf.slice(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (
offset !== 0 ||
length !== buf.length ||
position !== null
) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) {
callback(enosys());
},
chown(path, uid, gid, callback) {
callback(enosys());
},
close(fd, callback) {
callback(enosys());
},
fchmod(fd, mode, callback) {
callback(enosys());
},
fchown(fd, uid, gid, callback) {
callback(enosys());
},
fstat(fd, callback) {
callback(enosys());
},
fsync(fd, callback) {
callback(null);
},
ftruncate(fd, length, callback) {
callback(enosys());
},
lchown(path, uid, gid, callback) {
callback(enosys());
},
link(path, link, callback) {
callback(enosys());
},
lstat(path, callback) {
callback(enosys());
},
mkdir(path, perm, callback) {
callback(enosys());
},
open(path, flags, mode, callback) {
callback(enosys());
},
read(fd, buffer, offset, length, position, callback) {
callback(enosys());
},
readdir(path, callback) {
callback(enosys());
},
readlink(path, callback) {
callback(enosys());
},
rename(from, to, callback) {
callback(enosys());
},
rmdir(path, callback) {
callback(enosys());
},
stat(path, callback) {
callback(enosys());
},
symlink(path, link, callback) {
callback(enosys());
},
truncate(path, length, callback) {
callback(enosys());
},
unlink(path, callback) {
callback(enosys());
},
utimes(path, atime, mtime, callback) {
callback(enosys());
},
};
}
if (!global.process) {
global.process = {
getuid() {
return -1;
},
getgid() {
return -1;
},
geteuid() {
return -1;
},
getegid() {
return -1;
},
getgroups() {
throw enosys();
},
pid: -1,
ppid: -1,
umask() {
throw enosys();
},
cwd() {
throw enosys();
},
chdir() {
throw enosys();
},
};
}
if (!global.crypto && global.require) {
const nodeCrypto = require("crypto");
global.crypto = {
getRandomValues(b) {
nodeCrypto.randomFillSync(b);
},
};
}
if (!global.crypto) {
throw new Error(
"global.crypto is not available, polyfill required (getRandomValues only)"
);
}
if (!global.performance) {
global.performance = {
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
}
if (!global.TextEncoder && global.require) {
global.TextEncoder = require("util").TextEncoder;
}
if (!global.TextEncoder) {
throw new Error(
"global.TextEncoder is not available, polyfill required"
);
}
if (!global.TextDecoder && global.require) {
global.TextDecoder = require("util").TextDecoder;
}
if (!global.TextDecoder) {
throw new Error(
"global.TextDecoder is not available, polyfill required"
);
}
// End of polyfills for common API.
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
const Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
term.warn("exit code:", code);
}
};
this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve;
});
this._pendingEvent = null;
this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const setInt64 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
};
const getInt64 = (addr) => {
const low = this.mem.getUint32(addr + 0, true);
const high = this.mem.getInt32(addr + 4, true);
return low + high * 4294967296;
};
const loadValue = (addr) => {
const f = this.mem.getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = this.mem.getUint32(addr, true);
return this._values[id];
};
const storeValue = (addr, v) => {
const nanHead = 0x7ff80000;
if (typeof v === "number" && v !== 0) {
if (isNaN(v)) {
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true);
return;
}
this.mem.setFloat64(addr, v, true);
return;
}
if (v === undefined) {
this.mem.setFloat64(addr, 0, true);
return;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = this._values.length;
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 0;
switch (typeof v) {
case "object":
if (v !== null) {
typeFlag = 1;
}
break;
case "string":
typeFlag = 2;
break;
case "symbol":
typeFlag = 3;
break;
case "function":
typeFlag = 4;
break;
}
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true);
};
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(
this._inst.exports.mem.buffer,
array,
len
);
};
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
};
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(
new DataView(this._inst.exports.mem.buffer, saddr, len)
);
};
const timeOrigin = Date.now() - performance.now();
this.importObject = {
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
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
sp >>>= 0;
const code = this.mem.getInt32(sp + 8, true);
this.exited = true;
delete this._inst;
delete this._values;
delete this._goRefCounts;
delete this._ids;
delete this._idPool;
this.exit(code);
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": (sp) => {
sp >>>= 0;
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = this.mem.getInt32(sp + 24, true);
fs.writeSync(
fd,
new Uint8Array(this._inst.exports.mem.buffer, p, n)
);
},
// func resetMemoryDataView()
"runtime.resetMemoryDataView": (sp) => {
sp >>>= 0;
this.mem = new DataView(this._inst.exports.mem.buffer);
},
// func nanotime1() int64
"runtime.nanotime1": (sp) => {
sp >>>= 0;
setInt64(
sp + 8,
(timeOrigin + performance.now()) * 1000000
);
},
// func walltime() (sec int64, nsec int32)
"runtime.walltime": (sp) => {
sp >>>= 0;
const msec = new Date().getTime();
setInt64(sp + 8, msec / 1000);
this.mem.setInt32(
sp + 16,
(msec % 1000) * 1000000,
true
);
},
// func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set(
id,
setTimeout(
() => {
this._resume();
while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
term.warn(
"scheduleTimeoutEvent: missed timeout event"
);
this._resume();
}
},
getInt64(sp + 8) + 1 // setTimeout has been seen to fire up to 1 millisecond early
)
);
this.mem.setInt32(sp + 16, id, true);
},
// func clearTimeoutEvent(id int32)
"runtime.clearTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this.mem.getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id));
this._scheduledTimeouts.delete(id);
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
sp >>>= 0;
crypto.getRandomValues(loadSlice(sp + 8));
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (sp) => {
sp >>>= 0;
const id = this.mem.getUint32(sp + 8, true);
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
sp >>>= 0;
storeValue(sp + 24, loadString(sp + 8));
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
sp >>>= 0;
const result = Reflect.get(
loadValue(sp + 8),
loadString(sp + 16)
);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (sp) => {
sp >>>= 0;
Reflect.set(
loadValue(sp + 8),
loadString(sp + 16),
loadValue(sp + 32)
);
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (sp) => {
sp >>>= 0;
Reflect.deleteProperty(
loadValue(sp + 8),
loadString(sp + 16)
);
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (sp) => {
sp >>>= 0;
storeValue(
sp + 24,
Reflect.get(loadValue(sp + 8), getInt64(sp + 16))
);
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (sp) => {
sp >>>= 0;
Reflect.set(
loadValue(sp + 8),
getInt64(sp + 16),
loadValue(sp + 24)
);
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
const result = Reflect.apply(m, v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, result);
this.mem.setUint8(sp + 64, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, err);
this.mem.setUint8(sp + 64, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.apply(v, undefined, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.construct(v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
sp >>>= 0;
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (sp) => {
sp >>>= 0;
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (sp) => {
sp >>>= 0;
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
sp >>>= 0;
this.mem.setUint8(
sp + 24,
loadValue(sp + 8) instanceof loadValue(sp + 16)
? 1
: 0
);
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (sp) => {
sp >>>= 0;
const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
if (
!(
src instanceof Uint8Array ||
src instanceof Uint8ClampedArray
)
) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
// func copyBytesToJS(dst ref, src []byte) (int, bool)
"syscall/js.copyBytesToJS": (sp) => {
sp >>>= 0;
const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16);
if (
!(
dst instanceof Uint8Array ||
dst instanceof Uint8ClampedArray
)
) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
debug: (value) => {
term.log(value);
},
},
};
}
async run(instance) {
if (!(instance instanceof WebAssembly.Instance)) {
throw new Error("Go.run: WebAssembly.Instance expected");
}
this._inst = instance;
this.mem = new DataView(this._inst.exports.mem.buffer);
this._values = [
// JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
global,
this,
];
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map([
// mapping from JS values to reference ids
[0, 1],
[null, 2],
[true, 3],
[false, 4],
[global, 5],
[this, 6],
]);
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
const ptr = offset;
const bytes = encoder.encode(str + "\0");
new Uint8Array(this.mem.buffer, offset, bytes.length).set(
bytes
);
offset += bytes.length;
if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
}
return ptr;
};
const argc = this.argv.length;
const argvPtrs = [];
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
argvPtrs.push(0);
const keys = Object.keys(this.env).sort();
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
argvPtrs.push(0);
const argv = offset;
argvPtrs.forEach((ptr) => {
this.mem.setUint32(offset, ptr, true);
this.mem.setUint32(offset + 4, 0, true);
offset += 8;
});
this._inst.exports.run(argc, argv);
if (this.exited) {
this._resolveExitPromise();
}
await this._exitPromise;
}
_resume() {
if (this.exited) {
(async () => {
await run();
this._inst.exports.resume();
})();
return;
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
};
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();
};

106
wasm/index.html Normal file
View File

@@ -0,0 +1,106 @@
<!DOCTYPE html>
<html>
<head>
<title>Argon WASM Runtime Example</title>
<style>
.terminal {
font-family: monospace;
font-size: 12px;
color: #fff;
background-color: #000;
padding: 10px;
margin: 10px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
min-height: 250px;
max-width: 500px;
}
.terminal * {
min-height: 1rem;
}
.terminal .command {
color: #0f0;
}
.terminal .output {
color: #fff;
}
.terminal .error {
color: #f00;
}
.editbox {
font-family: monospace;
font-size: 12px;
color: #000;
background-color: #fff;
padding: 10px;
margin: 10px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
min-height: 250px;
max-width: 500px;
width: 100%;
resize: none;
border: none;
}
.runButton {
border: 2.5px solid #ffffff;
background-color: #3a9200;
box-shadow: none;
color: #ffffff;
font-weight: 900;
font-size: 1rem;
width: 100px;
display: block;
padding: 10px;
margin: 10px;
border-radius: 5px;
cursor: pointer;
}
.runButton:active {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
</style>
<script src="argon_wasm.js"></script>
</head>
<body>
<h1>Argon WASM Runtime Example</h1>
<button id="run" class="runButton">run</button>
<textarea id="editbox" class="editbox">
term.log("hello world")</textarea
>
<pre id="terminal" class="terminal">
</pre>
<script>
const output = document.getElementById("terminal");
const editbox = document.getElementById("editbox");
const run = document.getElementById("run");
run.addEventListener("click", () => {
output.innerHTML = "";
setTimeout(()=>Ar.eval(editbox.value), 100)
});
ArgonWASMRuntime({
console: {
log: (...msg) => {
const p = document.createElement("div");
p.innerText = msg.join(" ");
output.appendChild(p);
},
warn: (...msg) => {
const p = document.createElement("div");
p.innerText = msg.join(" ");
p.style.color = "orange";
output.appendChild(p);
},
error: (...msg) => {
const p = document.createElement("div");
p.innerText = msg.join(" ");
p.style.color = "red";
output.appendChild(p);
},
},
});
</script>
</body>
</html>