From 97ec428acab34d84845f31f1ce731236c8f127b9 Mon Sep 17 00:00:00 2001 From: Ugric Date: Sat, 25 Mar 2023 00:45:13 +0000 Subject: [PATCH 01/21] make wasm work --- build | 2 +- build.bat | 4 +- run | 2 - run.bat | 5 - src/error.go | 8 +- src/import.go | 188 +++++++------ src/main.go | 31 ++- src/parseImport.go | 1 + src/translate.go | 1 - src/wasm.go | 71 +++++ wasm/argon_wasm.js | 680 +++++++++++++++++++++++++++++++++++++++++++++ wasm/index.html | 106 +++++++ 12 files changed, 984 insertions(+), 115 deletions(-) delete mode 100755 run delete mode 100644 run.bat create mode 100644 src/wasm.go create mode 100644 wasm/argon_wasm.js create mode 100644 wasm/index.html diff --git a/build b/build index c9fb28a..bf55fad 100755 --- a/build +++ b/build @@ -1 +1 @@ -go build -o bin/argon ./src \ No newline at end of file +GOOS=js GOARCH=wasm go build -o wasm/bin/argon.wasm ./src \ No newline at end of file diff --git a/build.bat b/build.bat index 9af9c18..a36fb7b 100644 --- a/build.bat +++ b/build.bat @@ -1,2 +1,4 @@ @echo off -go build -o bin/argon.exe ./src \ No newline at end of file +set GOOS=js +set GOARCH=wasm +go build -o wasm/bin/argon.wasm ./src \ No newline at end of file diff --git a/run b/run deleted file mode 100755 index 0994fe7..0000000 --- a/run +++ /dev/null @@ -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 $@ \ No newline at end of file diff --git a/run.bat b/run.bat deleted file mode 100644 index bbe3f9c..0000000 --- a/run.bat +++ /dev/null @@ -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 %* \ No newline at end of file diff --git a/src/error.go b/src/error.go index ff41ed3..3517784 100644 --- a/src/error.go +++ b/src/error.go @@ -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() } - fmt.Printf("\x1b[%dm%s\x1b[0m", 91, fmt.Sprint(err.TYPE, ": ", err.message, "\n")) + 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)) + } } diff --git a/src/import.go b/src/import.go index 385fe8d..3254422 100644 --- a/src/import.go +++ b/src/import.go @@ -5,7 +5,6 @@ import ( "errors" "log" "os" - "path/filepath" ) var imported = make(map[string]ArObject) @@ -48,101 +47,108 @@ func readFile(path string) []UNPARSEcode { } func importMod(realpath string, origin string, main bool) (ArObject, ArErr) { - extention := filepath.Ext(realpath) - path := realpath - if extention == "" { - path += ".ar" + return ArObject{}, ArErr{ + TYPE: "Import Error", + message: "importing in WASM is currently not supported", + EXISTS: true, } - ex, err := os.Getwd() - if err != nil { - return ArObject{}, ArErr{TYPE: "Import Error", message: "Could not get working directory", EXISTS: true} - } - exc, err := os.Executable() - if err != nil { - return ArObject{}, ArErr{TYPE: "Import Error", message: "Could not get executable", EXISTS: true} - } - executable := filepath.Dir(exc) - isABS := filepath.IsAbs(path) - var pathsToTest []string - if isABS { - pathsToTest = []string{ - filepath.Join(path), - filepath.Join(realpath, "init.ar"), + /* + extention := filepath.Ext(realpath) + path := realpath + if extention == "" { + path += ".ar" } - } else { - pathsToTest = []string{ - filepath.Join(origin, realpath, "init.ar"), - filepath.Join(origin, path), - filepath.Join(origin, "modules", path), - filepath.Join(origin, "modules", realpath, "init.ar"), - filepath.Join(ex, path), - filepath.Join(ex, "modules", realpath, "init.ar"), - filepath.Join(ex, "modules", path), - filepath.Join(executable, "modules", realpath, "init.ar"), - filepath.Join(executable, "modules", path), + ex, err := os.Getwd() + if err != nil { + return ArObject{}, ArErr{TYPE: "Import Error", message: "Could not get working directory", EXISTS: true} } - } - - var p string - var found bool - for _, p = range pathsToTest { - if FileExists(p) { - found = true - break + exc, err := os.Executable() + if err != nil { + return ArObject{}, ArErr{TYPE: "Import Error", message: "Could not get executable", EXISTS: true} + } + executable := filepath.Dir(exc) + isABS := filepath.IsAbs(path) + var pathsToTest []string + if isABS { + pathsToTest = []string{ + filepath.Join(path), + filepath.Join(realpath, "init.ar"), + } + } else { + pathsToTest = []string{ + filepath.Join(origin, realpath, "init.ar"), + filepath.Join(origin, path), + filepath.Join(origin, "modules", path), + filepath.Join(origin, "modules", realpath, "init.ar"), + filepath.Join(ex, path), + filepath.Join(ex, "modules", realpath, "init.ar"), + filepath.Join(ex, "modules", path), + filepath.Join(executable, "modules", realpath, "init.ar"), + filepath.Join(executable, "modules", path), + } } - } - if !found { - return ArObject{}, ArErr{TYPE: "Import Error", message: "File does not exist: " + realpath, EXISTS: true} - } else if importing[p] { - return ArObject{}, ArErr{TYPE: "Import Error", message: "Circular import: " + realpath, EXISTS: true} - } else if _, ok := imported[p]; ok { - return imported[p], ArErr{} - } - importing[p] = true - codelines := readFile(p) + var p string + var found bool + for _, p = range pathsToTest { + if FileExists(p) { + found = true + break + } + } - translated, translationerr := translate(codelines) - if translationerr.EXISTS { - return ArObject{}, translationerr - } - ArgsArArray := []any{} - withoutarfile := []string{} - if len(Args) > 1 { - withoutarfile = Args[1:] - } - for _, arg := range withoutarfile { - ArgsArArray = append(ArgsArArray, arg) - } - global := newscope() - localvars := Map(anymap{ - "program": Map(anymap{ - "args": ArArray(ArgsArArray), - "origin": origin, - "import": builtinFunc{"import", func(args ...any) (any, ArErr) { - if len(args) != 1 { - return nil, ArErr{"Import Error", "Invalid number of arguments", 0, realpath, "", true} - } - if _, ok := args[0].(string); !ok { - return nil, ArErr{"Import Error", "Invalid argument type", 0, realpath, "", true} - } - return importMod(args[0].(string), filepath.Dir(p), false) - }}, - "cwd": ex, - "exc": exc, - "file": Map(anymap{ - "name": filepath.Base(p), - "path": p, + if !found { + return ArObject{}, ArErr{TYPE: "Import Error", message: "File does not exist: " + realpath, EXISTS: true} + } else if importing[p] { + return ArObject{}, ArErr{TYPE: "Import Error", message: "Circular import: " + realpath, EXISTS: true} + } else if _, ok := imported[p]; ok { + return imported[p], ArErr{} + } + importing[p] = true + codelines := readFile(p) + + translated, translationerr := translate(codelines) + if translationerr.EXISTS { + return ArObject{}, translationerr + } + ArgsArArray := []any{} + withoutarfile := []string{} + if len(Args) > 1 { + withoutarfile = Args[1:] + } + for _, arg := range withoutarfile { + ArgsArArray = append(ArgsArArray, arg) + } + global := newscope() + localvars := Map(anymap{ + "program": Map(anymap{ + "args": ArArray(ArgsArArray), + "origin": origin, + "import": builtinFunc{"import", func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{"Import Error", "Invalid number of arguments", 0, realpath, "", true} + } + if _, ok := args[0].(string); !ok { + return nil, ArErr{"Import Error", "Invalid argument type", 0, realpath, "", true} + } + return importMod(args[0].(string), filepath.Dir(p), false) + }}, + "cwd": ex, + "exc": exc, + "file": Map(anymap{ + "name": filepath.Base(p), + "path": p, + }), + "main": main, + "scope": global, }), - "main": main, - "scope": global, - }), - }) - _, runimeErr := ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) - importing[p] = false - if runimeErr.EXISTS { - return ArObject{}, runimeErr - } - imported[p] = global - return global, ArErr{} + }) + _, runimeErr := ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) + importing[p] = false + if runimeErr.EXISTS { + return ArObject{}, runimeErr + } + imported[p] = global + return global, ArErr{} + */ } diff --git a/src/main.go b/src/main.go index 1084ea9..8b1c2a8 100644 --- a/src/main.go +++ b/src/main.go @@ -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) - } - if len(Args) == 0 { - shell() - os.Exit(0) - } - _, err := importMod(Args[0], ex, true) - if err.EXISTS { - panicErr(err) - os.Exit(1) - } + 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() + } + val, err := wasmRun(code) + if err.EXISTS { + panicErr(err) + return js.Null() + } + + return js.ValueOf(argonToJsValid(val)) + })) + js.Global().Set("Ar", obj) + <-c } diff --git a/src/parseImport.go b/src/parseImport.go index f54fa48..324318e 100644 --- a/src/parseImport.go +++ b/src/parseImport.go @@ -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 { diff --git a/src/translate.go b/src/translate.go index ba69431..e6d13a2 100644 --- a/src/translate.go +++ b/src/translate.go @@ -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) diff --git a/src/wasm.go b/src/wasm.go new file mode 100644 index 0000000..0e14842 --- /dev/null +++ b/src/wasm.go @@ -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, + "", + }) + } + + 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, "", "", true} + }}, + "cwd": "", + "exc": "", + "file": Map(anymap{ + "name": "", + "path": "", + }), + "main": true, + "scope": global, + }), + }) + return ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) +} diff --git a/wasm/argon_wasm.js b/wasm/argon_wasm.js new file mode 100644 index 0000000..0db04ac --- /dev/null +++ b/wasm/argon_wasm.js @@ -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(); +}; diff --git a/wasm/index.html b/wasm/index.html new file mode 100644 index 0000000..e77a0c9 --- /dev/null +++ b/wasm/index.html @@ -0,0 +1,106 @@ + + + + Argon WASM Runtime Example + + + + +

Argon WASM Runtime Example

+ + +
+
+        
+ + + From a6c14b49a5bb13d3a768693b9f87cad7f92474db Mon Sep 17 00:00:00 2001 From: Ugric Date: Sat, 25 Mar 2023 19:50:26 +0000 Subject: [PATCH 02/21] make wasm not require reinit --- serve.bat | 3 + src/built-ins.go | 5 +- src/document.go | 210 +++++++++++++++++++++++++++++++++++++++++++++ src/main.go | 8 +- src/wasm.go | 8 +- wasm/argon_wasm.js | 26 +++--- wasm/index.html | 19 ++-- 7 files changed, 250 insertions(+), 29 deletions(-) create mode 100644 serve.bat create mode 100644 src/document.go 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 @@ Argon WASM Runtime Example + - +

Argon WASM Runtime Example

- -
-
-        
+
____
/\ |___ \
/ \ _ __ __ _ ___ _ __ __ ____) |
/ /\ \ | '__/ _` |/ _ \| '_ \ \ \ / /__ <
/ ____ \| | | (_| | (_) | | | | \ V /___) |
/_/ \_\_| \__, |\___/|_| |_| \_/|____/
__/ |
|___/
----------------------------------------------
Welcome to ARGON for WASM!
write code above and click run to see it work like magic!
+

Argon WASM Runtime Example

