Skip to content

Commit 50cd487

Browse files
committed
Implement a web based REPL using gpython
This implements a wasm and gopherjs REPL frontend for gpython.
1 parent 08903fc commit 50cd487

12 files changed

+729
-2
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ It includes:
1414
* lexer
1515
* parser
1616
* compiler
17-
* interactive mode (REPL)
17+
* interactive mode (REPL) ([try online!](https://gpython.org))
1818

1919
It does not include very many python modules as many of the core
2020
modules are written in C not python. The converted modules are:

go.mod

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module github.com/go-python/gpython
22

3-
require github.com/peterh/liner v1.1.0
3+
require (
4+
github.com/gopherjs/gopherwasm v1.0.0 // indirect
5+
github.com/peterh/liner v1.1.0
6+
)

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
2+
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
3+
github.com/gopherjs/gopherwasm v1.0.0 h1:32nge/RlujS1Im4HNCJPp0NbBOAeBXFuT1KonUuLl+Y=
4+
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
15
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
26
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
37
github.com/peterh/liner v1.1.0 h1:f+aAedNJA6uk7+6rXsYBnhdo4Xux7ESLe+kcuVUF5os=

repl/web/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
gpython.wasm
2+
gpython.js
3+
gpython.js.map

repl/web/Makefile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
build:
2+
GOARCH=wasm GOOS=js go build -o gpython.wasm
3+
gopherjs build -m -o gpython.js
4+
5+
serve: build
6+
go run serve.go
7+
8+
upload: build
9+
rclone -P copy --include="*.{wasm,js,css,html}" . gpythonweb:
10+
@echo See https://gpython.org/

repl/web/README.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Gpython Web
2+
3+
This implements a web viewable version of the gpython REPL.
4+
5+
This is done by compiling gpython into wasm and running that in the
6+
browser.
7+
8+
[Try it online.](https://www.craig-wood.com/nick/gpython/)
9+
10+
## Build and run
11+
12+
`make build` will build with go wasm (you'll need go1.11 minimum)
13+
14+
`make serve` will run a local webserver you can see the results on
15+
16+
## Thanks
17+
18+
Thanks to [jQuery Terminal](https://terminal.jcubic.pl/) for the
19+
terminal emulator and the go team for great [wasm
20+
support](https://github.com/golang/go/wiki/WebAssembly).

repl/web/index.html

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
5+
<script src="https://code.jquery.com/jquery-latest.js"></script>
6+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.terminal/1.23.2/js/jquery.terminal.min.js"></script>
7+
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery.terminal/1.23.2/css/jquery.terminal.min.css" rel="stylesheet"/>
8+
<style>
9+
/* position the outer div filling the whole browser window */
10+
#outer {
11+
position: fixed;
12+
display: flex;
13+
flex-direction: column;
14+
top: 0; right: 0; bottom: 0; left: 0;
15+
}
16+
17+
/* Terminal output at the top should grow to fill the space */
18+
#term {
19+
order: 0;
20+
flex-grow: 1;
21+
}
22+
23+
/* Navigation bar is fixed size at the bottom */
24+
#navbar {
25+
order: 1;
26+
flex-grow: 0;
27+
background-color: #333;
28+
}
29+
30+
/* Style the links inside the navigation bar */
31+
#navbar a {
32+
float: left;
33+
color: #fff;
34+
text-align: center;
35+
padding: 4px 12px;
36+
text-decoration: none;
37+
font-size: 17px;
38+
}
39+
40+
/* Change the color of links on hover */
41+
#navbar a:hover {
42+
background-color: #ddd;
43+
color: black;
44+
}
45+
46+
/* Add a color to the active/current link */
47+
#navbar a.active {
48+
background-color: #4CAF50;
49+
color: white;
50+
}
51+
</style>
52+
</head>
53+
<body>
54+
<div id="outer">
55+
<div id="term">Loading...</div>
56+
<div id="navbar">
57+
<a id="gopherjs" href=".">Gopherjs</a>
58+
<a id="go/wasm" href="?wasm">Go/wasm</a>
59+
<a href="https://github.com/go-python/gpython" target="_blank">About</a>
60+
</div>
61+
</div>
62+
<script src="loader.js"></script>
63+
</body>
64+
</html>

