ngxson HF staff commited on
Commit
66191a5
·
0 Parent(s):
Files changed (9) hide show
  1. .gitattributes +37 -0
  2. README.md +39 -0
  3. build.sh +6 -0
  4. go.mod +15 -0
  5. go.sum +22 -0
  6. index.html +100 -0
  7. main.go +53 -0
  8. main.wasm +3 -0
  9. wasm_exec.js +561 -0
.gitattributes ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *.wasm filter=lfs diff=lfs merge=lfs -text
36
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
37
+
README.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Ollama Template Test
3
+ emoji: 🚧
4
+ colorFrom: yellow
5
+ colorTo: orange
6
+ sdk: static
7
+ pinned: true
8
+ license: mit
9
+ short_description: This is a web-based tool for testing Ollama chat template
10
+ ---
11
+
12
+ # Ollama Go Template Web Demo
13
+
14
+ This is a web-based demonstration tool for testing Ollama chat templates using Go's template engine compiled to WebAssembly.
15
+
16
+ ## Features
17
+
18
+ - Live preview of chat template formatting
19
+ - WebAssembly-powered Go template engine
20
+ - Simple and intuitive web interface
21
+
22
+ ## Usage
23
+
24
+ 1. Enter your conversation JSON in the first text area
25
+ 2. Modify the Ollama template in the second text area
26
+ 3. Click "Apply chat template" to see the results
27
+
28
+ ## How to build it
29
+
30
+ ```sh
31
+ # Make sure to install golang
32
+ # Clone the repo
33
+ ./build.sh
34
+ # Enjoy
35
+ ```
36
+
37
+ ## License
38
+
39
+ MIT License
build.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ GOOS=js GOARCH=wasm go build -o main.wasm
6
+ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
go.mod ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module github.com/ngxson/ollama_gotmpl_js
2
+
3
+ go 1.23.4
4
+
5
+ require (
6
+ github.com/google/go-cmp v0.6.0
7
+ github.com/ollama/ollama v0.5.4
8
+ )
9
+
10
+ require (
11
+ github.com/agnivade/levenshtein v1.1.1 // indirect
12
+ golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
13
+ golang.org/x/sync v0.10.0 // indirect
14
+ golang.org/x/sys v0.20.0 // indirect
15
+ )
go.sum ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
2
+ github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
3
+ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
4
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6
+ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
7
+ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
8
+ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
9
+ github.com/ollama/ollama v0.5.4 h1:CzsHBNDeli5hiqe8yj7M4cg8X7qnFg2B3fFNhaUmHw0=
10
+ github.com/ollama/ollama v0.5.4/go.mod h1:etr//7OWrZeFfWnnx5QHeH435jHBBsNtjntDP7WVxco=
11
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
12
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
13
+ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
14
+ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
15
+ golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
16
+ golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
17
+ golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
18
+ golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
19
+ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
20
+ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
21
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
22
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
index.html ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Ollama Template Test</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ main {
15
+ margin: 0 auto;
16
+ max-width: 800px;
17
+ padding: 20px;
18
+ }
19
+
20
+ textarea {
21
+ width: 100%;
22
+ }
23
+
24
+ pre {
25
+ background-color: #f4f4f4;
26
+ padding: 10px;
27
+ border-radius: 5px;
28
+ }
29
+ </style>
30
+ </head>
31
+ <body>
32
+ <main>
33
+ <h1>Ollama Template Test</h1>
34
+ <p>
35
+ Given a conversation and a template, the Go template engine will apply the chat template.
36
+ </p>
37
+ Conversation JSON:<br/>
38
+ <textarea id="conversation" rows="10" cols="50"></textarea><br/>
39
+ <br/>
40
+ Ollama Go template:<br/>
41
+ <textarea id="template" rows="10" cols="50"></textarea><br/>
42
+ <br/>
43
+ <button onclick="applyChatTemplate()" style="font-size: 1.2em">🚀 Apply chat template</button><br/>
44
+ <br/>
45
+ Output:<br/>
46
+ <pre id="output"></pre>
47
+ <br/>
48
+ <br/>
49
+ <br/>
50
+ <center>
51
+ <small>Made by <a href="https://github.com/ngxson">ngxson</a> from 🤗</small>
52
+ </center>
53
+ <br/>
54
+ <br/>
55
+ </main>
56
+
57
+ <script src="wasm_exec.js"></script>
58
+ <script>
59
+ const DEFAULT_CONV = [
60
+ { role: "system", content: "You are a helpful assistant."},
61
+ { role: "user", content: "Hello, how are you?"},
62
+ { role: "assistant", content: "I'm doing great. How can I help you today?"},
63
+ { role: "user", content: "I'd like to show off how chat templating works!"},
64
+ ];
65
+ const DEFAULT_TMPL = "{{ if .System }}<|system|>\n{{ .System }}<|end|>\n{{ end }}{{ if .Prompt }}<|user|>\n{{ .Prompt }}<|end|>\n{{ end }}<|assistant|>\n{{ .Response }}<|end|>";
66
+
67
+ const elTemplate = document.getElementById("template");
68
+ const elConversation = document.getElementById("conversation");
69
+
70
+ elTemplate.value = DEFAULT_TMPL;
71
+ elConversation.value = JSON.stringify(DEFAULT_CONV, null, 2);
72
+
73
+ if (WebAssembly) {
74
+ // WebAssembly.instantiateStreaming is not currently available in Safari
75
+ if (WebAssembly && !WebAssembly.instantiateStreaming) { // polyfill
76
+ WebAssembly.instantiateStreaming = async (resp, importObject) => {
77
+ const source = await (await resp).arrayBuffer();
78
+ return await WebAssembly.instantiate(source, importObject);
79
+ };
80
+ }
81
+
82
+ const go = new Go();
83
+ WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
84
+ go.run(result.instance);
85
+ applyChatTemplate();
86
+ });
87
+ } else {
88
+ alert("WebAssembly is not supported in your browser");
89
+ }
90
+
91
+ function applyChatTemplate() {
92
+ const conversation = elConversation.value;
93
+ const template = elTemplate.value.trim();
94
+
95
+ const output = formatChatTemplate(conversation, template);
96
+ document.getElementById("output").textContent = output;
97
+ }
98
+ </script>
99
+ </body>
100
+ </html>
main.go ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "fmt"
6
+ "encoding/json"
7
+ "syscall/js"
8
+
9
+ "github.com/ollama/ollama/api"
10
+ "github.com/ollama/ollama/template"
11
+ )
12
+
13
+ func formatChatTemplate() js.Func {
14
+ jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
15
+ if len(args) != 2 {
16
+ return fmt.Errorf("expected 2 arguments, got %d", len(args))
17
+ }
18
+
19
+ inputConv := args[0].String()
20
+ inputTmpl := args[1].String()
21
+
22
+ var messages []api.Message
23
+ if err := json.Unmarshal([]byte(inputConv), &messages); err != nil {
24
+ return string(err.Error())
25
+ }
26
+
27
+ for _, message := range messages {
28
+ fmt.Printf("%s: %s\n", message.Role, message.Content)
29
+ }
30
+
31
+ tmpl, err := template.Parse(inputTmpl)
32
+ if err != nil {
33
+ return string(err.Error())
34
+ }
35
+
36
+ var buf bytes.Buffer
37
+ err = tmpl.Execute(&buf, template.Values{
38
+ Messages: messages,
39
+ })
40
+ if err != nil {
41
+ return string(err.Error())
42
+ }
43
+
44
+ return string(buf.String())
45
+ })
46
+ return jsonFunc
47
+ }
48
+
49
+ func main() {
50
+ fmt.Println("Go Program Started")
51
+ js.Global().Set("formatChatTemplate", formatChatTemplate())
52
+ <-make(chan struct{})
53
+ }
main.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:60fe6587228339bf3cd32c603a70e464f3dd0df07f53dd4c7365c88c8be2e7bd
3
+ size 6550865
wasm_exec.js ADDED
@@ -0,0 +1,561 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Copyright 2018 The Go Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ "use strict";
6
+
7
+ (() => {
8
+ const enosys = () => {
9
+ const err = new Error("not implemented");
10
+ err.code = "ENOSYS";
11
+ return err;
12
+ };
13
+
14
+ if (!globalThis.fs) {
15
+ let outputBuf = "";
16
+ globalThis.fs = {
17
+ constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
18
+ writeSync(fd, buf) {
19
+ outputBuf += decoder.decode(buf);
20
+ const nl = outputBuf.lastIndexOf("\n");
21
+ if (nl != -1) {
22
+ console.log(outputBuf.substring(0, nl));
23
+ outputBuf = outputBuf.substring(nl + 1);
24
+ }
25
+ return buf.length;
26
+ },
27
+ write(fd, buf, offset, length, position, callback) {
28
+ if (offset !== 0 || length !== buf.length || position !== null) {
29
+ callback(enosys());
30
+ return;
31
+ }
32
+ const n = this.writeSync(fd, buf);
33
+ callback(null, n);
34
+ },
35
+ chmod(path, mode, callback) { callback(enosys()); },
36
+ chown(path, uid, gid, callback) { callback(enosys()); },
37
+ close(fd, callback) { callback(enosys()); },
38
+ fchmod(fd, mode, callback) { callback(enosys()); },
39
+ fchown(fd, uid, gid, callback) { callback(enosys()); },
40
+ fstat(fd, callback) { callback(enosys()); },
41
+ fsync(fd, callback) { callback(null); },
42
+ ftruncate(fd, length, callback) { callback(enosys()); },
43
+ lchown(path, uid, gid, callback) { callback(enosys()); },
44
+ link(path, link, callback) { callback(enosys()); },
45
+ lstat(path, callback) { callback(enosys()); },
46
+ mkdir(path, perm, callback) { callback(enosys()); },
47
+ open(path, flags, mode, callback) { callback(enosys()); },
48
+ read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
49
+ readdir(path, callback) { callback(enosys()); },
50
+ readlink(path, callback) { callback(enosys()); },
51
+ rename(from, to, callback) { callback(enosys()); },
52
+ rmdir(path, callback) { callback(enosys()); },
53
+ stat(path, callback) { callback(enosys()); },
54
+ symlink(path, link, callback) { callback(enosys()); },
55
+ truncate(path, length, callback) { callback(enosys()); },
56
+ unlink(path, callback) { callback(enosys()); },
57
+ utimes(path, atime, mtime, callback) { callback(enosys()); },
58
+ };
59
+ }
60
+
61
+ if (!globalThis.process) {
62
+ globalThis.process = {
63
+ getuid() { return -1; },
64
+ getgid() { return -1; },
65
+ geteuid() { return -1; },
66
+ getegid() { return -1; },
67
+ getgroups() { throw enosys(); },
68
+ pid: -1,
69
+ ppid: -1,
70
+ umask() { throw enosys(); },
71
+ cwd() { throw enosys(); },
72
+ chdir() { throw enosys(); },
73
+ }
74
+ }
75
+
76
+ if (!globalThis.crypto) {
77
+ throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
78
+ }
79
+
80
+ if (!globalThis.performance) {
81
+ throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
82
+ }
83
+
84
+ if (!globalThis.TextEncoder) {
85
+ throw new Error("globalThis.TextEncoder is not available, polyfill required");
86
+ }
87
+
88
+ if (!globalThis.TextDecoder) {
89
+ throw new Error("globalThis.TextDecoder is not available, polyfill required");
90
+ }
91
+
92
+ const encoder = new TextEncoder("utf-8");
93
+ const decoder = new TextDecoder("utf-8");
94
+
95
+ globalThis.Go = class {
96
+ constructor() {
97
+ this.argv = ["js"];
98
+ this.env = {};
99
+ this.exit = (code) => {
100
+ if (code !== 0) {
101
+ console.warn("exit code:", code);
102
+ }
103
+ };
104
+ this._exitPromise = new Promise((resolve) => {
105
+ this._resolveExitPromise = resolve;
106
+ });
107
+ this._pendingEvent = null;
108
+ this._scheduledTimeouts = new Map();
109
+ this._nextCallbackTimeoutID = 1;
110
+
111
+ const setInt64 = (addr, v) => {
112
+ this.mem.setUint32(addr + 0, v, true);
113
+ this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
114
+ }
115
+
116
+ const setInt32 = (addr, v) => {
117
+ this.mem.setUint32(addr + 0, v, true);
118
+ }
119
+
120
+ const getInt64 = (addr) => {
121
+ const low = this.mem.getUint32(addr + 0, true);
122
+ const high = this.mem.getInt32(addr + 4, true);
123
+ return low + high * 4294967296;
124
+ }
125
+
126
+ const loadValue = (addr) => {
127
+ const f = this.mem.getFloat64(addr, true);
128
+ if (f === 0) {
129
+ return undefined;
130
+ }
131
+ if (!isNaN(f)) {
132
+ return f;
133
+ }
134
+
135
+ const id = this.mem.getUint32(addr, true);
136
+ return this._values[id];
137
+ }
138
+
139
+ const storeValue = (addr, v) => {
140
+ const nanHead = 0x7FF80000;
141
+
142
+ if (typeof v === "number" && v !== 0) {
143
+ if (isNaN(v)) {
144
+ this.mem.setUint32(addr + 4, nanHead, true);
145
+ this.mem.setUint32(addr, 0, true);
146
+ return;
147
+ }
148
+ this.mem.setFloat64(addr, v, true);
149
+ return;
150
+ }
151
+
152
+ if (v === undefined) {
153
+ this.mem.setFloat64(addr, 0, true);
154
+ return;
155
+ }
156
+
157
+ let id = this._ids.get(v);
158
+ if (id === undefined) {
159
+ id = this._idPool.pop();
160
+ if (id === undefined) {
161
+ id = this._values.length;
162
+ }
163
+ this._values[id] = v;
164
+ this._goRefCounts[id] = 0;
165
+ this._ids.set(v, id);
166
+ }
167
+ this._goRefCounts[id]++;
168
+ let typeFlag = 0;
169
+ switch (typeof v) {
170
+ case "object":
171
+ if (v !== null) {
172
+ typeFlag = 1;
173
+ }
174
+ break;
175
+ case "string":
176
+ typeFlag = 2;
177
+ break;
178
+ case "symbol":
179
+ typeFlag = 3;
180
+ break;
181
+ case "function":
182
+ typeFlag = 4;
183
+ break;
184
+ }
185
+ this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
186
+ this.mem.setUint32(addr, id, true);
187
+ }
188
+
189
+ const loadSlice = (addr) => {
190
+ const array = getInt64(addr + 0);
191
+ const len = getInt64(addr + 8);
192
+ return new Uint8Array(this._inst.exports.mem.buffer, array, len);
193
+ }
194
+
195
+ const loadSliceOfValues = (addr) => {
196
+ const array = getInt64(addr + 0);
197
+ const len = getInt64(addr + 8);
198
+ const a = new Array(len);
199
+ for (let i = 0; i < len; i++) {
200
+ a[i] = loadValue(array + i * 8);
201
+ }
202
+ return a;
203
+ }
204
+
205
+ const loadString = (addr) => {
206
+ const saddr = getInt64(addr + 0);
207
+ const len = getInt64(addr + 8);
208
+ return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
209
+ }
210
+
211
+ const timeOrigin = Date.now() - performance.now();
212
+ this.importObject = {
213
+ _gotest: {
214
+ add: (a, b) => a + b,
215
+ },
216
+ gojs: {
217
+ // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
218
+ // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
219
+ // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
220
+ // This changes the SP, thus we have to update the SP used by the imported function.
221
+
222
+ // func wasmExit(code int32)
223
+ "runtime.wasmExit": (sp) => {
224
+ sp >>>= 0;
225
+ const code = this.mem.getInt32(sp + 8, true);
226
+ this.exited = true;
227
+ delete this._inst;
228
+ delete this._values;
229
+ delete this._goRefCounts;
230
+ delete this._ids;
231
+ delete this._idPool;
232
+ this.exit(code);
233
+ },
234
+
235
+ // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
236
+ "runtime.wasmWrite": (sp) => {
237
+ sp >>>= 0;
238
+ const fd = getInt64(sp + 8);
239
+ const p = getInt64(sp + 16);
240
+ const n = this.mem.getInt32(sp + 24, true);
241
+ fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
242
+ },
243
+
244
+ // func resetMemoryDataView()
245
+ "runtime.resetMemoryDataView": (sp) => {
246
+ sp >>>= 0;
247
+ this.mem = new DataView(this._inst.exports.mem.buffer);
248
+ },
249
+
250
+ // func nanotime1() int64
251
+ "runtime.nanotime1": (sp) => {
252
+ sp >>>= 0;
253
+ setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
254
+ },
255
+
256
+ // func walltime() (sec int64, nsec int32)
257
+ "runtime.walltime": (sp) => {
258
+ sp >>>= 0;
259
+ const msec = (new Date).getTime();
260
+ setInt64(sp + 8, msec / 1000);
261
+ this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
262
+ },
263
+
264
+ // func scheduleTimeoutEvent(delay int64) int32
265
+ "runtime.scheduleTimeoutEvent": (sp) => {
266
+ sp >>>= 0;
267
+ const id = this._nextCallbackTimeoutID;
268
+ this._nextCallbackTimeoutID++;
269
+ this._scheduledTimeouts.set(id, setTimeout(
270
+ () => {
271
+ this._resume();
272
+ while (this._scheduledTimeouts.has(id)) {
273
+ // for some reason Go failed to register the timeout event, log and try again
274
+ // (temporary workaround for https://github.com/golang/go/issues/28975)
275
+ console.warn("scheduleTimeoutEvent: missed timeout event");
276
+ this._resume();
277
+ }
278
+ },
279
+ getInt64(sp + 8),
280
+ ));
281
+ this.mem.setInt32(sp + 16, id, true);
282
+ },
283
+
284
+ // func clearTimeoutEvent(id int32)
285
+ "runtime.clearTimeoutEvent": (sp) => {
286
+ sp >>>= 0;
287
+ const id = this.mem.getInt32(sp + 8, true);
288
+ clearTimeout(this._scheduledTimeouts.get(id));
289
+ this._scheduledTimeouts.delete(id);
290
+ },
291
+
292
+ // func getRandomData(r []byte)
293
+ "runtime.getRandomData": (sp) => {
294
+ sp >>>= 0;
295
+ crypto.getRandomValues(loadSlice(sp + 8));
296
+ },
297
+
298
+ // func finalizeRef(v ref)
299
+ "syscall/js.finalizeRef": (sp) => {
300
+ sp >>>= 0;
301
+ const id = this.mem.getUint32(sp + 8, true);
302
+ this._goRefCounts[id]--;
303
+ if (this._goRefCounts[id] === 0) {
304
+ const v = this._values[id];
305
+ this._values[id] = null;
306
+ this._ids.delete(v);
307
+ this._idPool.push(id);
308
+ }
309
+ },
310
+
311
+ // func stringVal(value string) ref
312
+ "syscall/js.stringVal": (sp) => {
313
+ sp >>>= 0;
314
+ storeValue(sp + 24, loadString(sp + 8));
315
+ },
316
+
317
+ // func valueGet(v ref, p string) ref
318
+ "syscall/js.valueGet": (sp) => {
319
+ sp >>>= 0;
320
+ const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
321
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
322
+ storeValue(sp + 32, result);
323
+ },
324
+
325
+ // func valueSet(v ref, p string, x ref)
326
+ "syscall/js.valueSet": (sp) => {
327
+ sp >>>= 0;
328
+ Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
329
+ },
330
+
331
+ // func valueDelete(v ref, p string)
332
+ "syscall/js.valueDelete": (sp) => {
333
+ sp >>>= 0;
334
+ Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
335
+ },
336
+
337
+ // func valueIndex(v ref, i int) ref
338
+ "syscall/js.valueIndex": (sp) => {
339
+ sp >>>= 0;
340
+ storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
341
+ },
342
+
343
+ // valueSetIndex(v ref, i int, x ref)
344
+ "syscall/js.valueSetIndex": (sp) => {
345
+ sp >>>= 0;
346
+ Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
347
+ },
348
+
349
+ // func valueCall(v ref, m string, args []ref) (ref, bool)
350
+ "syscall/js.valueCall": (sp) => {
351
+ sp >>>= 0;
352
+ try {
353
+ const v = loadValue(sp + 8);
354
+ const m = Reflect.get(v, loadString(sp + 16));
355
+ const args = loadSliceOfValues(sp + 32);
356
+ const result = Reflect.apply(m, v, args);
357
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
358
+ storeValue(sp + 56, result);
359
+ this.mem.setUint8(sp + 64, 1);
360
+ } catch (err) {
361
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
362
+ storeValue(sp + 56, err);
363
+ this.mem.setUint8(sp + 64, 0);
364
+ }
365
+ },
366
+
367
+ // func valueInvoke(v ref, args []ref) (ref, bool)
368
+ "syscall/js.valueInvoke": (sp) => {
369
+ sp >>>= 0;
370
+ try {
371
+ const v = loadValue(sp + 8);
372
+ const args = loadSliceOfValues(sp + 16);
373
+ const result = Reflect.apply(v, undefined, args);
374
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
375
+ storeValue(sp + 40, result);
376
+ this.mem.setUint8(sp + 48, 1);
377
+ } catch (err) {
378
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
379
+ storeValue(sp + 40, err);
380
+ this.mem.setUint8(sp + 48, 0);
381
+ }
382
+ },
383
+
384
+ // func valueNew(v ref, args []ref) (ref, bool)
385
+ "syscall/js.valueNew": (sp) => {
386
+ sp >>>= 0;
387
+ try {
388
+ const v = loadValue(sp + 8);
389
+ const args = loadSliceOfValues(sp + 16);
390
+ const result = Reflect.construct(v, args);
391
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
392
+ storeValue(sp + 40, result);
393
+ this.mem.setUint8(sp + 48, 1);
394
+ } catch (err) {
395
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
396
+ storeValue(sp + 40, err);
397
+ this.mem.setUint8(sp + 48, 0);
398
+ }
399
+ },
400
+
401
+ // func valueLength(v ref) int
402
+ "syscall/js.valueLength": (sp) => {
403
+ sp >>>= 0;
404
+ setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
405
+ },
406
+
407
+ // valuePrepareString(v ref) (ref, int)
408
+ "syscall/js.valuePrepareString": (sp) => {
409
+ sp >>>= 0;
410
+ const str = encoder.encode(String(loadValue(sp + 8)));
411
+ storeValue(sp + 16, str);
412
+ setInt64(sp + 24, str.length);
413
+ },
414
+
415
+ // valueLoadString(v ref, b []byte)
416
+ "syscall/js.valueLoadString": (sp) => {
417
+ sp >>>= 0;
418
+ const str = loadValue(sp + 8);
419
+ loadSlice(sp + 16).set(str);
420
+ },
421
+
422
+ // func valueInstanceOf(v ref, t ref) bool
423
+ "syscall/js.valueInstanceOf": (sp) => {
424
+ sp >>>= 0;
425
+ this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
426
+ },
427
+
428
+ // func copyBytesToGo(dst []byte, src ref) (int, bool)
429
+ "syscall/js.copyBytesToGo": (sp) => {
430
+ sp >>>= 0;
431
+ const dst = loadSlice(sp + 8);
432
+ const src = loadValue(sp + 32);
433
+ if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
434
+ this.mem.setUint8(sp + 48, 0);
435
+ return;
436
+ }
437
+ const toCopy = src.subarray(0, dst.length);
438
+ dst.set(toCopy);
439
+ setInt64(sp + 40, toCopy.length);
440
+ this.mem.setUint8(sp + 48, 1);
441
+ },
442
+
443
+ // func copyBytesToJS(dst ref, src []byte) (int, bool)
444
+ "syscall/js.copyBytesToJS": (sp) => {
445
+ sp >>>= 0;
446
+ const dst = loadValue(sp + 8);
447
+ const src = loadSlice(sp + 16);
448
+ if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
449
+ this.mem.setUint8(sp + 48, 0);
450
+ return;
451
+ }
452
+ const toCopy = src.subarray(0, dst.length);
453
+ dst.set(toCopy);
454
+ setInt64(sp + 40, toCopy.length);
455
+ this.mem.setUint8(sp + 48, 1);
456
+ },
457
+
458
+ "debug": (value) => {
459
+ console.log(value);
460
+ },
461
+ }
462
+ };
463
+ }
464
+
465
+ async run(instance) {
466
+ if (!(instance instanceof WebAssembly.Instance)) {
467
+ throw new Error("Go.run: WebAssembly.Instance expected");
468
+ }
469
+ this._inst = instance;
470
+ this.mem = new DataView(this._inst.exports.mem.buffer);
471
+ this._values = [ // JS values that Go currently has references to, indexed by reference id
472
+ NaN,
473
+ 0,
474
+ null,
475
+ true,
476
+ false,
477
+ globalThis,
478
+ this,
479
+ ];
480
+ this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
481
+ this._ids = new Map([ // mapping from JS values to reference ids
482
+ [0, 1],
483
+ [null, 2],
484
+ [true, 3],
485
+ [false, 4],
486
+ [globalThis, 5],
487
+ [this, 6],
488
+ ]);
489
+ this._idPool = []; // unused ids that have been garbage collected
490
+ this.exited = false; // whether the Go program has exited
491
+
492
+ // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
493
+ let offset = 4096;
494
+
495
+ const strPtr = (str) => {
496
+ const ptr = offset;
497
+ const bytes = encoder.encode(str + "\0");
498
+ new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
499
+ offset += bytes.length;
500
+ if (offset % 8 !== 0) {
501
+ offset += 8 - (offset % 8);
502
+ }
503
+ return ptr;
504
+ };
505
+
506
+ const argc = this.argv.length;
507
+
508
+ const argvPtrs = [];
509
+ this.argv.forEach((arg) => {
510
+ argvPtrs.push(strPtr(arg));
511
+ });
512
+ argvPtrs.push(0);
513
+
514
+ const keys = Object.keys(this.env).sort();
515
+ keys.forEach((key) => {
516
+ argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
517
+ });
518
+ argvPtrs.push(0);
519
+
520
+ const argv = offset;
521
+ argvPtrs.forEach((ptr) => {
522
+ this.mem.setUint32(offset, ptr, true);
523
+ this.mem.setUint32(offset + 4, 0, true);
524
+ offset += 8;
525
+ });
526
+
527
+ // The linker guarantees global data starts from at least wasmMinDataAddr.
528
+ // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
529
+ const wasmMinDataAddr = 4096 + 8192;
530
+ if (offset >= wasmMinDataAddr) {
531
+ throw new Error("total length of command line and environment variables exceeds limit");
532
+ }
533
+
534
+ this._inst.exports.run(argc, argv);
535
+ if (this.exited) {
536
+ this._resolveExitPromise();
537
+ }
538
+ await this._exitPromise;
539
+ }
540
+
541
+ _resume() {
542
+ if (this.exited) {
543
+ throw new Error("Go program has already exited");
544
+ }
545
+ this._inst.exports.resume();
546
+ if (this.exited) {
547
+ this._resolveExitPromise();
548
+ }
549
+ }
550
+
551
+ _makeFuncWrapper(id) {
552
+ const go = this;
553
+ return function () {
554
+ const event = { id: id, this: this, args: arguments };
555
+ go._pendingEvent = event;
556
+ go._resume();
557
+ return event.result;
558
+ };
559
+ }
560
+ }
561
+ })();