├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── builtins.go ├── cmd ├── nebula │ ├── main.go │ └── readme.md └── star │ └── main.go ├── examples ├── requests.star.py ├── scratch.star.py ├── script.star.py └── server.star.py ├── go.mod ├── go.sum ├── pkg └── repl │ └── repl.go ├── readme.md ├── src ├── io │ ├── ioutil │ │ └── lib.go │ └── lib.go ├── net │ └── http │ │ └── lib.go ├── packages.go ├── sync │ └── lib.go └── time │ └── lib.go ├── star.go ├── types.go ├── values.go └── web-repl ├── .babelrc ├── .gitignore ├── .npmrc ├── Dockerfile ├── package.json ├── src ├── app.css ├── app.ts ├── index.html ├── main.go └── wasm_exec.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.gz 2 | .DS_Store 3 | dist 4 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - #foo 3 | id: "star" 4 | dir: cmd/star 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Max McDonnell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = /usr/bin/env bash 2 | 3 | 4 | install: 5 | cd cmd/star && go install 6 | 7 | run_star_requests: install 8 | cd examples && star requests.star.py 9 | 10 | run_star_server: install 11 | cd examples && star server.star.py 12 | 13 | run_star: install 14 | cd cmd/star && go install 15 | star 16 | 17 | 18 | deploy_repl: 19 | cd web-repl && npm run deploy 20 | -------------------------------------------------------------------------------- /builtins.go: -------------------------------------------------------------------------------- 1 | package star 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "go.starlark.net/starlark" 6 | "go.starlark.net/starlarkstruct" 7 | ) 8 | 9 | func AddPackages(packages map[string]map[string]starlark.Value) { 10 | for name, members := range packages { 11 | mappings[name] = members 12 | } 13 | } 14 | 15 | var mappings = map[string]map[string]starlark.Value{ 16 | "star": Star, 17 | } 18 | 19 | func Require(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (v starlark.Value, err error) { 20 | if args.Len() != 1 || args.Index(0).Type() != "string" { 21 | err = errors.New("require takes one string argument") 22 | return 23 | } 24 | 25 | packageName := string(args.Index(0).(starlark.String)) 26 | values, ok := mappings[packageName] 27 | if !ok { 28 | err = errors.Errorf(`package doesn't exist with name "%s"`, packageName) 29 | return 30 | } 31 | members := []starlark.Tuple{} 32 | for name, value := range values { 33 | members = append(members, starlark.Tuple{starlark.String(name), value}) 34 | } 35 | v, err = starlarkstruct.Make(nil, nil, nil, members) 36 | return 37 | } 38 | 39 | var Star = map[string]starlark.Value{ 40 | "bytes_to_string": starlark.NewBuiltin("bytes_to_string", func(thread *starlark.Thread, 41 | fn *starlark.Builtin, args starlark.Tuple, 42 | kwargs []starlark.Tuple) (v starlark.Value, err error) { 43 | out := []starlark.Value{starlark.None, Error{}} 44 | if args.Len() != 1 || args.Index(0).Type() != "[]byte" { 45 | out[1] = Error{err: errors.New("bytes_to_string takes one argument of type []byte")} 46 | } else { 47 | out[0] = starlark.String(string(args.Index(0).(ByteArray).b)) 48 | } 49 | v = starlark.Tuple(out) 50 | return 51 | }), 52 | "chan": starlark.NewBuiltin("chan", func(thread *starlark.Thread, 53 | fn *starlark.Builtin, args starlark.Tuple, 54 | kwargs []starlark.Tuple) (v starlark.Value, err error) { 55 | out := []starlark.Value{starlark.None, Error{}} 56 | if args.Len() != 1 || args.Index(0).Type() != "[]byte" { 57 | out[1] = Error{err: errors.New("bytes_to_string takes one argument of type []byte")} 58 | } else { 59 | out[0] = starlark.String(string(args.Index(0).(ByteArray).b)) 60 | } 61 | v = starlark.Tuple(out) 62 | return 63 | }), 64 | "go": starlark.NewBuiltin("go", func(thread *starlark.Thread, 65 | fn *starlark.Builtin, args starlark.Tuple, 66 | kwargs []starlark.Tuple) (v starlark.Value, err error) { 67 | 68 | go func() { 69 | argsToPass := []starlark.Value(args)[1:] 70 | thread := &starlark.Thread{Name: ""} 71 | _, err := starlark.Call(thread, args.Index(0), starlark.Tuple(argsToPass), nil) 72 | if err != nil { 73 | panic(err) 74 | } 75 | }() 76 | v = starlark.None 77 | return 78 | }), 79 | } 80 | -------------------------------------------------------------------------------- /cmd/nebula/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/build" 6 | "go/doc" 7 | "go/parser" 8 | "go/token" 9 | "log" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | wd, err := os.Getwd() 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | pkg, err := build.Import("io/ioutil", wd, build.ImportComment) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | fs := token.NewFileSet() 24 | include := func(info os.FileInfo) bool { 25 | for _, name := range pkg.GoFiles { 26 | if name == info.Name() { 27 | return true 28 | } 29 | } 30 | for _, name := range pkg.CgoFiles { 31 | if name == info.Name() { 32 | return true 33 | } 34 | } 35 | return false 36 | } 37 | pkgs, err := parser.ParseDir(fs, pkg.Dir, include, parser.ParseComments) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | astPkg := pkgs[pkg.Name] 42 | mode := doc.AllDecls 43 | docPkg := doc.New(astPkg, pkg.ImportPath, mode) 44 | 45 | fmt.Println(docPkg.Funcs[0].Decl.Type.Params.List[0].Type) 46 | } 47 | -------------------------------------------------------------------------------- /cmd/nebula/readme.md: -------------------------------------------------------------------------------- 1 | # Nebula 2 | 3 | Nebula will generate Go code to let star users use Go libs. Currently WIP 4 | -------------------------------------------------------------------------------- /cmd/star/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/embly/star" 8 | "github.com/embly/star/pkg/repl" 9 | "github.com/embly/star/src" 10 | ) 11 | 12 | func main() { 13 | star.AddPackages(src.Packages) 14 | if len(os.Args) > 1 { 15 | if err := star.RunScript(os.Args[1]); err != nil { 16 | log.Fatal(err) 17 | } 18 | } 19 | repl.REPL() 20 | 21 | } 22 | -------------------------------------------------------------------------------- /examples/requests.star.py: -------------------------------------------------------------------------------- 1 | http = require("net/http") 2 | ioutil = require("io/ioutil") 3 | sync = require("sync") 4 | star = require("star") 5 | time = require("time") 6 | 7 | 8 | def get_url(url, wg): 9 | resp, err = http.Get(url) 10 | if err: 11 | return print(err) 12 | b, err = ioutil.ReadAll(resp.Body) 13 | if err: 14 | return print(err) 15 | body, err = star.bytes_to_string(b) 16 | if err: 17 | return print(err) 18 | time.Sleep(time.Second * 2) 19 | wg.Done() 20 | 21 | 22 | def main(): 23 | wg = sync.WaitGroup() 24 | wg.Add(3) 25 | urls = [ 26 | "https://www.embly.run/hello/", 27 | "https://www.embly.run/hello/", 28 | "https://www.embly.run/hello/", 29 | ] 30 | for url in urls: 31 | star.go(get_url, url, wg) 32 | wg.Wait() 33 | -------------------------------------------------------------------------------- /examples/scratch.star.py: -------------------------------------------------------------------------------- 1 | def thing(): 2 | print("hi") 3 | -------------------------------------------------------------------------------- /examples/script.star.py: -------------------------------------------------------------------------------- 1 | """ 2 | hello it is 3 | """ 4 | 5 | require("ioutil") 6 | 7 | # star = require("star") 8 | # print(star.channel) 9 | 10 | 11 | def handle_err(args): 12 | for a in args: 13 | if type(a) == "error" and a: 14 | print(a, a.stacktrace()) 15 | return args 16 | 17 | 18 | def hello(w, req): 19 | w.write(req.content_type) 20 | w.write(req.path) 21 | 22 | b, err = handle_err(req.sample_resp) 23 | print(b, err) 24 | 25 | print(req.Body) 26 | 27 | print(req.some_bytes) 28 | for b in req.some_bytes: 29 | print(b) 30 | 31 | # print(str(req.Body)) 32 | # print(str(1 * 4)) 33 | # print(req.error) 34 | # handle_err(req.error) 35 | # print(len(req.some_bytes)) 36 | # ords = str(list("hi".elem_ords())) 37 | # w.write("Hello World\n" + str([x for x in range(10)]) + "\n" + ords) 38 | 39 | return 40 | -------------------------------------------------------------------------------- /examples/server.star.py: -------------------------------------------------------------------------------- 1 | http = require("net/http") 2 | 3 | 4 | def hello(w, req): 5 | w.WriteHeader(201) 6 | w.Write("hello world\n") 7 | 8 | 9 | http.HandleFunc("/hello", hello) 10 | 11 | http.ListenAndServe(":8080", http.Handler) 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/embly/star 2 | 3 | go 1.13 4 | 5 | replace go.starlark.net => github.com/embly/starlark-go v0.0.0-20200126005305-451bf671df23 6 | 7 | require ( 8 | github.com/pkg/errors v0.9.1 9 | go.starlark.net v0.0.0-20200126005305-451bf671df23 10 | golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 2 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 3 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 5 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 6 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 7 | github.com/embly/starlark-go v0.0.0-20200126005305-451bf671df23 h1:H5JvT+n1kJRS2x+NBNzaK2B0fi5Guzs9z4QeCvuYwY8= 8 | github.com/embly/starlark-go v0.0.0-20200126005305-451bf671df23/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= 9 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 10 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 11 | golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 12 | golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab h1:FvshnhkKW+LO3HWHodML8kuVX8rnJTxKm9dFPuI68UM= 13 | golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | -------------------------------------------------------------------------------- /pkg/repl/repl.go: -------------------------------------------------------------------------------- 1 | package repl 2 | 3 | // This has to be in its own package so that we don't have to compile 4 | // github.com/chzyer/readline when compiling with webassembly 5 | 6 | import ( 7 | "github.com/embly/star" 8 | "go.starlark.net/repl" 9 | "go.starlark.net/starlark" 10 | ) 11 | 12 | func REPL() { 13 | thread := &starlark.Thread{Name: ""} 14 | repl.REPL(thread, starlark.StringDict{ 15 | "require": starlark.NewBuiltin("require", star.Require), 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Star 2 | 3 | Star uses [starlark](https://github.com/google/starlark-go) to provide a python-like environment to run Go code. This is a quick and dirty experiment and should not be taken seriously. 4 | 5 | The [intro blog post](https://embly.run/star) is likely the best place to get started. Or you can play around in the repl: https://embly.github.com/star 6 | 7 | ## How to use 8 | 9 | Head to the releases section to download a binary or if you have Go installed just run: 10 | ```bash 11 | go get github.com/embly/star/cmd/star 12 | ``` 13 | 14 | Star provides a python-like environment to run Go packages. A small subset of the standard library is currently supported. You can see the supported packages here: [https://github.com/embly/star/blob/master/src/packages.go](https://github.com/embly/star/blob/master/src/packages.go) 15 | 16 | Some example scripts: 17 | 18 | Use Go's concurrency model to fetch some urls in parallel: 19 | ```python 20 | http = require("net/http") 21 | ioutil = require("io/ioutil") 22 | sync = require("sync") 23 | star = require("star") 24 | time = require("time") 25 | 26 | def get_url(url, wg): 27 | resp, err = http.Get(url) 28 | if err: 29 | return print(err) 30 | b, err = ioutil.ReadAll(resp.Body) 31 | if err: 32 | return print(err) 33 | body, err = star.bytes_to_string(b) 34 | if err: 35 | return print(err) 36 | time.Sleep(time.Second * 2) 37 | wg.Done() 38 | 39 | 40 | def main(): 41 | wg = sync.WaitGroup() 42 | wg.Add(3) 43 | urls = [ 44 | "https://www.embly.run/hello/", 45 | "https://www.embly.run/hello/", 46 | "https://www.embly.run/hello/", 47 | ] 48 | for url in urls: 49 | star.go(get_url, url, wg) 50 | wg.Wait() 51 | ``` 52 | 53 | 54 | Run a web server: 55 | 56 | ```python 57 | http = require("net/http") 58 | 59 | 60 | def hello(w, req): 61 | w.WriteHeader(201) 62 | w.Write("hello world\n") 63 | 64 | 65 | http.HandleFunc("/hello", hello) 66 | 67 | http.ListenAndServe(":8080", http.Handler) 68 | ``` 69 | -------------------------------------------------------------------------------- /src/io/ioutil/lib.go: -------------------------------------------------------------------------------- 1 | package ioutil 2 | 3 | import ( 4 | go_io "io" 5 | "io/ioutil" 6 | 7 | "github.com/embly/star" 8 | "github.com/embly/star/src/io" 9 | "go.starlark.net/starlark" 10 | ) 11 | 12 | var NopCloser = star.Function{ 13 | FunctionName: "NopCloser", 14 | Args: []starlark.Value{io.Reader}, 15 | Returns: []starlark.Value{io.ReadCloser}, 16 | Call: func(args []interface{}) (resp []interface{}) { 17 | resp = make([]interface{}, 1) 18 | resp[0] = ioutil.NopCloser(args[0].(go_io.Reader)) 19 | return 20 | }, 21 | } 22 | 23 | var ReadAll = star.Function{ 24 | FunctionName: "ReadAll", 25 | Args: []starlark.Value{io.Reader}, 26 | Returns: []starlark.Value{star.ByteArray{}, star.Error{}}, 27 | Call: func(args []interface{}) (resp []interface{}) { 28 | resp = make([]interface{}, 2) 29 | resp[0], resp[1] = ioutil.ReadAll(args[0].(go_io.Reader)) 30 | return 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /src/io/lib.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/embly/star" 7 | "go.starlark.net/starlark" 8 | ) 9 | 10 | var Reader = star.Interface{ 11 | Name: "io.Reader", 12 | Methods: map[string]star.Method{ 13 | "Read": star.Method{ 14 | Args: []starlark.Value{star.ByteArray{}}, 15 | Returns: []starlark.Value{starlark.Int{}, star.Error{}}, 16 | Call: func(recv interface{}, args []interface{}) (resp []interface{}) { 17 | resp = make([]interface{}, 2) 18 | resp[0], resp[1] = recv.(io.Reader).Read(args[0].([]byte)) 19 | return 20 | }, 21 | }, 22 | }, 23 | } 24 | 25 | var ReadCloser = star.Interface{ 26 | Name: "io.ReadCloser", 27 | Methods: map[string]star.Method{ 28 | "Read": star.Method{ 29 | Args: []starlark.Value{star.ByteArray{}}, 30 | Returns: []starlark.Value{starlark.Int{}, star.Error{}}, 31 | Call: func(recv interface{}, args []interface{}) (resp []interface{}) { 32 | resp = make([]interface{}, 2) 33 | resp[0], resp[1] = recv.(io.ReadCloser).Read(args[0].([]byte)) 34 | return 35 | }, 36 | }, 37 | "Close": star.Method{ 38 | Returns: []starlark.Value{star.Error{}}, 39 | Call: func(recv interface{}, args []interface{}) (resp []interface{}) { 40 | resp = make([]interface{}, 1) 41 | resp[0] = recv.(io.ReadCloser).Close() 42 | return 43 | }, 44 | }, 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /src/net/http/lib.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/embly/star" 7 | "github.com/embly/star/src/io" 8 | "go.starlark.net/starlark" 9 | ) 10 | 11 | var Response = star.Struct{ 12 | TypeName: "Response", 13 | Attributes: map[string]starlark.Value{ 14 | "Body": io.ReadCloser, 15 | }, 16 | } 17 | var Request = star.Struct{ 18 | TypeName: "Request", 19 | } 20 | 21 | var ResponseWriter = star.Interface{ 22 | Name: "ResponseWriter", 23 | Methods: map[string]star.Method{ 24 | "Write": star.Method{ 25 | // TODO: should accept lots of things? 26 | Args: []starlark.Value{starlark.String("")}, 27 | Returns: []starlark.Value{starlark.Int{}, star.Error{}}, 28 | Call: func(recv interface{}, args []interface{}) (resp []interface{}) { 29 | resp = make([]interface{}, 2) 30 | resp[0], resp[1] = recv.(http.ResponseWriter).Write([]byte(args[0].(string))) 31 | return 32 | }, 33 | }, 34 | "WriteHeader": star.Method{ 35 | Args: []starlark.Value{starlark.Int{}}, 36 | Call: func(recv interface{}, args []interface{}) (resp []interface{}) { 37 | recv.(http.ResponseWriter).WriteHeader(args[0].(int)) 38 | return 39 | }, 40 | }, 41 | }, 42 | } 43 | 44 | var Handler = star.Struct{ 45 | TypeName: "Handler", 46 | } 47 | 48 | var Get = star.Function{ 49 | FunctionName: "Get", 50 | Args: []starlark.Value{starlark.String("")}, 51 | Returns: []starlark.Value{Response, star.Error{}}, 52 | Call: func(args []interface{}) (resp []interface{}) { 53 | resp = make([]interface{}, 2) 54 | resp[0], resp[1] = webGet(args[0].(string)) 55 | return 56 | }, 57 | } 58 | 59 | // use a cors fetch mode so that the web demo will work 60 | // https://github.com/golang/go/wiki/WebAssembly#configuring-fetch-options-while-using-nethttp 61 | func webGet(url string) (resp *http.Response, err error) { 62 | req, err := http.NewRequest("GET", url, nil) 63 | req.Header.Add("js.fetch:mode", "cors") 64 | if err != nil { 65 | return 66 | } 67 | resp, err = http.DefaultClient.Do(req) 68 | if err != nil { 69 | req.Header.Del("js.fetch:mode") 70 | } 71 | resp, err = http.DefaultClient.Do(req) 72 | return 73 | } 74 | 75 | var ListenAndServe = star.Function{ 76 | FunctionName: "ListenAndServe", 77 | Args: []starlark.Value{starlark.String(""), Handler}, 78 | Returns: []starlark.Value{star.Error{}}, 79 | Call: func(args []interface{}) (resp []interface{}) { 80 | var handler http.Handler 81 | if args[1] != nil { 82 | handler = args[1].(http.Handler) 83 | } 84 | resp = make([]interface{}, 1) 85 | resp[0] = http.ListenAndServe(args[0].(string), handler) 86 | return 87 | }, 88 | } 89 | 90 | var HandleFunc = star.Function{ 91 | FunctionName: "HandleFunc", 92 | Args: []starlark.Value{starlark.String(""), &starlark.Function{}}, 93 | Call: func(args []interface{}) (resp []interface{}) { 94 | http.HandleFunc(args[0].(string), func(w http.ResponseWriter, req *http.Request) { 95 | fn := args[1].(*starlark.Function) 96 | starRequest := Request 97 | starRequest.Value = req 98 | starWriter := ResponseWriter 99 | starWriter.Value = w 100 | 101 | // we want a thread for every request goroutine 102 | thread := &starlark.Thread{Name: ""} 103 | _, err := starlark.Call(thread, fn, starlark.Tuple([]starlark.Value{starWriter, starRequest}), []starlark.Tuple{}) 104 | if err != nil { 105 | panic(err) 106 | } 107 | }) 108 | return 109 | }, 110 | } 111 | -------------------------------------------------------------------------------- /src/packages.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "github.com/embly/star/src/io" 5 | "github.com/embly/star/src/io/ioutil" 6 | "github.com/embly/star/src/net/http" 7 | "github.com/embly/star/src/sync" 8 | "github.com/embly/star/src/time" 9 | "go.starlark.net/starlark" 10 | ) 11 | 12 | var Packages = map[string]map[string]starlark.Value{ 13 | "io": map[string]starlark.Value{ 14 | "Reader": io.Reader, 15 | "ReadCloser": io.ReadCloser, 16 | }, 17 | "io/ioutil": map[string]starlark.Value{ 18 | "NopCloser": ioutil.NopCloser, 19 | "ReadAll": ioutil.ReadAll, 20 | }, 21 | "net/http": map[string]starlark.Value{ 22 | "Get": http.Get, 23 | "Response": http.Response, 24 | "ResponseWriter": http.ResponseWriter, 25 | "Handler": http.Handler, 26 | "ListenAndServe": http.ListenAndServe, 27 | "HandleFunc": http.HandleFunc, 28 | }, 29 | "sync": map[string]starlark.Value{ 30 | "WaitGroup": sync.WaitGroup, 31 | }, 32 | "time": map[string]starlark.Value{ 33 | "Sleep": time.Sleep, 34 | "Duration": time.Duration, 35 | "Nanosecond": time.Nanosecond, 36 | "Microsecond": time.Microsecond, 37 | "Millisecond": time.Millisecond, 38 | "Second": time.Second, 39 | "Minute": time.Minute, 40 | "Hour": time.Hour, 41 | "Now": time.Now, 42 | "Time": time.Time, 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /src/sync/lib.go: -------------------------------------------------------------------------------- 1 | package sync 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/embly/star" 7 | "go.starlark.net/starlark" 8 | ) 9 | 10 | var WaitGroup = star.Struct{ 11 | TypeName: "WaitGroup", 12 | Initialize: func() interface{} { return &sync.WaitGroup{} }, 13 | Methods: map[string]star.Method{ 14 | "Add": star.Method{ 15 | Args: []starlark.Value{starlark.Int{}}, 16 | Call: func(recv interface{}, args []interface{}) (resp []interface{}) { 17 | recv.(*sync.WaitGroup).Add(args[0].(int)) 18 | return 19 | }, 20 | }, 21 | "Done": star.Method{ 22 | Call: func(recv interface{}, args []interface{}) (resp []interface{}) { 23 | recv.(*sync.WaitGroup).Done() 24 | return 25 | }, 26 | }, 27 | "Wait": star.Method{ 28 | Call: func(recv interface{}, args []interface{}) (resp []interface{}) { 29 | recv.(*sync.WaitGroup).Wait() 30 | return 31 | }, 32 | }, 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /src/time/lib.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/embly/star" 7 | "go.starlark.net/starlark" 8 | ) 9 | 10 | var Duration = starlark.Int{} 11 | var Nanosecond = starlark.MakeInt64(1) 12 | var Microsecond = starlark.MakeInt64(1000).Mul(Nanosecond) 13 | var Millisecond = starlark.MakeInt64(1000).Mul(Microsecond) 14 | var Second = starlark.MakeInt64(1000).Mul(Millisecond) 15 | var Minute = starlark.MakeInt64(60).Mul(Second) 16 | var Hour = starlark.MakeInt64(60).Mul(Minute) 17 | 18 | var Sleep = star.Function{ 19 | FunctionName: "Sleep", 20 | Args: []starlark.Value{starlark.Int{}}, 21 | Call: func(args []interface{}) (resp []interface{}) { 22 | time.Sleep(time.Duration(args[0].(int))) 23 | return 24 | }, 25 | } 26 | 27 | var Now = star.Function{ 28 | FunctionName: "Now", 29 | Returns: []starlark.Value{Time}, 30 | Call: func(args []interface{}) (resp []interface{}) { 31 | return []interface{}{time.Now()} 32 | }, 33 | } 34 | 35 | var Time = star.Struct{ 36 | TypeName: "Time", 37 | } 38 | -------------------------------------------------------------------------------- /star.go: -------------------------------------------------------------------------------- 1 | package star 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "go.starlark.net/starlark" 8 | ) 9 | 10 | func RunScript(file string) (err error) { 11 | thread := &starlark.Thread{Name: ""} 12 | globals, err := starlark.ExecFile(thread, os.Args[1], nil, starlark.StringDict{ 13 | "require": starlark.NewBuiltin("require", Require), 14 | }) 15 | if err != nil { 16 | return 17 | } 18 | main := globals["main"] 19 | _, err = starlark.Call(thread, main, starlark.Tuple{}, nil) 20 | if err != nil { 21 | if er, ok := err.(*starlark.EvalError); ok { 22 | fmt.Println(er.Backtrace()) 23 | } 24 | return 25 | } 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package star 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go.starlark.net/starlark" 8 | ) 9 | 10 | type ByteArray struct { 11 | b []byte 12 | } 13 | 14 | func (ba ByteArray) String() string { return fmt.Sprint(ba.b) } 15 | func (ba ByteArray) Type() string { return "[]byte" } 16 | func (ba ByteArray) Freeze() {} 17 | func (ba ByteArray) Len() int { return len(ba.b) } 18 | func (ba ByteArray) Truth() starlark.Bool { return starlark.True } 19 | func (ba ByteArray) Iterate() starlark.Iterator { return &byteIterator{ByteArray: &ba} } 20 | func (ba ByteArray) Hash() (uint32, error) { return 0, errors.New("not hashable") } 21 | 22 | type byteIterator struct { 23 | *ByteArray 24 | i int 25 | } 26 | 27 | func (r *byteIterator) Next(p *starlark.Value) bool { 28 | i := starlark.MakeInt(int(r.ByteArray.b[r.i])) 29 | *p = &i 30 | r.i++ 31 | return !(r.i > len(r.ByteArray.b)-1) 32 | } 33 | 34 | func (r *byteIterator) Done() {} 35 | 36 | type Error struct { 37 | err error 38 | } 39 | 40 | func (err Error) Type() string { return "error" } 41 | func (err Error) Freeze() {} 42 | func (err Error) Truth() starlark.Bool { return starlark.Bool(err.err != nil) } 43 | func (err Error) Hash() (uint32, error) { return 0, errors.New("not hashable") } 44 | func (err Error) AttrNames() []string { return []string{"stacktrace"} } 45 | 46 | func (err Error) String() string { 47 | if err.err != nil { 48 | return err.err.Error() 49 | } else { 50 | return "" 51 | } 52 | } 53 | 54 | func (e Error) Attr(name string) (v starlark.Value, err error) { 55 | if name != "stacktrace" { 56 | return 57 | } 58 | v = starlark.NewBuiltin("stacktrace", func(thread *starlark.Thread, 59 | fn *starlark.Builtin, args starlark.Tuple, 60 | kwargs []starlark.Tuple) (v starlark.Value, err error) { 61 | v = starlark.String(fmt.Sprint(thread.CallStack())) 62 | return 63 | }) 64 | return 65 | } 66 | 67 | type Channel struct { 68 | c chan starlark.Value 69 | capacity int 70 | } 71 | 72 | func (c Channel) String() string { return c.Type() } 73 | func (c Channel) Type() string { return fmt.Sprintf("chan(%d)", c.capacity) } 74 | func (c Channel) Freeze() {} 75 | func (c Channel) Truth() starlark.Bool { return starlark.True } 76 | func (c Channel) Hash() (uint32, error) { return 0, errors.New("not hashable") } 77 | func (c Channel) AttrNames() []string { return []string{"next"} } 78 | func (c Channel) Attr(name string) (v starlark.Value, err error) { 79 | if name != c.AttrNames()[0] { 80 | return 81 | } 82 | v = starlark.NewBuiltin(c.AttrNames()[0], func(_ *starlark.Thread, 83 | fn *starlark.Builtin, args starlark.Tuple, 84 | kwargs []starlark.Tuple) (v starlark.Value, err error) { 85 | return <-c.c, nil 86 | }) 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /values.go: -------------------------------------------------------------------------------- 1 | package star 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/pkg/errors" 8 | "go.starlark.net/starlark" 9 | ) 10 | 11 | type ValueSetter interface { 12 | SetValue(v interface{}) 13 | GetValue() (v interface{}) 14 | } 15 | 16 | func makeOut(returns []starlark.Value, resp []interface{}) []starlark.Value { 17 | out := []starlark.Value{} 18 | for i, kind := range returns { 19 | switch t := kind.(type) { 20 | case Struct: 21 | t.Value = resp[i] 22 | out = append(out, t) 23 | case Interface: 24 | t.Value = resp[i] 25 | out = append(out, t) 26 | case Error: 27 | var err error 28 | if er, ok := resp[i].(error); ok { 29 | err = er 30 | } 31 | out = append(out, Error{err: err}) 32 | case ByteArray: 33 | b, _ := resp[i].([]byte) 34 | out = append(out, ByteArray{b: b}) 35 | case starlark.Int: 36 | out = append(out, starlark.MakeInt(resp[i].(int))) 37 | default: 38 | fmt.Println(kind.Type(), resp[i]) 39 | panic("no match") 40 | } 41 | } 42 | if len(out) == 0 || out[len(out)-1].Type() != "error" { 43 | out = append(out, Error{}) 44 | } 45 | return out 46 | } 47 | 48 | type Struct struct { 49 | TypeName string 50 | Value interface{} 51 | Initialize func() interface{} 52 | Print func(interface{}) string 53 | Methods map[string]Method 54 | Attributes map[string]starlark.Value 55 | } 56 | 57 | func (s Struct) String() string { 58 | if s.Value != nil { 59 | return fmt.Sprint(s.Value) 60 | } else { 61 | return s.TypeName 62 | } 63 | } 64 | func (s Struct) Type() string { return s.TypeName } 65 | func (s Struct) Name() string { return s.TypeName } 66 | func (s Struct) Freeze() {} 67 | func (s Struct) Truth() starlark.Bool { return starlark.True } 68 | func (s Struct) Hash() (uint32, error) { return 0, errors.New("not hashable") } 69 | func (s Struct) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 70 | copiedS := s 71 | copiedS.Value = copiedS.Initialize() 72 | return copiedS, nil 73 | } 74 | 75 | func (s Struct) AttrNames() []string { 76 | out := []string{} 77 | for name := range s.Methods { 78 | out = append(out, name) 79 | } 80 | for name := range s.Attributes { 81 | out = append(out, name) 82 | } 83 | return out 84 | } 85 | 86 | func (s Struct) Attr(name string) (v starlark.Value, err error) { 87 | method, ok := s.Methods[name] 88 | if ok { 89 | v = starlark.NewBuiltin(name, func(thread *starlark.Thread, 90 | fn *starlark.Builtin, args starlark.Tuple, 91 | kwargs []starlark.Tuple) (v starlark.Value, err error) { 92 | values, err := ValidateArgs(method.Args, args) 93 | if err != nil { 94 | out := makeOut(method.Returns, make([]interface{}, len(method.Returns))) 95 | out[len(out)-1] = Error{err: err} 96 | return 97 | } 98 | resp := method.Call(s.Value, values) 99 | return starlark.Tuple(makeOut(method.Returns, resp)), nil 100 | }) 101 | } 102 | 103 | attrType, ok := s.Attributes[name] 104 | if ok { 105 | s := reflect.ValueOf(s.Value).Elem() 106 | switch t := attrType.(type) { 107 | case Struct: 108 | t.Value = s.FieldByName(name).Interface() 109 | v = t 110 | return 111 | case Interface: 112 | t.Value = s.FieldByName(name).Interface() 113 | v = t 114 | return 115 | default: 116 | panic("NO ATTRIB") 117 | } 118 | } 119 | return 120 | } 121 | 122 | type Method struct { 123 | Args []starlark.Value 124 | Returns []starlark.Value 125 | Call MethodCall 126 | } 127 | 128 | type MethodCall func(interface{}, []interface{}) []interface{} 129 | 130 | type Interface struct { 131 | Name string 132 | Methods map[string]Method 133 | Value interface{} 134 | } 135 | 136 | func (i Interface) String() string { return i.Name } 137 | func (i Interface) Type() string { return i.Name } 138 | func (i Interface) Freeze() {} 139 | func (i Interface) Truth() starlark.Bool { return starlark.True } 140 | func (i Interface) Hash() (uint32, error) { return 0, errors.New("not hashable") } 141 | func (i Interface) AttrNames() []string { 142 | out := []string{} 143 | for name := range i.Methods { 144 | out = append(out, name) 145 | } 146 | return out 147 | } 148 | 149 | func (i Interface) Attr(name string) (v starlark.Value, err error) { 150 | method, ok := i.Methods[name] 151 | if ok { 152 | v = starlark.NewBuiltin(name, func(thread *starlark.Thread, 153 | fn *starlark.Builtin, args starlark.Tuple, 154 | kwargs []starlark.Tuple) (v starlark.Value, err error) { 155 | values, err := ValidateArgs(method.Args, args) 156 | if err != nil { 157 | out := makeOut(method.Returns, make([]interface{}, len(method.Returns))) 158 | out[len(out)-1] = Error{err: err} 159 | return 160 | } 161 | resp := method.Call(i.Value, values) 162 | return starlark.Tuple(makeOut(method.Returns, resp)), nil 163 | }) 164 | } 165 | return 166 | } 167 | 168 | type Function struct { 169 | FunctionName string 170 | Args []starlark.Value 171 | Returns []starlark.Value 172 | Call func([]interface{}) []interface{} 173 | } 174 | 175 | func (f Function) String() string { return f.FunctionName } 176 | func (f Function) Name() string { return f.FunctionName } 177 | func (f Function) Type() string { return f.FunctionName } // TODO: return the actual type signature? 178 | func (f Function) Freeze() {} 179 | func (f Function) Truth() starlark.Bool { return starlark.True } 180 | func (f Function) Hash() (uint32, error) { return 0, errors.New("not hashable") } 181 | 182 | func (f Function) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (v starlark.Value, err error) { 183 | values, err := ValidateArgs(f.Args, args) 184 | if err != nil { 185 | out := makeOut(f.Returns, make([]interface{}, len(f.Returns))) 186 | out[len(out)-1] = Error{err: err} 187 | return 188 | } 189 | resp := f.Call(values) 190 | return starlark.Tuple(makeOut(f.Returns, resp)), nil 191 | } 192 | 193 | func (f Function) Builtin() *starlark.Builtin { 194 | return starlark.NewBuiltin(f.Name(), func(thread *starlark.Thread, 195 | fn *starlark.Builtin, args starlark.Tuple, 196 | kwargs []starlark.Tuple) (v starlark.Value, err error) { 197 | return f.CallInternal(thread, args, kwargs) 198 | }) 199 | } 200 | 201 | // ValidateArgs 202 | func ValidateArgs(types []starlark.Value, args starlark.Tuple) (values []interface{}, err error) { 203 | // TODO: could take kwargs and check against type name 204 | 205 | if len(types) != args.Len() { 206 | // TODO: better error message 207 | err = errors.Errorf(`not enough arguments`) 208 | return 209 | } 210 | if args.Len() == 0 { 211 | return 212 | } 213 | 214 | for i := 0; i < args.Len(); i++ { 215 | val := args.Index(i) 216 | in, ok := val.(Interface) 217 | in2, ok2 := types[i].(Interface) 218 | // check if interface satisfies another 219 | 220 | _, fnok := types[i].(*starlark.Function) 221 | f2, fn2ok := val.(*starlark.Function) 222 | // functions are different types 223 | 224 | if ok && ok2 { 225 | for name := range in2.Methods { 226 | _, ok := in.Methods[name] 227 | if !ok { 228 | err = errors.Errorf( 229 | `argument %d was passed interface %s which doesn't `+ 230 | `implement "%s". missing method "%s"`, 231 | i+1, 232 | val.Type(), 233 | types[i], 234 | name, 235 | ) 236 | return 237 | } 238 | } 239 | } else if fnok && fn2ok { 240 | // TODO: further checks that the functions match? 241 | values = append(values, f2) 242 | continue 243 | } else if types[i].Type() != val.Type() { 244 | 245 | err = errors.Errorf( 246 | `argument %d is the wrong type, must `+ 247 | `be "%s" but got "%s"`, 248 | i+1, types[i], val.Type()) 249 | return 250 | } 251 | values = append(values, underlyingValue(val)) 252 | } 253 | return 254 | } 255 | 256 | func underlyingValue(val starlark.Value) interface{} { 257 | switch v := val.(type) { 258 | case Struct: 259 | return v.Value 260 | case Interface: 261 | return v.Value 262 | case starlark.String: 263 | return string(v) 264 | case starlark.Int: 265 | i, err := starlark.AsInt32(v) 266 | if err != nil { 267 | panic(err) 268 | } 269 | return i 270 | default: 271 | panic(v) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /web-repl/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-transform-runtime", { 4 | "corejs": 2 5 | }] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /web-repl/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .cache 4 | src/main.js 5 | src/main.js.map 6 | -------------------------------------------------------------------------------- /web-repl/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /web-repl/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maxmcd/webtty-node-go:latest as builder 2 | 3 | COPY . . 4 | RUN go get -v . \ 5 | && cd ./web-client \ 6 | && npm install \ 7 | && npm run build 8 | 9 | FROM nginx:alpine 10 | 11 | RUN echo "application/wasm wasm" >> /etc/mime.types \ 12 | && sed -i -e 's/wmlc;/wmlc;\n application\/wasm wasm;\n/g' /etc/nginx/mime.types 13 | 14 | COPY --from=builder \ 15 | /go/src/github.com/maxmcd/webtty/web-client/dist \ 16 | /usr/share/nginx/html 17 | 18 | -------------------------------------------------------------------------------- /web-repl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "star-repl", 3 | "version": "0.0.1", 4 | "description": "a repl for star", 5 | "homepage": "https://embly.github.io/star/", 6 | "main": "src/app.ts", 7 | "scripts": { 8 | "build": "npm run go-build && parcel build src/index.html --public-url=https://embly.github.io/star/ ", 9 | "go-build": "GOOS=js GOARCH=wasm go build -o ./dist/main.wasm ./src", 10 | "start": "parcel serve src/index.html", 11 | "test": "echo notests", 12 | "deploy": "npm run build && gh-pages -d dist" 13 | }, 14 | "author": "Max McDonnell", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@babel/runtime-corejs2": "^7.1.5", 18 | "gh-pages": "^2.0.1", 19 | "parcel-bundler": "^1.12.4", 20 | "typescript": "^3.1.6", 21 | "xterm": "^3.8.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.8.4", 25 | "@babel/plugin-transform-runtime": "^7.8.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web-repl/src/app.css: -------------------------------------------------------------------------------- 1 | #terminal { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | width: auto; 8 | height: auto; 9 | z-index: 255; 10 | } 11 | -------------------------------------------------------------------------------- /web-repl/src/app.ts: -------------------------------------------------------------------------------- 1 | import Terminal from "xterm/src/xterm.ts"; 2 | import * as fullscreen from "xterm/src/addons/fullscreen/fullscreen.ts"; 3 | import * as fit from "xterm/src/addons/fit/fit.ts"; 4 | 5 | import "xterm/dist/xterm.css"; 6 | import "xterm/dist/addons/fullscreen/fullscreen.css"; 7 | 8 | // imports "Go" 9 | import "./wasm_exec.js"; 10 | 11 | Terminal.applyAddon(fullscreen); 12 | Terminal.applyAddon(fit); 13 | 14 | // Polyfill for WebAssembly on Safari 15 | if (!WebAssembly.instantiateStreaming) { 16 | WebAssembly.instantiateStreaming = async (resp, importObject) => { 17 | const source = await (await resp).arrayBuffer(); 18 | return await WebAssembly.instantiate(source, importObject); 19 | }; 20 | } 21 | 22 | const term = new Terminal(); 23 | term.open(document.getElementById("terminal")); 24 | term.toggleFullScreen(); 25 | term.fit(); 26 | 27 | term.writeln("loading..."); 28 | term.writeln("(downloading and parsing wasm binary, this could take a while)"); 29 | window.onresize = () => { 30 | term.fit(); 31 | }; 32 | 33 | const go = new Go(); 34 | WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then( 35 | result => { 36 | let mod = result.module; 37 | let inst = result.instance; 38 | go.run(inst); 39 | } 40 | ); 41 | 42 | let prompt_prefix = ">>> "; 43 | 44 | window.set_prompt = val => { 45 | prompt_prefix = val; 46 | }; 47 | 48 | const up = 38; 49 | const down = 40; 50 | 51 | function prompt(term) { 52 | term.write("\r\n" + prompt_prefix); 53 | } 54 | 55 | window.term_write = val => { 56 | term.write("\r\n"); 57 | term.write(val); 58 | }; 59 | 60 | term.attachCustomKeyEventHandler(e => { 61 | let out = e.key !== "v" && !e.ctrlKey; 62 | return out; 63 | }); 64 | // called when go is done initializing 65 | window.go_ready = () => { 66 | term.clear(); 67 | term.writeln("Welcome to the star repl."); 68 | term.writeln( 69 | 'Try writing some python or import a Go lib with require("net/http")' 70 | ); 71 | term.prompt = () => { 72 | term.write("\r\n" + prompt_prefix); 73 | }; 74 | 75 | prompt(term); 76 | var buffer = []; 77 | term.onKey(e => { 78 | const printable = 79 | !e.domEvent.altKey && 80 | !e.domEvent.altGraphKey && 81 | !e.domEvent.ctrlKey && 82 | !e.domEvent.metaKey; 83 | 84 | if (e.domEvent.keyCode === up || e.domEvent.keyCode == down) { 85 | } else if (e.domEvent.keyCode === 13) { 86 | newLine(buffer.join("")); 87 | buffer = []; 88 | prompt(term); 89 | } else if (e.domEvent.keyCode === 8) { 90 | // Do not delete the prompt 91 | if (term._core.buffer.x > 4) { 92 | buffer.pop(); 93 | term.write("\b \b"); 94 | } 95 | } else if (printable) { 96 | buffer.push(e.key); 97 | term.write(e.key); 98 | } 99 | }); 100 | }; 101 | -------------------------------------------------------------------------------- /web-repl/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web-repl/src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "strconv" 8 | "strings" 9 | "syscall/js" 10 | 11 | "github.com/embly/star" 12 | "github.com/embly/star/src" 13 | "go.starlark.net/resolve" 14 | "go.starlark.net/starlark" 15 | "go.starlark.net/syntax" 16 | ) 17 | 18 | func main() { 19 | registerCallbacks() 20 | c := make(chan struct{}, 0) 21 | star.AddPackages(src.Packages) 22 | thread := &starlark.Thread{Name: ""} 23 | globals := starlark.StringDict{ 24 | "require": starlark.NewBuiltin("require", star.Require), 25 | } 26 | js.Global().Get("window").Call("go_ready") 27 | REPL(thread, globals) 28 | <-c 29 | 30 | } 31 | 32 | var lineChan = make(chan string) 33 | 34 | func registerCallbacks() { 35 | js.Global().Set("newLine", js.FuncOf(newLine)) 36 | } 37 | 38 | func newLine(this js.Value, args []js.Value) interface{} { 39 | lineChan <- args[0].String() 40 | return nil 41 | } 42 | 43 | func REPL(thread *starlark.Thread, globals starlark.StringDict) { 44 | for { 45 | if err := rep(thread, globals); err != nil { 46 | log.Fatal(err) 47 | } 48 | } 49 | fmt.Println() 50 | } 51 | 52 | // rep reads, evaluates, and prints one item. 53 | // 54 | // It returns an error (possibly readline.ErrInterrupt) 55 | // only if readline failed. Starlark errors are printed. 56 | func rep(thread *starlark.Thread, globals starlark.StringDict) error { 57 | // Each item gets its own context, 58 | // which is cancelled by a SIGINT. 59 | // 60 | // Note: during Readline calls, Control-C causes Readline to return 61 | // ErrInterrupt but does not generate a SIGINT. 62 | 63 | eof := false 64 | js.Global().Get("window").Call("set_prompt", js.ValueOf(">>> ")) 65 | // readline returns EOF, ErrInterrupted, or a line including "\n". 66 | // rl.SetPrompt(">>> ") 67 | readline := func() ([]byte, error) { 68 | line := <-lineChan 69 | // line, err := rl.Readline() 70 | js.Global().Get("window").Call("set_prompt", js.ValueOf("... ")) 71 | // rl.SetPrompt("... ") 72 | // if err != nil { 73 | // if err == io.EOF { 74 | // eof = true 75 | // } 76 | // return nil, err 77 | // } 78 | return []byte(line + "\n"), nil 79 | } 80 | 81 | // parse 82 | f, err := syntax.ParseCompoundStmt("", readline) 83 | if err != nil { 84 | if eof { 85 | return io.EOF 86 | } 87 | PrintError(err) 88 | return nil 89 | } 90 | 91 | // Treat load bindings as global (like they used to be) in the REPL. 92 | // This is a workaround for github.com/google/starlark-go/issues/224. 93 | // TODO(adonovan): not safe wrt concurrent interpreters. 94 | // Come up with a more principled solution (or plumb options everywhere). 95 | defer func(prev bool) { resolve.LoadBindsGlobally = prev }(resolve.LoadBindsGlobally) 96 | resolve.LoadBindsGlobally = true 97 | 98 | if expr := soleExpr(f); expr != nil { 99 | // eval 100 | v, err := starlark.EvalExpr(thread, expr, globals) 101 | if err != nil { 102 | PrintError(err) 103 | return nil 104 | } 105 | 106 | // print 107 | if v != starlark.None { 108 | Println(v) 109 | } 110 | } else if err := starlark.ExecREPLChunk(f, thread, globals); err != nil { 111 | PrintError(err) 112 | return nil 113 | } 114 | 115 | return nil 116 | } 117 | 118 | func soleExpr(f *syntax.File) syntax.Expr { 119 | if len(f.Stmts) == 1 { 120 | if stmt, ok := f.Stmts[0].(*syntax.ExprStmt); ok { 121 | return stmt.X 122 | } 123 | } 124 | return nil 125 | } 126 | 127 | func Println(v interface{}) { 128 | fmt.Println(strconv.Quote(fmt.Sprint(v))) 129 | 130 | js.Global().Get("window").Call( 131 | "term_write", 132 | js.ValueOf( 133 | strings.Replace( 134 | fmt.Sprintln(v), "\n", "\n\r", -1), 135 | )) 136 | } 137 | 138 | // PrintError prints the error to stderr, 139 | // or its backtrace if it is a Starlark evaluation error. 140 | func PrintError(err error) { 141 | if evalErr, ok := err.(*starlark.EvalError); ok { 142 | Println(evalErr.Backtrace()) 143 | } else { 144 | Println(err) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /web-repl/src/wasm_exec.js: -------------------------------------------------------------------------------- 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 | (() => { 6 | // Map multiple JavaScript environments to a single common API, 7 | // preferring web standards over Node.js API. 8 | // 9 | // Environments considered: 10 | // - Browsers 11 | // - Node.js 12 | // - Electron 13 | // - Parcel 14 | 15 | if (typeof global !== "undefined") { 16 | // global already exists 17 | } else if (typeof window !== "undefined") { 18 | window.global = window; 19 | } else if (typeof self !== "undefined") { 20 | self.global = self; 21 | } else { 22 | throw new Error( 23 | "cannot export Go (neither global, window nor self is defined)" 24 | ); 25 | } 26 | 27 | if (!global.require && typeof require !== "undefined") { 28 | global.require = require; 29 | } 30 | 31 | if (!global.fs && global.require) { 32 | global.fs = require("fs"); 33 | } 34 | 35 | // no sure where fs is being set as an empty object... 36 | if (!global.fs || !global.fs.foo) { 37 | let outputBuf = ""; 38 | global.fs = { 39 | constants: { 40 | O_WRONLY: -1, 41 | O_RDWR: -1, 42 | O_CREAT: -1, 43 | O_TRUNC: -1, 44 | O_APPEND: -1, 45 | O_EXCL: -1 46 | }, // unused 47 | writeSync(fd, buf) { 48 | outputBuf += decoder.decode(buf); 49 | const nl = outputBuf.lastIndexOf("\n"); 50 | if (nl != -1) { 51 | console.log(outputBuf.substr(0, nl)); 52 | outputBuf = outputBuf.substr(nl + 1); 53 | } 54 | return buf.length; 55 | }, 56 | write(fd, buf, offset, length, position, callback) { 57 | if (offset !== 0 || length !== buf.length || position !== null) { 58 | throw new Error("not implemented"); 59 | } 60 | const n = this.writeSync(fd, buf); 61 | callback(null, n); 62 | }, 63 | open(path, flags, mode, callback) { 64 | const err = new Error("not implemented"); 65 | err.code = "ENOSYS"; 66 | callback(err); 67 | }, 68 | read(fd, buffer, offset, length, position, callback) { 69 | const err = new Error("not implemented"); 70 | err.code = "ENOSYS"; 71 | callback(err); 72 | }, 73 | fsync(fd, callback) { 74 | callback(null); 75 | } 76 | }; 77 | } 78 | 79 | if (!global.crypto) { 80 | const nodeCrypto = require("crypto"); 81 | global.crypto = { 82 | getRandomValues(b) { 83 | nodeCrypto.randomFillSync(b); 84 | } 85 | }; 86 | } 87 | 88 | if (!global.performance) { 89 | global.performance = { 90 | now() { 91 | const [sec, nsec] = process.hrtime(); 92 | return sec * 1000 + nsec / 1000000; 93 | } 94 | }; 95 | } 96 | 97 | if (!global.TextEncoder) { 98 | global.TextEncoder = require("util").TextEncoder; 99 | } 100 | 101 | if (!global.TextDecoder) { 102 | global.TextDecoder = require("util").TextDecoder; 103 | } 104 | 105 | // End of polyfills for common API. 106 | 107 | const encoder = new TextEncoder("utf-8"); 108 | const decoder = new TextDecoder("utf-8"); 109 | 110 | global.Go = class { 111 | constructor() { 112 | this.argv = ["js"]; 113 | this.env = {}; 114 | this.exit = code => { 115 | if (code !== 0) { 116 | console.warn("exit code:", code); 117 | } 118 | }; 119 | this._exitPromise = new Promise(resolve => { 120 | this._resolveExitPromise = resolve; 121 | }); 122 | this._pendingEvent = null; 123 | this._scheduledTimeouts = new Map(); 124 | this._nextCallbackTimeoutID = 1; 125 | 126 | const mem = () => { 127 | // The buffer may change when requesting more memory. 128 | return new DataView(this._inst.exports.mem.buffer); 129 | }; 130 | 131 | const setInt64 = (addr, v) => { 132 | mem().setUint32(addr + 0, v, true); 133 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); 134 | }; 135 | 136 | const getInt64 = addr => { 137 | const low = mem().getUint32(addr + 0, true); 138 | const high = mem().getInt32(addr + 4, true); 139 | return low + high * 4294967296; 140 | }; 141 | 142 | const loadValue = addr => { 143 | const f = mem().getFloat64(addr, true); 144 | if (f === 0) { 145 | return undefined; 146 | } 147 | if (!isNaN(f)) { 148 | return f; 149 | } 150 | 151 | const id = mem().getUint32(addr, true); 152 | return this._values[id]; 153 | }; 154 | 155 | const storeValue = (addr, v) => { 156 | const nanHead = 0x7ff80000; 157 | 158 | if (typeof v === "number") { 159 | if (isNaN(v)) { 160 | mem().setUint32(addr + 4, nanHead, true); 161 | mem().setUint32(addr, 0, true); 162 | return; 163 | } 164 | if (v === 0) { 165 | mem().setUint32(addr + 4, nanHead, true); 166 | mem().setUint32(addr, 1, true); 167 | return; 168 | } 169 | mem().setFloat64(addr, v, true); 170 | return; 171 | } 172 | 173 | switch (v) { 174 | case undefined: 175 | mem().setFloat64(addr, 0, true); 176 | return; 177 | case null: 178 | mem().setUint32(addr + 4, nanHead, true); 179 | mem().setUint32(addr, 2, true); 180 | return; 181 | case true: 182 | mem().setUint32(addr + 4, nanHead, true); 183 | mem().setUint32(addr, 3, true); 184 | return; 185 | case false: 186 | mem().setUint32(addr + 4, nanHead, true); 187 | mem().setUint32(addr, 4, true); 188 | return; 189 | } 190 | 191 | let ref = this._refs.get(v); 192 | if (ref === undefined) { 193 | ref = this._values.length; 194 | this._values.push(v); 195 | this._refs.set(v, ref); 196 | } 197 | let typeFlag = 0; 198 | switch (typeof v) { 199 | case "string": 200 | typeFlag = 1; 201 | break; 202 | case "symbol": 203 | typeFlag = 2; 204 | break; 205 | case "function": 206 | typeFlag = 3; 207 | break; 208 | } 209 | mem().setUint32(addr + 4, nanHead | typeFlag, true); 210 | mem().setUint32(addr, ref, true); 211 | }; 212 | 213 | const loadSlice = addr => { 214 | const array = getInt64(addr + 0); 215 | const len = getInt64(addr + 8); 216 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 217 | }; 218 | 219 | const loadSliceOfValues = addr => { 220 | const array = getInt64(addr + 0); 221 | const len = getInt64(addr + 8); 222 | const a = new Array(len); 223 | for (let i = 0; i < len; i++) { 224 | a[i] = loadValue(array + i * 8); 225 | } 226 | return a; 227 | }; 228 | 229 | const loadString = addr => { 230 | const saddr = getInt64(addr + 0); 231 | const len = getInt64(addr + 8); 232 | return decoder.decode( 233 | new DataView(this._inst.exports.mem.buffer, saddr, len) 234 | ); 235 | }; 236 | 237 | const timeOrigin = Date.now() - performance.now(); 238 | this.importObject = { 239 | go: { 240 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 241 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 242 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 243 | // This changes the SP, thus we have to update the SP used by the imported function. 244 | 245 | // func wasmExit(code int32) 246 | "runtime.wasmExit": sp => { 247 | const code = mem().getInt32(sp + 8, true); 248 | this.exited = true; 249 | delete this._inst; 250 | delete this._values; 251 | delete this._refs; 252 | this.exit(code); 253 | }, 254 | 255 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 256 | "runtime.wasmWrite": sp => { 257 | const fd = getInt64(sp + 8); 258 | const p = getInt64(sp + 16); 259 | const n = mem().getInt32(sp + 24, true); 260 | fs.writeSync( 261 | fd, 262 | new Uint8Array(this._inst.exports.mem.buffer, p, n) 263 | ); 264 | }, 265 | 266 | // func nanotime() int64 267 | "runtime.nanotime": sp => { 268 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 269 | }, 270 | 271 | // func walltime() (sec int64, nsec int32) 272 | "runtime.walltime": sp => { 273 | const msec = new Date().getTime(); 274 | setInt64(sp + 8, msec / 1000); 275 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); 276 | }, 277 | 278 | // func scheduleTimeoutEvent(delay int64) int32 279 | "runtime.scheduleTimeoutEvent": sp => { 280 | const id = this._nextCallbackTimeoutID; 281 | this._nextCallbackTimeoutID++; 282 | this._scheduledTimeouts.set( 283 | id, 284 | setTimeout( 285 | () => { 286 | this._resume(); 287 | while (this._scheduledTimeouts.has(id)) { 288 | // for some reason Go failed to register the timeout event, log and try again 289 | // (temporary workaround for https://github.com/golang/go/issues/28975) 290 | console.warn("scheduleTimeoutEvent: missed timeout event"); 291 | this._resume(); 292 | } 293 | }, 294 | getInt64(sp + 8) + 1 // setTimeout has been seen to fire up to 1 millisecond early 295 | ) 296 | ); 297 | mem().setInt32(sp + 16, id, true); 298 | }, 299 | 300 | // func clearTimeoutEvent(id int32) 301 | "runtime.clearTimeoutEvent": sp => { 302 | const id = mem().getInt32(sp + 8, true); 303 | clearTimeout(this._scheduledTimeouts.get(id)); 304 | this._scheduledTimeouts.delete(id); 305 | }, 306 | 307 | // func getRandomData(r []byte) 308 | "runtime.getRandomData": sp => { 309 | crypto.getRandomValues(loadSlice(sp + 8)); 310 | }, 311 | 312 | // func stringVal(value string) ref 313 | "syscall/js.stringVal": sp => { 314 | storeValue(sp + 24, loadString(sp + 8)); 315 | }, 316 | 317 | // func valueGet(v ref, p string) ref 318 | "syscall/js.valueGet": sp => { 319 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 320 | sp = this._inst.exports.getsp(); // see comment above 321 | storeValue(sp + 32, result); 322 | }, 323 | 324 | // func valueSet(v ref, p string, x ref) 325 | "syscall/js.valueSet": sp => { 326 | Reflect.set( 327 | loadValue(sp + 8), 328 | loadString(sp + 16), 329 | loadValue(sp + 32) 330 | ); 331 | }, 332 | 333 | // func valueIndex(v ref, i int) ref 334 | "syscall/js.valueIndex": sp => { 335 | storeValue( 336 | sp + 24, 337 | Reflect.get(loadValue(sp + 8), getInt64(sp + 16)) 338 | ); 339 | }, 340 | 341 | // valueSetIndex(v ref, i int, x ref) 342 | "syscall/js.valueSetIndex": sp => { 343 | Reflect.set( 344 | loadValue(sp + 8), 345 | getInt64(sp + 16), 346 | loadValue(sp + 24) 347 | ); 348 | }, 349 | 350 | // func valueCall(v ref, m string, args []ref) (ref, bool) 351 | "syscall/js.valueCall": sp => { 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(); // see comment above 358 | storeValue(sp + 56, result); 359 | mem().setUint8(sp + 64, 1); 360 | } catch (err) { 361 | storeValue(sp + 56, err); 362 | mem().setUint8(sp + 64, 0); 363 | } 364 | }, 365 | 366 | // func valueInvoke(v ref, args []ref) (ref, bool) 367 | "syscall/js.valueInvoke": sp => { 368 | try { 369 | const v = loadValue(sp + 8); 370 | const args = loadSliceOfValues(sp + 16); 371 | const result = Reflect.apply(v, undefined, args); 372 | sp = this._inst.exports.getsp(); // see comment above 373 | storeValue(sp + 40, result); 374 | mem().setUint8(sp + 48, 1); 375 | } catch (err) { 376 | storeValue(sp + 40, err); 377 | mem().setUint8(sp + 48, 0); 378 | } 379 | }, 380 | 381 | // func valueNew(v ref, args []ref) (ref, bool) 382 | "syscall/js.valueNew": sp => { 383 | try { 384 | const v = loadValue(sp + 8); 385 | const args = loadSliceOfValues(sp + 16); 386 | const result = Reflect.construct(v, args); 387 | sp = this._inst.exports.getsp(); // see comment above 388 | storeValue(sp + 40, result); 389 | mem().setUint8(sp + 48, 1); 390 | } catch (err) { 391 | storeValue(sp + 40, err); 392 | mem().setUint8(sp + 48, 0); 393 | } 394 | }, 395 | 396 | // func valueLength(v ref) int 397 | "syscall/js.valueLength": sp => { 398 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 399 | }, 400 | 401 | // valuePrepareString(v ref) (ref, int) 402 | "syscall/js.valuePrepareString": sp => { 403 | const str = encoder.encode(String(loadValue(sp + 8))); 404 | storeValue(sp + 16, str); 405 | setInt64(sp + 24, str.length); 406 | }, 407 | 408 | // valueLoadString(v ref, b []byte) 409 | "syscall/js.valueLoadString": sp => { 410 | const str = loadValue(sp + 8); 411 | loadSlice(sp + 16).set(str); 412 | }, 413 | 414 | // func valueInstanceOf(v ref, t ref) bool 415 | "syscall/js.valueInstanceOf": sp => { 416 | mem().setUint8( 417 | sp + 24, 418 | loadValue(sp + 8) instanceof loadValue(sp + 16) 419 | ); 420 | }, 421 | 422 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 423 | "syscall/js.copyBytesToGo": sp => { 424 | const dst = loadSlice(sp + 8); 425 | const src = loadValue(sp + 32); 426 | if (!(src instanceof Uint8Array)) { 427 | mem().setUint8(sp + 48, 0); 428 | return; 429 | } 430 | const toCopy = src.subarray(0, dst.length); 431 | dst.set(toCopy); 432 | setInt64(sp + 40, toCopy.length); 433 | mem().setUint8(sp + 48, 1); 434 | }, 435 | 436 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 437 | "syscall/js.copyBytesToJS": sp => { 438 | const dst = loadValue(sp + 8); 439 | const src = loadSlice(sp + 16); 440 | if (!(dst instanceof Uint8Array)) { 441 | mem().setUint8(sp + 48, 0); 442 | return; 443 | } 444 | const toCopy = src.subarray(0, dst.length); 445 | dst.set(toCopy); 446 | setInt64(sp + 40, toCopy.length); 447 | mem().setUint8(sp + 48, 1); 448 | }, 449 | 450 | debug: value => { 451 | console.log(value); 452 | } 453 | } 454 | }; 455 | } 456 | 457 | async run(instance) { 458 | this._inst = instance; 459 | this._values = [ 460 | // TODO: garbage collection 461 | NaN, 462 | 0, 463 | null, 464 | true, 465 | false, 466 | global, 467 | this 468 | ]; 469 | this._refs = new Map(); 470 | this.exited = false; 471 | 472 | const mem = new DataView(this._inst.exports.mem.buffer); 473 | 474 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 475 | let offset = 4096; 476 | 477 | const strPtr = str => { 478 | const ptr = offset; 479 | const bytes = encoder.encode(str + "\0"); 480 | new Uint8Array(mem.buffer, offset, bytes.length).set(bytes); 481 | offset += bytes.length; 482 | if (offset % 8 !== 0) { 483 | offset += 8 - (offset % 8); 484 | } 485 | return ptr; 486 | }; 487 | 488 | const argc = this.argv.length; 489 | 490 | const argvPtrs = []; 491 | this.argv.forEach(arg => { 492 | argvPtrs.push(strPtr(arg)); 493 | }); 494 | 495 | const keys = Object.keys(this.env).sort(); 496 | argvPtrs.push(keys.length); 497 | keys.forEach(key => { 498 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 499 | }); 500 | 501 | const argv = offset; 502 | argvPtrs.forEach(ptr => { 503 | mem.setUint32(offset, ptr, true); 504 | mem.setUint32(offset + 4, 0, true); 505 | offset += 8; 506 | }); 507 | 508 | this._inst.exports.run(argc, argv); 509 | if (this.exited) { 510 | this._resolveExitPromise(); 511 | } 512 | await this._exitPromise; 513 | } 514 | 515 | _resume() { 516 | if (this.exited) { 517 | throw new Error("Go program has already exited"); 518 | } 519 | this._inst.exports.resume(); 520 | if (this.exited) { 521 | this._resolveExitPromise(); 522 | } 523 | } 524 | 525 | _makeFuncWrapper(id) { 526 | const go = this; 527 | return function() { 528 | const event = { id: id, this: this, args: arguments }; 529 | go._pendingEvent = event; 530 | go._resume(); 531 | return event.result; 532 | }; 533 | } 534 | }; 535 | 536 | if ( 537 | global.require && 538 | global.require.main === module && 539 | global.process && 540 | global.process.versions && 541 | !global.process.versions.electron 542 | ) { 543 | if (process.argv.length < 3) { 544 | console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 545 | process.exit(1); 546 | } 547 | 548 | const go = new Go(); 549 | go.argv = process.argv.slice(2); 550 | go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); 551 | go.exit = process.exit; 552 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject) 553 | .then(result => { 554 | process.on("exit", code => { 555 | // Node.js exits if no event handler is pending 556 | if (code === 0 && !go.exited) { 557 | // deadlock, make Go print error and stack traces 558 | go._pendingEvent = { id: 0 }; 559 | go._resume(); 560 | } 561 | }); 562 | return go.run(result.instance); 563 | }) 564 | .catch(err => { 565 | console.error(err); 566 | process.exit(1); 567 | }); 568 | } 569 | })(); 570 | -------------------------------------------------------------------------------- /web-repl/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "newLine": "LF", 6 | "outDir": "./lib/", 7 | "target": "es5", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "jsx": "preserve", 11 | "lib": [ 12 | "es2017", 13 | "dom" 14 | ], 15 | "strict": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": [ 22 | "src/**/*" 23 | ], 24 | "exclude": [ 25 | ".git", 26 | "node_modules" 27 | ] 28 | } 29 | --------------------------------------------------------------------------------