From 477f89c783f0290fa5c30f272ec42da841038163 Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Wed, 4 May 2022 00:00:52 +0200 Subject: [PATCH] Add goja --- go.mod | 6 ++-- go.sum | 15 ++++++++ main.go | 2 +- pkg/reducer/js.go | 77 ++++++++++++++++++++++++++++++++++++++++ pkg/reducer/nodejs.go | 16 --------- pkg/reducer/reduce.go | 81 ++++++++++++++++++++++--------------------- pkg/reducer/utils.go | 51 +++++++++++++++++++++++++++ 7 files changed, 190 insertions(+), 58 deletions(-) create mode 100644 pkg/reducer/js.go create mode 100644 pkg/reducer/utils.go diff --git a/go.mod b/go.mod index 569fff6..0360559 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/charmbracelet/bubbles v0.10.3 github.com/charmbracelet/bubbletea v0.20.0 github.com/charmbracelet/lipgloss v0.5.0 + github.com/dop251/goja v0.0.0-20220501172647-e1eca0b61fa9 github.com/mattn/go-isatty v0.0.14 github.com/mazznoer/colorgrad v0.8.1 github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 @@ -16,7 +17,8 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.0 // indirect - github.com/kr/pretty v0.3.0 // indirect + github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mazznoer/csscolorparser v0.1.2 // indirect @@ -26,6 +28,6 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + golang.org/x/text v0.3.7 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index f8ccfa9..b9655d0 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,15 @@ github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkX github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja v0.0.0-20220501172647-e1eca0b61fa9 h1:BXEAWJOT2C6ex9iOzVnrYWMFjTRccNs7p8fpLCLLcm0= +github.com/dop251/goja v0.0.0-20220501172647-e1eca0b61fa9/go.mod h1:TQJQ+ZNyFVvUtUEtCZxBhfWiH7RJqR3EivNmvD6Waik= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -72,10 +81,16 @@ golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 829148b..12ec3c0 100644 --- a/main.go +++ b/main.go @@ -94,7 +94,7 @@ func main() { lang, ok := os.LookupEnv("FX_LANG") if !ok { - lang = "node" + lang = "js" } if dec.More() { diff --git a/pkg/reducer/js.go b/pkg/reducer/js.go new file mode 100644 index 0000000..1b6dcf5 --- /dev/null +++ b/pkg/reducer/js.go @@ -0,0 +1,77 @@ +package reducer + +import ( + _ "embed" + "fmt" + "strings" +) + +func js(args []string) string { + rs := "\n" + for i, a := range args { + rs += " try {" + switch { + case a == ".": + rs += ` + x = function () + { return this } + .call(x) +` + + case flatMapRegex.MatchString(a): + code := fold(strings.Split(a, "[]")) + rs += fmt.Sprintf( + ` + x = ( + %v + )(x) +`, code) + + case strings.HasPrefix(a, ".["): + rs += fmt.Sprintf( + ` + x = function () + { return this%v } + .call(x) +`, a[1:]) + + case strings.HasPrefix(a, "."): + rs += fmt.Sprintf( + ` + x = function () + { return this%v } + .call(x) +`, a) + + default: + rs += fmt.Sprintf( + ` + let f = function () + { return %v } + .call(x) + x = typeof f === 'function' ? f(x) : f +`, a) + } + // Generate a beautiful error message. + rs += " } catch (e) {\n" + pre, post, pointer := trace(args, i) + rs += fmt.Sprintf( + " throw `\\n ${%q} ${%q} ${%q}\\n %v\\n\\n${e.stack || e}`\n", + pre, a, post, pointer, + ) + rs += " }\n" + } + + fn := `function reduce(input) { + let x = JSON.parse(input) + + // Reducers %v + if (typeof x === 'undefined') { + return 'null' + } else { + return JSON.stringify(x) + } +} +` + return fmt.Sprintf(fn, rs) +} diff --git a/pkg/reducer/nodejs.go b/pkg/reducer/nodejs.go index 160c1d8..aed7eab 100644 --- a/pkg/reducer/nodejs.go +++ b/pkg/reducer/nodejs.go @@ -6,7 +6,6 @@ import ( "os" "os/exec" "path" - "regexp" "strings" ) @@ -97,18 +96,3 @@ func nodejs(args []string) string { } return fmt.Sprintf(templateJs, fxrc, rs) } - -var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]") - -func fold(s []string) string { - if len(s) == 1 { - return "x => x" + s[0] - } - obj := s[0] - if obj == "." { - obj = "x" - } else { - obj = "x" + obj - } - return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:])) -} diff --git a/pkg/reducer/reduce.go b/pkg/reducer/reduce.go index 1431115..8e782bf 100644 --- a/pkg/reducer/reduce.go +++ b/pkg/reducer/reduce.go @@ -12,10 +12,13 @@ import ( . "github.com/antonmedv/fx/pkg/dict" . "github.com/antonmedv/fx/pkg/json" . "github.com/antonmedv/fx/pkg/theme" + "github.com/dop251/goja" ) func GenerateCode(lang string, args []string) string { switch lang { + case "js": + return js(args) case "node": return nodejs(args) case "python", "python3": @@ -27,45 +30,71 @@ func GenerateCode(lang string, args []string) string { } } -func Reduce(object interface{}, lang string, args []string, theme Theme) int { +func Reduce(input interface{}, lang string, args []string, theme Theme) int { path, ok := split(args) if ok { for _, get := range path { switch get := get.(type) { case string: - switch o := object.(type) { + switch o := input.(type) { case *Dict: - object = o.Values[get] + input = o.Values[get] case string: if get == "length" { - object = Number(strconv.Itoa(len([]rune(o)))) + input = Number(strconv.Itoa(len([]rune(o)))) } else { - object = nil + input = nil } case Array: if get == "length" { - object = Number(strconv.Itoa(len(o))) + input = Number(strconv.Itoa(len(o))) } else { - object = nil + input = nil } default: - object = nil + input = nil } case int: - switch o := object.(type) { + switch o := input.(type) { case Array: - object = o[get] + input = o[get] default: - object = nil + input = nil } } } - echo(object, theme) + echo(input, theme) return 0 } var cmd *exec.Cmd switch lang { + case "js": + vm := goja.New() + _, err := vm.RunString(js(args)) + if err != nil { + fmt.Println(err) + return 1 + } + sum, ok := goja.AssertFunction(vm.Get("reduce")) + if !ok { + panic("Not a function") + } + res, err := sum(goja.Undefined(), vm.ToValue(Stringify(input))) + if err != nil { + fmt.Println(err) + return 1 + } + output := res.String() + dec := json.NewDecoder(strings.NewReader(output)) + dec.UseNumber() + jsonObject, err := Parse(dec) + if err != nil { + fmt.Print(output) + return 0 + } + echo(jsonObject, theme) + return 0 case "node": cmd = CreateNodejs(args) case "python", "python3": @@ -77,7 +106,7 @@ func Reduce(object interface{}, lang string, args []string, theme Theme) int { } // TODO: Reimplement stringify with io.Reader. - cmd.Stdin = strings.NewReader(Stringify(object)) + cmd.Stdin = strings.NewReader(Stringify(input)) output, err := cmd.CombinedOutput() if err != nil { exitCode := 1 @@ -104,29 +133,3 @@ func Reduce(object interface{}, lang string, args []string, theme Theme) int { } return 0 } - -func echo(object interface{}, theme Theme) { - if s, ok := object.(string); ok { - fmt.Println(s) - } else { - fmt.Println(PrettyPrint(object, 1, theme)) - } -} - -func trace(args []string, i int) (pre, post, pointer string) { - pre = strings.Join(args[:i], " ") - if len(pre) > 20 { - pre = "..." + pre[len(pre)-20:] - } - post = strings.Join(args[i+1:], " ") - if len(post) > 20 { - post = post[:20] + "..." - } - pointer = fmt.Sprintf( - "%v %v %v", - strings.Repeat(" ", len(pre)), - strings.Repeat("^", len(args[i])), - strings.Repeat(" ", len(post)), - ) - return -} diff --git a/pkg/reducer/utils.go b/pkg/reducer/utils.go new file mode 100644 index 0000000..b73de15 --- /dev/null +++ b/pkg/reducer/utils.go @@ -0,0 +1,51 @@ +package reducer + +import ( + "fmt" + "regexp" + "strings" + + . "github.com/antonmedv/fx/pkg/json" + . "github.com/antonmedv/fx/pkg/theme" +) + +func echo(object interface{}, theme Theme) { + if s, ok := object.(string); ok { + fmt.Println(s) + } else { + fmt.Println(PrettyPrint(object, 1, theme)) + } +} + +func trace(args []string, i int) (pre, post, pointer string) { + pre = strings.Join(args[:i], " ") + if len(pre) > 20 { + pre = "..." + pre[len(pre)-20:] + } + post = strings.Join(args[i+1:], " ") + if len(post) > 20 { + post = post[:20] + "..." + } + pointer = fmt.Sprintf( + "%v %v %v", + strings.Repeat(" ", len(pre)), + strings.Repeat("^", len(args[i])), + strings.Repeat(" ", len(post)), + ) + return +} + +var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]") + +func fold(s []string) string { + if len(s) == 1 { + return "x => x" + s[0] + } + obj := s[0] + if obj == "." { + obj = "x" + } else { + obj = "x" + obj + } + return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:])) +}