github.com/teddydd/sh@v2.6.4+incompatible/_js/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "math" 8 "os" 9 "strings" 10 11 "github.com/gopherjs/gopherjs/js" 12 13 "mvdan.cc/sh/syntax" 14 ) 15 16 func main() { 17 exps := js.Module.Get("exports") 18 19 exps.Set("syntax", map[string]interface{}{}) 20 21 stx := exps.Get("syntax") 22 23 // Type helpers just for JS 24 stx.Set("NodeType", func(v interface{}) string { 25 if v == nil { 26 return "nil" 27 } 28 node, ok := v.(syntax.Node) 29 if !ok { 30 throw("NodeType requires a Node argument") 31 } 32 typ := fmt.Sprintf("%T", node) 33 if i := strings.LastIndexAny(typ, "*.]"); i >= 0 { 34 typ = typ[i+1:] 35 } 36 return typ 37 }) 38 39 // Parser 40 stx.Set("NewParser", func(options ...func(interface{})) *js.Object { 41 p := syntax.NewParser() 42 jp := js.MakeFullWrapper(jsParser{p}) 43 // Apply the options after we've wrapped the parser, as 44 // otherwise we cannot internalise the value. 45 for _, opt := range options { 46 opt(jp) 47 } 48 return jp 49 }) 50 51 stx.Set("KeepComments", func(v interface{}) { 52 syntax.KeepComments(v.(jsParser).Parser) 53 }) 54 stx.Set("Variant", func(l syntax.LangVariant) func(interface{}) { 55 if math.IsNaN(float64(l)) { 56 throw("Variant requires a LangVariant argument") 57 } 58 return func(v interface{}) { 59 syntax.Variant(l)(v.(jsParser).Parser) 60 } 61 }) 62 stx.Set("LangBash", syntax.LangBash) 63 stx.Set("LangPOSIX", syntax.LangPOSIX) 64 stx.Set("LangMirBSDKorn", syntax.LangMirBSDKorn) 65 stx.Set("StopAt", func(word string) func(interface{}) { 66 return func(v interface{}) { 67 syntax.StopAt(word)(v.(jsParser).Parser) 68 } 69 }) 70 71 // Printer 72 stx.Set("NewPrinter", func() *js.Object { 73 p := syntax.NewPrinter() 74 return js.MakeFullWrapper(jsPrinter{p}) 75 }) 76 77 // Syntax utilities 78 stx.Set("Walk", func(node syntax.Node, jsFn func(*js.Object) bool) { 79 f := func(node syntax.Node) bool { 80 if node == nil { 81 return jsFn(nil) 82 } 83 return jsFn(js.MakeFullWrapper(node)) 84 } 85 syntax.Walk(node, f) 86 87 }) 88 stx.Set("DebugPrint", func(node syntax.Node) { 89 syntax.DebugPrint(os.Stdout, node) 90 }) 91 } 92 93 func throw(v interface{}) { 94 js.Global.Call("$throwRuntimeError", fmt.Sprint(v)) 95 } 96 97 // streamReader is an io.Readder wrapper for Node's stream.Readable. See 98 // https://nodejs.org/api/stream.html#stream_class_stream_readable 99 // TODO: support https://streams.spec.whatwg.org/#rs-class too? 100 type streamReader struct { 101 stream *js.Object 102 } 103 104 func (r streamReader) Read(p []byte) (n int, err error) { 105 obj := r.stream.Call("read", len(p)) 106 if obj == nil { 107 return 0, io.EOF 108 } 109 bs := []byte(obj.String()) 110 return copy(p, bs), nil 111 } 112 113 type jsParser struct { 114 *syntax.Parser 115 } 116 117 func adaptReader(src *js.Object) io.Reader { 118 if src.Get("read") != js.Undefined { 119 return streamReader{stream: src} 120 } 121 return strings.NewReader(src.String()) 122 } 123 124 func (p jsParser) Parse(src *js.Object, name string) *js.Object { 125 f, err := p.Parser.Parse(adaptReader(src), name) 126 if err != nil { 127 throw(err) 128 } 129 return js.MakeFullWrapper(f) 130 } 131 132 func (p jsParser) Incomplete() bool { 133 return p.Parser.Incomplete() 134 } 135 136 func (p jsParser) Interactive(src *js.Object, fn func([]*syntax.Stmt) bool) { 137 err := p.Parser.Interactive(adaptReader(src), fn) 138 if err != nil { 139 throw(err) 140 } 141 } 142 143 type jsPrinter struct { 144 *syntax.Printer 145 } 146 147 func (p jsPrinter) Print(file *syntax.File) string { 148 var buf bytes.Buffer 149 if err := p.Printer.Print(&buf, file); err != nil { 150 throw(err) 151 } 152 return buf.String() 153 }