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/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/merge.bat b/merge.bat new file mode 100644 index 0000000..c41102f --- /dev/null +++ b/merge.bat @@ -0,0 +1,2 @@ +git fetch upstream +git merge upstream/master master \ No newline at end of file diff --git a/readme.md b/readme.md index 9db9b17..6f602d5 100644 --- a/readme.md +++ b/readme.md @@ -2,23 +2,37 @@

-

Argon v3

+

Argon v3.wasm

+### HEADS UP! 🙂 + +Argon v3.wasm is a fork of Argon v3 which has had parts rewritten for support in WebAssembly (WASM). + +It includes all the same features and functionality as Argon v3, but has been compiled to run efficiently on WebAssembly. + +Please note that Argon v3.wasm can only be compiled for use with WebAssembly. + + +--- + ARGON 3 is a math-driven programming language designed to make code easy to read and write. It's not meant to be fast, as it's interpreted. This specification should be used as a guideline, and is subject to change for later versions. Later updates for Argon 3 should be backwards compatible (where possible) with code designed for older versions of the interpreter. ## 📚 Features - - Easy to read and write: Argon 3 is designed with clarity of code in mind, making it easier for you and others to read and write code. - - All numbers are stored as rational numbers, preventing precision errors. - - Math-driven: Designed for mathematical computations, Argon 3 uses techniques and rules set in maths. It's designed to be easy for mathematicians to write and understand algorithms in. - - Interpreted: Argon 3 is an interpreted language, so you don't need to compile your code before running it. - - Cross-platform: Argon 3 can be run on any platform that has an interpreter for it. - - Lightweight: The Argon 3 interpreter is small and doesn't require a lot of system resources to run. + +- Easy to read and write: Argon 3 is designed with clarity of code in mind, making it easier for you and others to read and write code. +- All numbers are stored as rational numbers, preventing precision errors. +- Math-driven: Designed for mathematical computations, Argon 3 uses techniques and rules set in maths. It's designed to be easy for mathematicians to write and understand algorithms in. +- Interpreted: Argon 3 is an interpreted language, so you don't need to compile your code before running it. +- Cross-platform: Argon 3 can be run on any platform that has an interpreter for it. +- Lightweight: The Argon 3 interpreter is small and doesn't require a lot of system resources to run. ## 💻 Installation + As of now, Argon 3 does not have an installer. Feel free to clone this repo and run the `build` file for your plateform. the build will be found in `bin/argon(.exe)`. ## 📖 Usage + To use Argon 3, you can create a file with the .ar extension and write your code in it. Then, you can run your code using the interpreter. For example, if you have a file called example.ar, you can run it using the following command: ``` @@ -38,7 +52,7 @@ f(x) = x^2 + 2*x + 1 term.log('f(10) =', f(10)) ``` -This code defines a function f(x) that calculates x^2 + 2*x + 1. It then calls the function with an argument of 10 and logs the result to the console. +This code defines a function f(x) that calculates x^2 + 2\*x + 1. It then calls the function with an argument of 10 and logs the result to the console. Please note that this example is subject to change as the specification is in beta and may be updated frequently. 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/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 81baebc..006988d 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -5,10 +5,14 @@ import ( "os" ) -func makeGlobal() ArObject { +func makeGlobal(allowDocument bool) ArObject { var vars = anymap{} vars["global"] = vars vars["term"] = ArTerm + if allowDocument { + vars["document"] = ArDocument + } + vars["js"] = ArJS vars["number"] = builtinFunc{"number", ArgonNumber} vars["string"] = builtinFunc{"string", ArgonString} vars["infinity"] = infinity 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" diff --git a/src/document.go b/src/document.go new file mode 100644 index 0000000..4e8638b --- /dev/null +++ b/src/document.go @@ -0,0 +1,213 @@ +package main + +import ( + "syscall/js" +) + +func windowElement(element js.Value) ArObject { + return ArObject{ + 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": 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} + } + 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/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 e3a34e1..ef99282 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, global ArObject) (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) - } - local := 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, global) - }}, - "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{global, localvars, local})) - importing[p] = false - if runimeErr.EXISTS { - return ArObject{}, runimeErr - } - imported[p] = local - return local, 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/input.go b/src/input.go index adf7d69..b0e976f 100644 --- a/src/input.go +++ b/src/input.go @@ -1,9 +1,9 @@ package main import ( - "bufio" "fmt" "os" + "syscall/js" "golang.org/x/term" ) @@ -12,12 +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...) - scanner := bufio.NewScanner(os.Stdin) - scanner.Scan() - input := scanner.Text() - return input + 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) { diff --git a/src/main.go b/src/main.go index 3dcbe80..a7ad568 100644 --- a/src/main.go +++ b/src/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "syscall/js" ) // args without the program path @@ -15,6 +16,7 @@ func newscope() ArObject { } func main() { + c := make(chan ArObject) defer func() { if r := recover(); r != nil { fmt.Println("There was a fundamental error in argon v3 that caused it to crash.") @@ -37,18 +39,24 @@ func main() { }() initRandom() garbageCollect() - global := makeGlobal() - if len(Args) == 0 { - shell(global) - os.Exit(0) - } - ex, e := os.Getwd() - if e != nil { - panic(e) - } - _, err := importMod(Args[0], ex, true, global) - if err.EXISTS { - panicErr(err) - os.Exit(1) - } + obj := js.Global().Get("Object").New() + obj.Set("eval", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + code := "" + allowDocument := false + if len(args) >= 1 { + code = args[0].String() + } + if len(args) >= 2 { + allowDocument = args[1].Bool() + } + val, err := wasmRun(code, allowDocument) + 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 8c90a51..2ec7ce7 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 81d3543..785099c 100644 --- a/src/translate.go +++ b/src/translate.go @@ -46,7 +46,6 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i return parseTryCatch(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..7215cac --- /dev/null +++ b/src/wasm.go @@ -0,0 +1,221 @@ +package main + +import ( + "fmt" + "strings" + "syscall/js" +) + +func argonToJsValid(argon any) any { + switch x := argon.(type) { + case number: + f, _ := x.Float64() + return f + case ArObject: + 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 typeof(x) == "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, allowDocument bool) (any, ArErr) { + JSclearTimers() + initRandom() + global := makeGlobal(allowDocument) + 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 + } + local := 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{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{} + +func JSclearTimers() { + for _, v := range IntervalList { + js.Global().Call("clearInterval", v) + } + for _, v := range TimeoutList { + js.Global().Call("clearTimeout", v) + } +} + +var ArJS = Map(anymap{ + "setTimeout": builtinFunc{"setTimeout", func(args ...any) (any, ArErr) { + if len(args) > 2 || len(args) < 1 { + return nil, ArErr{"TypeError", "Expected 1 or 2 argument, got " + fmt.Sprint(len(args)), 0, "", "", true} + } + if typeof(args[0]) != "function" { + return nil, ArErr{"TypeError", "Expected function, got " + typeof(args[0]), 0, "", "", true} + } + var ms int64 = 0 + if len(args) == 2 { + if typeof(args[1]) != "number" { + return nil, ArErr{"TypeError", "Expected number, got " + typeof(args[1]), 0, "", "", true} + } + if !args[1].(number).IsInt() { + return nil, ArErr{"TypeError", "Expected integer, got float", 0, "", "", true} + } + ms = args[1].(number).Num().Int64() + } + f := js.FuncOf(func(this js.Value, a []js.Value) interface{} { + runCall( + call{ + callable: args[0], + args: []any{}, + }, + stack{}, + 0, + ) + return nil + }) + n := js.Global().Call("setTimeout", f, ms).Int() + TimeoutList = append(TimeoutList, n) + return newNumber().SetInt64(int64(n)), ArErr{} + }}, + "setInterval": builtinFunc{"setInterval", func(args ...any) (any, ArErr) { + if len(args) > 2 || len(args) < 1 { + return nil, ArErr{"TypeError", "Expected 1 or 2 argument, got " + fmt.Sprint(len(args)), 0, "", "", true} + } + if typeof(args[0]) != "function" { + return nil, ArErr{"TypeError", "Expected function, got " + typeof(args[0]), 0, "", "", true} + } + var ms int64 = 0 + if len(args) == 2 { + if typeof(args[1]) != "number" { + return nil, ArErr{"TypeError", "Expected number, got " + typeof(args[1]), 0, "", "", true} + } + if !args[1].(number).IsInt() { + return nil, ArErr{"TypeError", "Expected integer, got float", 0, "", "", true} + } + ms = args[1].(number).Num().Int64() + } + f := js.FuncOf(func(this js.Value, a []js.Value) interface{} { + runCall( + call{ + callable: args[0], + args: []any{}, + }, + stack{}, + 0, + ) + return nil + }) + n := js.Global().Call("setInterval", f, ms).Int() + IntervalList = append(IntervalList, n) + return newNumber().SetInt64(int64(n)), ArErr{} + }}, + "clearTimeout": builtinFunc{"clearTimeout", func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{"TypeError", "Expected 1 argument, got " + fmt.Sprint(len(args)), 0, "", "", true} + } + if typeof(args[0]) != "number" { + return nil, ArErr{"TypeError", "Expected number, got " + typeof(args[0]), 0, "", "", true} + } + if !args[0].(number).IsInt() { + return nil, ArErr{"TypeError", "Expected integer, got float", 0, "", "", true} + } + n := args[0].(number).Num().Int64() + for i, v := range TimeoutList { + if v == int(n) { + TimeoutList = append(TimeoutList[:i], TimeoutList[i+1:]...) + break + } + } + js.Global().Call("clearTimeout", n) + return nil, ArErr{} + }}, + "clearInterval": builtinFunc{"clearInterval", func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{"TypeError", "Expected 1 argument, got " + fmt.Sprint(len(args)), 0, "", "", true} + } + if typeof(args[0]) != "number" { + return nil, ArErr{"TypeError", "Expected number, got " + typeof(args[0]), 0, "", "", true} + } + if !args[0].(number).IsInt() { + return nil, ArErr{"TypeError", "Expected integer, got float", 0, "", "", true} + } + n := args[0].(number).Num().Int64() + for i, v := range IntervalList { + if v == int(n) { + IntervalList = append(IntervalList[:i], IntervalList[i+1:]...) + break + } + } + js.Global().Call("clearInterval", n) + return nil, ArErr{} + }}, +})