From de95c3e8b57c88c291abaa544a9db4387e59df56 Mon Sep 17 00:00:00 2001 From: William Bell Date: Sun, 26 Mar 2023 21:55:03 +0100 Subject: [PATCH 10/21] add sort to strings --- src/sortany.go | 4 +++ src/string.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/sortany.go b/src/sortany.go index c090a77..19261f5 100644 --- a/src/sortany.go +++ b/src/sortany.go @@ -78,6 +78,10 @@ func compare(a, b any) (bool, error) { return anyToBool(resp), nil } } + } else if x, ok := b.(byte); ok { + if y, ok := a.(byte); ok { + return y < x, nil + } } return false, fmt.Errorf("cannot compare %s to %s", typeof(a), typeof(b)) } diff --git a/src/string.go b/src/string.go index 2fe649c..eace6e0 100644 --- a/src/string.go +++ b/src/string.go @@ -366,6 +366,80 @@ func ArString(str string) ArObject { } return strings.Count(str, a[0].(string)), ArErr{} }} + + obj.obj["sort"] = builtinFunc{ + "sort", + func(args ...any) (any, ArErr) { + if len(args) > 2 { + return nil, ArErr{ + TYPE: "TypeError", + message: "too many arguments", + EXISTS: true, + } + } + reverse := false + if len(args) >= 1 { + if typeof(args[0]) != "boolean" { + return nil, ArErr{ + TYPE: "TypeError", + message: "argument must be a boolean", + EXISTS: true, + } + } + reverse = args[0].(bool) + } + bytes := []byte(str) + anyarr := make([]any, len(bytes)) + for i, b := range bytes { + anyarr[i] = b + } + if len(args) == 2 { + if typeof(args[1]) != "function" { + return nil, ArErr{ + TYPE: "TypeError", + message: "argument must be a function", + EXISTS: true, + } + } + output, err := quickSort(anyarr, func(a any) (any, ArErr) { + return runCall(call{ + args[1], + []any{a}, "", 0, "", + }, stack{}, 0) + }) + if err.EXISTS { + return nil, err + } + bytes = make([]byte, len(output)) + for i, b := range output { + bytes[i] = b.(byte) + } + str = string(bytes) + obj.obj["length"] = len(str) + obj.obj["__value__"] = str + return nil, ArErr{} + } + output, err := quickSort(anyarr, func(a any) (any, ArErr) { + return a, ArErr{} + }) + if err.EXISTS { + return nil, err + } + if reverse { + for i, j := 0, len(output)-1; i < j; i, j = i+1, j-1 { + output[i], output[j] = output[j], output[i] + } + } + bytes = make([]byte, len(output)) + for i, b := range output { + bytes[i] = b.(byte) + } + str = string(bytes) + obj.obj["length"] = len(str) + obj.obj["__value__"] = str + return nil, ArErr{} + }, + } obj.obj["strip"] = builtinFunc{ "strip", func(a ...any) (any, ArErr) { From 4c412a763a903c253dfcbd88b294aa1cec6dc76a Mon Sep 17 00:00:00 2001 From: William Bell Date: Sun, 26 Mar 2023 22:08:54 +0100 Subject: [PATCH 11/21] move spec to docs --- spec.md | 115 -------------------------------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 spec.md diff --git a/spec.md b/spec.md deleted file mode 100644 index 582505b..0000000 --- a/spec.md +++ /dev/null @@ -1,115 +0,0 @@ -# ARGON 3 dev 1.0 - -This is the specification on how to create an Argon interpreter. - -This specification should be used as a guildline, and is subject to change for later versions. - -Later updates for Argon 3 should be backwards compatable (where possible) with code designed for older versions -of the interpreter. - -The Argon 3 interpreter is not intentionally designed to understand code written for Argon 1 and/or 2, but may -follow some design patterns found in those interpreters. - -Argon 3 is a programming language primarily designed for maths computation, it is designed to use techniques and -rules set in maths. It's designed to be easy for mathematicians to write and understand algorithms in. -(e.g. `f(x) = x^2` to set a function) - -reused variables, and infomation for use in understanding the pseudo REGEX: - -- NAME = [a-zA-Z][a-za-z0-9]\* -- spaces used in the pseudo REGEX should be taken as 1 or many spaces. -- there can be 0 or more spaces around any of the pseudo REGEX - ---- - -## set variable - -`(let or nothing) {NAME} = {ANY}` - -let variables will set variables. at the end of a opperation (e.g. if, while, or function) drop the -variable stack. - -having no verb at the start suggests the program would like to edit a variables from a different stack. -if there is no variable found in the other stacks, then it sets one in the current stack as a let. - -setting variables returns the value, which can be used. - -example: - -```javascript -if ((x = 10) > 5) do - term.log(x, 'is bigger than 5') -``` - ---- - -## functions - -`(let or nothing) {NAME}({PARAMS}) = {CODE}` - -the starting verb follows the rules set by [set variable](#set-variable). - -function can be called by using its name followed by brackets with the params. - -example: - -```javascript -f(x) = x^2 + 2*x + 1 -term.log('f(10) =', f(10)) -``` - -output: - -```javascript -f(10) = 121 -``` - -if the function does not return, then the value returned is `null` - ---- - -## wrap - -``` -do - {CODE} -``` - -a wrap encloses code indented in after the word `do` its used to create a new scope, so variables set from -inside the wraps scope are deleted once the wrap is finished. - -example: - -```javascript -let name = null - -do - name = input('name: ') - let age = input('age: ') - log('you are', age, 'years old!') - -term.log('hello', name) -term.log('we do not know your age anymore because it got deleted when the wrap finished.') -``` - -A wrap, unless specificifed otherwise, can have a return value. This value can be used as the value of the wrap. - -example: - -```javascript -let password = do - let password = passwordInput("set password: ") - while (password.length < 8) do - term.log("password must be longer then 8 characters!") - password = passwordInput("set password: ") - return password - -term.log("your password is", password) -``` - -If the wrap does not take a return value, then the wrap passes the return value back to a parent wrap. - -## Comments -`//{COMMENT}` - -Comments allow the programmer to write a message into their code, without the message being processed by the computer. \ No newline at end of file From e05267c8869f9d2ce22bb1ec32a08f62445a93fa Mon Sep 17 00:00:00 2001 From: William Bell Date: Tue, 28 Mar 2023 00:17:58 +0100 Subject: [PATCH 12/21] fix built in functions --- example.ar | 2 ++ src/array.go | 4 ++-- src/built-in-functions.go | 2 +- src/built-ins.go | 28 +++++++++++++--------------- src/call.go | 3 --- src/string.go | 38 +++++++++++++++++++------------------- 6 files changed, 37 insertions(+), 40 deletions(-) create mode 100644 example.ar diff --git a/example.ar b/example.ar new file mode 100644 index 0000000..265b901 --- /dev/null +++ b/example.ar @@ -0,0 +1,2 @@ +forever do + term.log(random()) \ No newline at end of file diff --git a/src/array.go b/src/array.go index fed25d4..aac033b 100644 --- a/src/array.go +++ b/src/array.go @@ -507,9 +507,9 @@ func ArArray(arr []any) ArObject { EXISTS: true, } } - output = append(output, v.(string)) + output = append(output, v.(ArObject).obj["__value__"].(string)) } - return ArString(strings.Join(output, args[0].(string))), ArErr{} + return ArString(strings.Join(output, args[0].(ArObject).obj["__value__"].(string))), ArErr{} }, } val.obj["concat"] = builtinFunc{ diff --git a/src/built-in-functions.go b/src/built-in-functions.go index 1a0b309..330b58c 100644 --- a/src/built-in-functions.go +++ b/src/built-in-functions.go @@ -33,7 +33,7 @@ func ArgonNumber(args ...any) (any, ArErr) { if x { return newNumber().SetInt64(1), ArErr{} } - return newNumber().SetInt64(0), ArErr{} + return newNumber(), ArErr{} case nil: return newNumber(), ArErr{} } diff --git a/src/built-ins.go b/src/built-ins.go index 0c4a158..2f08917 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -1,7 +1,5 @@ package main -import "fmt" - func makeGlobal(allowDocument bool) ArObject { var vars = Map(anymap{}) vars.obj["global"] = vars @@ -35,14 +33,14 @@ func makeGlobal(allowDocument bool) ArObject { newmap[i] = v } return Map(newmap), ArErr{} + } else if x.TYPE == "string" { + newmap := anymap{} + for i, v := range x.obj["__value__"].(string) { + newmap[i] = ArString(string(v)) + } + return Map(newmap), ArErr{} } return x, ArErr{} - case string: - newmap := anymap{} - for i, v := range x { - newmap[i] = ArString(string(v)) - } - return Map(newmap), ArErr{} } return nil, ArErr{TYPE: "TypeError", message: "Cannot create map from '" + typeof(a[0]) + "'", EXISTS: true} }} @@ -51,15 +49,16 @@ func makeGlobal(allowDocument bool) ArObject { return ArArray([]any{}), ArErr{} } switch x := a[0].(type) { - case string: - newarray := []any{} - for _, v := range x { - newarray = append(newarray, ArString(string(v))) - } - return ArArray(newarray), ArErr{} case ArObject: if x.TYPE == "array" { return x, ArErr{} + } else if x.TYPE == "string" { + + newarray := []any{} + for _, v := range x.obj["__value__"].(string) { + newarray = append(newarray, ArString(string(v))) + } + return ArArray(newarray), ArErr{} } newarray := []any{} for key, val := range x.obj { @@ -151,7 +150,6 @@ func makeGlobal(allowDocument bool) ArObject { vars.obj["torad"] = ArToRad vars.obj["abs"] = ArAbs vars.obj["dir"] = builtinFunc{"dir", func(a ...any) (any, ArErr) { - fmt.Println(a) if len(a) == 0 { return ArArray([]any{}), ArErr{} } diff --git a/src/call.go b/src/call.go index 1d0425c..c504edc 100644 --- a/src/call.go +++ b/src/call.go @@ -92,9 +92,6 @@ func runCall(c call, stack stack, stacklevel int) (any, ArErr) { } switch x := callable.(type) { case builtinFunc: - for i := range args { - args[i] = ArValidToAny(args[i]) - } resp, err := x.FUNC(args...) resp = AnyToArValid(resp) if err.EXISTS { diff --git a/src/string.go b/src/string.go index eace6e0..bb08ae4 100644 --- a/src/string.go +++ b/src/string.go @@ -253,7 +253,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - splitby := a[0].(string) + splitby := a[0].(ArObject).obj["__value__"].(string) output := []any{} splitted := (strings.Split(str, splitby)) for _, v := range splitted { @@ -297,7 +297,7 @@ func ArString(str string) ArObject { if typeof(a[1]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[1]), 0, "", "", true} } - return strings.Replace(str, a[0].(string), a[1].(string), -1), ArErr{} + return strings.Replace(str, a[0].(ArObject).obj["__value__"].(string), a[1].(string), -1), ArErr{} }} obj.obj["contains"] = builtinFunc{ "contains", @@ -308,7 +308,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - return strings.Contains(str, a[0].(string)), ArErr{} + return strings.Contains(str, a[0].(ArObject).obj["__value__"].(string)), ArErr{} }} obj.obj["startswith"] = builtinFunc{ "startswith", @@ -319,7 +319,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - return strings.HasPrefix(str, a[0].(string)), ArErr{} + return strings.HasPrefix(str, a[0].(ArObject).obj["__value__"].(string)), ArErr{} }} obj.obj["endswith"] = builtinFunc{ "endswith", @@ -330,7 +330,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - return strings.HasSuffix(str, a[0].(string)), ArErr{} + return strings.HasSuffix(str, a[0].(ArObject).obj["__value__"].(string)), ArErr{} }} obj.obj["index"] = builtinFunc{ "index", @@ -341,7 +341,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - return strings.Index(str, a[0].(string)), ArErr{} + return strings.Index(str, a[0].(ArObject).obj["__value__"].(string)), ArErr{} }} obj.obj["rindex"] = builtinFunc{ "rindex", @@ -352,7 +352,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - return strings.LastIndex(str, a[0].(string)), ArErr{} + return strings.LastIndex(str, a[0].(ArObject).obj["__value__"].(string)), ArErr{} }} obj.obj["count"] = builtinFunc{ "count", @@ -364,7 +364,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - return strings.Count(str, a[0].(string)), ArErr{} + return strings.Count(str, a[0].(ArObject).obj["__value__"].(string)), ArErr{} }} obj.obj["sort"] = builtinFunc{ @@ -451,7 +451,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - cutset = a[0].(string) + cutset = a[0].(ArObject).obj["__value__"].(string) } return strings.Trim(str, cutset), ArErr{} }} @@ -466,7 +466,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - cutset = a[0].(string) + cutset = a[0].(ArObject).obj["__value__"].(string) } return strings.TrimLeft(str, cutset), ArErr{} }} @@ -481,7 +481,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[0]), 0, "", "", true} } - cutset = a[0].(string) + cutset = a[0].(ArObject).obj["__value__"].(string) } return strings.TrimRight(str, cutset), ArErr{} }} @@ -494,7 +494,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot get less than or equal to of type " + typeof(a[0]) + " from string", 0, "", "", true} } - return str <= a[0].(string), ArErr{} + return str <= a[0].(ArObject).obj["__value__"].(string), ArErr{} }} obj.obj["__LessThan__"] = builtinFunc{ "__LessThan__", @@ -505,7 +505,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot get less than of type " + typeof(a[0]) + " from string", 0, "", "", true} } - return str < a[0].(string), ArErr{} + return str < a[0].(ArObject).obj["__value__"].(string), ArErr{} }} obj.obj["__GreaterThan__"] = builtinFunc{ "__GreaterThan__", @@ -516,7 +516,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot get greater than of type " + typeof(a[0]) + " from string", 0, "", "", true} } - return str > a[0].(string), ArErr{} + return str > a[0].(ArObject).obj["__value__"].(string), ArErr{} }} obj.obj["__GreaterThanEqual__"] = builtinFunc{ @@ -528,7 +528,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot get greater than or equal to of type " + typeof(a[0]) + " from string", 0, "", "", true} } - return str >= a[0].(string), ArErr{} + return str >= a[0].(ArObject).obj["__value__"].(string), ArErr{} }} obj.obj["__Equal__"] = builtinFunc{ "__Equal__", @@ -555,7 +555,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot add " + typeof(a[0]) + " to string", 0, "", "", true} } - return strings.Join([]string{str, a[0].(string)}, ""), ArErr{} + return strings.Join([]string{str, a[0].(ArObject).obj["__value__"].(string)}, ""), ArErr{} }} obj.obj["__Multiply__"] = builtinFunc{ "__Multiply__", @@ -584,7 +584,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot check if string contains " + typeof(a[0]), 0, "", "", true} } - return strings.Contains(str, a[0].(string)), ArErr{} + return strings.Contains(str, a[0].(ArObject).obj["__value__"].(string)), ArErr{} }} obj.obj["__Subtract__"] = builtinFunc{ "__Subtract__", @@ -595,7 +595,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot subtract " + typeof(a[0]) + " from string", 0, "", "", true} } - return strings.Replace(str, a[0].(string), "", -1), ArErr{} + return strings.Replace(str, a[0].(ArObject).obj["__value__"].(string), "", -1), ArErr{} }} obj.obj["__Divide__"] = builtinFunc{ "__Divide__", @@ -606,7 +606,7 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot divide string by " + typeof(a[0]), 0, "", "", true} } - splitby := a[0].(string) + splitby := a[0].(ArObject).obj["__value__"].(string) output := []any{} splitted := (strings.Split(str, splitby)) for _, v := range splitted { From 280635d46e835801d47b6d188a1a2c3d085d5af7 Mon Sep 17 00:00:00 2001 From: William Bell Date: Tue, 28 Mar 2023 00:22:54 +0100 Subject: [PATCH 13/21] update --- src/input.go | 7 +- src/wasm.go | 27 ++ wasm/argon_wasm.js | 675 --------------------------------------------- wasm/index.html | 109 -------- 4 files changed, 29 insertions(+), 789 deletions(-) delete mode 100644 wasm/argon_wasm.js delete mode 100644 wasm/index.html diff --git a/src/input.go b/src/input.go index 8f5c94e..d990f74 100644 --- a/src/input.go +++ b/src/input.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "fmt" "os" @@ -14,10 +13,8 @@ func input(args ...any) string { output = append(output, anyToArgon(args[i], false, true, 3, 0, true, 0)) } fmt.Print(output...) - scanner := bufio.NewScanner(os.Stdin) - scanner.Scan() - input := scanner.Text() - return input + inputData := []byte{} + return string(inputData) } func getPassword(args ...any) (string, error) { diff --git a/src/wasm.go b/src/wasm.go index 0092945..8483296 100644 --- a/src/wasm.go +++ b/src/wasm.go @@ -74,6 +74,33 @@ func wasmRun(code string, allowDocument bool) (any, ArErr) { return ThrowOnNonLoop(run(translated, stack{global, localvars, local})) } +func await(awaitable js.Value) ([]js.Value, []js.Value) { + then := make(chan []js.Value) + defer close(then) + thenFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + then <- args + return nil + }) + defer thenFunc.Release() + + catch := make(chan []js.Value) + defer close(catch) + catchFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + catch <- args + return nil + }) + defer catchFunc.Release() + + awaitable.Call("then", thenFunc).Call("catch", catchFunc) + + select { + case result := <-then: + return result, nil + case err := <-catch: + return nil, err + } +} + var IntervalList = []int{} var TimeoutList = []int{} diff --git a/wasm/argon_wasm.js b/wasm/argon_wasm.js deleted file mode 100644 index 85d4600..0000000 --- a/wasm/argon_wasm.js +++ /dev/null @@ -1,675 +0,0 @@ -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; - } 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 = { - 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 - // 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) { - throw new Error("Go program has already exited"); - } - 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 result = await WebAssembly.instantiateStreaming( - fetch(path), - go.importObject - ); - - go.run(result.instance); -}; diff --git a/wasm/index.html b/wasm/index.html deleted file mode 100644 index 45879d8..0000000 --- a/wasm/index.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - Argon WASM Runtime Example - - - - - -

Argon WASM Runtime Example

- - -
____
/\ |___ \
/ \ _ __ __ _ ___ _ __ __ ____) |
/ /\ \ | '__/ _` |/ _ \| '_ \ \ \ / /__ <
/ ____ \| | | (_| | (_) | | | | \ V /___) |
/_/ \_\_| \__, |\___/|_| |_| \_/|____/
__/ |
|___/
----------------------------------------------
Welcome to ARGON for WASM!
write code above and click run to see it work like magic!
- - - From 58c57f154b8816fcba238c8ed232fc30d18e2370 Mon Sep 17 00:00:00 2001 From: William Bell Date: Tue, 28 Mar 2023 16:00:17 +0100 Subject: [PATCH 14/21] add try catch --- src/run.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/run.go b/src/run.go index e49a2d7..b42f702 100644 --- a/src/run.go +++ b/src/run.go @@ -189,11 +189,23 @@ func runVal(line any, stack stack, stacklevel int) (any, ArErr) { break } return runImport(x, stack, stacklevel+1) - case bool: - return x, ArErr{} - case nil: - return x, ArErr{} - case ArObject: + case ABS: + if stackoverflow { + linenum = x.line + path = x.path + code = x.code + break + } + return runAbs(x, stack, stacklevel+1) + case TryCatch: + if stackoverflow { + linenum = x.line + path = x.path + code = x.code + break + } + return runTryCatch(x, stack, stacklevel+1) + case bool, ArObject, number, nil: return x, ArErr{} } if stackoverflow { From c881d06cb5428a47dcf181d11bedb098e37af665 Mon Sep 17 00:00:00 2001 From: William Bell Date: Tue, 28 Mar 2023 17:03:39 +0100 Subject: [PATCH 15/21] fix arvalid bug --- merge | 2 ++ serve | 2 -- src/built-in-functions.go | 2 ++ src/document.go | 8 ++++++-- src/input.go | 12 +++++++++--- 5 files changed, 19 insertions(+), 7 deletions(-) create mode 100755 merge delete mode 100755 serve diff --git a/merge b/merge new file mode 100755 index 0000000..c41102f --- /dev/null +++ b/merge @@ -0,0 +1,2 @@ +git fetch upstream +git merge upstream/master master \ No newline at end of file diff --git a/serve b/serve deleted file mode 100755 index 8f2b81d..0000000 --- a/serve +++ /dev/null @@ -1,2 +0,0 @@ -cd ./wasm -npx serve \ No newline at end of file diff --git a/src/built-in-functions.go b/src/built-in-functions.go index 330b58c..1dd49ff 100644 --- a/src/built-in-functions.go +++ b/src/built-in-functions.go @@ -13,6 +13,7 @@ func ArgonString(args ...any) (any, ArErr) { if len(args) == 0 { return ArString(""), ArErr{} } + args[0] = ArValidToAny(args[0]) return ArString(anyToArgon(args[0], true, false, 3, 0, false, 0)), ArErr{} } @@ -20,6 +21,7 @@ func ArgonNumber(args ...any) (any, ArErr) { if len(args) == 0 { return newNumber(), ArErr{} } + args[0] = ArValidToAny(args[0]) switch x := args[0].(type) { case string: if !isNumber(UNPARSEcode{code: x}) { diff --git a/src/document.go b/src/document.go index c498bf0..85f5453 100644 --- a/src/document.go +++ b/src/document.go @@ -114,8 +114,12 @@ func windowElement(element js.Value) ArObject { var ArDocument = Map( anymap{ - "body": windowElement(js.Global().Get("document").Get("body")), - "head": windowElement(js.Global().Get("document").Get("head")), + "body": builtinFunc{"getElementById", func(args ...any) (any, ArErr) { + return windowElement(js.Global().Get("document").Get("body")), ArErr{} + }}, + "head": builtinFunc{"getElementById", func(args ...any) (any, ArErr) { + return windowElement(js.Global().Get("document").Get("head")), ArErr{} + }}, "getElementById": builtinFunc{"getElementById", func(args ...any) (any, ArErr) { if len(args) < 1 { return nil, ArErr{"Argument Error", "Not enough arguments for getElementById", 0, "", "", true} diff --git a/src/input.go b/src/input.go index d990f74..88c9ac5 100644 --- a/src/input.go +++ b/src/input.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "syscall/js" "golang.org/x/term" ) @@ -11,10 +12,15 @@ func input(args ...any) string { output := []any{} for i := 0; i < len(args); i++ { output = append(output, anyToArgon(args[i], false, true, 3, 0, true, 0)) + if i != len(args)-1 { + output = append(output, " ") + } } - fmt.Print(output...) - inputData := []byte{} - return string(inputData) + text := fmt.Sprint(output...) + fmt.Print(text) + inputData := js.Global().Get("getInput").Invoke(fmt.Sprint(output...)).String() + fmt.Println(inputData) + return (inputData) } func getPassword(args ...any) (string, error) { From 6226cc52acc2ca9cf4681d1d59f9132442224abf Mon Sep 17 00:00:00 2001 From: William Bell Date: Tue, 28 Mar 2023 17:04:14 +0100 Subject: [PATCH 16/21] undo arvalid bug --- src/built-in-functions.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/built-in-functions.go b/src/built-in-functions.go index 1dd49ff..330b58c 100644 --- a/src/built-in-functions.go +++ b/src/built-in-functions.go @@ -13,7 +13,6 @@ func ArgonString(args ...any) (any, ArErr) { if len(args) == 0 { return ArString(""), ArErr{} } - args[0] = ArValidToAny(args[0]) return ArString(anyToArgon(args[0], true, false, 3, 0, false, 0)), ArErr{} } @@ -21,7 +20,6 @@ func ArgonNumber(args ...any) (any, ArErr) { if len(args) == 0 { return newNumber(), ArErr{} } - args[0] = ArValidToAny(args[0]) switch x := args[0].(type) { case string: if !isNumber(UNPARSEcode{code: x}) { From 90b506d22f5430aaf1eb838f440c03b7dc77b9ce Mon Sep 17 00:00:00 2001 From: Ugric Date: Tue, 28 Mar 2023 23:30:24 +0100 Subject: [PATCH 17/21] update config --- src/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.go b/src/config.go index 1aa3275..0ff9bfc 100644 --- a/src/config.go +++ b/src/config.go @@ -4,6 +4,6 @@ var websiteLang = "https://argon.wbell.dev/" var docs = "https://argon.wbell.dev/docs/" var mainrepo = "https://github.com/Open-Argon/argon-v3" var mainissuesPage = "https://github.com/Open-Argon/argon-v3/issues" -var fork = false -var forkrepo = "" -var forkissuesPage = "" +var fork = true +var forkrepo = "https://github.com/Open-Argon/argon-v3.wasm" +var forkissuesPage = "https://github.com/Open-Argon/argon-v3.wasm/issues" From 2d3c7c42cee86acafa6389b5bb9c15085ffe99a7 Mon Sep 17 00:00:00 2001 From: William Bell Date: Sat, 8 Apr 2023 15:34:15 +0200 Subject: [PATCH 18/21] make maps oop --- .gitignore | 3 +- app.py | 46 ++++++++++ modules/csv/init.ar | 9 +- src/array.go | 30 ++++++- src/arvalid.go | 16 ++-- src/boolean.go | 2 +- src/built-ins.go | 154 ++++++++++++++++++++++----------- src/call.go | 28 +++++- src/file.go | 2 + src/getIndex.go | 96 ++++++++------------ src/input.go | 3 +- src/main.go | 5 +- src/map.go | 151 +++++++++++++++++++++++++++++--- src/operations.go | 12 ++- src/sequence-and-series.go | 35 ++++++++ src/sortany.go | 10 +-- src/string.go | 35 +++++--- src/term-class.go | 13 +-- src/time.go | 109 ++++++++++++++++++----- src/to-argon.go | 6 +- src/translate.go | 17 ++-- src/trycatch.go | 6 +- src/typeof.go | 8 +- src/variable.go | 130 +++++++++++++++++----------- test | 4 + tests/brainfuck.ar | 34 ++++++++ tests/{test.ar => csv_test.ar} | 0 tests/example.ar | 3 +- tests/memoryLeakTest.ar | 5 +- tests/stack test.ar | 7 ++ tests/welcomemessage.ar | 4 +- 31 files changed, 708 insertions(+), 275 deletions(-) create mode 100644 app.py create mode 100644 src/sequence-and-series.go create mode 100755 test create mode 100644 tests/brainfuck.ar rename tests/{test.ar => csv_test.ar} (100%) create mode 100644 tests/stack test.ar diff --git a/.gitignore b/.gitignore index c5e82d7..57f219c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -bin \ No newline at end of file +bin +array.json \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..65ede69 --- /dev/null +++ b/app.py @@ -0,0 +1,46 @@ +def interpret(code): + memory = {} + pointer = 0 + code_ptr = 0 + loops = [] + + while code_ptr < len(code): + command = code[code_ptr] + + if command == '>': + pointer += 1 + elif command == '<': + pointer -= 1 + elif command == '+': + if pointer not in memory: + memory[pointer] = 0 + memory[pointer] += 1 + elif command == '-': + if pointer not in memory: + memory[pointer] = 0 + memory[pointer] -= 1 + elif command == '.': + print(chr(memory.get(pointer, 0)), end='') + elif command == ',': + memory[pointer] = ord(input()) + elif command == '[': + if memory.get(pointer, 0) == 0: + loop_depth = 1 + while loop_depth > 0: + code_ptr += 1 + if code[code_ptr] == '[': + loop_depth += 1 + elif code[code_ptr] == ']': + loop_depth -= 1 + else: + loops.append(code_ptr) + elif command == ']': + if memory.get(pointer, 0) != 0: + code_ptr = loops[-1] + else: + loops.pop() + code_ptr += 1 + +# Example usage +interpret("++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.") +# Output: Hello World! diff --git a/modules/csv/init.ar b/modules/csv/init.ar index d2accd1..91cd790 100644 --- a/modules/csv/init.ar +++ b/modules/csv/init.ar @@ -12,17 +12,14 @@ let read(path) = do char = line[j] if (char == "," && not in_quotes) do value = line[start:j].rightstrip("\r").strip() - if (value && value[0] == "\"" && value[-1] == "\"") do - value = value[1:value.length - 1] + if (value && value[0] == "\"" && value[-1] == "\"") value = value[1:value.length - 1] value = value.replace("\"\"", "\"") values.append(value) start = j + 1 - else if (char == "\"") do - in_quotes = not in_quotes + else if (char == "\"") in_quotes = not in_quotes if (j == line.length - 1) do value = line[start:j + 1].rightstrip("\r").strip() - if (value && value[0] == "\"" && value[-1] == "\"") do - value = value[1:value.length - 1] + if (value && value[0] == "\"" && value[-1] == "\"") value = value[1:value.length - 1] value = value.replace("\"\"", "\"") values.append(value) data.append(values) diff --git a/src/array.go b/src/array.go index aac033b..e093a67 100644 --- a/src/array.go +++ b/src/array.go @@ -20,8 +20,8 @@ func isArray(code UNPARSEcode) bool { func ArArray(arr []any) ArObject { val := ArObject{ - "array", anymap{ + "__name__": "array", "__value__": arr, "length": newNumber().SetUint64(uint64(len(arr))), }, @@ -39,7 +39,7 @@ func ArArray(arr []any) ArObject { if typeof(a[0]) != "number" { return nil, ArErr{ TYPE: "TypeError", - message: "index must be a number", + message: "dex must be a number", EXISTS: true, } } @@ -499,7 +499,6 @@ func ArArray(arr []any) ArObject { } output := []string{} for _, v := range arr { - v = ArValidToAny(v) if typeof(v) != "string" { return nil, ArErr{ TYPE: "TypeError", @@ -563,6 +562,31 @@ func ArArray(arr []any) ArObject { } return true, ArErr{} }} + val.obj["__Contains__"] = builtinFunc{ + "__Contains__", + func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{ + TYPE: "TypeError", + message: "missing argument", + EXISTS: true, + } + } + for _, v := range arr { + res, err := runOperation(operationType{ + operation: 8, + values: []any{v, args[0]}, + }, stack{}, 0) + if err.EXISTS { + return nil, err + } + if anyToBool(res) { + return true, ArErr{} + } + } + return false, ArErr{} + }, + } return val } diff --git a/src/arvalid.go b/src/arvalid.go index 298bccd..c1c4b59 100644 --- a/src/arvalid.go +++ b/src/arvalid.go @@ -6,6 +6,8 @@ func AnyToArValid(arr any) any { return ArArray(arr) case string: return ArString(arr) + case anymap: + return Map(arr) default: return arr } @@ -14,17 +16,9 @@ func AnyToArValid(arr any) any { func ArValidToAny(a any) any { switch a := a.(type) { case ArObject: - switch a.TYPE { - case "string": - return a.obj["__value__"] - case "array": - return a.obj["__value__"] - case "class": - return a.obj["__value__"] - default: - return a.obj + if v, ok := a.obj["__value__"]; ok { + return v } - default: - return a } + return a } diff --git a/src/boolean.go b/src/boolean.go index 1e20239..b545f25 100644 --- a/src/boolean.go +++ b/src/boolean.go @@ -13,7 +13,7 @@ func anyToBool(x any) bool { case nil: return false case ArObject: - if x.TYPE == "array" { + if typeof(x) == "array" { return len(x.obj["__value__"].([]any)) != 0 } return len(x.obj) != 0 diff --git a/src/built-ins.go b/src/built-ins.go index 6016c14..006988d 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -1,23 +1,28 @@ package main +import ( + "fmt" + "os" +) + func makeGlobal(allowDocument bool) ArObject { - var vars = Map(anymap{}) - vars.obj["global"] = vars + var vars = anymap{} + vars["global"] = vars + vars["term"] = ArTerm if allowDocument { - vars.obj["document"] = ArDocument + vars["document"] = ArDocument } - vars.obj["js"] = ArJS - vars.obj["term"] = ArTerm - vars.obj["number"] = builtinFunc{"number", ArgonNumber} - vars.obj["string"] = builtinFunc{"string", ArgonString} - vars.obj["infinity"] = infinity - vars.obj["map"] = builtinFunc{"map", func(a ...any) (any, ArErr) { + vars["js"] = ArJS + vars["number"] = builtinFunc{"number", ArgonNumber} + vars["string"] = builtinFunc{"string", ArgonString} + vars["infinity"] = infinity + vars["map"] = builtinFunc{"map", func(a ...any) (any, ArErr) { if len(a) == 0 { return Map(anymap{}), ArErr{} } switch x := a[0].(type) { case ArObject: - if x.TYPE == "array" { + if typeof(x) == "array" { newmap := anymap{} for i, v := range x.obj["__value__"].([]any) { v := ArValidToAny(v) @@ -35,7 +40,7 @@ func makeGlobal(allowDocument bool) ArObject { newmap[i] = v } return Map(newmap), ArErr{} - } else if x.TYPE == "string" { + } else if typeof(x) == "string" { newmap := anymap{} for i, v := range x.obj["__value__"].(string) { newmap[i] = ArString(string(v)) @@ -46,15 +51,15 @@ func makeGlobal(allowDocument bool) ArObject { } return nil, ArErr{TYPE: "TypeError", message: "Cannot create map from '" + typeof(a[0]) + "'", EXISTS: true} }} - vars.obj["array"] = builtinFunc{"array", func(a ...any) (any, ArErr) { + vars["array"] = builtinFunc{"array", func(a ...any) (any, ArErr) { if len(a) == 0 { return ArArray([]any{}), ArErr{} } switch x := a[0].(type) { case ArObject: - if x.TYPE == "array" { + if typeof(x) == "array" { return x, ArErr{} - } else if x.TYPE == "string" { + } else if typeof(x) == "string" { newarray := []any{} for _, v := range x.obj["__value__"].(string) { @@ -70,22 +75,22 @@ func makeGlobal(allowDocument bool) ArObject { } return nil, ArErr{TYPE: "TypeError", message: "Cannot create array from '" + typeof(a[0]) + "'", EXISTS: true} }} - vars.obj["boolean"] = builtinFunc{"boolean", func(a ...any) (any, ArErr) { + vars["boolean"] = builtinFunc{"boolean", func(a ...any) (any, ArErr) { if len(a) == 0 { return false, ArErr{} } return anyToBool(a[0]), ArErr{} }} - vars.obj["time"] = ArTime - vars.obj["PI"] = PI - vars.obj["Ï€"] = PI - vars.obj["e"] = e - vars.obj["ln"] = builtinFunc{"ln", ArgonLn} - vars.obj["log"] = builtinFunc{"log", ArgonLog} - vars.obj["logN"] = builtinFunc{"logN", ArgonLogN} - vars.obj["thread"] = builtinFunc{"thread", ArThread} - vars.obj["input"] = ArInput - vars.obj["round"] = builtinFunc{"round", func(a ...any) (any, ArErr) { + vars["time"] = ArTime + vars["PI"] = PI + vars["Ï€"] = PI + vars["e"] = e + vars["ln"] = builtinFunc{"ln", ArgonLn} + vars["log"] = builtinFunc{"log", ArgonLog} + vars["logN"] = builtinFunc{"logN", ArgonLogN} + vars["thread"] = builtinFunc{"thread", ArThread} + vars["input"] = ArInput + vars["round"] = builtinFunc{"round", func(a ...any) (any, ArErr) { if len(a) == 0 { return nil, ArErr{TYPE: "round", message: "round takes 1 argument", EXISTS: true} @@ -109,7 +114,7 @@ func makeGlobal(allowDocument bool) ArObject { } return nil, ArErr{TYPE: "TypeError", message: "Cannot round '" + typeof(a[0]) + "'", EXISTS: true} }} - vars.obj["floor"] = builtinFunc{"floor", func(a ...any) (any, ArErr) { + vars["floor"] = builtinFunc{"floor", func(a ...any) (any, ArErr) { if len(a) == 0 { return nil, ArErr{TYPE: "floor", message: "floor takes 1 argument", EXISTS: true} @@ -120,7 +125,7 @@ func makeGlobal(allowDocument bool) ArObject { } return nil, ArErr{TYPE: "TypeError", message: "Cannot floor '" + typeof(a[0]) + "'", EXISTS: true} }} - vars.obj["ceil"] = builtinFunc{"ceil", func(a ...any) (any, ArErr) { + vars["ceil"] = builtinFunc{"ceil", func(a ...any) (any, ArErr) { if len(a) == 0 { return nil, ArErr{TYPE: "ceil", message: "ceil takes 1 argument", EXISTS: true} @@ -132,26 +137,26 @@ func makeGlobal(allowDocument bool) ArObject { } return nil, ArErr{TYPE: "TypeError", message: "Cannot ceil '" + typeof(a[0]) + "'", EXISTS: true} }} - vars.obj["sqrt"] = builtinFunc{"sqrt", ArgonSqrt} - vars.obj["file"] = ArFile - vars.obj["random"] = ArRandom - vars.obj["json"] = ArJSON - vars.obj["sin"] = ArSin - vars.obj["arcsin"] = ArArcsin - vars.obj["cos"] = ArCos - vars.obj["arccos"] = ArArccos - vars.obj["tan"] = ArTan - vars.obj["arctan"] = ArArctan - vars.obj["cosec"] = ArCosec - vars.obj["arccosec"] = ArArccosec - vars.obj["sec"] = ArSec - vars.obj["arcsec"] = ArArcsec - vars.obj["cot"] = ArCot - vars.obj["arccot"] = ArArccot - vars.obj["todeg"] = ArToDeg - vars.obj["torad"] = ArToRad - vars.obj["abs"] = ArAbs - vars.obj["dir"] = builtinFunc{"dir", func(a ...any) (any, ArErr) { + vars["sqrt"] = builtinFunc{"sqrt", ArgonSqrt} + vars["file"] = ArFile + vars["random"] = ArRandom + vars["json"] = ArJSON + vars["sin"] = ArSin + vars["arcsin"] = ArArcsin + vars["cos"] = ArCos + vars["arccos"] = ArArccos + vars["tan"] = ArTan + vars["arctan"] = ArArctan + vars["cosec"] = ArCosec + vars["arccosec"] = ArArccosec + vars["sec"] = ArSec + vars["arcsec"] = ArArcsec + vars["cot"] = ArCot + vars["arccot"] = ArArccot + vars["todeg"] = ArToDeg + vars["torad"] = ArToRad + vars["abs"] = ArAbs + vars["dir"] = builtinFunc{"dir", func(a ...any) (any, ArErr) { if len(a) == 0 { return ArArray([]any{}), ArErr{} } @@ -166,17 +171,17 @@ func makeGlobal(allowDocument bool) ArObject { } return ArArray([]any{}), ArErr{} }} - vars.obj["subprocess"] = builtinFunc{"subprocess", ArSubprocess} - vars.obj["class"] = builtinFunc{"class", func(a ...any) (any, ArErr) { + vars["subprocess"] = builtinFunc{"subprocess", ArSubprocess} + vars["object"] = builtinFunc{"object", func(a ...any) (any, ArErr) { if len(a) == 0 { return nil, ArErr{TYPE: "TypeError", message: "Cannot create class from '" + typeof(a[0]) + "'", EXISTS: true} } switch x := a[0].(type) { case ArObject: - if x.TYPE == "class" { + if typeof(x) == "object" { return x, ArErr{} } - newclass := ArObject{TYPE: "class", obj: anymap{}} + newclass := ArObject{obj: anymap{}} for key, val := range x.obj { newclass.obj[key] = val } @@ -184,5 +189,50 @@ func makeGlobal(allowDocument bool) ArObject { } return nil, ArErr{TYPE: "TypeError", message: "Cannot create class from '" + typeof(a[0]) + "'", EXISTS: true} }} - return vars + vars["sequence"] = builtinFunc{"sequence", ArSequence} + vars["exit"] = builtinFunc{"exit", func(a ...any) (any, ArErr) { + if len(a) == 0 { + os.Exit(0) + } + switch x := a[0].(type) { + case number: + os.Exit(int(floor(x).Num().Int64())) + } + os.Exit(0) + return nil, ArErr{} + }} + vars["error"] = builtinFunc{"error", func(a ...any) (any, ArErr) { + if len(a) < 1 || len(a) > 2 { + return nil, ArErr{TYPE: "error", message: "error takes 1 or 2 arguments, got " + fmt.Sprint(len(a)), EXISTS: true} + } + if len(a) == 1 { + a[0] = ArValidToAny(a[0]) + switch x := a[0].(type) { + case string: + return nil, ArErr{TYPE: "Error", message: x, EXISTS: true} + } + } else { + a[0] = ArValidToAny(a[0]) + a[1] = ArValidToAny(a[1]) + switch x := a[0].(type) { + case string: + switch y := a[1].(type) { + case string: + return nil, ArErr{TYPE: x, message: y, EXISTS: true} + } + } + } + return nil, ArErr{TYPE: "TypeError", message: "Cannot create error from '" + typeof(a[0]) + "'", EXISTS: true} + }} + vars["chr"] = builtinFunc{"chr", func(a ...any) (any, ArErr) { + if len(a) != 1 { + return nil, ArErr{TYPE: "chr", message: "chr takes 1 argument, got " + fmt.Sprint(len(a)), EXISTS: true} + } + switch x := a[0].(type) { + case number: + return string([]rune{rune(floor(x).Num().Int64())}), ArErr{} + } + return nil, ArErr{TYPE: "TypeError", message: "Cannot convert '" + typeof(a[0]) + "' to string", EXISTS: true} + }} + return Map(vars) } diff --git a/src/call.go b/src/call.go index c504edc..7604cd6 100644 --- a/src/call.go +++ b/src/call.go @@ -16,6 +16,7 @@ type call struct { } type Callable struct { + name string params []string run any code string @@ -110,12 +111,33 @@ func runCall(c call, stack stack, stacklevel int) (any, ArErr) { if len(x.params) != len(args) { return nil, ArErr{"Runtime Error", "expected " + fmt.Sprint(len(x.params)) + " arguments, got " + fmt.Sprint(len(args)), c.line, c.path, c.code, true} } - level := newscope() + l := anymap{} for i, param := range x.params { - level.obj[param] = args[i] + l[param] = args[i] } - resp, err := runVal(x.run, append(x.stack, level), stacklevel+1) + resp, err := runVal(x.run, append(x.stack, Map(l)), stacklevel+1) return ThrowOnNonLoop(openReturn(resp), err) } return nil, ArErr{"Runtime Error", "type '" + typeof(callable) + "' is not callable", c.line, c.path, c.code, true} } + +func builtinCall(callable any, args []any) (any, ArErr) { + + switch x := callable.(type) { + case builtinFunc: + resp, err := x.FUNC(args...) + resp = AnyToArValid(resp) + return resp, err + case Callable: + if len(x.params) != len(args) { + return nil, ArErr{TYPE: "Runtime Error", message: "expected " + fmt.Sprint(len(x.params)) + " arguments, got " + fmt.Sprint(len(args)), EXISTS: true} + } + level := newscope() + for i, param := range x.params { + level.obj[param] = args[i] + } + resp, err := runVal(x.run, append(x.stack, level), 0) + return ThrowOnNonLoop(openReturn(resp), err) + } + return nil, ArErr{TYPE: "Runtime Error", message: "type '" + typeof(callable) + "' is not callable", EXISTS: true} +} diff --git a/src/file.go b/src/file.go index b69ef2f..ff58052 100644 --- a/src/file.go +++ b/src/file.go @@ -28,6 +28,7 @@ func ArRead(args ...any) (any, ArErr) { if typeof(args[0]) != "string" { return ArObject{}, ArErr{TYPE: "Runtime Error", message: "read takes a string not type '" + typeof(args[0]) + "'", EXISTS: true} } + args[0] = ArValidToAny(args[0]) filename := args[0].(string) file, err := os.Open(filename) if err != nil { @@ -58,6 +59,7 @@ func ArWrite(args ...any) (any, ArErr) { if typeof(args[0]) != "string" { return ArObject{}, ArErr{TYPE: "Runtime Error", message: "write takes a string not type '" + typeof(args[0]) + "'", EXISTS: true} } + args[0] = ArValidToAny(args[0]) filename := args[0].(string) file, err := os.Create(filename) if err != nil { diff --git a/src/getIndex.go b/src/getIndex.go index 6d82ded..7f992e3 100644 --- a/src/getIndex.go +++ b/src/getIndex.go @@ -1,13 +1,11 @@ package main import ( - "fmt" "strings" ) type ArObject struct { - TYPE string - obj anymap + obj anymap } type anymap map[any]any @@ -16,12 +14,11 @@ var mapGetCompile = makeRegex(`(.|\n)+\.([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|( var indexGetCompile = makeRegex(`(.|\n)+\[(.|\n)+\]( *)`) type ArMapGet struct { - VAL any - args []any - index bool - line int - code string - path string + VAL any + args []any + line int + code string + path string } func mapGet(r ArMapGet, stack stack, stacklevel int) (any, ArErr) { @@ -31,57 +28,24 @@ func mapGet(r ArMapGet, stack stack, stacklevel int) (any, ArErr) { } switch m := resp.(type) { case ArObject: - if r.index && m.TYPE != "map" { - if _, ok := m.obj["__getindex__"]; ok { - callable := m.obj["__getindex__"] - resp, err := runCall(call{ - callable: callable, - args: r.args, - line: r.line, - path: r.path, - code: r.code, - }, stack, stacklevel+1) - if !err.EXISTS { - return resp, ArErr{} + if _, ok := m.obj["__getindex__"]; ok { + callable := m.obj["__getindex__"] + resp, err := runCall(call{ + callable: callable, + args: r.args, + line: r.line, + path: r.path, + code: r.code, + }, stack, stacklevel+1) + if !err.EXISTS { + return resp, ArErr{} + } + if len(r.args) == 1 && !isUnhashable(r.args[0]) { + if _, ok := m.obj[r.args[0]]; ok { + return m.obj[r.args[0]], ArErr{} } } } - if len(r.args) > 1 { - return nil, ArErr{ - "IndexError", - "index not found", - r.line, - r.path, - r.code, - true, - } - } - key, err := runVal(r.args[0], stack, stacklevel+1) - if err.EXISTS { - return nil, err - } - key = ArValidToAny(key) - if isUnhashable(key) { - return nil, ArErr{ - "TypeError", - "unhashable type: '" + typeof(key) + "'", - r.line, - r.path, - r.code, - true, - } - } - if _, ok := m.obj[key]; !ok { - return nil, ArErr{ - "KeyError", - "key '" + fmt.Sprint(key) + "' not found", - r.line, - r.path, - r.code, - true, - } - } - return AnyToArValid(m.obj[key]), ArErr{} } key, err := runVal(r.args[0], stack, stacklevel+1) @@ -111,7 +75,7 @@ func mapGetParse(code UNPARSEcode, index int, codelines []UNPARSEcode) (ArMapGet if !worked { return ArMapGet{}, false, err, i } - return ArMapGet{resp, []any{key}, false, code.line, code.realcode, code.path}, true, ArErr{}, 1 + return ArMapGet{resp, []any{key}, code.line, code.realcode, code.path}, true, ArErr{}, 1 } func isIndexGet(code UNPARSEcode) bool { @@ -149,7 +113,7 @@ func indexGetParse(code UNPARSEcode, index int, codelines []UNPARSEcode) (ArMapG } continue } - return ArMapGet{tival, args, true, code.line, code.realcode, code.path}, true, ArErr{}, 1 + return ArMapGet{tival, args, code.line, code.realcode, code.path}, true, ArErr{}, 1 } return ArMapGet{}, false, ArErr{ "Syntax Error", @@ -161,7 +125,19 @@ func indexGetParse(code UNPARSEcode, index int, codelines []UNPARSEcode) (ArMapG }, 1 } +var hashabletypes = []string{ + "number", + "string", + "bool", + "null", +} + func isUnhashable(val any) bool { keytype := typeof(val) - return keytype == "array" || keytype == "map" + for _, v := range hashabletypes { + if v == keytype { + return false + } + } + return true } diff --git a/src/input.go b/src/input.go index 88c9ac5..b0e976f 100644 --- a/src/input.go +++ b/src/input.go @@ -41,8 +41,7 @@ func getPassword(args ...any) (string, error) { char := make([]byte, 1) _, err := os.Stdin.Read(char) if err != nil { - fmt.Println(err) - break + return "", err } if char[0] == 3 || char[0] == 4 { return "", fmt.Errorf("keyboard interupt") diff --git a/src/main.go b/src/main.go index 90054bf..a7ad568 100644 --- a/src/main.go +++ b/src/main.go @@ -12,10 +12,7 @@ var Args = os.Args[1:] type stack = []ArObject func newscope() ArObject { - return ArObject{ - TYPE: "map", - obj: make(anymap), - } + return Map(anymap{}) } func main() { diff --git a/src/map.go b/src/map.go index 08b616b..c975ffb 100644 --- a/src/map.go +++ b/src/map.go @@ -3,6 +3,7 @@ package main import ( "fmt" "strings" + "sync" ) var mapCompiled = makeRegex(`( *)\{(((( *).+( *):( *).+( *))|(` + spacelessVariable + `))(( *)\,(( *).+( *):( *).+( *))|(` + spacelessVariable + `)))*\}( *)`) @@ -25,16 +26,146 @@ func parseMap(code UNPARSEcode) (any, UNPARSEcode) { return nil, UNPARSEcode{} } -func Map(val anymap) ArObject { +func Map(m anymap) ArObject { + var mutex = sync.RWMutex{} return ArObject{ - TYPE: "map", - obj: val, - } -} - -func Class(val anymap) ArObject { - return ArObject{ - TYPE: "class", - obj: val, + obj: anymap{ + "__value__": m, + "__name__": "map", + "get": builtinFunc{ + "get", + func(args ...any) (any, ArErr) { + if len(args) < 1 || len(args) > 2 { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "expected 1 or 2 argument, got " + fmt.Sprint(len(args)), + EXISTS: true, + } + } + var DEFAULT any + key := ArValidToAny(args[0]) + if isUnhashable(key) { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "unhashable type: " + typeof(key), + EXISTS: true, + } + } + if len(args) == 2 { + DEFAULT = (args[1]) + } + mutex.RLock() + if _, ok := m[key]; !ok { + mutex.RUnlock() + return DEFAULT, ArErr{} + } + v := m[key] + mutex.RUnlock() + return v, ArErr{} + }, + }, + "__Contains__": builtinFunc{ + "__Contains__", + func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected 1 argument, got " + fmt.Sprint(len(args)), + EXISTS: true, + } + } + key := ArValidToAny(args[0]) + if isUnhashable(key) { + return false, ArErr{} + } + mutex.RLock() + if _, ok := m[key]; !ok { + mutex.RUnlock() + return false, ArErr{} + } + mutex.RUnlock() + return true, ArErr{} + }, + }, + "__NotContains__": builtinFunc{ + "__NotContains__", + func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected 1 argument, got " + fmt.Sprint(len(args)), + EXISTS: true, + } + } + key := ArValidToAny(args[0]) + if isUnhashable(key) { + return true, ArErr{} + } + mutex.RLock() + if _, ok := m[key]; !ok { + mutex.RUnlock() + return true, ArErr{} + } + mutex.RUnlock() + return false, ArErr{} + }, + }, + "__setindex__": builtinFunc{ + "__setindex__", + func(args ...any) (any, ArErr) { + if len(args) != 2 { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected 2 arguments, got " + fmt.Sprint(len(args)), + EXISTS: true, + } + } + if isUnhashable(args[0]) { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "unhashable type: " + typeof(args[0]), + EXISTS: true, + } + } + key := ArValidToAny(args[0]) + mutex.Lock() + m[key] = args[1] + mutex.Unlock() + return nil, ArErr{} + }, + }, + "__getindex__": builtinFunc{ + "__getindex__", + func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected 1 argument, got " + fmt.Sprint(len(args)), + EXISTS: true, + } + } + key := ArValidToAny(args[0]) + if isUnhashable(key) { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "unhashable type: " + typeof(key), + EXISTS: true, + } + } + mutex.RLock() + if _, ok := m[key]; !ok { + mutex.RUnlock() + return nil, ArErr{ + TYPE: "KeyError", + message: "key " + fmt.Sprint(key) + " not found", + EXISTS: true, + } + } + v := m[key] + mutex.RUnlock() + return v, ArErr{} + }, + }, + }, } } diff --git a/src/operations.go b/src/operations.go index 3648826..9df0132 100644 --- a/src/operations.go +++ b/src/operations.go @@ -557,12 +557,12 @@ func calcNotIn(o operationType, stack stack, stacklevel int) (any, ArErr) { if err.EXISTS { return false, err } - if x, ok := resp.(ArObject); ok { + if x, ok := resp2.(ArObject); ok { if y, ok := x.obj["__NotContains__"]; ok { return runCall( call{ y, - []any{resp2}, + []any{resp}, o.code, o.line, o.path, @@ -571,7 +571,7 @@ func calcNotIn(o operationType, stack stack, stacklevel int) (any, ArErr) { } return false, ArErr{ "Runtime Error", - "Cannot check if type '" + typeof(resp) + "' is in type '" + typeof(resp2) + "'", + "Cannot check if type '" + typeof(resp) + "' is not in type '" + typeof(resp2) + "'", o.line, o.path, o.code, @@ -697,7 +697,11 @@ func calcMod(o operationType, stack stack, stacklevel int) (any, ArErr) { return nil, err } if typeof(resp) == "number" && typeof(output) == "number" { - output = floor(newNumber().Quo(resp.(number), output.(number))) + x := newNumber().Set(resp.(number)) + x.Quo(output.(number), x) + x = floor(x) + x.Mul(x, resp.(number)) + output.(number).Sub(output.(number), x) continue } else if x, ok := output.(ArObject); ok { if y, ok := x.obj["__Modulo__"]; ok { diff --git a/src/sequence-and-series.go b/src/sequence-and-series.go new file mode 100644 index 0000000..5914db9 --- /dev/null +++ b/src/sequence-and-series.go @@ -0,0 +1,35 @@ +package main + +import "fmt" + +func ArSequence(a ...any) (any, ArErr) { + if len(a) < 1 || len(a) > 2 { + return nil, ArErr{TYPE: "Runtime Error", + message: fmt.Sprintf("sequence expected 1 or 2 arguments, got %d", len(a)), + EXISTS: true, + } + } + f := a[0] + initial := newNumber() + if typeof(f) != "function" { + return nil, ArErr{TYPE: "Runtime Error", + message: fmt.Sprintf("sequence expected function, got %s", typeof(f)), + EXISTS: true, + } + } + if len(a) == 2 { + if typeof(a[1]) != "number" { + return nil, ArErr{TYPE: "Runtime Error", + message: fmt.Sprintf("sequence expected number, got %s", typeof(a[1])), + EXISTS: true, + } + } + initial.Set(a[1].(number)) + } + return ArObject{ + obj: map[any]any{ + "__name__": "sequence", + "__value__": "test", + }, + }, ArErr{} +} diff --git a/src/sortany.go b/src/sortany.go index 19261f5..49518f6 100644 --- a/src/sortany.go +++ b/src/sortany.go @@ -2,16 +2,16 @@ package main import "fmt" -type keyCache map[interface{}]interface{} +type keyCache map[any]any -func quickSort(list []interface{}, getKey func(interface{}) (interface{}, ArErr)) ([]interface{}, ArErr) { +func quickSort(list []any, getKey func(any) (any, ArErr)) ([]any, ArErr) { if len(list) <= 1 { return list, ArErr{} } pivot := list[0] - var left []interface{} - var right []interface{} + var left []any + var right []any var cache = make(keyCache) @@ -51,7 +51,7 @@ func quickSort(list []interface{}, getKey func(interface{}) (interface{}, ArErr) return append(append(left, pivot), right...), ArErr{} } -func getkeyCache(getKey func(interface{}) (interface{}, ArErr), index interface{}, cache keyCache) (interface{}, ArErr) { +func getkeyCache(getKey func(any) (any, ArErr), index any, cache keyCache) (any, ArErr) { key := ArValidToAny(index) if cacheval, ok := cache[key]; ok { return cacheval, ArErr{} diff --git a/src/string.go b/src/string.go index bb08ae4..7b67ba6 100644 --- a/src/string.go +++ b/src/string.go @@ -47,9 +47,9 @@ func parseString(code UNPARSEcode) (string, bool, ArErr, int) { func ArString(str string) ArObject { obj := ArObject{ - "string", anymap{ "__value__": str, + "__name__": "string", "length": newNumber().SetUint64(uint64(len(str))), }, } @@ -173,6 +173,7 @@ func ArString(str string) ArObject { } output := []string{str} for _, v := range a { + v = ArValidToAny(v) if typeof(v) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(v), 0, "", "", true} } @@ -297,7 +298,9 @@ func ArString(str string) ArObject { if typeof(a[1]) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(a[1]), 0, "", "", true} } - return strings.Replace(str, a[0].(ArObject).obj["__value__"].(string), a[1].(string), -1), ArErr{} + a[0] = ArValidToAny(a[0]) + a[1] = ArValidToAny(a[1]) + return strings.Replace(str, a[0].(string), a[1].(string), -1), ArErr{} }} obj.obj["contains"] = builtinFunc{ "contains", @@ -494,7 +497,8 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot get less than or equal to of type " + typeof(a[0]) + " from string", 0, "", "", true} } - return str <= a[0].(ArObject).obj["__value__"].(string), ArErr{} + a[0] = ArValidToAny(a[0]) + return str <= a[0].(string), ArErr{} }} obj.obj["__LessThan__"] = builtinFunc{ "__LessThan__", @@ -505,7 +509,8 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot get less than of type " + typeof(a[0]) + " from string", 0, "", "", true} } - return str < a[0].(ArObject).obj["__value__"].(string), ArErr{} + a[0] = ArValidToAny(a[0]) + return str < a[0].(string), ArErr{} }} obj.obj["__GreaterThan__"] = builtinFunc{ "__GreaterThan__", @@ -516,7 +521,8 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot get greater than of type " + typeof(a[0]) + " from string", 0, "", "", true} } - return str > a[0].(ArObject).obj["__value__"].(string), ArErr{} + a[0] = ArValidToAny(a[0]) + return str > a[0].(string), ArErr{} }} obj.obj["__GreaterThanEqual__"] = builtinFunc{ @@ -528,7 +534,8 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot get greater than or equal to of type " + typeof(a[0]) + " from string", 0, "", "", true} } - return str >= a[0].(ArObject).obj["__value__"].(string), ArErr{} + a[0] = ArValidToAny(a[0]) + return str >= a[0].(string), ArErr{} }} obj.obj["__Equal__"] = builtinFunc{ "__Equal__", @@ -536,6 +543,7 @@ func ArString(str string) ArObject { if len(a) != 1 { return nil, ArErr{"TypeError", "expected 1 argument, got " + fmt.Sprint(len(a)), 0, "", "", true} } + a[0] = ArValidToAny(a[0]) return str == a[0], ArErr{} }} obj.obj["__NotEqual__"] = builtinFunc{ @@ -544,6 +552,7 @@ func ArString(str string) ArObject { if len(a) != 1 { return nil, ArErr{"TypeError", "expected 1 argument, got " + fmt.Sprint(len(a)), 0, "", "", true} } + a[0] = ArValidToAny(a[0]) return str != a[0], ArErr{} }} obj.obj["__Add__"] = builtinFunc{ @@ -552,10 +561,11 @@ func ArString(str string) ArObject { if len(a) != 1 { return nil, ArErr{"TypeError", "expected 1 argument, got " + fmt.Sprint(len(a)), 0, "", "", true} } + a[0] = ArValidToAny(a[0]) if typeof(a[0]) != "string" { - return nil, ArErr{"TypeError", "cannot add " + typeof(a[0]) + " to string", 0, "", "", true} + a[0] = anyToArgon(a[0], false, false, 3, 0, false, 0) } - return strings.Join([]string{str, a[0].(ArObject).obj["__value__"].(string)}, ""), ArErr{} + return strings.Join([]string{str, a[0].(string)}, ""), ArErr{} }} obj.obj["__Multiply__"] = builtinFunc{ "__Multiply__", @@ -584,7 +594,8 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot check if string contains " + typeof(a[0]), 0, "", "", true} } - return strings.Contains(str, a[0].(ArObject).obj["__value__"].(string)), ArErr{} + a[0] = ArValidToAny(a[0]) + return strings.Contains(str, a[0].(string)), ArErr{} }} obj.obj["__Subtract__"] = builtinFunc{ "__Subtract__", @@ -595,7 +606,8 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot subtract " + typeof(a[0]) + " from string", 0, "", "", true} } - return strings.Replace(str, a[0].(ArObject).obj["__value__"].(string), "", -1), ArErr{} + a[0] = ArValidToAny(a[0]) + return strings.Replace(str, a[0].(string), "", -1), ArErr{} }} obj.obj["__Divide__"] = builtinFunc{ "__Divide__", @@ -606,7 +618,8 @@ func ArString(str string) ArObject { if typeof(a[0]) != "string" { return nil, ArErr{"TypeError", "cannot divide string by " + typeof(a[0]), 0, "", "", true} } - splitby := a[0].(ArObject).obj["__value__"].(string) + a[0] = ArValidToAny(a[0]) + splitby := a[0].(string) output := []any{} splitted := (strings.Split(str, splitby)) for _, v := range splitted { diff --git a/src/term-class.go b/src/term-class.go index a985e57..f61f784 100644 --- a/src/term-class.go +++ b/src/term-class.go @@ -98,7 +98,7 @@ var ArTerm = Map(anymap{ "time": builtinFunc{"time", func(args ...any) (any, ArErr) { var id any = nil if len(args) > 0 { - id = args[0] + id = ArValidToAny(args[0]) } timing[id] = time.Now() return nil, ArErr{} @@ -107,7 +107,7 @@ var ArTerm = Map(anymap{ "timeEnd": builtinFunc{"timeEnd", func(args ...any) (any, ArErr) { var id any = nil if len(args) > 0 { - id = args[0] + id = ArValidToAny(args[0]) } if _, ok := timing[id]; !ok { return nil, ArErr{TYPE: "TypeError", message: "Cannot find timer with id '" + fmt.Sprint(id) + "'", EXISTS: true} @@ -131,8 +131,11 @@ var ArInput = Map( } return ArString(resp), ArErr{} }}, - "__call__": builtinFunc{"input", func(args ...any) (any, ArErr) { - return input(args...), ArErr{} - }}, }, ) + +func init() { + ArInput.obj["__call__"] = builtinFunc{"input", func(args ...any) (any, ArErr) { + return input(args...), ArErr{} + }} +} diff --git a/src/time.go b/src/time.go index 1c1707b..c5f77de 100644 --- a/src/time.go +++ b/src/time.go @@ -7,8 +7,7 @@ import ( var MicroSeconds = newNumber().SetInt64(1000000) func ArTimeClass(N time.Time) ArObject { - return Class(anymap{ - "__value__": newNumber().Quo(newNumber().SetInt64(N.UnixMicro()), MicroSeconds), + m := Map(anymap{ "year": builtinFunc{ "year", func(a ...any) (any, ArErr) { @@ -97,6 +96,8 @@ func ArTimeClass(N time.Time) ArObject { }, }, }) + m.obj["__value__"] = newNumber().Quo(newNumber().SetInt64(N.UnixMicro()), MicroSeconds) + return m } var ArTime = Map(anymap{ @@ -112,19 +113,43 @@ var ArTime = Map(anymap{ }}, "parse": builtinFunc{"parse", func(a ...any) (any, ArErr) { if len(a) == 1 { + if typeof(a[0]) != "string" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "parse requires a string", + EXISTS: true, + } + } + a[0] = ArValidToAny(a[0]) N, err := time.Parse(time.UnixDate, a[0].(string)) if err != nil { return nil, ArErr{ - TYPE: "ArErr", + TYPE: "Runtime Error", message: err.Error(), } } return ArTimeClass(N), ArErr{} } else if len(a) > 1 { + if typeof(a[0]) != "string" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "parse requires a string", + EXISTS: true, + } + } + a[0] = ArValidToAny(a[0]) + if typeof(a[1]) != "string" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "parse requires a string", + EXISTS: true, + } + } + a[1] = ArValidToAny(a[1]) N, err := time.Parse(a[0].(string), a[1].(string)) if err != nil { return nil, ArErr{ - TYPE: "ArErr", + TYPE: "Runtime Error", message: err.Error(), EXISTS: true, } @@ -132,17 +157,26 @@ var ArTime = Map(anymap{ return ArTimeClass(N), ArErr{} } return nil, ArErr{ - TYPE: "ArErr", - message: "parse requires 2 arguments", + TYPE: "Runtime Error", + message: "parse requires 1 or 2 arguments", EXISTS: true, } }}, "parseInLocation": builtinFunc{"parseInLocation", func(a ...any) (any, ArErr) { - if len(a) > 2 { + if len(a) != 2 { + if typeof(a[0]) != "string" || typeof(a[1]) != "string" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "parseInLocation requires a string", + EXISTS: true, + } + } + a[0] = ArValidToAny(a[0]) + a[1] = ArValidToAny(a[1]) N, err := time.ParseInLocation(a[0].(string), a[1].(string), time.Local) if err != nil { return nil, ArErr{ - TYPE: "ArErr", + TYPE: "Runtime Error", message: err.Error(), EXISTS: true, } @@ -150,18 +184,26 @@ var ArTime = Map(anymap{ return ArTimeClass(N), ArErr{} } return nil, ArErr{ - TYPE: "ArErr", - message: "parseInLocation requires 3 arguments", + TYPE: "Runtime Error", + message: "parseInLocation requires 2 arguments", EXISTS: true, } }, }, "date": builtinFunc{"date", func(a ...any) (any, ArErr) { - if len(a) > 0 { + if len(a) != 1 { + if typeof(a[0]) != "string" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "date requires a string", + EXISTS: true, + } + } + a[0] = ArValidToAny(a[0]) N, err := time.Parse(time.UnixDate, a[0].(string)) if err != nil { return nil, ArErr{ - TYPE: "ArErr", + TYPE: "Runtime Error", message: err.Error(), EXISTS: true, } @@ -169,45 +211,66 @@ var ArTime = Map(anymap{ return ArTimeClass(N), ArErr{} } return nil, ArErr{ - TYPE: "ArErr", + TYPE: "Runtime Error", message: "date requires 1 argument", EXISTS: true, } }, }, - "Unix": builtinFunc{"Unix", func(a ...any) (any, ArErr) { - if len(a) > 1 { + "unix": builtinFunc{"unix", func(a ...any) (any, ArErr) { + if len(a) != 2 { + if typeof(a[0]) != "number" || typeof(a[1]) != "number" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "unix requires a number", + EXISTS: true, + } + } sec, _ := a[0].(number).Float64() nsec, _ := a[1].(number).Float64() return ArTimeClass(time.Unix(int64(sec), int64(nsec))), ArErr{} } return nil, ArErr{ - TYPE: "ArErr", - message: "Unix requires 2 arguments", + TYPE: "Runtime Error", + message: "unix requires 2 arguments", EXISTS: true, } }, }, - "UnixMilli": builtinFunc{"UnixMilli", func(a ...any) (any, ArErr) { - if len(a) > 0 { + "unixMilli": builtinFunc{"unixMilli", func(a ...any) (any, ArErr) { + if len(a) != 1 { + if typeof(a[0]) != "number" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "unixMilli requires a number", + EXISTS: true, + } + } msec, _ := a[0].(number).Float64() return ArTimeClass(time.UnixMilli(int64(msec))), ArErr{} } return nil, ArErr{ - TYPE: "ArErr", + TYPE: "Runtime Error", message: "UnixMilli requires 1 argument", EXISTS: true, } }, }, - "UnixMicro": builtinFunc{"UnixMicro", func(a ...any) (any, ArErr) { + "unixMicro": builtinFunc{"unixMicro", func(a ...any) (any, ArErr) { if len(a) > 0 { + if typeof(a[0]) != "number" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "unixMicro requires a number", + EXISTS: true, + } + } usec, _ := a[0].(number).Float64() return ArTimeClass(time.UnixMicro(int64(usec))), ArErr{} } return nil, ArErr{ - TYPE: "ArErr", - message: "UnixMicro requires 1 argument", + TYPE: "Runtime Error", + message: "unixMicro requires 1 argument", EXISTS: true, } }, diff --git a/src/to-argon.go b/src/to-argon.go index 2de7e75..c45307f 100644 --- a/src/to-argon.go +++ b/src/to-argon.go @@ -3,6 +3,7 @@ package main import ( "fmt" "math" + "sort" "strconv" "strings" @@ -78,6 +79,9 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, colored return "{}" } keys := make([]any, len(x)) + sort.Slice(keys, func(i, j int) bool { + return anyToArgon(keys[i], false, true, 0, 0, false, 0) < anyToArgon(keys[j], false, true, 0, 0, false, 0) + }) i := 0 for k := range x { @@ -148,7 +152,7 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, colored if colored { output = append(output, "\x1b[38;5;240m") } - output = append(output, "") + output = append(output, "") if colored { output = append(output, "\x1b[0m") } diff --git a/src/translate.go b/src/translate.go index 00c549a..785099c 100644 --- a/src/translate.go +++ b/src/translate.go @@ -103,6 +103,12 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i return resp, worked, err, i } } + if isCall(code) { + resp, worked, err, i = parseCall(code, index, codelines) + if worked { + return resp, worked, err, i + } + } { operation, worked, err, step := parseOperations(code, index, codelines) if worked { @@ -113,16 +119,9 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i } if isNegative(code) { return parseNegative(code, index, codelines) - } else if isCall(code) { - resp, worked, err, i = parseCall(code, index, codelines) - if worked { - return resp, worked, err, i - } - } - if isMapGet(code) { + } else if isMapGet(code) { return mapGetParse(code, index, codelines) - } - if isIndexGet(code) { + } else if isIndexGet(code) { resp, worked, err, i = indexGetParse(code, index, codelines) if worked { return resp, worked, err, i diff --git a/src/trycatch.go b/src/trycatch.go index 9521950..1fd0c52 100644 --- a/src/trycatch.go +++ b/src/trycatch.go @@ -59,15 +59,15 @@ func parseTryCatch(code UNPARSEcode, index int, codelines []UNPARSEcode) (TryCat func runTryCatch(t TryCatch, stack stack, stacklevel int) (any, ArErr) { val, err := runVal(t.Try, stack, stacklevel) if err.EXISTS { - scope := newscope() - scope.obj[t.errorName] = Map(anymap{ + vars := anymap{} + vars[t.errorName] = Map(anymap{ "type": err.TYPE, "message": err.message, "line": newNumber().SetInt64(int64(err.line)), "path": err.path, "code": err.code, }) - val, err = runVal(t.Catch, append(stack, scope), stacklevel) + val, err = runVal(t.Catch, append(stack, Map(vars)), stacklevel) if err.EXISTS { return nil, err } diff --git a/src/typeof.go b/src/typeof.go index 3984d16..c00f44c 100644 --- a/src/typeof.go +++ b/src/typeof.go @@ -19,7 +19,13 @@ func typeof(val any) string { case builtinFunc: return "function" case ArObject: - return x.TYPE + if val, ok := x.obj["__name__"]; ok { + val := ArValidToAny(val) + if val, ok := val.(string); ok { + return val + } + } + return "object" case accessVariable: return "variable" } diff --git a/src/variable.go b/src/variable.go index 37b8bfd..aca7d7f 100644 --- a/src/variable.go +++ b/src/variable.go @@ -2,19 +2,16 @@ package main import ( "strings" - "sync" ) var spacelessVariable = `([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*` var SpacelessVariableCompiled = makeRegex(spacelessVariable) -var variableCompile = makeRegex(`( *)([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*( *)`) +var variableCompile = makeRegex(`( *)` + spacelessVariable + `( *)`) var validname = makeRegex(`(.|\n)*(\(( *)((([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*)(( *)\,( *)([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*)*)?( *)\))`) var setVariableCompile = makeRegex(`( *)(let( +))(.|\n)+( *)=(.|\n)+`) var autoAsignVariableCompile = makeRegex(`(.|\n)+=(.|\n)+`) var deleteVariableCompile = makeRegex(`( *)delete( +)(.|\n)+( *)`) -var varMutex = sync.RWMutex{} - var blockedVariableNames = map[string]bool{ "if": true, "else": true, @@ -80,11 +77,20 @@ func parseVariable(code UNPARSEcode) (accessVariable, bool, ArErr, int) { func readVariable(v accessVariable, stack stack) (any, ArErr) { for i := len(stack) - 1; i >= 0; i-- { - varMutex.RLock() - val, ok := stack[i].obj[v.name] - varMutex.RUnlock() - if ok { - return val, ArErr{} + callable, ok := stack[i].obj["__Contains__"] + if !ok { + continue + } + contains, err := builtinCall(callable, []any{v.name}) + if err.EXISTS { + return nil, err + } + if anyToBool(contains) { + callable, ok := stack[i].obj["__getindex__"] + if !ok { + continue + } + return builtinCall(callable, []any{v.name}) } } return nil, ArErr{"Name Error", "variable \"" + v.name + "\" does not exist", v.line, v.path, v.code, true} @@ -200,7 +206,7 @@ func parseAutoAsignVariable(code UNPARSEcode, index int, lines []UNPARSEcode, is func setVariableValue(v setVariable, stack stack, stacklevel int) (any, ArErr) { var resp any if v.function { - resp = Callable{v.params, v.value, v.code, stack, v.line} + resp = Callable{"anonymous", v.params, v.value, v.code, stack, v.line} } else { respp, err := runVal(v.value, stack, stacklevel+1) if err.EXISTS { @@ -210,32 +216,48 @@ func setVariableValue(v setVariable, stack stack, stacklevel int) (any, ArErr) { } if v.TYPE == "let" { - varMutex.RLock() - _, ok := stack[len(stack)-1].obj[v.toset.(accessVariable).name] - varMutex.RUnlock() - if ok { - return nil, ArErr{"Name Error", "variable \"" + v.toset.(accessVariable).name + "\" already exists", v.line, v.path, v.code, true} + stackcallable, ok := stack[len(stack)-1].obj["__setindex__"] + if !ok { + return nil, ArErr{"Type Error", "stack doesn't have __setindex__", v.line, v.path, v.code, true} + } + _, err := builtinCall(stackcallable, []any{v.toset.(accessVariable).name, resp}) + if err.EXISTS { + return nil, err } - varMutex.Lock() - stack[len(stack)-1].obj[v.toset.(accessVariable).name] = resp - varMutex.Unlock() } else { switch x := v.toset.(type) { case accessVariable: + name := x.name + hasSet := false + if v.function { + resp = Callable{name, v.params, v.value, v.code, stack, v.line} + } for i := len(stack) - 1; i >= 0; i-- { - varMutex.RLock() - _, ok := stack[i].obj[x.name] - varMutex.RUnlock() - if ok { - varMutex.Lock() - stack[i].obj[x.name] = resp - varMutex.Unlock() - return ThrowOnNonLoop(resp, ArErr{}) + callable, ok := stack[i].obj["__Contains__"] + if !ok { + continue + } + contains, err := builtinCall(callable, []any{name}) + if err.EXISTS { + return nil, err + } + if anyToBool(contains) { + callable, ok := stack[i].obj["__setindex__"] + if !ok { + continue + } + builtinCall(callable, []any{name, resp}) + hasSet = true + break } } - varMutex.Lock() - stack[len(stack)-1].obj[x.name] = resp - varMutex.Unlock() + if !hasSet { + callable, ok := stack[len(stack)-1].obj["__setindex__"] + if !ok { + return nil, ArErr{"Type Error", "stack doesn't have __setindex__", v.line, v.path, v.code, true} + } + builtinCall(callable, []any{name, resp}) + } case ArMapGet: respp, err := runVal(x.VAL, stack, stacklevel+1) if err.EXISTS { @@ -251,29 +273,22 @@ func setVariableValue(v setVariable, stack stack, stacklevel int) (any, ArErr) { } switch y := respp.(type) { case ArObject: - if y.TYPE != "map" { - if _, ok := y.obj["__setindex__"]; ok { - callable := y.obj["__setindex__"] - r := ArValidToAny(resp) - _, err := runCall(call{ - callable: callable, - args: []any{key, r}, - line: v.line, - path: v.path, - code: v.code, - }, stack, stacklevel+1) + if _, ok := y.obj["__setindex__"]; ok { + callable := y.obj["__setindex__"] + r := ArValidToAny(resp) + _, err := runCall(call{ + callable: callable, + args: []any{key, r}, + line: v.line, + path: v.path, + code: v.code, + }, stack, stacklevel+1) + if err.EXISTS { return nil, err } - } else { - if isUnhashable(key) { - return nil, ArErr{"Runtime Error", "can't use unhashable type as map key: " + typeof(key), v.line, v.path, v.code, true} - } - varMutex.Lock() - y.obj[key] = resp - varMutex.Unlock() } default: - return nil, ArErr{"Runtime Error", "can't set for non map", v.line, v.path, v.code, true} + return nil, ArErr{"Runtime Error", "can't set for non object", v.line, v.path, v.code, true} } } } @@ -304,9 +319,20 @@ func runDelete(d ArDelete, stack stack, stacklevel int) (any, ArErr) { switch x := d.value.(type) { case accessVariable: for i := len(stack) - 1; i >= 0; i-- { - if _, ok := stack[i].obj[x.name]; ok { - delete(stack[i].obj, x.name) - return nil, ArErr{} + callable, ok := stack[i].obj["__Contains__"] + if !ok { + continue + } + contains, err := builtinCall(callable, []any{x.name}) + if err.EXISTS { + return nil, err + } + if anyToBool(contains) { + callable, ok := stack[i].obj["__deleteindex__"] + if !ok { + continue + } + return builtinCall(callable, []any{x.name}) } } return nil, ArErr{"Name Error", "variable \"" + x.name + "\" does not exist", d.line, d.path, d.code, true} @@ -324,7 +350,7 @@ func runDelete(d ArDelete, stack stack, stacklevel int) (any, ArErr) { } switch y := respp.(type) { case ArObject: - if y.TYPE == "array" { + if typeof(y) == "array" { return nil, ArErr{"Runtime Error", "can't delete from array", d.line, d.path, d.code, true} } if isUnhashable(key) { diff --git a/test b/test new file mode 100755 index 0000000..8b83815 --- /dev/null +++ b/test @@ -0,0 +1,4 @@ +for FILE in ./tests/*.ar; do + echo "running : $FILE"; + ./run "$FILE"; +done \ No newline at end of file diff --git a/tests/brainfuck.ar b/tests/brainfuck.ar new file mode 100644 index 0000000..3568287 --- /dev/null +++ b/tests/brainfuck.ar @@ -0,0 +1,34 @@ +let interpret(code) = do + memory = map() + pointer = 0 + code_ptr = 0 + loops = [] + + while (code_ptr < code.length) do + command = code[code_ptr] + + if (command == '>') pointer = pointer + 1 + else if (command == '<') pointer = pointer - 1 + else if (command == '+') do + if (pointer not in memory) memory[pointer] = 0 + memory[pointer] = memory[pointer] + 1 + else if (command == '-') do + if (pointer not in memory) memory[pointer] = 0 + memory[pointer] = memory[pointer] - 1 + else if (command == '.') term.plain.oneLine(chr(memory.get(pointer, 0)), end='') + else if (command == ',') memory[pointer] = ord(input()) + else if (command == '[') do + if (memory.get(pointer, 0) == 0) do + loop_depth = 1 + while (loop_depth > 0) do + code_ptr = code_ptr + 1 + if (code[code_ptr] == '[') loop_depth = loop_depth + 1 + else if (code[code_ptr] == ']') loop_depth = loop_depth - 1 + else loops.append(code_ptr) + else if (command == ']') do + if (memory.get(pointer, 0) != 0) code_ptr = loops[-1] + else loops.pop() + code_ptr = code_ptr + 1 + + +interpret('>++++++++[<+++++++++>-]<.>++++[<+++++++>-]<+.+++++++..+++.>>++++++[<+++++++>-]<++.------------.>++++++[<+++++++++>-]<+.<.+++.------.--------.>>>++++[<++++++++>-]<+.') \ No newline at end of file diff --git a/tests/test.ar b/tests/csv_test.ar similarity index 100% rename from tests/test.ar rename to tests/csv_test.ar diff --git a/tests/example.ar b/tests/example.ar index 4dfad77..8cf0833 100644 --- a/tests/example.ar +++ b/tests/example.ar @@ -11,8 +11,7 @@ name = "william bell" term.log(getInitials(name)) term.log(name) -let addLastName(name) = do - name.append(" Bell") +let addLastName(name) = name.append(" Bell") name = "William" addLastName(name) diff --git a/tests/memoryLeakTest.ar b/tests/memoryLeakTest.ar index 00ecb63..6fc19ec 100644 --- a/tests/memoryLeakTest.ar +++ b/tests/memoryLeakTest.ar @@ -1,8 +1,7 @@ f() = do a = [] - for (i from 0 to 10000000) a.append(i) + for (i from 0 to 10000) a.append(i) term.log("start") f() -term.log("end") -time.snooze(100000000) \ No newline at end of file +term.log("end") \ No newline at end of file diff --git a/tests/stack test.ar b/tests/stack test.ar new file mode 100644 index 0000000..a22007d --- /dev/null +++ b/tests/stack test.ar @@ -0,0 +1,7 @@ +x = 10 + +do + let x = 20 + term.log(x) + +term.log(x) \ No newline at end of file diff --git a/tests/welcomemessage.ar b/tests/welcomemessage.ar index da84222..727813b 100644 --- a/tests/welcomemessage.ar +++ b/tests/welcomemessage.ar @@ -1,4 +1,3 @@ -term.log(" ____ ") term.log(" /\\ |___ \\ ") term.log(" / \\ _ __ __ _ ___ _ __ __ ____) |") term.log(" / /\\ \\ | '__/ _` |/ _ \\| '_ \\ \\ \\ / /__ < ") @@ -7,5 +6,4 @@ term.log(" /_/ \\_\\_| \\__, |\\___/|_| |_| \\_/|____/ ") term.log(" __/ | ") term.log(" |___/ ") term.log("----------------------------------------------") -term.log("Welcome to ARGON for WASM!") -term.log("write code above and click run to see it work like magic!") \ No newline at end of file +term.log("Welcome to ARGON!") \ No newline at end of file From 5ceff0ddd2872c927ece9a0081a68c9d3918823e Mon Sep 17 00:00:00 2001 From: William Bell <62452284+Ugric@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:21:47 +0100 Subject: [PATCH 19/21] fix incorrect function name --- src/array.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array.go b/src/array.go index e093a67..2365898 100644 --- a/src/array.go +++ b/src/array.go @@ -533,7 +533,7 @@ func ArArray(arr []any) ArObject { }, } val.obj["__Equal__"] = builtinFunc{ - "__LessThanEqual__", + "__Equal__", func(args ...any) (any, ArErr) { if len(args) != 1 { return nil, ArErr{ From 857eb442c2349610dea9498ae745e51f2884db4e Mon Sep 17 00:00:00 2001 From: William Bell Date: Tue, 13 Jun 2023 22:39:33 +0100 Subject: [PATCH 20/21] update to support new oop --- src/document.go | 1 - src/wasm.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/document.go b/src/document.go index 85f5453..4e8638b 100644 --- a/src/document.go +++ b/src/document.go @@ -6,7 +6,6 @@ import ( func windowElement(element js.Value) ArObject { return ArObject{ - TYPE: "map", obj: anymap{ "innerHTML": builtinFunc{"innerHTML", func(args ...any) (any, ArErr) { if len(args) > 0 { diff --git a/src/wasm.go b/src/wasm.go index 8483296..7215cac 100644 --- a/src/wasm.go +++ b/src/wasm.go @@ -12,13 +12,13 @@ func argonToJsValid(argon any) any { f, _ := x.Float64() return f case ArObject: - if x.TYPE == "array" { + if typeof(x) == "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" { + } else if typeof(x) == "string" { return x.obj["__value__"].(string) } From 199b2320dd5cab31fa55a9764644ee4f0fef4210 Mon Sep 17 00:00:00 2001 From: William Bell Date: Sat, 8 Apr 2023 15:34:15 +0200 Subject: [PATCH 21/21] make maps oop --- run | 2 ++ 1 file changed, 2 insertions(+) create mode 100755 run diff --git a/run b/run new file mode 100755 index 0000000..496b1a7 --- /dev/null +++ b/run @@ -0,0 +1,2 @@ +# 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 "$@" \ No newline at end of file