github.com/coyove/nj@v0.0.0-20221110084952-c7f8db1065c3/playground.go (about) 1 package nj 2 3 import ( 4 "bytes" 5 _ "embed" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "reflect" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/coyove/nj/bas" 15 "github.com/coyove/nj/internal" 16 "github.com/coyove/nj/typ" 17 ) 18 19 //go:embed playground.html 20 var playgroundHTML []byte 21 var playgroundCode = ` 22 -- Author: coyove 23 _, author = re([[Author: (\S+)]]).find(Program.Source) 24 println("Author is:", author) 25 26 -- Print all global values 27 local g = debug.globals() 28 29 print("version %d, total global values: %d".format(VERSION, #g/3)) 30 31 function pp(idx, f) 32 if f == nil then return end 33 if f is callable then 34 print(idx, ": function ", f) 35 else 36 print(idx, ": ", json.stringify(f)) 37 end 38 end 39 40 for i=0,#g,3 do 41 pp(i//3, g[i + 2]) 42 end` 43 44 func PlaygroundHandler(defaultCode string, opt *LoadOptions) func(w http.ResponseWriter, r *http.Request) { 45 return func(w http.ResponseWriter, r *http.Request) { 46 defer func() { recover() }() 47 48 c := getCode(r) 49 if c == "" { 50 w.Header().Add("Content-Type", "text/html") 51 var names []string 52 var dedup = map[string]bool{} 53 var add = func(n string) { 54 if !dedup[n] { 55 dedup[n], names = true, append(names, strconv.Quote(n)) 56 } 57 } 58 var add2 = func(f, n string, force bool) { 59 if force || n[0] >= 'A' && n[0] <= 'Z' { 60 add("(" + f + ")." + n) 61 } 62 } 63 var addType func(reflect.Type) 64 addType = func(rf reflect.Type) { 65 rfs := rf.String() 66 if dedup[rfs] { 67 return 68 } 69 dedup[rfs] = true 70 rff := rf 71 if rf.Kind() == reflect.Ptr { 72 rff = rff.Elem() 73 } 74 if rff.Kind() == reflect.Struct { 75 s := rff.String() 76 for i := 0; i < rff.NumField(); i++ { 77 add2(s, rff.Field(i).Name, false) 78 addType(rff.Field(i).Type) 79 } 80 for i := 0; i < rf.NumMethod(); i++ { 81 add2(rfs, rf.Method(i).Name, false) 82 } 83 if rf != rff { 84 for i := 0; i < rff.NumMethod(); i++ { 85 add2(s, rff.Method(i).Name, false) 86 } 87 } 88 } 89 } 90 x := bas.TopSymbols() 91 if opt != nil { 92 x.Merge(&opt.Globals) 93 } 94 x.Foreach(func(k bas.Value, v *bas.Value) bool { 95 add(k.String()) 96 switch v.Type() { 97 case typ.Object: 98 v.Object().Foreach(func(kk bas.Value, vv *bas.Value) bool { 99 add2(k.String(), kk.String(), true) 100 return true 101 }) 102 case typ.Native: 103 addType(reflect.ValueOf(v.Interface()).Type()) 104 } 105 return true 106 }) 107 108 buf := bytes.Replace(playgroundHTML, []byte("__NAMES__"), []byte(strings.Join(names, ",")), -1) 109 if defaultCode != "" { 110 buf = bytes.Replace(buf, []byte("__CODE__"), []byte(defaultCode), -1) 111 } else { 112 buf = bytes.Replace(buf, []byte("__CODE__"), []byte(playgroundCode), -1) 113 } 114 w.Write(buf) 115 return 116 } 117 118 start := time.Now() 119 bufOut := &internal.LimitedBuffer{Limit: 32 * 1024} 120 121 p, err := LoadString(c, opt) 122 if err != nil { 123 writeJSON(w, map[string]interface{}{"error": err.Error()}) 124 return 125 } 126 p.MaxStackSize = 1000 127 p.Stdout = bufOut 128 p.Stderr = bufOut 129 code := p.GoString() 130 v := p.Run() 131 switch outf := r.URL.Query().Get("output"); outf { 132 case "stdout": 133 writeJSON(w, map[string]interface{}{"stdout": bufOut.String()}) 134 case "result": 135 writeJSON(w, map[string]interface{}{"result": fmt.Sprint(v)}) 136 default: 137 writeJSON(w, map[string]interface{}{ 138 "elapsed": time.Since(start).Seconds(), 139 "result": fmt.Sprint(v), 140 "stdout": bufOut.String(), 141 "opcode": code, 142 }) 143 } 144 } 145 } 146 147 func writeJSON(w http.ResponseWriter, m map[string]interface{}) { 148 // w.Header().Add("Access-Control-Allow-Origin", "*") 149 w.Header().Add("Content-Type", "application/json") 150 buf, _ := json.Marshal(m) 151 w.Write(buf) 152 } 153 154 func getCode(r *http.Request) string { 155 c := strings.TrimSpace(r.FormValue("code")) 156 if c == "" { 157 c = strings.TrimSpace(r.URL.Query().Get("code")) 158 } 159 if len(c) > 16*1024 { 160 c = c[:16*1024] 161 } 162 return c 163 }