├── .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("