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  }