repl/web/loader.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Set the default global log for use by wasm_exec.js
2+
go_log = console.log;
3+
4+
var useWasm = location.href.includes("?wasm");
5+
6+
console.log("useWasm =", useWasm);
7+
8+
var script = document.createElement('script');
9+
if (useWasm) {
10+
script.src = "wasm_exec.js";
11+
script.onload = function () {
12+
// polyfill
13+
if (!WebAssembly.instantiateStreaming) {
14+
WebAssembly.instantiateStreaming = async (resp, importObject) => {
15+
const source = await (await resp).arrayBuffer();
16+
return await WebAssembly.instantiate(source, importObject);
17+
};
18+
}
19+
20+
const go = new Go();
21+
let mod, inst;
22+
WebAssembly.instantiateStreaming(fetch("gpython.wasm"), go.importObject).then((result) => {
23+
mod = result.module;
24+
inst = result.instance;
25+
run();
26+
});
27+
28+
async function run() {
29+
console.clear();
30+
await go.run(inst);
31+
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
32+
}
33+
};
34+
} else {
35+
script.src = "gpython.js";
36+
}
37+
document.head.appendChild(script);

repl/web/main.go

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// An online REPL for gpython using wasm
2+
3+
// Copyright 2018 The go-python Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// +build js
8+
9+
package main
10+
11+
import (
12+
"log"
13+
"runtime"
14+
15+
"github.com/gopherjs/gopherwasm/js" // gopherjs to wasm converter shim
16+
17+
// import required modules
18+
_ "github.com/go-python/gpython/builtin"
19+
_ "github.com/go-python/gpython/math"
20+
"github.com/go-python/gpython/repl"
21+
_ "github.com/go-python/gpython/sys"
22+
_ "github.com/go-python/gpython/time"
23+
)
24+
25+
// Implement the replUI interface
26+
type termIO struct {
27+
js.Value
28+
}
29+
30+
// SetPrompt sets the UI prompt
31+
func (t *termIO) SetPrompt(prompt string) {
32+
t.Call("set_prompt", prompt)
33+
}
34+
35+
// Print outputs the string to the output
36+
func (t *termIO) Print(out string) {
37+
t.Call("echo", out)
38+
}
39+
40+
var document js.Value
41+
42+
func isUndefined(node js.Value) bool {
43+
return node == js.Undefined()
44+
}
45+
46+
func getElementById(name string) js.Value {
47+
node := document.Call("getElementById", name)
48+
if isUndefined(node) {
49+
log.Fatalf("Couldn't find element %q", name)
50+
}
51+
return node
52+
}
53+
54+
func running() string {
55+
switch {
56+
case runtime.GOOS == "js" && runtime.GOARCH == "wasm":
57+
return "go/wasm"
58+
case runtime.GOARCH == "js":
59+
return "gopherjs"
60+
}
61+
return "unknown"
62+
}
63+
64+
func main() {
65+
document = js.Global().Get("document")
66+
if isUndefined(document) {
67+
log.Fatalf("Didn't find document - not running in browser")
68+
}
69+
70+
// Clear the loading text
71+
termNode := getElementById("term")
72+
termNode.Set("innerHTML", "")
73+
74+
// work out what we are running on and mark active
75+
tech := running()
76+
node := getElementById(tech)
77+
node.Get("classList").Call("add", "active")
78+
79+
// Make a repl referring to an empty term for the moment
80+
REPL := repl.New()
81+
cb := js.NewCallback(func(args []js.Value) {
82+
REPL.Run(args[0].String())
83+
})
84+
85+
// Create a jquery terminal instance
86+
opts := js.ValueOf(map[string]interface{}{
87+
"greetings": "Gpython 3.4.0 running in your browser with " + tech,
88+
"name": "gpython",
89+
"prompt": repl.NormalPrompt,
90+
})
91+
terminal := js.Global().Call("$", "#term").Call("terminal", cb, opts)
92+
93+
// Send the console log direct to the terminal
94+
js.Global().Get("console").Set("log", terminal.Get("echo"))
95+
96+
// Set the implementation of term
97+
REPL.SetUI(&termIO{terminal})
98+
99+
// wait for callbacks
100+
select {}
101+
}

repl/web/serve.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//+build none
2+
3+
package main
4+
5+
import (
6+
"fmt"
7+
"log"
8+
"mime"
9+
"net/http"
10+
)
11+
12+
func main() {
13+
mime.AddExtensionType(".wasm", "application/wasm")
14+
mime.AddExtensionType(".js", "application/javascript")
15+
mux := http.NewServeMux()
16+
mux.Handle("/", http.FileServer(http.Dir(".")))
17+
fmt.Printf("Serving on http://localhost:3000/\n")
18+
log.Fatal(http.ListenAndServe(":3000", mux))
19+
}

0 commit comments

Comments
 (0)