diff --git a/pkg/reducer/nodejs.go b/pkg/reducer/nodejs.go new file mode 100644 index 0000000..be8fd50 --- /dev/null +++ b/pkg/reducer/nodejs.go @@ -0,0 +1,93 @@ +package reducer + +import ( + _ "embed" + "fmt" + "os" + "os/exec" + "regexp" + "strings" +) + +func CreateNodejs(args []string) *exec.Cmd { + cmd := exec.Command("node", "-e", GenerateCode(args)) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "NODE_OPTIONS=--max-old-space-size=8192") + return cmd +} + +//go:embed reduce.js +var templateJs string + +func GenerateCodeNodejs(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( + ` + f = function () + { return %v } + .call(x) + x = typeof f === 'function' ? f(x) : fn +`, 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" + } + return fmt.Sprintf(templateJs, 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/python.go b/pkg/reducer/python.go new file mode 100644 index 0000000..c34b93b --- /dev/null +++ b/pkg/reducer/python.go @@ -0,0 +1,46 @@ +package reducer + +import ( + _ "embed" + "fmt" + "os/exec" +) + +func CreatePython(bin string, args []string) *exec.Cmd { + cmd := exec.Command(bin, "-c", GenerateCode(args)) + return cmd +} + +//go:embed reduce.py +var templatePython string + +func GenerateCodePython(args []string) string { + rs := "\n" + for i, a := range args { + rs += "try:" + switch { + case a == ".": + rs += ` + x = x +` + + default: + rs += fmt.Sprintf( + ` + f = (lambda json: (%v))(x) + x = f(x) if callable(f) else f +`, a) + } + // Generate a beautiful error message. + rs += "except Exception as e:\n" + pre, post, pointer := trace(args, i) + rs += fmt.Sprintf( + ` sys.stderr.write('\n {} {} {}\n %v\n\n{}\n'.format(%q, %q, %q, e)) + sys.exit(1)`, + pointer, + pre, a, post, + ) + rs += "\n" + } + return fmt.Sprintf(templatePython, rs) +} diff --git a/pkg/reducer/reduce.go b/pkg/reducer/reduce.go index f4f555f..d3a2b34 100644 --- a/pkg/reducer/reduce.go +++ b/pkg/reducer/reduce.go @@ -5,112 +5,45 @@ import ( _ "embed" "encoding/json" "fmt" - . "github.com/antonmedv/fx/pkg/json" - . "github.com/antonmedv/fx/pkg/theme" "os" "os/exec" - "regexp" "strings" -) -//go:embed reduce.js -var template string - -var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]") + . "github.com/antonmedv/fx/pkg/json" + . "github.com/antonmedv/fx/pkg/theme" +) func GenerateCode(args []string) string { - rs := "\n" - for i, a := range args { - rs += " try {" - switch { - case a == ".": - rs += ` - json = function () - { return this } - .call(json) -` - - case flatMapRegex.MatchString(a): - code := fold(strings.Split(a, "[]")) - rs += fmt.Sprintf( - ` - json = ( - %v - )(json) -`, code) - - case strings.HasPrefix(a, ".["): - rs += fmt.Sprintf( - ` - json = function () - { return this%v } - .call(json) -`, a[1:]) - - case strings.HasPrefix(a, "."): - rs += fmt.Sprintf( - ` - json = function () - { return this%v } - .call(json) -`, a) - - default: - rs += fmt.Sprintf( - ` - fn = function () - { return %v } - .call(json) - json = typeof fn === 'function' ? json = fn(json) : fn -`, a) - } - // Generate a beautiful error message. - rs += " } catch (e) {\n" - 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(a)), - strings.Repeat(" ", len(post)), - ) - rs += fmt.Sprintf( - " throw `\\n"+ - " ${%q} ${%q} ${%q}\\n"+ - " %v\\n"+ - "\\n${e.stack || e}`\n", - pre, a, post, - pointer, - ) - rs += " }\n" + lang, ok := os.LookupEnv("FX_LANG") + if !ok { + lang = "node" + } + switch { + case lang == "node": + return GenerateCodeNodejs(args) + case strings.HasPrefix(lang, "python"): + return GenerateCodePython(args) + default: + panic("unknown lang") } - return fmt.Sprintf(template, rs) } -func fold(s []string) string { - if len(s) == 1 { - return "x => x" + s[0] +func Reduce(object interface{}, args []string, theme Theme) { + var cmd *exec.Cmd + lang, ok := os.LookupEnv("FX_LANG") + if !ok { + lang = "node" } - obj := s[0] - if obj == "." { - obj = "x" - } else { - obj = "x" + obj + switch { + case lang == "node": + cmd = CreateNodejs(args) + case strings.HasPrefix(lang, "python"): + cmd = CreatePython(lang, args) + default: + panic("unknown lang") } - return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:])) -} -func Reduce(object interface{}, args []string, theme Theme) { var stdout, stderr bytes.Buffer - cmd := exec.Command("node", "-e", GenerateCode(args)) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "NODE_OPTIONS=--max-old-space-size=8192") cmd.Stdin = strings.NewReader(Stringify(object)) cmd.Stdout = &stdout cmd.Stderr = &stderr @@ -138,3 +71,21 @@ func Reduce(object interface{}, args []string, theme Theme) { os.Exit(exitCode) } } + +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/reduce.js b/pkg/reducer/reduce.js index c337fa5..5589aee 100644 --- a/pkg/reducer/reduce.js +++ b/pkg/reducer/reduce.js @@ -14,14 +14,14 @@ void async function () { for await (let chunk of process.stdin) { buffer += chunk } - let json = JSON.parse(buffer) + let x = JSON.parse(buffer) // Reducers %v - if (typeof json === 'undefined') { + if (typeof x === 'undefined') { process.stderr.write('undefined') } else { - process.stdout.write(JSON.stringify(json)) + process.stdout.write(JSON.stringify(x)) } }().catch(err => { console.error(err) diff --git a/pkg/reducer/reduce.py b/pkg/reducer/reduce.py new file mode 100644 index 0000000..6ef511f --- /dev/null +++ b/pkg/reducer/reduce.py @@ -0,0 +1,9 @@ +import json, sys, os +x = json.load(sys.stdin) + +# Reducers %v + +try: + print(json.dumps(x)) +except: + print(json.dumps(list(x)))