diff --git a/README.md b/README.md
index 503bd00..ab67548 100644
--- a/README.md
+++ b/README.md
@@ -9,30 +9,6 @@
import "github.com/euforic/templit"
```
-## Index
-
-- [Variables](<#variables>)
-- [func ToCamelCase(s string) string](<#ToCamelCase>)
-- [func ToKebabCase(s string) string](<#ToKebabCase>)
-- [func ToPascalCase(s string) string](<#ToPascalCase>)
-- [func ToSnakeCase(s string) string](<#ToSnakeCase>)
-- [type DefaultGitClient](<#DefaultGitClient>)
- - [func NewDefaultGitClient(token string) *DefaultGitClient](<#NewDefaultGitClient>)
- - [func (d *DefaultGitClient) Checkout(path, branch string) error](<#DefaultGitClient.Checkout>)
- - [func (d *DefaultGitClient) Clone(host, owner, repo, dest string) error](<#DefaultGitClient.Clone>)
-- [type DepInfo](<#DepInfo>)
- - [func ParseDepURL(rawURL string) (*DepInfo, error)](<#ParseDepURL>)
-- [type Executor](<#Executor>)
- - [func NewExecutor() *Executor](<#NewExecutor>)
- - [func (e *Executor) EmbedFunc(client GitClient) func(remotePath string, data interface{}) (string, error)](<#Executor.EmbedFunc>)
- - [func (e *Executor) ImportFunc(client GitClient) func(repoAndTag, destPath string, data interface{}) (string, error)](<#Executor.ImportFunc>)
- - [func (e *Executor) ParsePath(inputPath string) error](<#Executor.ParsePath>)
- - [func (e Executor) Render(name string, data interface{}) (string, error)](<#Executor.Render>)
- - [func (e Executor) StringRender(templateString string, data interface{}) (string, error)](<#Executor.StringRender>)
- - [func (e *Executor) WalkAndProcessDir(inputDir, outputDir string, data interface{}) error](<#Executor.WalkAndProcessDir>)
-- [type GitClient](<#GitClient>)
-
-
## Variables
DefaultFuncMap is the default function map for templates.
@@ -177,6 +153,7 @@ Executor is a wrapper around the template.Template type
```go
type Executor struct {
*template.Template
+ git GitClient
}
```
@@ -219,7 +196,7 @@ Placeholders:
### func (*Executor) ImportFunc
```go
-func (e *Executor) ImportFunc() func(repoAndTag, destPath string, data interface{}) (string, error)
+func (e *Executor) ImportFunc(destPath string) func(repoAndTag, path string, data interface{}) (string, error)
```
ImportFunc returns a function that can be used as a template function to import and process a template from a remote git repository. ImportFunc allows embedding content from a remote repository into a Go template.
diff --git a/docs/go.mod b/docs/go.mod
new file mode 100644
index 0000000..d784009
--- /dev/null
+++ b/docs/go.mod
@@ -0,0 +1,5 @@
+module github.com/euforic/exp
+
+go 1.21.1
+
+require github.com/davecgh/go-spew v1.1.1
diff --git a/docs/go.sum b/docs/go.sum
new file mode 100644
index 0000000..b5e2922
--- /dev/null
+++ b/docs/go.sum
@@ -0,0 +1,2 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..896eed2
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ Go Template Renderer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/main.go b/docs/main.go
new file mode 100644
index 0000000..bf6d4c8
--- /dev/null
+++ b/docs/main.go
@@ -0,0 +1,155 @@
+//go:build js && wasm
+// +build js,wasm
+
+//go:generate cp $GOROOT/misc/wasm/wasm_exec.js .
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "syscall/js"
+ "text/template"
+)
+
+type App struct {
+ templateEditor js.Value
+ jsonEditor js.Value
+ outputEditor js.Value
+}
+
+func main() {
+ app := &App{}
+ app.init()
+
+ // Block forever to keep the Go program running
+ select {}
+}
+
+func (app *App) init() {
+ app.initExports()
+ app.initCodeMirror()
+ app.initVimToggle()
+ app.initRender()
+}
+
+func (app *App) initExports() {
+ js.Global().Set("renderTemplate", js.FuncOf(app.renderTemplate))
+}
+
+func (app *App) initCodeMirror() {
+ cm := js.Global().Get("CodeMirror")
+ app.templateEditor = cm.New(js.Global().Get("document").Call("getElementById", "template"), js.ValueOf(map[string]interface{}{
+ "mode": "go",
+ "theme": "material-darker",
+ "lineNumbers": true,
+ "value": "Hello, {{.Name}}",
+ }))
+
+ app.jsonEditor = cm.New(js.Global().Get("document").Call("getElementById", "json"), js.ValueOf(map[string]interface{}{
+ "mode": "javascript",
+ "theme": "material-darker",
+ "lineNumbers": true,
+ "value": `{
+ "Name": "World"
+}`,
+ }))
+
+ app.outputEditor = cm.New(js.Global().Get("document").Call("getElementById", "output"), js.ValueOf(map[string]interface{}{
+ "lineNumbers": true,
+ "readOnly": true,
+ "theme": "material-darker",
+ "value": "Hello, World",
+ }))
+}
+
+func (app *App) initVimToggle() {
+ vimToggle := js.Global().Get("document").Call("getElementById", "vim-toggle")
+ isVimEnabled := js.Global().Get("localStorage").Call("getItem", "vimEnabled").String() == "true"
+ app.setKeyMap(isVimEnabled)
+ vimToggle.Set("checked", isVimEnabled)
+ vimToggle.Call("addEventListener", "change", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+ app.setKeyMap(vimToggle.Get("checked").Bool())
+ js.Global().Get("localStorage").Call("setItem", "vimEnabled", vimToggle.Get("checked").Bool())
+ return nil
+ }))
+}
+
+func (app *App) setKeyMap(vimEnabled bool) {
+ keyMap := "default"
+ if vimEnabled {
+ keyMap = "vim"
+ }
+ app.templateEditor.Call("setOption", "keyMap", keyMap)
+ app.jsonEditor.Call("setOption", "keyMap", keyMap)
+ app.outputEditor.Call("setOption", "keyMap", keyMap)
+}
+
+func (app *App) initRender() {
+ saveFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+ app.saveCurrentData()
+ return nil
+ })
+ renderFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+ go app.render()
+ return nil
+ })
+ app.templateEditor.Call("on", "change", saveFunc)
+ app.jsonEditor.Call("on", "change", saveFunc)
+ app.templateEditor.Call("on", "change", renderFunc)
+ app.jsonEditor.Call("on", "change", renderFunc)
+ app.loadPersistedData()
+}
+
+func (app *App) render() {
+ template := app.templateEditor.Call("getValue").String()
+ json := app.jsonEditor.Call("getValue").String()
+ result := app.renderTemplate(js.Null(), []js.Value{js.ValueOf(template), js.ValueOf(json)}).(string)
+ app.outputEditor.Call("setValue", result)
+}
+
+func (app *App) renderTemplate(this js.Value, args []js.Value) interface{} {
+ tmplStr := args[0].String()
+ jsonStr := args[1].String()
+
+ tmpl, err := template.New("tmpl").Parse(tmplStr)
+ if err != nil {
+ return fmt.Sprintf("Template error: %v", err)
+ }
+
+ var data interface{}
+ err = json.Unmarshal([]byte(jsonStr), &data)
+ if err != nil {
+ return fmt.Sprintf("JSON error: %v", err)
+ }
+
+ var buf bytes.Buffer
+ err = tmpl.Execute(&buf, data)
+ if err != nil {
+ return fmt.Sprintf("Execution error: %v", err)
+ }
+
+ return buf.String()
+}
+
+func (app *App) saveCurrentData() {
+ js.Global().Get("localStorage").Call("setItem", "templateData", app.templateEditor.Call("getValue").String())
+ js.Global().Get("localStorage").Call("setItem", "jsonData", app.jsonEditor.Call("getValue").String())
+}
+
+func (app *App) loadPersistedData() {
+ persistedTemplateData := js.Global().Get("localStorage").Call("getItem", "templateData").String()
+ persistedJsonData := js.Global().Get("localStorage").Call("getItem", "jsonData").String()
+
+ if persistedTemplateData != "" {
+ app.templateEditor.Call("setValue", persistedTemplateData)
+ }
+
+ if persistedJsonData != "" {
+ app.jsonEditor.Call("setValue", persistedJsonData)
+ }
+
+ if persistedTemplateData != "" || persistedJsonData != "" {
+ app.render()
+ }
+}
diff --git a/docs/main.wasm b/docs/main.wasm
new file mode 100755
index 0000000..fa64264
Binary files /dev/null and b/docs/main.wasm differ
diff --git a/docs/wasm_exec.js b/docs/wasm_exec.js
new file mode 100644
index 0000000..bc6f210
--- /dev/null
+++ b/docs/wasm_exec.js
@@ -0,0 +1,561 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+"use strict";
+
+(() => {
+ const enosys = () => {
+ const err = new Error("not implemented");
+ err.code = "ENOSYS";
+ return err;
+ };
+
+ if (!globalThis.fs) {
+ let outputBuf = "";
+ globalThis.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) {
+ console.log(outputBuf.substring(0, nl));
+ outputBuf = outputBuf.substring(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 (!globalThis.process) {
+ globalThis.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 (!globalThis.crypto) {
+ throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
+ }
+
+ if (!globalThis.performance) {
+ throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
+ }
+
+ if (!globalThis.TextEncoder) {
+ throw new Error("globalThis.TextEncoder is not available, polyfill required");
+ }
+
+ if (!globalThis.TextDecoder) {
+ throw new Error("globalThis.TextDecoder is not available, polyfill required");
+ }
+
+ const encoder = new TextEncoder("utf-8");
+ const decoder = new TextDecoder("utf-8");
+
+ globalThis.Go = class {
+ constructor() {
+ this.argv = ["js"];
+ this.env = {};
+ this.exit = (code) => {
+ if (code !== 0) {
+ console.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 setInt32 = (addr, v) => {
+ this.mem.setUint32(addr + 0, v, 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 = {
+ _gotest: {
+ add: (a, b) => a + b,
+ },
+ gojs: {
+ // 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)
+ console.warn("scheduleTimeoutEvent: missed timeout event");
+ this._resume();
+ }
+ },
+ getInt64(sp + 8),
+ ));
+ 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) => {
+ console.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,
+ globalThis,
+ 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],
+ [globalThis, 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;
+ });
+
+ // The linker guarantees global data starts from at least wasmMinDataAddr.
+ // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
+ const wasmMinDataAddr = 4096 + 8192;
+ if (offset >= wasmMinDataAddr) {
+ throw new Error("total length of command line and environment variables exceeds limit");
+ }
+
+ 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;
+ };
+ }
+ }
+})();