From 387caf0b77ee5628c0a842720a6ab45cd997bdd9 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 14:32:39 +0330 Subject: [PATCH 01/40] basic...? impl --- Makefile | 5 ++ jq/printer.jq | 10 ++++ jq/reader.jq | 108 +++++++++++++++++++++++++++++++++++++++++ jq/run | 2 + jq/step0_repl.jq | 28 +++++++++++ jq/step1_read_print.jq | 30 ++++++++++++ 6 files changed, 183 insertions(+) create mode 100644 jq/printer.jq create mode 100644 jq/reader.jq create mode 100755 jq/run create mode 100644 jq/step0_repl.jq create mode 100644 jq/step1_read_print.jq diff --git a/Makefile b/Makefile index bb63177b..2091235d 100644 --- a/Makefile +++ b/Makefile @@ -94,7 +94,11 @@ IMPLS = ada ada.2 awk bash basic bbc-basic c chuck clojure coffee common-lisp cp guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ plsql powershell ps python python.2 r racket rexx rpython ruby rust scala scheme skew \ +<<<<<<< HEAD swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick zig +======= + swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick zig jq +>>>>>>> basic...? impl EXTENSION = .mal @@ -218,6 +222,7 @@ hy_STEP_TO_PROG = hy/$($(1)).hy io_STEP_TO_PROG = io/$($(1)).io java_STEP_TO_PROG = java/target/classes/mal/$($(1)).class js_STEP_TO_PROG = js/$($(1)).js +jq_STEP_PROG = jq/$($(1)).jq julia_STEP_TO_PROG = julia/$($(1)).jl kotlin_STEP_TO_PROG = kotlin/$($(1)).jar livescript_STEP_TO_PROG = livescript/$($(1)).js diff --git a/jq/printer.jq b/jq/printer.jq new file mode 100644 index 00000000..d6c4616b --- /dev/null +++ b/jq/printer.jq @@ -0,0 +1,10 @@ +def pr_str: + (select(.kind == "symbol") | .value) // + (select(.kind == "string") | .value | tojson) // + (select(.kind == "keyword") | ":\(.value)") // + (select(.kind == "number") | .value | tostring) // + (select(.kind == "list") | .values | map(pr_str) | join(" ") | "(\(.))") // + (select(.kind == "nil") | "nil") // + (select(.kind == "true") | "true") // + (select(.kind == "false") | "false") // + "#"; \ No newline at end of file diff --git a/jq/reader.jq b/jq/reader.jq new file mode 100644 index 00000000..60ce259a --- /dev/null +++ b/jq/reader.jq @@ -0,0 +1,108 @@ +def tokenize: + [ . | scan("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)") | select(.|length > 0)[0] | select(.[0:1] != ";") ]; + +def read_str: + . | tokenize; + +# TODO +def read_string: + .; + +# stuff comes in as {tokens: [...], } +def read_atom: + (.tokens | first) as $lookahead | . | ( + if $lookahead == "nil" then + { + tokens: .tokens[1:], + value: { + kind: "nil" + } + } + else if $lookahead == "true" then + { + tokens: .tokens[1:], + value: { + kind: "true" + } + } + else if $lookahead == "false" then + { + tokens: .tokens[1:], + value: { + kind: "false" + } + } + else if $lookahead | test("^\"") then + if $lookahead | test("^\"(?:\\\\.|[^\\\\\"])*\"$") then + { + tokens: .tokens[1:], + value: { + kind: "string", + value: $lookahead[1:-1] | read_string + } + } + else + error("EOF while reading string") + end + else if $lookahead | test("^:") then + { + tokens: .tokens[1:], + value: { + kind: "keyword", + value: $lookahead[1:] + } + } + else if $lookahead | test("^-?[0-9]+(?:\\.[0-9]+)?$") then + { + tokens: .tokens[1:], + value: { + kind: "number", + value: $lookahead | tonumber + } + } + else if $lookahead == ")" then # this isn't our business + empty + else + { + tokens: .tokens[1:], + value: { + kind: "symbol", + value: $lookahead + } + } + end end end end end end end + ); + +def read_form_: + (.tokens | first) as $lookahead | . | ( + if $lookahead == null then + empty + else if $lookahead | test("^\\(") then + [ . | (.tokens |= .[1:]) | (label $out | foreach .tokens[] as $token ( + {tokens: .tokens, values: []}; + if $token | test("^\\)") then + break $out + else + (. | read_form_) as $res | . | { + tokens: $res.tokens, + values: (.values + [$res.value]) + } + end; + .)) ] | last as $result | + if $result.tokens | first != ")" then + error("unbalanced parentheses in \($result.tokens)") + else + { + tokens: $result.tokens[1:], + value: { + kind: "list", + values: $result.values + }, + } + end + else + . | read_atom + end end); + +def read_form: + {tokens: .} | read_form_; diff --git a/jq/run b/jq/run new file mode 100755 index 00000000..07d76552 --- /dev/null +++ b/jq/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec jq -n -r -R -f "$(dirname "$0")"/"${STEP:-stepA_mal}.jq" "${@}" diff --git a/jq/step0_repl.jq b/jq/step0_repl.jq new file mode 100644 index 00000000..a9a3be3d --- /dev/null +++ b/jq/step0_repl.jq @@ -0,0 +1,28 @@ + +def read_line: + . as $in + | label $top + | input; + +def READ: + .; + +def EVAL: + .; + +def PRINT: + .; + +def rep: + . | READ | EVAL | PRINT; + +def repl_: + [ + ("user> " | stderr), + (input | rep) + ] | last; + +def repl: + while(true; repl_); + +repl diff --git a/jq/step1_read_print.jq b/jq/step1_read_print.jq new file mode 100644 index 00000000..a6396ef7 --- /dev/null +++ b/jq/step1_read_print.jq @@ -0,0 +1,30 @@ +include "reader"; +include "printer"; + +def read_line: + . as $in + | label $top + | input; + +def READ: + read_str; + +def EVAL: + . | read_form | .value; + +def PRINT: + . | pr_str; + +def rep: + . | READ | EVAL | PRINT; + +def repl_: + [ + ("user> " | stderr), + (read_line | rep) + ] | last; + +def repl: + while(true; try repl_ catch "Error: \(.)"); + +repl From 186e33f2415c4fdd8f94312d136b1b05752092be Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 17:06:06 +0330 Subject: [PATCH 02/40] "fix" more reader stuff --- jq/printer.jq | 12 ++- jq/reader.jq | 203 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 188 insertions(+), 27 deletions(-) diff --git a/jq/printer.jq b/jq/printer.jq index d6c4616b..41638e65 100644 --- a/jq/printer.jq +++ b/jq/printer.jq @@ -1,9 +1,19 @@ +# {key: string, value: {kkind: kind, value: value}} -> [{kind: value.kkind, value: key}, value.value] +def _reconstruct_hash: + map([{ + kind: .value.kkind, + value: .key + }, + .value.value]); + def pr_str: (select(.kind == "symbol") | .value) // (select(.kind == "string") | .value | tojson) // (select(.kind == "keyword") | ":\(.value)") // (select(.kind == "number") | .value | tostring) // - (select(.kind == "list") | .values | map(pr_str) | join(" ") | "(\(.))") // + (select(.kind == "list") | .values | map(pr_str) | join(" ") | "(\(.))") // + (select(.kind == "vector") | .values | map(pr_str) | join(" ") | "[\(.)]") // + (select(.kind == "hashmap") | .values | to_entries | _reconstruct_hash | add // [] | map(pr_str) | join(" ") | "{\(.)}") // (select(.kind == "nil") | "nil") // (select(.kind == "true") | "true") // (select(.kind == "false") | "false") // diff --git a/jq/reader.jq b/jq/reader.jq index 60ce259a..09bc7e55 100644 --- a/jq/reader.jq +++ b/jq/reader.jq @@ -1,3 +1,5 @@ +include "utils"; + def tokenize: [ . | scan("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)") | select(.|length > 0)[0] | select(.[0:1] != ";") ]; @@ -6,7 +8,14 @@ def read_str: # TODO def read_string: - .; + . | fromjson; + +def extract_string: + . as $val | if ["keyword", "symbol", "string"] | contains([$val.kind]) then + $val.value + else + error("assoc called with non-string key of type \($val.kind)") + end; # stuff comes in as {tokens: [...], } def read_atom: @@ -38,7 +47,7 @@ def read_atom: tokens: .tokens[1:], value: { kind: "string", - value: $lookahead[1:-1] | read_string + value: $lookahead | read_string } } else @@ -60,7 +69,7 @@ def read_atom: value: $lookahead | tonumber } } - else if $lookahead == ")" then # this isn't our business + else if [")", "]", "}"] | contains([$lookahead]) then # this isn't our business empty else { @@ -73,36 +82,178 @@ def read_atom: end end end end end end end ); -def read_form_: +def read_form_(depth): (.tokens | first) as $lookahead | . | ( if $lookahead == null then empty - else if $lookahead | test("^\\(") then - [ . | (.tokens |= .[1:]) | (label $out | foreach .tokens[] as $token ( - {tokens: .tokens, values: []}; - if $token | test("^\\)") then - break $out - else - (. | read_form_) as $res | . | { - tokens: $res.tokens, - values: (.values + [$res.value]) + # read_list + else + if $lookahead | test("^\\(") then + [ (.tokens |= .[1:]) | {tokens: .tokens, values: [], finish: false} | (until(.finish; + if try (.tokens | first | test("^\\)")) catch true then + .finish |= true + else + . as $orig | read_form_(depth+1) as $res | { + tokens: $res.tokens, + values: ($orig.values + [$res.value]), + finish: $orig.finish + } + end)) ] | map(select(.tokens)) | last as $result | + if $result.tokens | first != ")" then + error("unbalanced parentheses in \($result.tokens)") + else + { + tokens: $result.tokens[1:], + value: { + kind: "list", + values: $result.values + }, } - end; - .)) ] | last as $result | - if $result.tokens | first != ")" then - error("unbalanced parentheses in \($result.tokens)") - else + end + # read_list '[' + else if $lookahead | test("^\\[") then + [ (.tokens |= .[1:]) | {tokens: .tokens, values: [], finish: false} | (until(.finish; + if try (.tokens | first | test("^\\]")) catch true then + .finish |= true + else + . as $orig | read_form_(depth+1) as $res | { + tokens: $res.tokens, + values: ($orig.values + [$res.value]), + finish: $orig.finish + } + end)) ] | map(select(.tokens)) | last as $result | + if $result.tokens | first != "]" then + error("unbalanced brackets in \($result.tokens)") + else + { + tokens: $result.tokens[1:], + value: { + kind: "vector", + values: $result.values + }, + } + end + # read_list '{' + else if $lookahead | test("^\\{") then + [ (.tokens |= .[1:]) | {tokens: .tokens, values: [], finish: false} | (until(.finish; + if try (.tokens | first | test("^\\}")) catch true then + .finish |= true + else + . as $orig | read_form_(depth+1) as $res | { + tokens: $res.tokens, + values: ($orig.values + [$res.value]), + finish: $orig.finish + } + end)) ] | map(select(.tokens)) | last as $result | + if $result.tokens | first != "}" then + error("unbalanced braces in \($result.tokens)") + else + if $result.values | length % 2 == 1 then + # odd number of elements not allowed + error("Odd number of parameters to assoc") + else + { + tokens: $result.tokens[1:], + value: { + kind: "hashmap", + values: + [ $result.values | + nwise(2) | + try { + key: (.[0] | extract_string), + value: { + kkind: .[0].kind, + value: .[1] + } + } + ] | from_entries + } + } + end + end + # quote + else if $lookahead == "'" then + (.tokens |= .[1:]) | read_form_(depth+1) | ( { - tokens: $result.tokens[1:], + tokens: .tokens, value: { kind: "list", - values: $result.values - }, - } - end + values: [ + { + kind: "symbol", + value: "quote" + }, + .value + ] + } + }) + # quasiquote + else if $lookahead == "`" then + (.tokens |= .[1:]) | read_form_(depth+1) | ( + { + tokens: .tokens, + value: { + kind: "list", + values: [ + { + kind: "symbol", + value: "quasiquote" + }, + .value + ] + } + }) + # unquote + else if $lookahead == "~" then + (.tokens |= .[1:]) | read_form_(depth+1) | ( + { + tokens: .tokens, + value: { + kind: "list", + values: [ + { + kind: "symbol", + value: "unquote" + }, + .value + ] + } + }) + # split-unquote + else if $lookahead == "~@" then + (.tokens |= .[1:]) | read_form_(depth+1) | ( + { + tokens: .tokens, + value: { + kind: "list", + values: [ + { + kind: "symbol", + value: "splice-unquote" + }, + .value + ] + } + }) + # deref + else if $lookahead == "@" then + (.tokens |= .[1:]) | read_form_(depth+1) | ( + { + tokens: .tokens, + value: { + kind: "list", + values: [ + { + kind: "symbol", + value: "deref" + }, + .value + ] + } + }) else - . | read_atom - end end); + . as $prev | read_atom + end end end end end end end end end); def read_form: - {tokens: .} | read_form_; + {tokens: .} | read_form_(0); From 4fdc0a0307897bff5c7d17e0ba7630218406feb7 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 17:33:09 +0330 Subject: [PATCH 03/40] all tests passing yay --- jq/reader.jq | 38 +++++++++++++++++++++++++++----------- jq/step0_repl.jq | 8 +++----- jq/step1_read_print.jq | 28 ++++++++++++++++++++-------- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/jq/reader.jq b/jq/reader.jq index 09bc7e55..a681888f 100644 --- a/jq/reader.jq +++ b/jq/reader.jq @@ -4,17 +4,16 @@ def tokenize: [ . | scan("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)") | select(.|length > 0)[0] | select(.[0:1] != ";") ]; def read_str: - . | tokenize; + tokenize; -# TODO def read_string: - . | fromjson; + fromjson; def extract_string: . as $val | if ["keyword", "symbol", "string"] | contains([$val.kind]) then $val.value else - error("assoc called with non-string key of type \($val.kind)") + jqmal_error("assoc called with non-string key of type \($val.kind)") end; # stuff comes in as {tokens: [...], } @@ -51,7 +50,7 @@ def read_atom: } } else - error("EOF while reading string") + jqmal_error("EOF while reading string") end else if $lookahead | test("^:") then { @@ -85,7 +84,7 @@ def read_atom: def read_form_(depth): (.tokens | first) as $lookahead | . | ( if $lookahead == null then - empty + null # read_list else if $lookahead | test("^\\(") then @@ -100,7 +99,7 @@ def read_form_(depth): } end)) ] | map(select(.tokens)) | last as $result | if $result.tokens | first != ")" then - error("unbalanced parentheses in \($result.tokens)") + jqmal_error("unbalanced parentheses in \($result.tokens)") else { tokens: $result.tokens[1:], @@ -123,7 +122,7 @@ def read_form_(depth): } end)) ] | map(select(.tokens)) | last as $result | if $result.tokens | first != "]" then - error("unbalanced brackets in \($result.tokens)") + jqmal_error("unbalanced brackets in \($result.tokens)") else { tokens: $result.tokens[1:], @@ -146,11 +145,11 @@ def read_form_(depth): } end)) ] | map(select(.tokens)) | last as $result | if $result.tokens | first != "}" then - error("unbalanced braces in \($result.tokens)") + jqmal_error("unbalanced braces in \($result.tokens)") else if $result.values | length % 2 == 1 then # odd number of elements not allowed - error("Odd number of parameters to assoc") + jqmal_error("Odd number of parameters to assoc") else { tokens: $result.tokens[1:], @@ -251,9 +250,26 @@ def read_form_(depth): ] } }) + # with-meta + else if $lookahead == "^" then + (.tokens |= .[1:]) | read_form_(depth+1) as $meta | $meta | read_form_(depth+1) as $value | ( + { + tokens: $value.tokens, + value: { + kind: "list", + values: [ + { + kind: "symbol", + value: "with-meta" + }, + $value.value, + $meta.value + ] + } + }) else . as $prev | read_atom - end end end end end end end end end); + end end end end end end end end end end); def read_form: {tokens: .} | read_form_(0); diff --git a/jq/step0_repl.jq b/jq/step0_repl.jq index a9a3be3d..e2fb9b34 100644 --- a/jq/step0_repl.jq +++ b/jq/step0_repl.jq @@ -14,13 +14,11 @@ def PRINT: .; def rep: - . | READ | EVAL | PRINT; + READ | EVAL | PRINT; def repl_: - [ - ("user> " | stderr), - (input | rep) - ] | last; + ("user> " | stderr) | + (read_line | rep); def repl: while(true; repl_); diff --git a/jq/step1_read_print.jq b/jq/step1_read_print.jq index a6396ef7..17797792 100644 --- a/jq/step1_read_print.jq +++ b/jq/step1_read_print.jq @@ -1,5 +1,6 @@ include "reader"; include "printer"; +include "utils"; def read_line: . as $in @@ -10,21 +11,32 @@ def READ: read_str; def EVAL: - . | read_form | .value; + read_form | .value; def PRINT: - . | pr_str; + pr_str; def rep: - . | READ | EVAL | PRINT; + READ | EVAL | + if . != null then + PRINT + else + null + end; def repl_: - [ - ("user> " | stderr), - (read_line | rep) - ] | last; + ("user> " | stderr) | + (read_line | rep); def repl: - while(true; try repl_ catch "Error: \(.)"); + {continue: true} | while( + .continue; + try {value: repl_, continue: true} + catch + if is_jqmal_error then + {value: "Error: \(.)", continue: true} + else + {value: ., continue: false} + end) | if .value then .value else empty end; repl From 9088c0fa753cecf48557cc80e50d9ca6d7139ab8 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 18:35:49 +0330 Subject: [PATCH 04/40] step2 - eval Since we can't store functions, this is gonna be a _mess_ --- jq/interp.jq | 24 ++++++++++ jq/printer.jq | 6 +-- jq/reader.jq | 34 ++++++------- jq/step1_read_print.jq | 4 +- jq/step2_eval.jq | 105 +++++++++++++++++++++++++++++++++++++++++ jq/utils.jq | 20 ++++++++ 6 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 jq/interp.jq create mode 100644 jq/step2_eval.jq create mode 100644 jq/utils.jq diff --git a/jq/interp.jq b/jq/interp.jq new file mode 100644 index 00000000..51642802 --- /dev/null +++ b/jq/interp.jq @@ -0,0 +1,24 @@ +include "utils"; + +def arg_check(args): + if .inputs != (args|length) then + jqmal_error("Invalid number of arguments (expected \(.inputs) got \(args|length): \(args))") + else + . + end; + + +def interpret(arguments; env): + arg_check(arguments) | ( + select(.function == "number_add") | + arguments | map(.value) | .[0] + .[1] | wrap("number") + ) // ( + select(.function == "number_sub") | + arguments | map(.value) | .[0] - .[1] | wrap("number") + ) // ( + select(.function == "number_mul") | + arguments | map(.value) | .[0] * .[1] | wrap("number") + ) // ( + select(.function == "number_div") | + arguments | map(.value) | .[0] / .[1] | wrap("number") + ) // jqmal_error("Unknown function \(.function)"); \ No newline at end of file diff --git a/jq/printer.jq b/jq/printer.jq index 41638e65..9b1d06b7 100644 --- a/jq/printer.jq +++ b/jq/printer.jq @@ -11,9 +11,9 @@ def pr_str: (select(.kind == "string") | .value | tojson) // (select(.kind == "keyword") | ":\(.value)") // (select(.kind == "number") | .value | tostring) // - (select(.kind == "list") | .values | map(pr_str) | join(" ") | "(\(.))") // - (select(.kind == "vector") | .values | map(pr_str) | join(" ") | "[\(.)]") // - (select(.kind == "hashmap") | .values | to_entries | _reconstruct_hash | add // [] | map(pr_str) | join(" ") | "{\(.)}") // + (select(.kind == "list") | .value | map(pr_str) | join(" ") | "(\(.))") // + (select(.kind == "vector") | .value | map(pr_str) | join(" ") | "[\(.)]") // + (select(.kind == "hashmap") | .value | to_entries | _reconstruct_hash | add // [] | map(pr_str) | join(" ") | "{\(.)}") // (select(.kind == "nil") | "nil") // (select(.kind == "true") | "true") // (select(.kind == "false") | "false") // diff --git a/jq/reader.jq b/jq/reader.jq index a681888f..6b958a8d 100644 --- a/jq/reader.jq +++ b/jq/reader.jq @@ -88,13 +88,13 @@ def read_form_(depth): # read_list else if $lookahead | test("^\\(") then - [ (.tokens |= .[1:]) | {tokens: .tokens, values: [], finish: false} | (until(.finish; + [ (.tokens |= .[1:]) | {tokens: .tokens, value: [], finish: false} | (until(.finish; if try (.tokens | first | test("^\\)")) catch true then .finish |= true else . as $orig | read_form_(depth+1) as $res | { tokens: $res.tokens, - values: ($orig.values + [$res.value]), + value: ($orig.value + [$res.value]), finish: $orig.finish } end)) ] | map(select(.tokens)) | last as $result | @@ -105,19 +105,19 @@ def read_form_(depth): tokens: $result.tokens[1:], value: { kind: "list", - values: $result.values + value: $result.value }, } end # read_list '[' else if $lookahead | test("^\\[") then - [ (.tokens |= .[1:]) | {tokens: .tokens, values: [], finish: false} | (until(.finish; + [ (.tokens |= .[1:]) | {tokens: .tokens, value: [], finish: false} | (until(.finish; if try (.tokens | first | test("^\\]")) catch true then .finish |= true else . as $orig | read_form_(depth+1) as $res | { tokens: $res.tokens, - values: ($orig.values + [$res.value]), + value: ($orig.value + [$res.value]), finish: $orig.finish } end)) ] | map(select(.tokens)) | last as $result | @@ -128,26 +128,26 @@ def read_form_(depth): tokens: $result.tokens[1:], value: { kind: "vector", - values: $result.values + value: $result.value }, } end # read_list '{' else if $lookahead | test("^\\{") then - [ (.tokens |= .[1:]) | {tokens: .tokens, values: [], finish: false} | (until(.finish; + [ (.tokens |= .[1:]) | {tokens: .tokens, value: [], finish: false} | (until(.finish; if try (.tokens | first | test("^\\}")) catch true then .finish |= true else . as $orig | read_form_(depth+1) as $res | { tokens: $res.tokens, - values: ($orig.values + [$res.value]), + value: ($orig.value + [$res.value]), finish: $orig.finish } end)) ] | map(select(.tokens)) | last as $result | if $result.tokens | first != "}" then jqmal_error("unbalanced braces in \($result.tokens)") else - if $result.values | length % 2 == 1 then + if $result.value | length % 2 == 1 then # odd number of elements not allowed jqmal_error("Odd number of parameters to assoc") else @@ -155,8 +155,8 @@ def read_form_(depth): tokens: $result.tokens[1:], value: { kind: "hashmap", - values: - [ $result.values | + value: + [ $result.value | nwise(2) | try { key: (.[0] | extract_string), @@ -177,7 +177,7 @@ def read_form_(depth): tokens: .tokens, value: { kind: "list", - values: [ + value: [ { kind: "symbol", value: "quote" @@ -193,7 +193,7 @@ def read_form_(depth): tokens: .tokens, value: { kind: "list", - values: [ + value: [ { kind: "symbol", value: "quasiquote" @@ -209,7 +209,7 @@ def read_form_(depth): tokens: .tokens, value: { kind: "list", - values: [ + value: [ { kind: "symbol", value: "unquote" @@ -225,7 +225,7 @@ def read_form_(depth): tokens: .tokens, value: { kind: "list", - values: [ + value: [ { kind: "symbol", value: "splice-unquote" @@ -241,7 +241,7 @@ def read_form_(depth): tokens: .tokens, value: { kind: "list", - values: [ + value: [ { kind: "symbol", value: "deref" @@ -257,7 +257,7 @@ def read_form_(depth): tokens: $value.tokens, value: { kind: "list", - values: [ + value: [ { kind: "symbol", value: "with-meta" diff --git a/jq/step1_read_print.jq b/jq/step1_read_print.jq index 17797792..7d416d44 100644 --- a/jq/step1_read_print.jq +++ b/jq/step1_read_print.jq @@ -8,10 +8,10 @@ def read_line: | input; def READ: - read_str; + read_str | read_form | .value; def EVAL: - read_form | .value; + .; def PRINT: pr_str; diff --git a/jq/step2_eval.jq b/jq/step2_eval.jq new file mode 100644 index 00000000..b54cd6fa --- /dev/null +++ b/jq/step2_eval.jq @@ -0,0 +1,105 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; + +def read_line: + . as $in + | label $top + | input; + +def READ: + read_str | read_form | .value; + +def lookup(env): + env.environment[.] // + if env.parent then + lookup(env.parent) + else + jqmal_error("Symbol \(.) not found") + end; + +def EVAL(env): + def eval_ast: + (select(.kind == "symbol") | .value | lookup(env)) // + (select(.kind == "list") | { + kind: "list", + value: .value | map(EVAL(env)) + }) // .; + (select(.kind == "list") | + if .value | length == 0 then + . + else + eval_ast|.value as $evald | $evald | first | interpret($evald[1:]; env) + end + ) // + (select(.kind == "vector") | + { + kind: "vector", + value: .value|map(EVAL(env)) + } + ) // + (select(.kind == "hashmap") | + { + kind: "hashmap", + value: .value|map_values(.value |= EVAL(env)) + } + ) // eval_ast; + +def PRINT: + pr_str; + +def rep(env): + READ | EVAL(env) | + if . != null then + PRINT + else + null + end; + +def repl_(env): + ("user> " | stderr) | + (read_line | rep(env)); + +def childEnv(binds; value): + { + parent: ., + environment: [binds, value] | transpose | map({(.[0]): .[1]}) | from_entries + }; + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: { + "+": { + inputs: 2, + function: "number_add" + }, + "-": { + inputs: 2, + function: "number_sub" + }, + "*": { + inputs: 2, + function: "number_mul" + }, + "/": { + inputs: 2, + function: "number_div" + }, + } + }; + +def repl(env): + {continue: true} | while( + .continue; + try {value: repl_(env), continue: true} + catch + if is_jqmal_error then + {value: "Error: \(.)", continue: true} + else + {value: ., continue: false} + end) | if .value then .value else empty end; + +repl(replEnv) \ No newline at end of file diff --git a/jq/utils.jq b/jq/utils.jq new file mode 100644 index 00000000..c031aeb1 --- /dev/null +++ b/jq/utils.jq @@ -0,0 +1,20 @@ +def nwise(n): + def _nwise: + if length <= n then + . + else + .[0:n], (.[n:] | _nwise) + end; + _nwise; + +def jqmal_error(e): + error("JqMAL :: " + e); + +def is_jqmal_error: + startswith("JqMAL :: "); + +def wrap(kind): + { + kind: kind, + value: . + }; \ No newline at end of file From b27d81d8268d6e7c8743d29687d635d0741e23b7 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 18:38:15 +0330 Subject: [PATCH 05/40] prepare for later --- jq/interp.jq | 28 +++++++++++++++------------- jq/step2_eval.jq | 4 ++++ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/jq/interp.jq b/jq/interp.jq index 51642802..0fa2f3a2 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -9,16 +9,18 @@ def arg_check(args): def interpret(arguments; env): - arg_check(arguments) | ( - select(.function == "number_add") | - arguments | map(.value) | .[0] + .[1] | wrap("number") - ) // ( - select(.function == "number_sub") | - arguments | map(.value) | .[0] - .[1] | wrap("number") - ) // ( - select(.function == "number_mul") | - arguments | map(.value) | .[0] * .[1] | wrap("number") - ) // ( - select(.function == "number_div") | - arguments | map(.value) | .[0] / .[1] | wrap("number") - ) // jqmal_error("Unknown function \(.function)"); \ No newline at end of file + select(.kind == "fn") | ( + arg_check(arguments) | ( + select(.function == "number_add") | + arguments | map(.value) | .[0] + .[1] | wrap("number") + ) // ( + select(.function == "number_sub") | + arguments | map(.value) | .[0] - .[1] | wrap("number") + ) // ( + select(.function == "number_mul") | + arguments | map(.value) | .[0] * .[1] | wrap("number") + ) // ( + select(.function == "number_div") | + arguments | map(.value) | .[0] / .[1] | wrap("number") + ) // jqmal_error("Unknown native function \(.function)"); + ) // jqmal_error("Unsupported function kind \(.kind)") \ No newline at end of file diff --git a/jq/step2_eval.jq b/jq/step2_eval.jq index b54cd6fa..6df358fe 100644 --- a/jq/step2_eval.jq +++ b/jq/step2_eval.jq @@ -73,18 +73,22 @@ def replEnv: parent: null, environment: { "+": { + kind: "fn", # native function inputs: 2, function: "number_add" }, "-": { + kind: "fn", # native function inputs: 2, function: "number_sub" }, "*": { + kind: "fn", # native function inputs: 2, function: "number_mul" }, "/": { + kind: "fn", # native function inputs: 2, function: "number_div" }, From 51ff32e2908a3cc171ce171ceae6088074d43821 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 21:19:41 +0330 Subject: [PATCH 06/40] step 3 done~ --- jq/core.jq | 16 +++++ jq/env.jq | 72 +++++++++++++++++++++++ jq/interp.jq | 17 +----- jq/step2_eval.jq | 57 +++++++----------- jq/step3_env.jq | 150 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 263 insertions(+), 49 deletions(-) create mode 100644 jq/core.jq create mode 100644 jq/env.jq create mode 100644 jq/step3_env.jq diff --git a/jq/core.jq b/jq/core.jq new file mode 100644 index 00000000..c4f70ad5 --- /dev/null +++ b/jq/core.jq @@ -0,0 +1,16 @@ +include "utils"; + +def core_interp(arguments; env): + ( + select(.function == "number_add") | + arguments | map(.value) | .[0] + .[1] | wrap("number") + ) // ( + select(.function == "number_sub") | + arguments | map(.value) | .[0] - .[1] | wrap("number") + ) // ( + select(.function == "number_mul") | + arguments | map(.value) | .[0] * .[1] | wrap("number") + ) // ( + select(.function == "number_div") | + arguments | map(.value) | .[0] / .[1] | wrap("number") + ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/env.jq b/jq/env.jq new file mode 100644 index 00000000..3785ac7c --- /dev/null +++ b/jq/env.jq @@ -0,0 +1,72 @@ +include "utils"; + +def childEnv(binds; value): + { + parent: ., + environment: [binds, value] | transpose | map({(.[0]): .[1]}) | from_entries + }; + +def pureChildEnv: + { + parent: ., + environment: {} + }; + +def rootEnv: + { + parent: null, + environment: {} + }; + +def env_set(key; value): + { + parent: .parent, + environment: (.environment + (.environment | .[key] |= value)) # merge together, as .environment[key] |= value does not work + }; + +def env_set(env; key; value): + { + parent: env.parent, + environment: (env.environment + (env.environment | .[key] |= value)) # merge together, as env.environment[key] |= value does not work + }; + +def env_find(env): + if env.environment[.] == null then + if env.parent then + env_find(env.parent) + else + null + end + else + env + end; + +def env_get(env): + . as $key | env_find(env).environment[$key] // jqmal_error("Symbol \($key) not found"); + +def addEnv(env): + { + expr: ., + env: env + }; + +def addToEnv(env; name; expr): + { + expr: expr, + env: env_set(env; name; expr) + }; + +def addToEnv(envexp; name): + { + expr: envexp.expr, + env: env_set(envexp.env; name; envexp.expr) + }; + +# for step2 +def lookup(env): + env.environment[.] // + if env.parent then + lookup(env.parent) + else + jqmal_error("Symbol \(.) not found") + end; diff --git a/jq/interp.jq b/jq/interp.jq index 0fa2f3a2..980412b6 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -1,4 +1,5 @@ include "utils"; +include "core"; def arg_check(args): if .inputs != (args|length) then @@ -10,17 +11,5 @@ def arg_check(args): def interpret(arguments; env): select(.kind == "fn") | ( - arg_check(arguments) | ( - select(.function == "number_add") | - arguments | map(.value) | .[0] + .[1] | wrap("number") - ) // ( - select(.function == "number_sub") | - arguments | map(.value) | .[0] - .[1] | wrap("number") - ) // ( - select(.function == "number_mul") | - arguments | map(.value) | .[0] * .[1] | wrap("number") - ) // ( - select(.function == "number_div") | - arguments | map(.value) | .[0] / .[1] | wrap("number") - ) // jqmal_error("Unknown native function \(.function)"); - ) // jqmal_error("Unsupported function kind \(.kind)") \ No newline at end of file + arg_check(arguments) | core_interp(arguments; env) + ) // jqmal_error("Unsupported function kind \(.kind)"); \ No newline at end of file diff --git a/jq/step2_eval.jq b/jq/step2_eval.jq index 6df358fe..f7dfa1d0 100644 --- a/jq/step2_eval.jq +++ b/jq/step2_eval.jq @@ -12,12 +12,8 @@ def READ: read_str | read_form | .value; def lookup(env): - env.environment[.] // - if env.parent then - lookup(env.parent) - else - jqmal_error("Symbol \(.) not found") - end; + env[.] // + jqmal_error("Symbol \(.) not found"); def EVAL(env): def eval_ast: @@ -61,38 +57,29 @@ def repl_(env): ("user> " | stderr) | (read_line | rep(env)); -def childEnv(binds; value): - { - parent: ., - environment: [binds, value] | transpose | map({(.[0]): .[1]}) | from_entries - }; - # we don't have no indirect functions, so we'll have to interpret the old way def replEnv: { - parent: null, - environment: { - "+": { - kind: "fn", # native function - inputs: 2, - function: "number_add" - }, - "-": { - kind: "fn", # native function - inputs: 2, - function: "number_sub" - }, - "*": { - kind: "fn", # native function - inputs: 2, - function: "number_mul" - }, - "/": { - kind: "fn", # native function - inputs: 2, - function: "number_div" - }, - } + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, }; def repl(env): diff --git a/jq/step3_env.jq b/jq/step3_env.jq new file mode 100644 index 00000000..87250c84 --- /dev/null +++ b/jq/step3_env.jq @@ -0,0 +1,150 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; +include "env"; + +def read_line: + . as $in + | label $top + | input; + +def READ: + read_str | read_form | .value; + +def EVAL(env): + def hmap_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem[1] | EVAL($env) as $resv | + { value: [$elem[0], $resv.expr], env: env }, + ({env: $resv.env, list: $rest} | hmap_with_env) + end; + def map_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem | EVAL($env) as $resv | + { value: $resv.expr, env: env }, + ({env: $resv.env, list: $rest} | map_with_env) + end; + (select(.kind == "list") | + if .value | length == 0 then + . + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL(env)) as $evval | + addToEnv($evval; $value[1].value) + ) // + ( + .value | select(.[0].value == "let*") as $value | + (env | pureChildEnv) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | { expr: EVAL($env).expr, env: env } + ) // + ( + reduce .value[] as $elem ( + {value: [], env: env}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + { + value: ($dot.value + [$eval_env.expr]), + env: $eval_env.env + } + ) | { expr: .value, env: .env } as $ev + | $ev.expr | first | + interpret($ev.expr[1:]; $ev.env) | addEnv($ev.env) + ) // + addEnv(env) + ) + end + ) // + (select(.kind == "vector") | + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } | addEnv($res | last.env) + ) // + (select(.kind == "hashmap") | + [ { env: env, list: .value | to_entries } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } | addEnv($res | last.env) + ) // + (select(.kind == "symbol") | + .value | env_get(env) | addEnv(env) + ) // addEnv(env); + +def PRINT: + pr_str; + +def rep(env): + READ | EVAL(env) as $expenv | + if $expenv.expr != null then + $expenv.expr | PRINT + else + null + end | addEnv($expenv.env); + +def repl_(env): + ("user> " | stderr) | + (read_line | rep(env)); + +def childEnv(binds; value): + { + parent: ., + environment: [binds, value] | transpose | map({(.[0]): .[1]}) | from_entries + }; + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: { + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, + } + }; + +def repl(env): + def xrepl: + (.env as $env | try repl_($env) catch addEnv($env)) as $expenv | + { + value: $expenv.expr, + stop: false, + env: ($expenv.env // .env) + } | ., xrepl; + {stop: false, env: env} | xrepl | if .value then .value else empty end; + +repl(replEnv) \ No newline at end of file From da5289bb9527d417eb1af74dc23a507d13a7d849 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 23:29:12 +0330 Subject: [PATCH 07/40] impl step5 cannot print raw strings, so a lot of things fail --- jq/core.jq | 104 +++++++++++++++++++++++++++++++++++++++++++++++++- jq/env.jq | 33 ++++++++++++++-- jq/interp.jq | 24 ++++++++++-- jq/printer.jq | 17 ++++++--- 4 files changed, 164 insertions(+), 14 deletions(-) diff --git a/jq/core.jq b/jq/core.jq index c4f70ad5..5ebf4e4d 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -1,10 +1,80 @@ include "utils"; +include "printer"; + +def core_identify: + { + "prn": { + kind: "fn", + function: "prn", + inputs: -1 + }, + "pr-str": { + kind: "fn", + function: "pr-str", + inputs: -1 + }, + "str": { + kind: "fn", + function: "str", + inputs: -1 + }, + "println": { + kind: "fn", + function: "println", + inputs: -1 + }, + "list": { + kind: "fn", + function: "list", + inputs: -1 + }, + "list?": { + kind: "fn", + function: "list?", + inputs: 1 + }, + "empty?": { + kind: "fn", + function: "empty?", + inputs: 1 + }, + "count": { + kind: "fn", + function: "count", + inputs: 1 + }, + "=": { + kind: "fn", + function: "=", + inputs: 2 + }, + "<": { + kind: "fn", + function: "<", + inputs: 2 + }, + "<=": { + kind: "fn", + function: "<=", + inputs: 2 + }, + ">": { + kind: "fn", + function: ">", + inputs: 2 + }, + ">=": { + kind: "fn", + function: ">=", + inputs: 2 + }, + }; def core_interp(arguments; env): ( select(.function == "number_add") | arguments | map(.value) | .[0] + .[1] | wrap("number") - ) // ( + ) /( select(.function == "number_sub") | arguments | map(.value) | .[0] - .[1] | wrap("number") ) // ( @@ -13,4 +83,36 @@ def core_interp(arguments; env): ) // ( select(.function == "number_div") | arguments | map(.value) | .[0] / .[1] | wrap("number") + ) // ( + select(.function == "prn") | + arguments | map(pr_str({readable: true})) | join(" ") | + stderr | null | wrap("nil") + ) // ( + select(.function == "pr-str") | + arguments | map(pr_str({readable: true})) | join(" ") | wrap("string") + ) // ( + select(.function == "str") | + arguments | map(pr_str({readable: false})) | join("") | wrap("string") + ) // ( + select(.function == "println") | + arguments | map(pr_str({readable: false})) | join(" ") | stderr | null | wrap("nil") + ) // ( + select(.function == "list") | + arguments | wrap("list") + ) // ( + select(.function == "list?") | null | wrap(arguments | first.kind == "list" | tostring) + ) // ( + select(.function == "empty?") | null | wrap(arguments|first.value | length == 0 | tostring) + ) // ( + select(.function == "count") | arguments|first.value | length | wrap("number") + ) // ( + select(.function == "=") | null | wrap(arguments[0] == arguments[1] | tostring) + ) // ( + select(.function == "<") | null | wrap(arguments[0].value < arguments[1].value | tostring) + ) // ( + select(.function == "<=") | null | wrap(arguments[0].value <= arguments[1].value | tostring) + ) // ( + select(.function == ">") | null | wrap(arguments[0].value > arguments[1].value | tostring) + ) // ( + select(.function == ">=") | null | wrap(arguments[0].value >= arguments[1].value | tostring) ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/env.jq b/jq/env.jq index 3785ac7c..9800a417 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -1,9 +1,36 @@ include "utils"; -def childEnv(binds; value): +def childEnv(binds; exprs): { parent: ., - environment: [binds, value] | transpose | map({(.[0]): .[1]}) | from_entries + environment: [binds, exprs] | transpose | ( + . as $dot | reduce .[] as $item ( + { value: [], seen: false, name: null, idx: 0 }; + if $item[1] != null then + if .seen then + { + value: (.value[1:-1] + (.value|last[1].value += [$item[1]])), + seen: true, + name: .name + } + else + if $item[0] == "&" then + $dot[.idx+1][0] as $name | { + value: (.value + [[$name, {kind:"list", value: [$item[1]]}]]), + seen: true, + name: $name + } + else + { + value: (.value + [$item]), + seen: false, + name: null + } + end + end | (.idx |= .idx + 1) + else . end + ) + ) | .value | map({(.[0]): .[1]}) | add }; def pureChildEnv: @@ -69,4 +96,4 @@ def lookup(env): lookup(env.parent) else jqmal_error("Symbol \(.) not found") - end; + end; \ No newline at end of file diff --git a/jq/interp.jq b/jq/interp.jq index 980412b6..4b29f7cd 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -1,15 +1,31 @@ include "utils"; include "core"; +include "env"; def arg_check(args): - if .inputs != (args|length) then + if .inputs == -1 then + . + else if .inputs != (args|length) then jqmal_error("Invalid number of arguments (expected \(.inputs) got \(args|length): \(args))") else . - end; + end end; def interpret(arguments; env): - select(.kind == "fn") | ( + ((select(.kind == "fn") | ( arg_check(arguments) | core_interp(arguments; env) - ) // jqmal_error("Unsupported function kind \(.kind)"); \ No newline at end of file + )) // + jqmal_error("Unsupported native function kind \(.kind)")) | addEnv(env); + +def interpret(arguments; env; _eval): + (select(.kind == "fn") | ( + arg_check(arguments) | core_interp(arguments; env) | addEnv(env) + )) // + (select(.kind == "function") as $fn | + # todo: arg_check + .env | childEnv($fn.binds; arguments) as $fnEnv | + { env: $fnEnv, expr: $fn.body } | _eval | { expr: .expr, env: env } + ) // + jqmal_error("Unsupported function kind \(.kind)"); + \ No newline at end of file diff --git a/jq/printer.jq b/jq/printer.jq index 9b1d06b7..c3765f7f 100644 --- a/jq/printer.jq +++ b/jq/printer.jq @@ -6,15 +6,20 @@ def _reconstruct_hash: }, .value.value]); -def pr_str: +def pr_str(opt): (select(.kind == "symbol") | .value) // - (select(.kind == "string") | .value | tojson) // + (select(.kind == "string") | .value | if opt.readable then tojson else . end) // (select(.kind == "keyword") | ":\(.value)") // (select(.kind == "number") | .value | tostring) // - (select(.kind == "list") | .value | map(pr_str) | join(" ") | "(\(.))") // - (select(.kind == "vector") | .value | map(pr_str) | join(" ") | "[\(.)]") // - (select(.kind == "hashmap") | .value | to_entries | _reconstruct_hash | add // [] | map(pr_str) | join(" ") | "{\(.)}") // + (select(.kind == "list") | .value | map(pr_str(opt)) | join(" ") | "(\(.))") // + (select(.kind == "vector") | .value | map(pr_str(opt)) | join(" ") | "[\(.)]") // + (select(.kind == "hashmap") | .value | to_entries | _reconstruct_hash | add // [] | map(pr_str(opt)) | join(" ") | "{\(.)}") // (select(.kind == "nil") | "nil") // (select(.kind == "true") | "true") // (select(.kind == "false") | "false") // - "#"; \ No newline at end of file + (select(.kind == "fn") | "#") // + (select(.kind == "function")| "#") // + "#"; + +def pr_str: + pr_str({readable: true}); \ No newline at end of file From 23f9ce8e84bab1015873423feb99a887b2c6ee55 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 23:52:44 +0330 Subject: [PATCH 08/40] fix (= list vector) and the case where (((& x) ...)) assigns nothing --- jq/core.jq | 14 ++++++++++++-- jq/env.jq | 10 +++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/jq/core.jq b/jq/core.jq index 5ebf4e4d..27cb25f1 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -70,11 +70,21 @@ def core_identify: }, }; +def vec2list(obj): + if obj.kind == "list" then + obj.value | map(vec2list(.)) | wrap("list") + else + if obj.kind == "vector" then + obj.value | map(vec2list(.)) | wrap("list") + else + obj + end end; + def core_interp(arguments; env): ( select(.function == "number_add") | arguments | map(.value) | .[0] + .[1] | wrap("number") - ) /( + ) // ( select(.function == "number_sub") | arguments | map(.value) | .[0] - .[1] | wrap("number") ) // ( @@ -106,7 +116,7 @@ def core_interp(arguments; env): ) // ( select(.function == "count") | arguments|first.value | length | wrap("number") ) // ( - select(.function == "=") | null | wrap(arguments[0] == arguments[1] | tostring) + select(.function == "=") | null | wrap(vec2list(arguments[0]) == vec2list(arguments[1]) | tostring) ) // ( select(.function == "<") | null | wrap(arguments[0].value < arguments[1].value | tostring) ) // ( diff --git a/jq/env.jq b/jq/env.jq index 9800a417..f2085cc1 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -28,7 +28,15 @@ def childEnv(binds; exprs): } end end | (.idx |= .idx + 1) - else . end + else + if $item[0] == "&" then + $dot[.idx+1][0] as $name | { + value: (.value + [[$name, {kind:"list", value: []}]]), + seen: true, + name: $name + } + else . end + end ) ) | .value | map({(.[0]): .[1]}) | add }; From 3ed32d37abfb314b825cf6df5d7804eee07a6588 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 5 Jan 2020 23:55:03 +0330 Subject: [PATCH 09/40] ...add step4... --- jq/step4_if_fn_do.jq | 199 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 jq/step4_if_fn_do.jq diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq new file mode 100644 index 00000000..24b58576 --- /dev/null +++ b/jq/step4_if_fn_do.jq @@ -0,0 +1,199 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; +include "env"; +include "core"; + +def read_line: + . as $in + | label $top + | input; + +def READ: + read_str | read_form | .value; + +# def eval_ast(env): +# (select(.kind == "symbol") | .value | env_get(env) | addEnv(env)) // +# (select(.kind == "list") | reduce .value[] as $elem ( +# {value: [], env: env}; +# . as $dot | $elem | EVAL($dot.env) as $eval_env | +# { +# value: ($dot.value + [$eval_env.expr]), +# env: $eval_env.env +# } +# ) | { expr: .value, env: .env }) // (addEnv(env)); + +def EVAL(env): + def _eval_here: + .env as $env | .expr | EVAL($env); + + def hmap_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem[1] | EVAL($env) as $resv | + { value: [$elem[0], $resv.expr], env: env }, + ({env: $resv.env, list: $rest} | hmap_with_env) + end; + def map_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem | EVAL($env) as $resv | + { value: $resv.expr, env: env }, + ({env: $resv.env, list: $rest} | map_with_env) + end; + (select(.kind == "list") | + if .value | length == 0 then + . + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL(env)) as $evval | + addToEnv($evval; $value[1].value) + ) // + ( + .value | select(.[0].value == "let*") as $value | + (env | pureChildEnv) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | { expr: EVAL($env).expr, env: env } + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: env, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL(env) as $condenv | + if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) | EVAL($condenv.env) + else + $value[2] | EVAL($condenv.env) + end + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + { + kind: "function", + binds: $value[1].value | map(.value), + env: env, + body: $value[2] + } | addEnv(env) + ) // + ( + reduce .value[] as $elem ( + {value: [], env: env}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + { + value: ($dot.value + [$eval_env.expr]), + env: $eval_env.env + } + ) | { expr: .value, env: .env } as $ev + | $ev.expr | first | + interpret($ev.expr[1:]; $ev.env; _eval_here) + ) // + addEnv(env) + ) + end + ) // + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } | addEnv(env) + else + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } | addEnv($res | last.env) + end + ) // + (select(.kind == "hashmap") | + [ { env: env, list: .value | to_entries } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } | addEnv($res | last.env) + ) // + (select(.kind == "function") | + . | addEnv(env) # return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get(env) | addEnv(env) + ) // addEnv(env); + +def PRINT: + pr_str; + +def rep(env): + READ | EVAL(env) as $expenv | + if $expenv.expr != null then + $expenv.expr | PRINT + else + null + end | addEnv($expenv.env); + +def repl_(env): + ("user> " | stderr) | + (read_line | rep(env)); + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: ({ + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, + } + core_identify) + }; + +def repl(env): + def xrepl: + (.env as $env | try repl_($env) catch addEnv($env)) as $expenv | + { + value: $expenv.expr, + stop: false, + env: ($expenv.env // .env) + } | ., xrepl; + {stop: false, env: env} | xrepl | if .value then .value else empty end; + +repl( + "(def! not (fn* (a) (if a false true)))" | rep(replEnv) | .env +) \ No newline at end of file From 136fb719d69e5466e5dd49ece31119427d6ff6c4 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Mon, 6 Jan 2020 03:10:03 +0330 Subject: [PATCH 10/40] EQUIRECURSION BABY! fix (cheat) print --- jq/core.jq | 13 ++++++++++--- jq/env.jq | 46 ++++++++++++++++++++++++++++++++++++++------ jq/interp.jq | 12 +++++++++--- jq/printer.jq | 2 +- jq/run | 2 +- jq/step4_if_fn_do.jq | 29 ++++++++++++++++++++++------ jq/utils.jq | 5 ++++- 7 files changed, 88 insertions(+), 21 deletions(-) diff --git a/jq/core.jq b/jq/core.jq index 27cb25f1..da5a900e 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -3,6 +3,11 @@ include "printer"; def core_identify: { + "env": { + kind: "fn", + function: "env", + inputs: 0 + }, "prn": { kind: "fn", function: "prn", @@ -93,10 +98,12 @@ def core_interp(arguments; env): ) // ( select(.function == "number_div") | arguments | map(.value) | .[0] / .[1] | wrap("number") + ) // ( + select(.function == "env") | + env | tojson | wrap("string") ) // ( select(.function == "prn") | - arguments | map(pr_str({readable: true})) | join(" ") | - stderr | null | wrap("nil") + arguments | map(pr_str({readable: true})) | join(" ") | _print | null | wrap("nil") ) // ( select(.function == "pr-str") | arguments | map(pr_str({readable: true})) | join(" ") | wrap("string") @@ -105,7 +112,7 @@ def core_interp(arguments; env): arguments | map(pr_str({readable: false})) | join("") | wrap("string") ) // ( select(.function == "println") | - arguments | map(pr_str({readable: false})) | join(" ") | stderr | null | wrap("nil") + arguments | map(pr_str({readable: false})) | join(" ") | _print | null | wrap("nil") ) // ( select(.function == "list") | arguments | wrap("list") diff --git a/jq/env.jq b/jq/env.jq index f2085cc1..11d590ae 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -53,16 +53,50 @@ def rootEnv: environment: {} }; -def env_set(key; value): - { +def inform_function(name): + (.names += [name]) | (.names |= unique); + +def inform_function_multi(names): + . as $dot | reduce names[] as $name( + $dot; + inform_function($name) + ); + +def env_multiset(keys; value): + (if value.kind == "function" then + value | inform_function_multi(keys) + else + value + end) as $value | { parent: .parent, - environment: (.environment + (.environment | .[key] |= value)) # merge together, as .environment[key] |= value does not work + environment: ( + .environment + (reduce keys[] as $key(.environment; .[$key] |= value)) + ) }; -def env_set(env; key; value): - { +def env_multiset(env; keys; value): + env | env_multiset(keys; value); + +def env_set($key; $value): + (if $value.kind == "function" then + # inform the function of its names + $value | inform_function($key) + else + $value + end) as $value | { + parent: .parent, + environment: (.environment + (.environment | .[$key] |= $value)) # merge together, as .environment[key] |= value does not work + }; + +def env_set(env; $key; $value): + (if $value.kind == "function" then + # inform the function of its names + $value | (.names += [$key]) + else + $value + end) as $value | { parent: env.parent, - environment: (env.environment + (env.environment | .[key] |= value)) # merge together, as env.environment[key] |= value does not work + environment: (env.environment + (env.environment | .[$key] |= $value)) # merge together, as env.environment[key] |= value does not work }; def env_find(env): diff --git a/jq/interp.jq b/jq/interp.jq index 4b29f7cd..dca7b223 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -24,8 +24,14 @@ def interpret(arguments; env; _eval): )) // (select(.kind == "function") as $fn | # todo: arg_check - .env | childEnv($fn.binds; arguments) as $fnEnv | - { env: $fnEnv, expr: $fn.body } | _eval | { expr: .expr, env: env } + .env as $oenv | .env | childEnv($fn.binds; arguments) as $fnEnv | + # tell it about its surroundings + (reduce $fn.corecursives[] as $name ( + $fnEnv; + env_set(.; $name[0]; $name[1] | setpath(["corecursives"]; $fn.corecursives)))) as $fnEnv | + # tell it about itself + env_multiset($fnEnv; $fn.names; $fn) as $fnEnv | + { env: env_multiset($fnEnv; $fn.names; $fn), expr: $fn.body } | _eval | { expr: .expr, env: env } ) // jqmal_error("Unsupported function kind \(.kind)"); - \ No newline at end of file + diff --git a/jq/printer.jq b/jq/printer.jq index c3765f7f..a6f2af62 100644 --- a/jq/printer.jq +++ b/jq/printer.jq @@ -18,7 +18,7 @@ def pr_str(opt): (select(.kind == "true") | "true") // (select(.kind == "false") | "false") // (select(.kind == "fn") | "#") // - (select(.kind == "function")| "#") // + (select(.kind == "function")| "#") // "#"; def pr_str: diff --git a/jq/run b/jq/run index 07d76552..bb5218d6 100755 --- a/jq/run +++ b/jq/run @@ -1,2 +1,2 @@ #!/bin/bash -exec jq -n -r -R -f "$(dirname "$0")"/"${STEP:-stepA_mal}.jq" "${@}" +exec jq -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" "${@}" |& jq -RrM -c 'try fromjson[1]' diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index 24b58576..ae9371f6 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -23,6 +23,16 @@ def READ: # env: $eval_env.env # } # ) | { expr: .value, env: .env }) // (addEnv(env)); +# (let* (f (fn* (n) (if (= n 0) 0 (g (- n 1)))) g (fn* (n) (f n))) (f 2)) + +def patch_with_env(env): + . as $dot | (reduce .[] as $fnv ( + []; + . + [$fnv | setpath([1, "corecursives"]; ($fnv[1].corecursives + $dot) | unique)] + )) as $functions | reduce $functions[] as $function ( + env; + env_set(.; $function[0]; $function[1]) + ) | { functions: $functions, env: . }; def EVAL(env): def _eval_here: @@ -64,9 +74,14 @@ def EVAL(env): .value | select(.[0].value == "let*") as $value | (env | pureChildEnv) as $subenv | (reduce ($value[1].value | nwise(2)) as $xvalue ( - $subenv; - . as $env | $xvalue[1] | EVAL($env) as $expenv | - env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + { functions: [], env: $subenv }; + . as $dot | .env as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set($expenv.env; $xvalue[0].value; $expenv.expr) as $newenv | + ($dot.functions + [if $expenv.expr.kind == "function" then [($xvalue[0].value), ($xvalue[0].value | env_get($newenv))] else empty end]) | patch_with_env($newenv) as $funcenv | + { + functions: $funcenv.functions, + env: $funcenv.env + }) | .env) as $env | $value[2] | { expr: EVAL($env).expr, env: env } ) // ( @@ -94,7 +109,9 @@ def EVAL(env): kind: "function", binds: $value[1].value | map(.value), env: env, - body: $value[2] + body: $value[2], + names: [], # we can't do that circular reference this + corecursives: [] # for equirecursive functions defined in let* } | addEnv(env) ) // ( @@ -153,7 +170,7 @@ def rep(env): end | addEnv($expenv.env); def repl_(env): - ("user> " | stderr) | + ("user> " | _print) | (read_line | rep(env)); # we don't have no indirect functions, so we'll have to interpret the old way @@ -192,7 +209,7 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then .value else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; repl( "(def! not (fn* (a) (if a false true)))" | rep(replEnv) | .env diff --git a/jq/utils.jq b/jq/utils.jq index c031aeb1..a5959b3a 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -17,4 +17,7 @@ def wrap(kind): { kind: kind, value: . - }; \ No newline at end of file + }; + +def _print: + debug; \ No newline at end of file From 086a79dc6b910ebb125ddb01414ea1e91968326e Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Mon, 6 Jan 2020 11:00:26 +0330 Subject: [PATCH 11/40] fix scoping and backport print patch add TCO file, no TCO though (seems ok?) --- jq/env.jq | 26 ++++- jq/interp.jq | 18 +++- jq/run | 2 +- jq/step0_repl.jq | 5 +- jq/step1_read_print.jq | 4 +- jq/step2_eval.jq | 4 +- jq/step3_env.jq | 8 +- jq/step4_if_fn_do.jq | 48 +++++---- jq/step5_tco.jq | 224 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 301 insertions(+), 38 deletions(-) create mode 100644 jq/step5_tco.jq diff --git a/jq/env.jq b/jq/env.jq index 11d590ae..671203c3 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -3,6 +3,7 @@ include "utils"; def childEnv(binds; exprs): { parent: ., + fallback: null, environment: [binds, exprs] | transpose | ( . as $dot | reduce .[] as $item ( { value: [], seen: false, name: null, idx: 0 }; @@ -44,12 +45,14 @@ def childEnv(binds; exprs): def pureChildEnv: { parent: ., - environment: {} + environment: {}, + fallback: null }; def rootEnv: { parent: null, + fallback: null, environment: {} }; @@ -88,10 +91,20 @@ def env_set($key; $value): environment: (.environment + (.environment | .[$key] |= $value)) # merge together, as .environment[key] |= value does not work }; +def env_dump_keys: + def _dump: + .environment | keys; + + if .parent == null then + _dump + else + .parent | env_dump_keys + _dump + end; + def env_set(env; $key; $value): (if $value.kind == "function" then # inform the function of its names - $value | (.names += [$key]) + $value | (.names += [$key]) | (.names |= unique) else $value end) as $value | { @@ -102,7 +115,7 @@ def env_set(env; $key; $value): def env_find(env): if env.environment[.] == null then if env.parent then - env_find(env.parent) + env_find(env.parent) // if env.fallback then env_find(env.fallback) else null end else null end @@ -110,6 +123,13 @@ def env_find(env): env end; +def env_setfallback(env; fallback): + { + parent: env.parent, + fallback: fallback, + environment: env.environment + }; + def env_get(env): . as $key | env_find(env).environment[$key] // jqmal_error("Symbol \($key) not found"); diff --git a/jq/interp.jq b/jq/interp.jq index dca7b223..58c2ff7a 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -16,7 +16,7 @@ def interpret(arguments; env): ((select(.kind == "fn") | ( arg_check(arguments) | core_interp(arguments; env) )) // - jqmal_error("Unsupported native function kind \(.kind)")) | addEnv(env); + jqmal_error("Unsupported native function kind \(.kind)")); def interpret(arguments; env; _eval): (select(.kind == "fn") | ( @@ -24,14 +24,22 @@ def interpret(arguments; env; _eval): )) // (select(.kind == "function") as $fn | # todo: arg_check - .env as $oenv | .env | childEnv($fn.binds; arguments) as $fnEnv | + .env as $oenv | env_setfallback(.env; env) | childEnv($fn.binds; arguments) as $fnEnv | # tell it about its surroundings - (reduce $fn.corecursives[] as $name ( + (reduce $fn.free_referencess[] as $name ( $fnEnv; - env_set(.; $name[0]; $name[1] | setpath(["corecursives"]; $fn.corecursives)))) as $fnEnv | + . as $env | try env_set( + .; + $name; + $name | env_get(env) | . as $xvalue + | if $xvalue.kind == "function" then + setpath(["free_referencess"]; $fn.free_referencess) + else + $xvalue + end + ) catch $env)) as $fnEnv | # tell it about itself env_multiset($fnEnv; $fn.names; $fn) as $fnEnv | { env: env_multiset($fnEnv; $fn.names; $fn), expr: $fn.body } | _eval | { expr: .expr, env: env } ) // jqmal_error("Unsupported function kind \(.kind)"); - diff --git a/jq/run b/jq/run index bb5218d6..984d9238 100755 --- a/jq/run +++ b/jq/run @@ -1,2 +1,2 @@ #!/bin/bash -exec jq -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" "${@}" |& jq -RrM -c 'try fromjson[1]' +exec jq -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" "${@}" |& jq -Rr 'try fromjson[1]' diff --git a/jq/step0_repl.jq b/jq/step0_repl.jq index e2fb9b34..51d4b3a2 100644 --- a/jq/step0_repl.jq +++ b/jq/step0_repl.jq @@ -1,3 +1,4 @@ +include "utils"; def read_line: . as $in @@ -14,10 +15,10 @@ def PRINT: .; def rep: - READ | EVAL | PRINT; + READ | EVAL | PRINT | _print; def repl_: - ("user> " | stderr) | + ("user> " | _print) | (read_line | rep); def repl: diff --git a/jq/step1_read_print.jq b/jq/step1_read_print.jq index 7d416d44..24293833 100644 --- a/jq/step1_read_print.jq +++ b/jq/step1_read_print.jq @@ -25,7 +25,7 @@ def rep: end; def repl_: - ("user> " | stderr) | + ("user> " | _print) | (read_line | rep); def repl: @@ -37,6 +37,6 @@ def repl: {value: "Error: \(.)", continue: true} else {value: ., continue: false} - end) | if .value then .value else empty end; + end) | if .value then .value|_print else empty end; repl diff --git a/jq/step2_eval.jq b/jq/step2_eval.jq index f7dfa1d0..cfc6ff65 100644 --- a/jq/step2_eval.jq +++ b/jq/step2_eval.jq @@ -54,7 +54,7 @@ def rep(env): end; def repl_(env): - ("user> " | stderr) | + ("user> " | _print) | (read_line | rep(env)); # we don't have no indirect functions, so we'll have to interpret the old way @@ -91,6 +91,6 @@ def repl(env): {value: "Error: \(.)", continue: true} else {value: ., continue: false} - end) | if .value then .value else empty end; + end) | if .value then .value|_print else empty end; repl(replEnv) \ No newline at end of file diff --git a/jq/step3_env.jq b/jq/step3_env.jq index 87250c84..e9396358 100644 --- a/jq/step3_env.jq +++ b/jq/step3_env.jq @@ -13,6 +13,8 @@ def READ: read_str | read_form | .value; def EVAL(env): + def _eval_here: + .env as $env | .expr | EVAL($env); def hmap_with_env: .env as $env | .list as $list | if $list|length == 0 then @@ -64,7 +66,7 @@ def EVAL(env): } ) | { expr: .value, env: .env } as $ev | $ev.expr | first | - interpret($ev.expr[1:]; $ev.env) | addEnv($ev.env) + interpret($ev.expr[1:]; $ev.env; _eval_here) ) // addEnv(env) ) @@ -100,7 +102,7 @@ def rep(env): end | addEnv($expenv.env); def repl_(env): - ("user> " | stderr) | + ("user> " | _print) | (read_line | rep(env)); def childEnv(binds; value): @@ -145,6 +147,6 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then .value else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; repl(replEnv) \ No newline at end of file diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index ae9371f6..a45e84c2 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -23,16 +23,29 @@ def READ: # env: $eval_env.env # } # ) | { expr: .value, env: .env }) // (addEnv(env)); -# (let* (f (fn* (n) (if (= n 0) 0 (g (- n 1)))) g (fn* (n) (f n))) (f 2)) -def patch_with_env(env): - . as $dot | (reduce .[] as $fnv ( - []; - . + [$fnv | setpath([1, "corecursives"]; ($fnv[1].corecursives + $dot) | unique)] - )) as $functions | reduce $functions[] as $function ( - env; - env_set(.; $function[0]; $function[1]) - ) | { functions: $functions, env: . }; +# def patch_with_env(env): +# . as $dot | (reduce .[] as $fnv ( +# []; +# . + [$fnv | setpath([1, "free_referencess"]; ($fnv[1].free_referencess + $dot) | unique)] +# )) as $functions | reduce $functions[] as $function ( +# env; +# env_set(.; $function[0]; $function[1]) +# ) | { functions: $functions, env: . }; + +def find_free_references(keys): + def _refs: + . as $dot + | if .kind == "symbol" then + if keys | contains([$dot.value]) then [] else [$dot.value] end + else if "list" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) + else if "vector" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) + else + [] + end end end; + _refs | unique; def EVAL(env): def _eval_here: @@ -74,14 +87,9 @@ def EVAL(env): .value | select(.[0].value == "let*") as $value | (env | pureChildEnv) as $subenv | (reduce ($value[1].value | nwise(2)) as $xvalue ( - { functions: [], env: $subenv }; - . as $dot | .env as $env | $xvalue[1] | EVAL($env) as $expenv | - env_set($expenv.env; $xvalue[0].value; $expenv.expr) as $newenv | - ($dot.functions + [if $expenv.expr.kind == "function" then [($xvalue[0].value), ($xvalue[0].value | env_get($newenv))] else empty end]) | patch_with_env($newenv) as $funcenv | - { - functions: $funcenv.functions, - env: $funcenv.env - }) | .env) as $env + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env | $value[2] | { expr: EVAL($env).expr, env: env } ) // ( @@ -105,13 +113,13 @@ def EVAL(env): # we can't do what the guide says, so we'll skip over this # and ues the later implementation # (fn* args body) - { + $value[1].value | map(.value) as $binds | { kind: "function", - binds: $value[1].value | map(.value), + binds: $binds, env: env, body: $value[2], names: [], # we can't do that circular reference this - corecursives: [] # for equirecursive functions defined in let* + free_referencess: $value[2] | find_free_references(env | env_dump_keys + $binds) # for dynamically scoped variables } | addEnv(env) ) // ( diff --git a/jq/step5_tco.jq b/jq/step5_tco.jq new file mode 100644 index 00000000..a45e84c2 --- /dev/null +++ b/jq/step5_tco.jq @@ -0,0 +1,224 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; +include "env"; +include "core"; + +def read_line: + . as $in + | label $top + | input; + +def READ: + read_str | read_form | .value; + +# def eval_ast(env): +# (select(.kind == "symbol") | .value | env_get(env) | addEnv(env)) // +# (select(.kind == "list") | reduce .value[] as $elem ( +# {value: [], env: env}; +# . as $dot | $elem | EVAL($dot.env) as $eval_env | +# { +# value: ($dot.value + [$eval_env.expr]), +# env: $eval_env.env +# } +# ) | { expr: .value, env: .env }) // (addEnv(env)); + +# def patch_with_env(env): +# . as $dot | (reduce .[] as $fnv ( +# []; +# . + [$fnv | setpath([1, "free_referencess"]; ($fnv[1].free_referencess + $dot) | unique)] +# )) as $functions | reduce $functions[] as $function ( +# env; +# env_set(.; $function[0]; $function[1]) +# ) | { functions: $functions, env: . }; + +def find_free_references(keys): + def _refs: + . as $dot + | if .kind == "symbol" then + if keys | contains([$dot.value]) then [] else [$dot.value] end + else if "list" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) + else if "vector" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) + else + [] + end end end; + _refs | unique; + +def EVAL(env): + def _eval_here: + .env as $env | .expr | EVAL($env); + + def hmap_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem[1] | EVAL($env) as $resv | + { value: [$elem[0], $resv.expr], env: env }, + ({env: $resv.env, list: $rest} | hmap_with_env) + end; + def map_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem | EVAL($env) as $resv | + { value: $resv.expr, env: env }, + ({env: $resv.env, list: $rest} | map_with_env) + end; + (select(.kind == "list") | + if .value | length == 0 then + . + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL(env)) as $evval | + addToEnv($evval; $value[1].value) + ) // + ( + .value | select(.[0].value == "let*") as $value | + (env | pureChildEnv) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | { expr: EVAL($env).expr, env: env } + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: env, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL(env) as $condenv | + if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) | EVAL($condenv.env) + else + $value[2] | EVAL($condenv.env) + end + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + $value[1].value | map(.value) as $binds | { + kind: "function", + binds: $binds, + env: env, + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $value[2] | find_free_references(env | env_dump_keys + $binds) # for dynamically scoped variables + } | addEnv(env) + ) // + ( + reduce .value[] as $elem ( + {value: [], env: env}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + { + value: ($dot.value + [$eval_env.expr]), + env: $eval_env.env + } + ) | { expr: .value, env: .env } as $ev + | $ev.expr | first | + interpret($ev.expr[1:]; $ev.env; _eval_here) + ) // + addEnv(env) + ) + end + ) // + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } | addEnv(env) + else + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } | addEnv($res | last.env) + end + ) // + (select(.kind == "hashmap") | + [ { env: env, list: .value | to_entries } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } | addEnv($res | last.env) + ) // + (select(.kind == "function") | + . | addEnv(env) # return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get(env) | addEnv(env) + ) // addEnv(env); + +def PRINT: + pr_str; + +def rep(env): + READ | EVAL(env) as $expenv | + if $expenv.expr != null then + $expenv.expr | PRINT + else + null + end | addEnv($expenv.env); + +def repl_(env): + ("user> " | _print) | + (read_line | rep(env)); + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: ({ + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, + } + core_identify) + }; + +def repl(env): + def xrepl: + (.env as $env | try repl_($env) catch addEnv($env)) as $expenv | + { + value: $expenv.expr, + stop: false, + env: ($expenv.env // .env) + } | ., xrepl; + {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + +repl( + "(def! not (fn* (a) (if a false true)))" | rep(replEnv) | .env +) \ No newline at end of file From 7650046a508690d753c8cfcecf94afd5281ad322 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Mon, 6 Jan 2020 19:56:48 +0330 Subject: [PATCH 12/40] start step6 and add the cheaty runtime of cheatiness --- jq/core.jq | 15 +++ jq/env.jq | 47 +++++++++- jq/interp.jq | 100 ++++++++++++++++++-- jq/run | 45 ++++++++- jq/step6_file.jq | 239 +++++++++++++++++++++++++++++++++++++++++++++++ jq/utils.jq | 25 ++++- 6 files changed, 459 insertions(+), 12 deletions(-) create mode 100644 jq/step6_file.jq diff --git a/jq/core.jq b/jq/core.jq index da5a900e..986d1101 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -1,5 +1,6 @@ include "utils"; include "printer"; +include "reader"; def core_identify: { @@ -73,6 +74,16 @@ def core_identify: function: ">=", inputs: 2 }, + "read-string": { + kind: "fn", + function: "read-string", + inputs: 1 + }, + "slurp": { + kind: "fn", + function: "slurp", + inputs: 1 + } }; def vec2list(obj): @@ -132,4 +143,8 @@ def core_interp(arguments; env): select(.function == ">") | null | wrap(arguments[0].value > arguments[1].value | tostring) ) // ( select(.function == ">=") | null | wrap(arguments[0].value >= arguments[1].value | tostring) + ) // ( + select(.function == "slurp") | arguments | map(.value) | issue_extern("read") | wrap("string") + ) // ( + select(.function == "read-string") | arguments | first.value | read_str | read_form.value ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/env.jq b/jq/env.jq index 671203c3..69fe6f96 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -145,11 +145,54 @@ def addToEnv(env; name; expr): env: env_set(env; name; expr) }; -def addToEnv(envexp; name): + +def wrapEnv: { + replEnv: ., + currentEnv: ., + isReplEnv: true + }; + +def wrapEnv(replEnv): + { + replEnv: replEnv, + currentEnv: ., + isReplEnv: (replEnv == .) # should we allow separate copies? + }; + +def unwrapReplEnv: + .replEnv; + +def unwrapCurrentEnv: + .currentEnv; + +def env_set6(env; key; value): + if env.isReplEnv then + env_set(env.currentEnv; key; value) | wrapEnv + else + env_set(env.currentEnv; key; value) | wrapEnv(env.replEnv) + end; + +def addToEnv6(envexp; name): + envexp.expr as $value + | envexp.env as $rawEnv + | (if $rawEnv.isReplEnv then + env_set($rawEnv.currentEnv; name; $value) | wrapEnv + else + env_set($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv) + end) as $newEnv + | { + expr: $value, + env: $newEnv + }; + +def addToEnv(envexp; name): + if envexp.env.replEnv != null then + addToEnv6(envexp; name) + else { expr: envexp.expr, env: env_set(envexp.env; name; envexp.expr) - }; + } end; # for step2 def lookup(env): diff --git a/jq/interp.jq b/jq/interp.jq index 58c2ff7a..eca916b1 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -13,18 +13,87 @@ def arg_check(args): def interpret(arguments; env): - ((select(.kind == "fn") | ( + (select(.kind == "fn") | arg_check(arguments) | core_interp(arguments; env) - )) // - jqmal_error("Unsupported native function kind \(.kind)")); + ) // + jqmal_error("Unsupported native function kind \(.kind)"); + +def extractReplEnv(env): + env | .replEnv // .; + +def extractEnv(env): + env | .currentEnv // .; + +def hasReplEnv(env): + env | has("replEnv"); + +def cWrapEnv(renv; cond): + if cond then + wrapEnv(renv) + else + . + end; + +def cUpdateReplEnv(renv; cond): + def findpath: + if .env.parent then + .path += ["parent"] | + .env |= .parent | + findpath + else + .path + end; + if cond then + ({ env: ., path: [] } | findpath) as $path | + setpath($path; renv) + else + . + end; + +def extractCurrentReplEnv(env): + def findpath: + if .env.parent then + .path += ["parent"] | + .env |= .parent | + findpath + else + .path + end; + if env.currentEnv != null then + ({ env: env.currentEnv, path: [] } | findpath) as $path | + env.currentEnv | getpath($path) + else + env + end; + +def cWithReplEnv(renv; cond): + if cond then + extractEnv(.) | wrapEnv(renv) + else + . + end; + def interpret(arguments; env; _eval): - (select(.kind == "fn") | ( - arg_check(arguments) | core_interp(arguments; env) | addEnv(env) - )) // + extractReplEnv(env) as $replEnv | + hasReplEnv(env) as $hasReplEnv | + (select(.kind == "fn") | + arg_check(arguments) | + (select(.function == "eval") | + # special function + { expr: arguments[0], env: $replEnv|cWrapEnv($replEnv; $hasReplEnv) } + | _eval + | .env as $xenv + | extractReplEnv($xenv) as $xreplenv + | setpath( + ["env", "currentEnv"]; + extractEnv(env) | cUpdateReplEnv($xreplenv; $hasReplEnv)) + ) // + (core_interp(arguments; env) | addEnv(env)) + ) // (select(.kind == "function") as $fn | # todo: arg_check - .env as $oenv | env_setfallback(.env; env) | childEnv($fn.binds; arguments) as $fnEnv | + env_setfallback(extractEnv(.env); extractEnv(env)) | childEnv($fn.binds; arguments) as $fnEnv | # tell it about its surroundings (reduce $fn.free_referencess[] as $name ( $fnEnv; @@ -40,6 +109,21 @@ def interpret(arguments; env; _eval): ) catch $env)) as $fnEnv | # tell it about itself env_multiset($fnEnv; $fn.names; $fn) as $fnEnv | - { env: env_multiset($fnEnv; $fn.names; $fn), expr: $fn.body } | _eval | { expr: .expr, env: env } + { + env: env_multiset($fnEnv; $fn.names; $fn) + | cWrapEnv($replEnv; $hasReplEnv), + expr: $fn.body + } + | _eval + | . as $envexp + | extractReplEnv($envexp.env) as $xreplenv + | + { + expr: .expr, + env: extractEnv(env) + | cUpdateReplEnv($xreplenv; $hasReplEnv) + | cWrapEnv($xreplenv; $hasReplEnv) + } ) // jqmal_error("Unsupported function kind \(.kind)"); + \ No newline at end of file diff --git a/jq/run b/jq/run index 984d9238..ca78941d 100755 --- a/jq/run +++ b/jq/run @@ -1,2 +1,45 @@ #!/bin/bash -exec jq -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" "${@}" |& jq -Rr 'try fromjson[1]' + +# let's do some sorcery to bestow IO upon jq +runjq() { + mkfifo jqmal-si.pipe || true + trap "rm -f jqmal-si.pipe" EXIT + ( + while true; do cat jqmal-si.pipe; done& + pid=$! + trap "kill $pid" EXIT + cat - + ) |\ + jq -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" --args "${@}" |&\ + tee \ + >(jq -Rr 'try fromjson[1]|if type == "string" then . else empty end') \ + >(while read -r line; do + command=$(echo $line | jq -c 'try if .[1] | has("command") then .[1].command else empty end' 2>/dev/null) + if [[ $command ]]; then + # echo ">>> " $command + cmd=$(echo "$command" | jq -rMc 'try .cmd catch "ignore"') + case "$cmd" in + read) + filename=$(echo "$command" | jq -Mrc '.args[0]') + tmp=$(mktemp) + # echo "Read $filename into $tmp" + jq -rRnc --rawfile content "$filename" '$content|tojson' > $tmp + # echo "dump $tmp to pipe" + size=$(du -k $tmp) + cat $tmp | pv -q -B $size > jqmal-si.pipe #>/dev/null 2>&1 + rm $tmp + ;; + fwrite) + filename=$(echo "$command" | jq -Mrc ".args[0]|fromjson") + content=$(echo "$command" | jq -Mrc ".args[1]|fromjson") + echo "Writing stuff to $filename" + echo "$content" > "$filename" + ;; + *) + echo $cmd + ;; + esac + fi + done) > /dev/null +} +runjq "${@}" \ No newline at end of file diff --git a/jq/step6_file.jq b/jq/step6_file.jq new file mode 100644 index 00000000..c0ccd1ab --- /dev/null +++ b/jq/step6_file.jq @@ -0,0 +1,239 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; +include "env"; +include "core"; + +def read_line: + . as $in + | label $top + | input; + +def READ: + read_str | read_form | .value; + +# def eval_ast(env): +# (select(.kind == "symbol") | .value | env_get(env) | addEnv(env)) // +# (select(.kind == "list") | reduce .value[] as $elem ( +# {value: [], env: env}; +# . as $dot | $elem | EVAL($dot.env) as $eval_env | +# { +# value: ($dot.value + [$eval_env.expr]), +# env: $eval_env.env +# } +# ) | { expr: .value, env: .env }) // (addEnv(env)); + +# def patch_with_env(env): +# . as $dot | (reduce .[] as $fnv ( +# []; +# . + [$fnv | setpath([1, "free_referencess"]; ($fnv[1].free_referencess + $dot) | unique)] +# )) as $functions | reduce $functions[] as $function ( +# env; +# env_set(.; $function[0]; $function[1]) +# ) | { functions: $functions, env: . }; + +def find_free_references(keys): + def _refs: + . as $dot + | if .kind == "symbol" then + if keys | contains([$dot.value]) then [] else [$dot.value] end + else if "list" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) + else if "vector" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) + else + [] + end end end; + _refs | unique; + +def EVAL(env): + def _eval_here: + .env as $env | .expr | EVAL($env); + + def hmap_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem[1] | EVAL($env) as $resv | + { value: [$elem[0], $resv.expr], env: env }, + ({env: $resv.env, list: $rest} | hmap_with_env) + end; + def map_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem | EVAL($env) as $resv | + { value: $resv.expr, env: env }, + ({env: $resv.env, list: $rest} | map_with_env) + end; + . as $init | + env | unwrapCurrentEnv as $currentEnv | # unwrap env "package" + env | unwrapReplEnv as $replEnv | # - + $init | + (select(.kind == "list") | + if .value | length == 0 then + . + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL(env)) as $evval | + addToEnv($evval; $value[1].value) + ) // + ( + .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set6($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | { expr: EVAL($env).expr, env: env } + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: env, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL(env) as $condenv | + if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) | EVAL($condenv.env) + else + $value[2] | EVAL($condenv.env) + end + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + $value[1].value | map(.value) as $binds | { + kind: "function", + binds: $binds, + env: env, + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $value[2] | find_free_references($currentEnv | env_dump_keys + $binds) # for dynamically scoped variables + } | addEnv(env) + ) // + ( + reduce .value[] as $elem ( + {value: [], env: env}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + { + value: ($dot.value + [$eval_env.expr]), + env: $eval_env.env + } + ) | { expr: .value, env: .env } as $ev + | $ev.expr | first | + interpret($ev.expr[1:]; $ev.env; _eval_here) + ) // + addEnv(env) + ) + end + ) // + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } | addEnv(env) + else + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } | addEnv($res | last.env) + end + ) // + (select(.kind == "hashmap") | + [ { env: env, list: .value | to_entries } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } | addEnv($res | last.env) + ) // + (select(.kind == "function") | + . | addEnv(env) # return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get($currentEnv) | addEnv(env) + ) // addEnv(env); + +def PRINT: + pr_str; + +def rep(env): + READ | EVAL(env) as $expenv | + if $expenv.expr != null then + $expenv.expr | PRINT + else + null + end | addEnv($expenv.env); + +def repl_(env): + ("user> " | _print) | + (read_line | rep(env)); + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: ({ + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, + "eval": { + kind: "fn", + inputs: 1, + function: "eval" + } + } + core_identify) + }; + +def repl(env): + def xrepl: + (.env as $env | try repl_($env) catch addEnv($env)) as $expenv | + { + value: $expenv.expr, + stop: false, + env: ($expenv.env // .env) + } | ., xrepl; + {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + +def eval_ign(expr): + . as $env | expr | rep($env) | .env; + +repl( + replEnv + | wrapEnv + | eval_ign("(def! not (fn* (a) (if a false true)))") + | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") +) \ No newline at end of file diff --git a/jq/utils.jq b/jq/utils.jq index a5959b3a..5bc66213 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -19,5 +19,28 @@ def wrap(kind): value: . }; +def _extern(options): + {command: .} + | debug + | if options.nowait | not then + input | fromjson + else + null + end; # oof + +def issue_extern(cmd; options): + {cmd: cmd, args: .} + # | (tostring | debug) as $ignore + | _extern(options); + +def issue_extern(cmd): + issue_extern(cmd; {}); + def _print: - debug; \ No newline at end of file + debug; + +def _write_to_file(name): + . as $value + | [name, .|tojson] + | issue_extern("fwrite"; {nowait: true}) + | $value; \ No newline at end of file From eedfbb43d8b7a3532dca98b1cbd62d016b04086a Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Tue, 7 Jan 2020 00:25:22 +0330 Subject: [PATCH 13/40] add atoms + fix lists (lists do not behave like let*) + add file io --- jq/core.jq | 31 +++++++++++++++++++++++++++++++ jq/env.jq | 25 ++++++++++++++++--------- jq/interp.jq | 40 +++++++++++++++++++++++++++++++++++++--- jq/printer.jq | 1 + jq/run | 15 +++++++++++---- jq/step3_env.jq | 11 ++++------- jq/step4_if_fn_do.jq | 11 ++++------- jq/step5_tco.jq | 11 ++++------- jq/step6_file.jq | 27 ++++++++++++++++----------- jq/utils.jq | 19 +++++++++++++++++-- 10 files changed, 141 insertions(+), 50 deletions(-) diff --git a/jq/core.jq b/jq/core.jq index 986d1101..17d4391c 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -83,6 +83,31 @@ def core_identify: kind: "fn", function: "slurp", inputs: 1 + }, + "atom": { + kind: "fn", + function: "atom", + inputs: 1 + }, + "atom?": { + kind: "fn", + function: "atom?", + inputs: 1 + }, + "deref": { + kind: "fn", + function: "deref", + inputs: 1 + }, + "reset!": { # defined in interp + kind: "fn", + function: "reset!", + inputs: 2 + }, + "swap!": { # defined in interp + kind: "fn", + function: "swap!", + inputs: -1 } }; @@ -147,4 +172,10 @@ def core_interp(arguments; env): select(.function == "slurp") | arguments | map(.value) | issue_extern("read") | wrap("string") ) // ( select(.function == "read-string") | arguments | first.value | read_str | read_form.value + ) // ( + select(.function == "atom") | arguments | first | wrap2("atom"; {names: []}) + ) // ( + select(.function == "atom?") | null | wrap(arguments | first.kind == "atom" | tostring) + ) // ( + select(.function == "deref") | arguments | first.value ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/env.jq b/jq/env.jq index 69fe6f96..bf3a6e36 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -81,7 +81,7 @@ def env_multiset(env; keys; value): env | env_multiset(keys; value); def env_set($key; $value): - (if $value.kind == "function" then + (if $value.kind == "function" or $value.kind == "atom" then # inform the function of its names $value | inform_function($key) else @@ -93,23 +93,23 @@ def env_set($key; $value): def env_dump_keys: def _dump: - .environment | keys; + .environment // {} | keys; if .parent == null then _dump else - .parent | env_dump_keys + _dump + (.parent | env_dump_keys + _dump) | unique end; def env_set(env; $key; $value): - (if $value.kind == "function" then - # inform the function of its names + (if $value.kind == "function" or $value.kind == "atom" then + # inform the function/atom of its names $value | (.names += [$key]) | (.names |= unique) else $value end) as $value | { parent: env.parent, - environment: (env.environment + (env.environment | .[$key] |= $value)) # merge together, as env.environment[key] |= value does not work + environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)) # merge together, as env.environment[key] |= value does not work }; def env_find(env): @@ -173,13 +173,20 @@ def env_set6(env; key; value): env_set(env.currentEnv; key; value) | wrapEnv(env.replEnv) end; +def env_set_(env; key; value): + if env.currentEnv != null then + env_set6(env; key; value) + else + env_set(env; key; value) + end; + def addToEnv6(envexp; name): envexp.expr as $value | envexp.env as $rawEnv | (if $rawEnv.isReplEnv then - env_set($rawEnv.currentEnv; name; $value) | wrapEnv + env_set_($rawEnv.currentEnv; name; $value) | wrapEnv else - env_set($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv) + env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv) end) as $newEnv | { expr: $value, @@ -191,7 +198,7 @@ def addToEnv(envexp; name): addToEnv6(envexp; name) else { expr: envexp.expr, - env: env_set(envexp.env; name; envexp.expr) + env: env_set_(envexp.env; name; envexp.expr) } end; # for step2 diff --git a/jq/interp.jq b/jq/interp.jq index eca916b1..1260ac8b 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -74,6 +74,14 @@ def cWithReplEnv(renv; cond): . end; +def updateAtoms(newEnv): + . as $env + | reduce (newEnv | env_dump_keys | map(env_get(newEnv) as $value | select($value.kind == "atom") | $value))[] as $atom ( + $env; + . as $e | reduce $atom.names[] as $name ( + $e; + env_set_(.; $name; $atom))); + def interpret(arguments; env; _eval): extractReplEnv(env) as $replEnv | hasReplEnv(env) as $hasReplEnv | @@ -88,6 +96,31 @@ def interpret(arguments; env; _eval): | setpath( ["env", "currentEnv"]; extractEnv(env) | cUpdateReplEnv($xreplenv; $hasReplEnv)) + ) // + (select(.function == "reset!") | + # env modifying function + arguments[0].names as $names | + arguments[1]|wrap2("atom"; {names: $names}) as $value | + (reduce $names[] as $name ( + env; + . as $env | env_set_($env; $name; $value) + )) as $env | + $value.value | addEnv($env) + ) // + (select(.function == "swap!") | + # env modifying function + arguments[0].names as $names | + arguments[0].value as $initValue | + arguments[1] as $function | + ([$initValue] + arguments[2:]) as $args | + ($function | interpret($args; env; _eval)) as $newEnvValue | + $newEnvValue.expr|wrap2("atom"; {names: $names}) as $newValue | + $newEnvValue.env as $newEnv | + (reduce $names[] as $name ( + $newEnv; + . as $env | env_set_($env; $name; $newValue) + )) as $newEnv | + $newValue.value | addEnv($newEnv) ) // (core_interp(arguments; env) | addEnv(env)) ) // @@ -97,7 +130,7 @@ def interpret(arguments; env; _eval): # tell it about its surroundings (reduce $fn.free_referencess[] as $name ( $fnEnv; - . as $env | try env_set( + . as $env | try env_set_( .; $name; $name | env_get(env) | . as $xvalue @@ -114,15 +147,16 @@ def interpret(arguments; env; _eval): | cWrapEnv($replEnv; $hasReplEnv), expr: $fn.body } - | _eval + | _eval | . as $envexp - | extractReplEnv($envexp.env) as $xreplenv + | (extractReplEnv($envexp.env)) as $xreplenv | { expr: .expr, env: extractEnv(env) | cUpdateReplEnv($xreplenv; $hasReplEnv) | cWrapEnv($xreplenv; $hasReplEnv) + | updateAtoms(extractEnv($envexp.env)) } ) // jqmal_error("Unsupported function kind \(.kind)"); diff --git a/jq/printer.jq b/jq/printer.jq index a6f2af62..ffadf5a8 100644 --- a/jq/printer.jq +++ b/jq/printer.jq @@ -19,6 +19,7 @@ def pr_str(opt): (select(.kind == "false") | "false") // (select(.kind == "fn") | "#") // (select(.kind == "function")| "#") // + (select(.kind == "atom")| "(atom \(.value | pr_str(opt)))") // "#"; def pr_str: diff --git a/jq/run b/jq/run index ca78941d..4d4a3cc3 100755 --- a/jq/run +++ b/jq/run @@ -30,10 +30,17 @@ runjq() { rm $tmp ;; fwrite) - filename=$(echo "$command" | jq -Mrc ".args[0]|fromjson") - content=$(echo "$command" | jq -Mrc ".args[1]|fromjson") - echo "Writing stuff to $filename" - echo "$content" > "$filename" + tmp=$(mktemp) + echo "$command" > $tmp + filename=$(cat $tmp | jq -Mrc ".args[0]|fromjson") + content=$(cat $tmp | jq -Mrc ".args[1]|fromjson") + app=$(cat $tmp | jq -Mrc ".args[2]|fromjson") + echo "'$app': Writing stuff to $filename" + if [[ $res == false ]]; then + echo "$content" > "$filename" + else + echo "$content" >> "$filename" + fi ;; *) echo $cmd diff --git a/jq/step3_env.jq b/jq/step3_env.jq index e9396358..f15f9ea4 100644 --- a/jq/step3_env.jq +++ b/jq/step3_env.jq @@ -58,13 +58,10 @@ def EVAL(env): ) // ( reduce .value[] as $elem ( - {value: [], env: env}; - . as $dot | $elem | EVAL($dot.env) as $eval_env | - { - value: ($dot.value + [$eval_env.expr]), - env: $eval_env.env - } - ) | { expr: .value, env: .env } as $ev + []; + . as $dot | $elem | EVAL(env) as $eval_env | + ($dot + [$eval_env.expr]) + ) | { expr: ., env: env } as $ev | $ev.expr | first | interpret($ev.expr[1:]; $ev.env; _eval_here) ) // diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index a45e84c2..c91fe9c4 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -124,13 +124,10 @@ def EVAL(env): ) // ( reduce .value[] as $elem ( - {value: [], env: env}; - . as $dot | $elem | EVAL($dot.env) as $eval_env | - { - value: ($dot.value + [$eval_env.expr]), - env: $eval_env.env - } - ) | { expr: .value, env: .env } as $ev + []; + . as $dot | $elem | EVAL(env) as $eval_env | + ($dot + [$eval_env.expr]) + ) | { expr: ., env: env } as $ev | $ev.expr | first | interpret($ev.expr[1:]; $ev.env; _eval_here) ) // diff --git a/jq/step5_tco.jq b/jq/step5_tco.jq index a45e84c2..c91fe9c4 100644 --- a/jq/step5_tco.jq +++ b/jq/step5_tco.jq @@ -124,13 +124,10 @@ def EVAL(env): ) // ( reduce .value[] as $elem ( - {value: [], env: env}; - . as $dot | $elem | EVAL($dot.env) as $eval_env | - { - value: ($dot.value + [$eval_env.expr]), - env: $eval_env.env - } - ) | { expr: .value, env: .env } as $ev + []; + . as $dot | $elem | EVAL(env) as $eval_env | + ($dot + [$eval_env.expr]) + ) | { expr: ., env: env } as $ev | $ev.expr | first | interpret($ev.expr[1:]; $ev.env; _eval_here) ) // diff --git a/jq/step6_file.jq b/jq/step6_file.jq index c0ccd1ab..b97181b1 100644 --- a/jq/step6_file.jq +++ b/jq/step6_file.jq @@ -58,8 +58,14 @@ def EVAL(env): else $list[0] as $elem | $list[1:] as $rest | - $elem[1] | EVAL($env) as $resv | - { value: [$elem[0], $resv.expr], env: env }, + $elem.value.value | EVAL($env) as $resv | + { + value: { + key: $elem.key, + value: { kkind: $elem.value.kkind, value: $resv.expr } + }, + env: env + }, ({env: $resv.env, list: $rest} | hmap_with_env) end; def map_with_env: @@ -93,7 +99,7 @@ def EVAL(env): (reduce ($value[1].value | nwise(2)) as $xvalue ( $subenv; . as $env | $xvalue[1] | EVAL($env) as $expenv | - env_set6($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env | $value[2] | { expr: EVAL($env).expr, env: env } ) // ( @@ -128,13 +134,10 @@ def EVAL(env): ) // ( reduce .value[] as $elem ( - {value: [], env: env}; - . as $dot | $elem | EVAL($dot.env) as $eval_env | - { - value: ($dot.value + [$eval_env.expr]), - env: $eval_env.env - } - ) | { expr: .value, env: .env } as $ev + []; + . as $dot | $elem | EVAL(env) as $eval_env | + ($dot + [$eval_env.expr]) + ) | { expr: ., env: env } as $ev | $ev.expr | first | interpret($ev.expr[1:]; $ev.env; _eval_here) ) // @@ -157,7 +160,7 @@ def EVAL(env): end ) // (select(.kind == "hashmap") | - [ { env: env, list: .value | to_entries } | hmap_with_env ] as $res | + [ { env: env, list: (.value | to_entries) } | hmap_with_env ] as $res | { kind: "hashmap", value: $res | map(.value) | from_entries @@ -236,4 +239,6 @@ repl( | wrapEnv | eval_ign("(def! not (fn* (a) (if a false true)))") | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") + | . as $env + | env_set_($env; "*ARGV*"; $ARGS.positional | wrap("list")) ) \ No newline at end of file diff --git a/jq/utils.jq b/jq/utils.jq index 5bc66213..e417f1df 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -19,6 +19,12 @@ def wrap(kind): value: . }; +def wrap2(kind; opts): + opts + { + kind: kind, + value: . + }; + def _extern(options): {command: .} | debug @@ -41,6 +47,15 @@ def _print: def _write_to_file(name): . as $value - | [name, .|tojson] + | [(name|tojson), (.|tojson), (false|tojson)] | issue_extern("fwrite"; {nowait: true}) - | $value; \ No newline at end of file + | $value; + +def _append_to_file(name): + . as $value + | [(name|tojson), (.|tojson), (true|tojson)] + | issue_extern("fwrite"; {nowait: true}) + | $value; + +def trap: + _write_to_file("trap_reason.json") | jqmal_error("trap"); \ No newline at end of file From a451ec51cbaee2a3f338d4433d4d3e8d3fd8d612 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Tue, 7 Jan 2020 20:13:49 +0330 Subject: [PATCH 14/40] properly implement tco and add step7:quote --- jq/core.jq | 14 ++ jq/step4_if_fn_do.jq | 12 ++ jq/step5_tco.jq | 204 ++++++++++++++++------------ jq/step6_file.jq | 260 ++++++++++++++++++++---------------- jq/step7_quote.jq | 310 +++++++++++++++++++++++++++++++++++++++++++ jq/utils.jq | 35 +++++ 6 files changed, 630 insertions(+), 205 deletions(-) create mode 100644 jq/step7_quote.jq diff --git a/jq/core.jq b/jq/core.jq index 17d4391c..2ffdeb55 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -108,6 +108,16 @@ def core_identify: kind: "fn", function: "swap!", inputs: -1 + }, + "cons": { + kind: "fn", + function: "cons", + inputs: 2 + }, + "concat": { + kind: "fn", + function: "concat", + inputs: -1 } }; @@ -178,4 +188,8 @@ def core_interp(arguments; env): select(.function == "atom?") | null | wrap(arguments | first.kind == "atom" | tostring) ) // ( select(.function == "deref") | arguments | first.value + ) // ( + select(.function == "cons") | ([arguments[0]] + arguments[1].value) | wrap("list") + ) // ( + select(.function == "concat") | arguments | map(.value) | (add//[]) | wrap("list") ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index c91fe9c4..da34e3bc 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -46,6 +46,18 @@ def find_free_references(keys): [] end end end; _refs | unique; + +def recurseflip(x; y): + recurse(y; x); + +def TCOWrap(env; retenv; continue): + { + ast: ., + env: env, + ret_env: retenv, + finish: (continue | not), + cont: true # set inside + }; def EVAL(env): def _eval_here: diff --git a/jq/step5_tco.jq b/jq/step5_tco.jq index c91fe9c4..24e636fa 100644 --- a/jq/step5_tco.jq +++ b/jq/step5_tco.jq @@ -47,6 +47,18 @@ def find_free_references(keys): end end end; _refs | unique; +def recurseflip(x; y): + recurse(y; x); + +def TCOWrap(env; retenv; continue): + { + ast: ., + env: env, + ret_env: retenv, + finish: (continue | not), + cont: true # set inside + }; + def EVAL(env): def _eval_here: .env as $env | .expr | EVAL($env); @@ -73,95 +85,113 @@ def EVAL(env): { value: $resv.expr, env: env }, ({env: $resv.env, list: $rest} | map_with_env) end; - (select(.kind == "list") | - if .value | length == 0 then - . + . as $ast + | { env: env, ast: ., cont: true, finish: false, ret_env: null } + | [ recurseflip(.cont; + .env as $_menv + | if .finish then + .cont |= false else - ( - ( - .value | select(.[0].value == "def!") as $value | - ($value[2] | EVAL(env)) as $evval | - addToEnv($evval; $value[1].value) - ) // - ( - .value | select(.[0].value == "let*") as $value | - (env | pureChildEnv) as $subenv | - (reduce ($value[1].value | nwise(2)) as $xvalue ( - $subenv; - . as $env | $xvalue[1] | EVAL($env) as $expenv | - env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env - | $value[2] | { expr: EVAL($env).expr, env: env } - ) // - ( - .value | select(.[0].value == "do") as $value | - (reduce ($value[1:][]) as $xvalue ( - { env: env, expr: {kind:"nil"} }; - .env as $env | $xvalue | EVAL($env) - )) - ) // - ( - .value | select(.[0].value == "if") as $value | - $value[1] | EVAL(env) as $condenv | - if (["false", "nil"] | contains([$condenv.expr.kind])) then - ($value[3] // {kind:"nil"}) | EVAL($condenv.env) - else - $value[2] | EVAL($condenv.env) - end - ) // - ( - .value | select(.[0].value == "fn*") as $value | - # we can't do what the guide says, so we'll skip over this - # and ues the later implementation - # (fn* args body) - $value[1].value | map(.value) as $binds | { - kind: "function", - binds: $binds, - env: env, - body: $value[2], - names: [], # we can't do that circular reference this - free_referencess: $value[2] | find_free_references(env | env_dump_keys + $binds) # for dynamically scoped variables - } | addEnv(env) - ) // - ( - reduce .value[] as $elem ( - []; - . as $dot | $elem | EVAL(env) as $eval_env | - ($dot + [$eval_env.expr]) - ) | { expr: ., env: env } as $ev - | $ev.expr | first | - interpret($ev.expr[1:]; $ev.env; _eval_here) - ) // - addEnv(env) - ) + (.ret_env//.env) as $_retenv + | .ret_env as $_orig_retenv + | .ast + | + (select(.kind == "list") | + if .value | length == 0 then + . | TCOWrap($_menv; $_orig_retenv; false) + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL($_menv)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "let*") as $value | + ($_menv | pureChildEnv) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | TCOWrap($env; $_retenv; true) + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: $_menv, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL(env) as $condenv | + (if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) + else + $value[2] + end) | TCOWrap($condenv.env; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + $value[1].value | map(.value) as $binds | { + kind: "function", + binds: $binds, + env: $_menv, + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $value[2] | find_free_references($_menv | env_dump_keys + $binds) # for dynamically scoped variables + } | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + reduce .value[] as $elem ( + []; + . as $dot | $elem | EVAL($_menv) as $eval_env | + ($dot + [$eval_env.expr]) + ) | . as $expr | first | + interpret($expr[1:]; $_menv; _eval_here) as $exprenv | + $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false) + ) // + TCOWrap($_menv; $_orig_retenv; false) + ) + end + ) // + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } | TCOWrap($_menv; $_orig_retenv; false) + else + [ { env: $_menv, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } | TCOWrap($res | last.env; $_orig_retenv; false) + end + ) // + (select(.kind == "hashmap") | + [ { env: $_menv, list: .value | to_entries } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } | TCOWrap($res | last.env; $_orig_retenv; false) + ) // + (select(.kind == "function") | + . | TCOWrap($_menv; $_orig_retenv; false) # return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get($_menv) | TCOWrap($_menv; null; false) + ) // TCOWrap($_menv; $_orig_retenv; false) end - ) // - (select(.kind == "vector") | - if .value|length == 0 then - { - kind: "vector", - value: [] - } | addEnv(env) - else - [ { env: env, list: .value } | map_with_env ] as $res | - { - kind: "vector", - value: $res | map(.value) - } | addEnv($res | last.env) - end - ) // - (select(.kind == "hashmap") | - [ { env: env, list: .value | to_entries } | hmap_with_env ] as $res | - { - kind: "hashmap", - value: $res | map(.value) | from_entries - } | addEnv($res | last.env) - ) // - (select(.kind == "function") | - . | addEnv(env) # return this unchanged, since it can only be applied to - ) // - (select(.kind == "symbol") | - .value | env_get(env) | addEnv(env) - ) // addEnv(env); + ) ] + | last as $result + | ($result.ret_env // $result.env) as $env + | $result.ast + | addEnv($env); def PRINT: pr_str; diff --git a/jq/step6_file.jq b/jq/step6_file.jq index b97181b1..dd5356e7 100644 --- a/jq/step6_file.jq +++ b/jq/step6_file.jq @@ -13,25 +13,8 @@ def read_line: def READ: read_str | read_form | .value; -# def eval_ast(env): -# (select(.kind == "symbol") | .value | env_get(env) | addEnv(env)) // -# (select(.kind == "list") | reduce .value[] as $elem ( -# {value: [], env: env}; -# . as $dot | $elem | EVAL($dot.env) as $eval_env | -# { -# value: ($dot.value + [$eval_env.expr]), -# env: $eval_env.env -# } -# ) | { expr: .value, env: .env }) // (addEnv(env)); - -# def patch_with_env(env): -# . as $dot | (reduce .[] as $fnv ( -# []; -# . + [$fnv | setpath([1, "free_referencess"]; ($fnv[1].free_referencess + $dot) | unique)] -# )) as $functions | reduce $functions[] as $function ( -# env; -# env_set(.; $function[0]; $function[1]) -# ) | { functions: $functions, env: . }; +def special_forms: + [ "if", "def!", "let*", "fn*", "do" ]; def find_free_references(keys): def _refs: @@ -39,13 +22,25 @@ def find_free_references(keys): | if .kind == "symbol" then if keys | contains([$dot.value]) then [] else [$dot.value] end else if "list" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) else if "vector" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) else [] end end end; - _refs | unique; + _refs | unique; + +def recurseflip(x; y): + recurse(y; x); + +def TCOWrap(env; retenv; continue): + { + ast: ., + env: env, + ret_env: retenv, + finish: (continue | not), + cont: true # set inside + }; def EVAL(env): def _eval_here: @@ -79,99 +74,117 @@ def EVAL(env): { value: $resv.expr, env: env }, ({env: $resv.env, list: $rest} | map_with_env) end; - . as $init | - env | unwrapCurrentEnv as $currentEnv | # unwrap env "package" - env | unwrapReplEnv as $replEnv | # - - $init | - (select(.kind == "list") | - if .value | length == 0 then - . + . as $ast + | { env: env, ast: ., cont: true, finish: false, ret_env: null } + | [ recurseflip(.cont; + .env as $_menv + | if .finish then + .cont |= false else - ( - ( - .value | select(.[0].value == "def!") as $value | - ($value[2] | EVAL(env)) as $evval | - addToEnv($evval; $value[1].value) - ) // - ( - .value | select(.[0].value == "let*") as $value | - ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | - (reduce ($value[1].value | nwise(2)) as $xvalue ( - $subenv; - . as $env | $xvalue[1] | EVAL($env) as $expenv | - env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env - | $value[2] | { expr: EVAL($env).expr, env: env } - ) // - ( - .value | select(.[0].value == "do") as $value | - (reduce ($value[1:][]) as $xvalue ( - { env: env, expr: {kind:"nil"} }; - .env as $env | $xvalue | EVAL($env) - )) - ) // - ( - .value | select(.[0].value == "if") as $value | - $value[1] | EVAL(env) as $condenv | - if (["false", "nil"] | contains([$condenv.expr.kind])) then - ($value[3] // {kind:"nil"}) | EVAL($condenv.env) - else - $value[2] | EVAL($condenv.env) - end - ) // - ( - .value | select(.[0].value == "fn*") as $value | - # we can't do what the guide says, so we'll skip over this - # and ues the later implementation - # (fn* args body) - $value[1].value | map(.value) as $binds | { - kind: "function", - binds: $binds, - env: env, - body: $value[2], - names: [], # we can't do that circular reference this - free_referencess: $value[2] | find_free_references($currentEnv | env_dump_keys + $binds) # for dynamically scoped variables - } | addEnv(env) - ) // - ( - reduce .value[] as $elem ( - []; - . as $dot | $elem | EVAL(env) as $eval_env | - ($dot + [$eval_env.expr]) - ) | { expr: ., env: env } as $ev - | $ev.expr | first | - interpret($ev.expr[1:]; $ev.env; _eval_here) - ) // - addEnv(env) - ) + (.ret_env//.env) as $_retenv + | .ret_env as $_orig_retenv + | .ast + | . as $init + | $_menv | unwrapCurrentEnv as $currentEnv # unwrap env "package" + | $_menv | unwrapReplEnv as $replEnv # - + | $init + | + (select(.kind == "list") | + if .value | length == 0 then + . | TCOWrap($_menv; $_orig_retenv; false) + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL($_menv)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) | . as $env + | $value[2] | TCOWrap($env; $_retenv; true) + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: $_menv, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL($_menv) as $condenv | + (if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) + else + $value[2] + end) | TCOWrap($condenv.env; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + $value[1].value | map(.value) as $binds | { + kind: "function", + binds: $binds, + env: env, + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $value[2] | find_free_references($currentEnv | env_dump_keys + $binds) # for dynamically scoped variables + } | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + reduce .value[] as $elem ( + []; + . as $dot | $elem | EVAL($_menv) as $eval_env | + ($dot + [$eval_env.expr]) + ) | . as $expr | first | + interpret($expr[1:]; $_menv; _eval_here) as $exprenv | + $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false) + ) // + TCOWrap($_menv; $_orig_retenv; false) + ) + end + ) // + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } | TCOWrap($_menv; $_orig_retenv; false) + else + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } | TCOWrap($res | last.env; $_orig_retenv; false) + end + ) // + (select(.kind == "hashmap") | + [ { env: env, list: (.value | to_entries) } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } | TCOWrap($res | last.env; $_orig_retenv; false) + ) // + (select(.kind == "function") | + . | TCOWrap($_menv; $_orig_retenv; false) # return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get($currentEnv) | TCOWrap($_menv; null; false) + ) // TCOWrap($_menv; $_orig_retenv; false) end - ) // - (select(.kind == "vector") | - if .value|length == 0 then - { - kind: "vector", - value: [] - } | addEnv(env) - else - [ { env: env, list: .value } | map_with_env ] as $res | - { - kind: "vector", - value: $res | map(.value) - } | addEnv($res | last.env) - end - ) // - (select(.kind == "hashmap") | - [ { env: env, list: (.value | to_entries) } | hmap_with_env ] as $res | - { - kind: "hashmap", - value: $res | map(.value) | from_entries - } | addEnv($res | last.env) - ) // - (select(.kind == "function") | - . | addEnv(env) # return this unchanged, since it can only be applied to - ) // - (select(.kind == "symbol") | - .value | env_get($currentEnv) | addEnv(env) - ) // addEnv(env); + ) ] + | last as $result + | ($result.ret_env // $result.env) as $env + | $result.ast + | addEnv($env); def PRINT: pr_str; @@ -234,11 +247,22 @@ def repl(env): def eval_ign(expr): . as $env | expr | rep($env) | .env; -repl( +def eval_val(expr): + . as $env | expr | rep($env) | .expr; + +def getEnv: replEnv | wrapEnv | eval_ign("(def! not (fn* (a) (if a false true)))") - | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") - | . as $env - | env_set_($env; "*ARGV*"; $ARGS.positional | wrap("list")) -) \ No newline at end of file + | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))"); + +def main: + if $ARGS.positional|length > 0 then + getEnv as $env | + env_set_($env; "*ARGV*"; $ARGS.positional[1:] | wrap("list")) | + eval_val("(load-file \($ARGS.positional[0] | tojson))") + else + repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) + end; + +main \ No newline at end of file diff --git a/jq/step7_quote.jq b/jq/step7_quote.jq new file mode 100644 index 00000000..53ccf0ab --- /dev/null +++ b/jq/step7_quote.jq @@ -0,0 +1,310 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; +include "env"; +include "core"; + +def read_line: + . as $in + | label $top + | input; + +def READ: + read_str | read_form | .value; + +def special_forms: + [ "if", "def!", "let*", "fn*", "do" ]; + +def find_free_references(keys): + def _refs: + . as $dot + | if .kind == "symbol" then + if keys | contains([$dot.value]) then [] else [$dot.value] end + else if "list" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) + else if "vector" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) + else + [] + end end end; + _refs | unique; + +def recurseflip(x; y): + recurse(y; x); + +def TCOWrap(env; retenv; continue): + { + ast: ., + env: env, + ret_env: retenv, + finish: (continue | not), + cont: true # set inside + }; + +def _symbol(name): + { + kind: "symbol", + value: name + }; + +def _symbol_v(name): + if .kind == "symbol" then + .value == name + else + false + end; + +def quasiquote: + if isPair then + .value as $value | null | + if ($value[0] | _symbol_v("unquote")) then + $value[1] + else + if isPair($value[0]) and ($value[0].value[0] | _symbol_v("splice-unquote")) then + [_symbol("concat")] + + [$value[0].value[1]] + + [($value[1:] | wrap("list") | quasiquote)] | wrap("list") + else + [_symbol("cons")] + + [($value[0] | quasiquote)] + + [($value[1:] | wrap("list") | quasiquote)] | wrap("list") + end + end + else + [_symbol("quote")] + + [.] | wrap("list") + end; + +def EVAL(env): + def _eval_here: + .env as $env | .expr | EVAL($env); + + def hmap_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem.value.value | EVAL($env) as $resv | + { + value: { + key: $elem.key, + value: { kkind: $elem.value.kkind, value: $resv.expr } + }, + env: env + }, + ({env: $resv.env, list: $rest} | hmap_with_env) + end; + def map_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem | EVAL($env) as $resv | + { value: $resv.expr, env: env }, + ({env: $resv.env, list: $rest} | map_with_env) + end; + . as $ast + | { env: env, ast: ., cont: true, finish: false, ret_env: null } + | [ recurseflip(.cont; + .env as $_menv + | if .finish then + .cont |= false + else + (.ret_env//.env) as $_retenv + | .ret_env as $_orig_retenv + | .ast + | . as $init + | $_menv | unwrapCurrentEnv as $currentEnv # unwrap env "package" + | $_menv | unwrapReplEnv as $replEnv # - + | $init + | + (select(.kind == "list") | + if .value | length == 0 then + . | TCOWrap($_menv; $_orig_retenv; false) + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL($_menv)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | TCOWrap($env; $_retenv; true) + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: $_menv, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL($_menv) as $condenv | + (if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) + else + $value[2] + end) | TCOWrap($condenv.env; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + $value[1].value | map(.value) as $binds | { + kind: "function", + binds: $binds, + env: env, + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $value[2] | find_free_references($currentEnv | env_dump_keys + $binds) # for dynamically scoped variables + } | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "quote") as $value | + $value[1] | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "quasiquote") as $value | + $value[1] | quasiquote | TCOWrap($_menv; $_orig_retenv; true) + ) // + ( + reduce .value[] as $elem ( + []; + . as $dot | $elem | EVAL($_menv) as $eval_env | + ($dot + [$eval_env.expr]) + ) | . as $expr | first | + interpret($expr[1:]; $_menv; _eval_here) as $exprenv | + $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false) + ) // + TCOWrap($_menv; $_orig_retenv; false) + ) + end + ) // + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } | TCOWrap($_menv; $_orig_retenv; false) + else + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } | TCOWrap($res | last.env; $_orig_retenv; false) + end + ) // + (select(.kind == "hashmap") | + [ { env: env, list: (.value | to_entries) } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } | TCOWrap($res | last.env; $_orig_retenv; false) + ) // + (select(.kind == "function") | + . | TCOWrap($_menv; $_orig_retenv; false) # return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get($currentEnv) | TCOWrap($_menv; null; false) + ) // TCOWrap($_menv; $_orig_retenv; false) + end + ) ] + | last as $result + | ($result.ret_env // $result.env) as $env + | $result.ast + | addEnv($env); + +def PRINT: + pr_str; + +def rep(env): + READ | EVAL(env) as $expenv | + if $expenv.expr != null then + $expenv.expr | PRINT + else + null + end | addEnv($expenv.env); + +def repl_(env): + ("user> " | _print) | + (read_line | rep(env)); + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: ({ + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, + "eval": { + kind: "fn", + inputs: 1, + function: "eval" + } + } + core_identify) + }; + +def repl(env): + def xrepl: + (.env as $env | try repl_($env) catch addEnv($env)) as $expenv | + { + value: $expenv.expr, + stop: false, + env: ($expenv.env // .env) + } | ., xrepl; + {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + +def eval_ign(expr): + . as $env | expr | rep($env) | .env; + +def eval_val(expr): + . as $env | expr | rep($env) | .expr; + +def getEnv: + replEnv + | wrapEnv + | eval_ign("(def! not (fn* (a) (if a false true)))") + | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))"); + +def main: + if $ARGS.positional|length > 0 then + getEnv as $env | + env_set_($env; "*ARGV*"; $ARGS.positional[1:] | wrap("list")) | + eval_val("(load-file \($ARGS.positional[0] | tojson))") + else + repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) + end; + +main \ No newline at end of file diff --git a/jq/utils.jq b/jq/utils.jq index e417f1df..666c11bf 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -25,6 +25,35 @@ def wrap2(kind; opts): value: . }; +def isPair: + if (.kind == "list" or .kind == "vector") then + .value | length > 0 + else + false + end; + +def isPair(x): + x | isPair; + +def tomal: + ( + select(type == "array") | ( + map(tomal) | wrap("list") + ) + ) // ( + select(type == "string") | ( + if startswith("sym/") then + .[4:] | wrap("symbol") + else + wrap("string") + end + ) + ) // ( + select(type == "number") | ( + wrap("number") + ) + ); + def _extern(options): {command: .} | debug @@ -42,6 +71,12 @@ def issue_extern(cmd; options): def issue_extern(cmd): issue_extern(cmd; {}); +def _debug(ex): + . as $top + | ex + | debug + | $top; + def _print: debug; From e41d9de3bfd932053e0313ab5251f7598d9841e5 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Tue, 7 Jan 2020 23:56:31 +0330 Subject: [PATCH 15/40] add step8 --- jq/core.jq | 28 ++++ jq/env.jq | 6 + jq/step8_macros.jq | 367 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 401 insertions(+) create mode 100644 jq/step8_macros.jq diff --git a/jq/core.jq b/jq/core.jq index 2ffdeb55..18e9119a 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -118,6 +118,21 @@ def core_identify: kind: "fn", function: "concat", inputs: -1 + }, + "nth": { + kind: "fn", + function: "nth", + inputs: 2 + }, + "first": { + kind: "fn", + function: "first", + inputs: 1 + }, + "rest": { + kind: "fn", + function: "rest", + inputs: 1 } }; @@ -192,4 +207,17 @@ def core_interp(arguments; env): select(.function == "cons") | ([arguments[0]] + arguments[1].value) | wrap("list") ) // ( select(.function == "concat") | arguments | map(.value) | (add//[]) | wrap("list") + ) // ( + select(.function == "nth") + | arguments[0].value as $lst + | arguments[1].value as $idx + | if ($lst|length < $idx) or ($idx < 0) then + jqmal_error("index out of range") + else + $lst[$idx] + end + ) // ( + select(.function == "first") | arguments[0].value | first // {kind:"nil"} + ) // ( + select(.function == "rest") | arguments[0]?.value?[1:]? // [] | wrap("list") ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/env.jq b/jq/env.jq index bf3a6e36..df61f6da 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -133,6 +133,12 @@ def env_setfallback(env; fallback): def env_get(env): . as $key | env_find(env).environment[$key] // jqmal_error("Symbol \($key) not found"); +def env_get(env; key): + key | env_get(env); + +def env_req(env; key): + key as $key | key | env_find(env).environment[$key] // null; + def addEnv(env): { expr: ., diff --git a/jq/step8_macros.jq b/jq/step8_macros.jq new file mode 100644 index 00000000..b1678c2e --- /dev/null +++ b/jq/step8_macros.jq @@ -0,0 +1,367 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; +include "env"; +include "core"; + +def read_line: + . as $in + | label $top + | input; + +def READ: + read_str | read_form | .value; + +def special_forms: + [ "if", "def!", "defmacro!", "let*", "fn*", "do", "quote", "unquote", "splice-unquote", "macroexpand" ]; + +def find_free_references(keys): + def _refs: + . as $dot + | if .kind == "symbol" then + if keys | contains([$dot.value]) then [] else [$dot.value] end + else if "list" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) + else if "vector" == $dot.kind then + ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) + else + [] + end end end; + _refs | unique; + +def recurseflip(x; y): + recurse(y; x); + +def TCOWrap(env; retenv; continue): + { + ast: ., + env: env, + ret_env: retenv, + finish: (continue | not), + cont: true # set inside + }; + +def _symbol(name): + { + kind: "symbol", + value: name + }; + +def _symbol_v(name): + if .kind == "symbol" then + .value == name + else + false + end; + +def quasiquote: + if isPair then + .value as $value | null | + if ($value[0] | _symbol_v("unquote")) then + $value[1] + else + if isPair($value[0]) and ($value[0].value[0] | _symbol_v("splice-unquote")) then + [_symbol("concat")] + + [$value[0].value[1]] + + [($value[1:] | wrap("list") | quasiquote)] | wrap("list") + else + [_symbol("cons")] + + [($value[0] | quasiquote)] + + [($value[1:] | wrap("list") | quasiquote)] | wrap("list") + end + end + else + [_symbol("quote")] + + [.] | wrap("list") + end; + +def set_macro_function: + if .kind != "function" then + jqmal_error("expected a function to be defined by defmacro!") + else + .is_macro |= true + end; + +def is_macro_call(env): + if .kind != "list" then + false + else + if (.value|first.kind == "symbol") then + env_req(env; .value|first.value) + | if .kind != "function" then + false + else + .is_macro + end + else + false + end + end; + +def EVAL(env): + def _eval_here: + .env as $env | .expr | EVAL($env); + + def _interpret($_menv): + reduce .value[] as $elem ( + []; + . as $dot | $elem | EVAL($_menv) as $eval_env | + ($dot + [$eval_env.expr]) + ) | . as $expr | first | + interpret($expr[1:]; $_menv; _eval_here); + + def macroexpand(env): + [ while(is_macro_call(env | unwrapCurrentEnv); + _interpret(env).expr) // . ] + | first + | if is_macro_call(env | unwrapCurrentEnv) then + _interpret(env).expr + else + . + end; + + def hmap_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem.value.value | EVAL($env) as $resv | + { + value: { + key: $elem.key, + value: { kkind: $elem.value.kkind, value: $resv.expr } + }, + env: env + }, + ({env: $resv.env, list: $rest} | hmap_with_env) + end; + def map_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem | EVAL($env) as $resv | + { value: $resv.expr, env: env }, + ({env: $resv.env, list: $rest} | map_with_env) + end; + def eval_ast(env): + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } + else + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } + end + ) // + (select(.kind == "hashmap") | + [ { env: env, list: (.value | to_entries) } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } + ) // + (select(.kind == "function") | + .# return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get(env | unwrapCurrentEnv) + ) // .; + + . as $ast + | { env: env, ast: ., cont: true, finish: false, ret_env: null } + | [ recurseflip(.cont; + .env as $_menv + | if .finish then + .cont |= false + else + (.ret_env//.env) as $_retenv + | .ret_env as $_orig_retenv + | .ast + | . as $init + | $_menv | unwrapCurrentEnv as $currentEnv # unwrap env "package" + | $_menv | unwrapReplEnv as $replEnv # - + | $init + | + (select(.kind == "list") | + macroexpand($_menv) | + if .kind != "list" then + eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false) + else + if .value | length == 0 then + . | TCOWrap($_menv; $_orig_retenv; false) + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL($_menv)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "defmacro!") as $value | + ($value[2] | EVAL($_menv) | (.expr |= set_macro_function)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | TCOWrap($env; $_retenv; true) + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: $_menv, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL($_menv) as $condenv | + (if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) + else + $value[2] + end) | TCOWrap($condenv.env; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + $value[1].value | map(.value) as $binds | { + kind: "function", + binds: $binds, + env: env, + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $value[2] | find_free_references($currentEnv | env_dump_keys + $binds), # for dynamically scoped variables + is_macro: false + } | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "quote") as $value | + $value[1] | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "quasiquote") as $value | + $value[1] | quasiquote | TCOWrap($_menv; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "macroexpand") as $value | + $value[1] | macroexpand(env) | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + . as $dot | _interpret($_menv) as $exprenv | + $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false) + ) // + TCOWrap($_menv; $_orig_retenv; false) + ) + end + end + ) // + (eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false)) + end + ) ] + | last as $result + | ($result.ret_env // $result.env) as $env + | $result.ast + | addEnv($env); + +def PRINT: + pr_str; + +def rep(env): + READ | EVAL(env) as $expenv | + if $expenv.expr != null then + $expenv.expr | PRINT + else + null + end | addEnv($expenv.env); + +def repl_(env): + ("user> " | _print) | + (read_line | rep(env)); + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: ({ + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, + "eval": { + kind: "fn", + inputs: 1, + function: "eval" + } + } + core_identify) + }; + +def repl(env): + def xrepl: + (.env as $env | try repl_($env) catch addEnv($env)) as $expenv | + { + value: $expenv.expr, + stop: false, + env: ($expenv.env // .env) + } | ., xrepl; + {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + +def eval_ign(expr): + . as $env | expr | rep($env) | .env; + +def eval_val(expr): + . as $env | expr | rep($env) | .expr; + +def getEnv: + replEnv + | wrapEnv + | eval_ign("(def! not (fn* (a) (if a false true)))") + | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") + | eval_ign("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + ; + +def main: + if $ARGS.positional|length > 0 then + getEnv as $env | + env_set_($env; "*ARGV*"; $ARGS.positional[1:] | wrap("list")) | + eval_val("(load-file \($ARGS.positional[0] | tojson))") + else + repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) + end; + +main \ No newline at end of file From d250ed673dabcc202f917c08f07e88addd76b24f Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Tue, 7 Jan 2020 20:13:49 +0330 Subject: [PATCH 16/40] properly implement tco and add step7:quote --- Makefile | 4 ---- jq/core.jq | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 2091235d..a6a1c1e0 100644 --- a/Makefile +++ b/Makefile @@ -94,11 +94,7 @@ IMPLS = ada ada.2 awk bash basic bbc-basic c chuck clojure coffee common-lisp cp guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ plsql powershell ps python python.2 r racket rexx rpython ruby rust scala scheme skew \ -<<<<<<< HEAD - swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick zig -======= swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick zig jq ->>>>>>> basic...? impl EXTENSION = .mal diff --git a/jq/core.jq b/jq/core.jq index 18e9119a..94364c2e 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -220,4 +220,4 @@ def core_interp(arguments; env): select(.function == "first") | arguments[0].value | first // {kind:"nil"} ) // ( select(.function == "rest") | arguments[0]?.value?[1:]? // [] | wrap("list") - ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file + ) // jqmal_error("Unknown native function \(.function)"); From e9cb5f03ae94d34c6928a15ab4768f4bdd991d15 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Wed, 8 Jan 2020 18:06:57 +0330 Subject: [PATCH 17/40] fix atom interactions and unfuck execution speed --- jq/env.jq | 60 +++++++++++++++++++++++++++++++++--------- jq/interp.jq | 42 +++++++++++++++++++++++------ jq/reader.jq | 38 +++++++++++++++++++++++++- jq/run | 29 +++++++++++++------- jq/step0_repl.jq | 2 +- jq/step1_read_print.jq | 2 +- jq/step2_eval.jq | 2 +- jq/step3_env.jq | 2 +- jq/step4_if_fn_do.jq | 20 +++----------- jq/step5_tco.jq | 40 +++------------------------- jq/step6_file.jq | 34 +++++++----------------- jq/step7_quote.jq | 36 +++++++++---------------- jq/step8_macros.jq | 44 +++++++++++-------------------- jq/utils.jq | 46 ++++++++++++++++++++++++++++++++ 14 files changed, 233 insertions(+), 164 deletions(-) diff --git a/jq/env.jq b/jq/env.jq index df61f6da..6882fda3 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -4,6 +4,7 @@ def childEnv(binds; exprs): { parent: ., fallback: null, + dirty_atoms: .dirty_atoms, environment: [binds, exprs] | transpose | ( . as $dot | reduce .[] as $item ( { value: [], seen: false, name: null, idx: 0 }; @@ -46,14 +47,16 @@ def pureChildEnv: { parent: ., environment: {}, - fallback: null + fallback: null, + dirty_atoms: .dirty_atoms }; def rootEnv: { parent: null, fallback: null, - environment: {} + environment: {}, + dirty_atoms: [] }; def inform_function(name): @@ -74,7 +77,9 @@ def env_multiset(keys; value): parent: .parent, environment: ( .environment + (reduce keys[] as $key(.environment; .[$key] |= value)) - ) + ), + fallback: .fallback, + dirty_atoms: .dirty_atoms }; def env_multiset(env; keys; value): @@ -88,19 +93,27 @@ def env_set($key; $value): $value end) as $value | { parent: .parent, - environment: (.environment + (.environment | .[$key] |= $value)) # merge together, as .environment[key] |= value does not work + environment: (.environment + (.environment | .[$key] |= $value)), # merge together, as .environment[key] |= value does not work + fallback: .fallback, + dirty_atoms: .dirty_atoms }; -def env_dump_keys: - def _dump: +def env_dump_keys(atoms): + def _dump0: + [ .environment // {} | to_entries[] | select(.value.kind != "atom") | .key ]; + def _dump1: .environment // {} | keys; - - if .parent == null then - _dump - else - (.parent | env_dump_keys + _dump) | unique + if . == null then [] else + if .parent == null then + (if atoms then _dump1 else _dump0 end + (.fallback | env_dump_keys(atoms))) | unique + else + (.parent | env_dump_keys(atoms) + (if atoms then _dump1 else _dump0 end) + (.fallback | env_dump_keys(atoms))) | unique + end end; +def env_dump_keys: + env_dump_keys(false); + def env_set(env; $key; $value): (if $value.kind == "function" or $value.kind == "atom" then # inform the function/atom of its names @@ -109,7 +122,9 @@ def env_set(env; $key; $value): $value end) as $value | { parent: env.parent, - environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)) # merge together, as env.environment[key] |= value does not work + environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)), # merge together, as env.environment[key] |= value does not work + fallback: env.fallback, + dirty_atoms: env.dirty_atoms }; def env_find(env): @@ -127,7 +142,8 @@ def env_setfallback(env; fallback): { parent: env.parent, fallback: fallback, - environment: env.environment + environment: env.environment, + dirty_atoms: env.dirty_atoms }; def env_get(env): @@ -207,6 +223,24 @@ def addToEnv(envexp; name): env: env_set_(envexp.env; name; envexp.expr) } end; +def _env_remove_references(refs): + if . != null then + { + environment: (.environment | to_entries | map(select(.key as $key | refs | contains([$key]) | not)) | from_entries), + parent: (.parent | _env_remove_references(refs)), + fallback: (.fallback | _env_remove_references(refs)), + dirty_atoms: (.dirty_atoms | map(select(. as $dot | refs | contains([$dot]) | not))) + } + else . end; + +def env_remove_references(refs): + . as $env + | if has("replEnv") then + .currentEnv |= _env_remove_references(refs) + else + _env_remove_references(refs) + end; + # for step2 def lookup(env): env.environment[.] // diff --git a/jq/interp.jq b/jq/interp.jq index 1260ac8b..6a95d858 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -1,6 +1,7 @@ include "utils"; include "core"; include "env"; +include "printer"; def arg_check(args): if .inputs == -1 then @@ -74,13 +75,30 @@ def cWithReplEnv(renv; cond): . end; -def updateAtoms(newEnv): +def cUpdateAtoms(newEnv; cond): . as $env - | reduce (newEnv | env_dump_keys | map(env_get(newEnv) as $value | select($value.kind == "atom") | $value))[] as $atom ( + | (reduce (extractEnv(newEnv)|.dirty_atoms)[] as $atom ( $env; . as $e | reduce $atom.names[] as $name ( $e; - env_set_(.; $name; $atom))); + . as $env | env_set_($env; $name; $atom)))) as $resEnv + | $resEnv | if cond then setpath(["currentEnv", "dirty_atoms"]; []) else . end + ; + +def addFrees(newEnv; frees): + . as $env + | reduce frees[] as $free ( + $env; + . as $dot + | extractEnv(newEnv) as $env + | env_req($env; $free) as $lookup + | if $lookup != null then + env_set_(.; $free; $lookup) + else + . + end) + | . as $env + | $env; def interpret(arguments; env; _eval): extractReplEnv(env) as $replEnv | @@ -105,7 +123,7 @@ def interpret(arguments; env; _eval): env; . as $env | env_set_($env; $name; $value) )) as $env | - $value.value | addEnv($env) + $value.value | addEnv($env | setpath(["currentEnv", "dirty_atoms"]; ($env.currentEnv.dirty_atoms + [$value])|unique)) ) // (select(.function == "swap!") | # env modifying function @@ -120,13 +138,16 @@ def interpret(arguments; env; _eval): $newEnv; . as $env | env_set_($env; $name; $newValue) )) as $newEnv | - $newValue.value | addEnv($newEnv) - ) // + $newValue.value | addEnv($newEnv | setpath(["currentEnv", "dirty_atoms"]; ($newEnv.currentEnv.dirty_atoms + [$newValue])|unique)) + )// (core_interp(arguments; env) | addEnv(env)) ) // (select(.kind == "function") as $fn | # todo: arg_check - env_setfallback(extractEnv(.env); extractEnv(env)) | childEnv($fn.binds; arguments) as $fnEnv | + (.body | pr_str) as $src | + # _debug("INTERP " + $src) | + # _debug("FREES " + ($fn.free_referencess | tostring)) | + env_setfallback(extractEnv(.env | addFrees(env; $fn.free_referencess)); extractEnv(env)) | childEnv($fn.binds; arguments) as $fnEnv | # tell it about its surroundings (reduce $fn.free_referencess[] as $name ( $fnEnv; @@ -147,6 +168,8 @@ def interpret(arguments; env; _eval): | cWrapEnv($replEnv; $hasReplEnv), expr: $fn.body } + | . as $dot + # | _debug("FNEXEC " + (.expr | pr_str) + " " + (env_req($dot.env; $fn.binds[0]) | pr_str)) | _eval | . as $envexp | (extractReplEnv($envexp.env)) as $xreplenv @@ -156,8 +179,11 @@ def interpret(arguments; env; _eval): env: extractEnv(env) | cUpdateReplEnv($xreplenv; $hasReplEnv) | cWrapEnv($xreplenv; $hasReplEnv) - | updateAtoms(extractEnv($envexp.env)) + | cUpdateAtoms(extractEnv($envexp.env); $hasReplEnv) } + # | . as $dot + # | _debug("FNPOST " + (.expr | pr_str) + " " + (env_req($dot.expr.env; $fn.binds[0]) | pr_str)) + # | _debug("INTERP " + $src + " = " + (.expr|pr_str)) ) // jqmal_error("Unsupported function kind \(.kind)"); \ No newline at end of file diff --git a/jq/reader.jq b/jq/reader.jq index 6b958a8d..d8c98198 100644 --- a/jq/reader.jq +++ b/jq/reader.jq @@ -6,8 +6,44 @@ def tokenize: def read_str: tokenize; +def escape_control: + (select(. == "\u0000") | "\\u0000") // + (select(. == "\u0001") | "\\u0001") // + (select(. == "\u0002") | "\\u0002") // + (select(. == "\u0003") | "\\u0003") // + (select(. == "\u0004") | "\\u0004") // + (select(. == "\u0005") | "\\u0005") // + (select(. == "\u0006") | "\\u0006") // + (select(. == "\u0007") | "\\u0007") // + (select(. == "\u0008") | "\\u0008") // + (select(. == "\u0009") | "\\u0009") // + (select(. == "\u0010") | "\\u0010") // + (select(. == "\u0011") | "\\u0011") // + (select(. == "\u0012") | "\\u0012") // + (select(. == "\u0013") | "\\u0013") // + (select(. == "\u0014") | "\\u0014") // + (select(. == "\u0015") | "\\u0015") // + (select(. == "\u0016") | "\\u0016") // + (select(. == "\u0017") | "\\u0017") // + (select(. == "\u0018") | "\\u0018") // + (select(. == "\u0019") | "\\u0019") // + (select(. == "\u0020") | "\\u0020") // + (select(. == "\u0021") | "\\u0021") // + (select(. == "\u0022") | "\\u0022") // + (select(. == "\u0023") | "\\u0023") // + (select(. == "\u0024") | "\\u0024") // + (select(. == "\u0025") | "\\u0025") // + (select(. == "\u0026") | "\\u0026") // + (select(. == "\u0027") | "\\u0027") // + (select(. == "\u0028") | "\\u0028") // + (select(. == "\u0029") | "\\u0029") // + (select(. == "\u0030") | "\\u0030") // + (select(. == "\u0031") | "\\u0031") // + (select(. == "\n") | "\\n") // + .; + def read_string: - fromjson; + gsub("(?[\u0000-\u001f])"; "\(.z | escape_control)") | fromjson; def extract_string: . as $val | if ["keyword", "symbol", "string"] | contains([$val.kind]) then diff --git a/jq/run b/jq/run index 4d4a3cc3..40019290 100755 --- a/jq/run +++ b/jq/run @@ -2,15 +2,17 @@ # let's do some sorcery to bestow IO upon jq runjq() { - mkfifo jqmal-si.pipe || true - trap "rm -f jqmal-si.pipe" EXIT + pipe_name=$(mktemp) + rm -f $pipe_name $ipipe_name + mkfifo $pipe_name || true + xstdout=$(readlink /proc/self/fd/1) + stdin=$(readlink /proc/self/fd/0) + trap "rm -f $pipe_name" EXIT SIGINT SIGHUP ( - while true; do cat jqmal-si.pipe; done& - pid=$! - trap "kill $pid" EXIT - cat - - ) |\ - jq -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" --args "${@}" |&\ + while [[ -e $pipe_name ]]; do + timeout 1 cat $pipe_name + done& + ) | jq -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" --args "${@}" |&\ tee \ >(jq -Rr 'try fromjson[1]|if type == "string" then . else empty end') \ >(while read -r line; do @@ -19,6 +21,12 @@ runjq() { # echo ">>> " $command cmd=$(echo "$command" | jq -rMc 'try .cmd catch "ignore"') case "$cmd" in + readline) + data=$(jq -nR input < $stdin) + size=${#data} + # echo "read $size bytes '$data'" + echo "$data" | pv -q -B $size > $pipe_name + ;; read) filename=$(echo "$command" | jq -Mrc '.args[0]') tmp=$(mktemp) @@ -26,7 +34,7 @@ runjq() { jq -rRnc --rawfile content "$filename" '$content|tojson' > $tmp # echo "dump $tmp to pipe" size=$(du -k $tmp) - cat $tmp | pv -q -B $size > jqmal-si.pipe #>/dev/null 2>&1 + cat $tmp | pv -q -B $size > $pipe_name #>/dev/null 2>&1 rm $tmp ;; fwrite) @@ -48,5 +56,8 @@ runjq() { esac fi done) > /dev/null + rm -f $pipe_name } +my_pid=$$ +trap 'kill -INT $my_pid' EXIT SIGINT runjq "${@}" \ No newline at end of file diff --git a/jq/step0_repl.jq b/jq/step0_repl.jq index 51d4b3a2..39f88d6c 100644 --- a/jq/step0_repl.jq +++ b/jq/step0_repl.jq @@ -3,7 +3,7 @@ include "utils"; def read_line: . as $in | label $top - | input; + | _readline; def READ: .; diff --git a/jq/step1_read_print.jq b/jq/step1_read_print.jq index 24293833..d943165e 100644 --- a/jq/step1_read_print.jq +++ b/jq/step1_read_print.jq @@ -5,7 +5,7 @@ include "utils"; def read_line: . as $in | label $top - | input; + | _readline; def READ: read_str | read_form | .value; diff --git a/jq/step2_eval.jq b/jq/step2_eval.jq index cfc6ff65..7f706571 100644 --- a/jq/step2_eval.jq +++ b/jq/step2_eval.jq @@ -6,7 +6,7 @@ include "interp"; def read_line: . as $in | label $top - | input; + | _readline; def READ: read_str | read_form | .value; diff --git a/jq/step3_env.jq b/jq/step3_env.jq index f15f9ea4..71e87207 100644 --- a/jq/step3_env.jq +++ b/jq/step3_env.jq @@ -7,7 +7,7 @@ include "env"; def read_line: . as $in | label $top - | input; + | _readline; def READ: read_str | read_form | .value; diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index da34e3bc..7051cd10 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -8,7 +8,7 @@ include "core"; def read_line: . as $in | label $top - | input; + | _readline; def READ: read_str | read_form | .value; @@ -32,20 +32,6 @@ def READ: # env; # env_set(.; $function[0]; $function[1]) # ) | { functions: $functions, env: . }; - -def find_free_references(keys): - def _refs: - . as $dot - | if .kind == "symbol" then - if keys | contains([$dot.value]) then [] else [$dot.value] end - else if "list" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) - else if "vector" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) - else - [] - end end end; - _refs | unique; def recurseflip(x; y): recurse(y; x); @@ -215,7 +201,9 @@ def replEnv: inputs: 2, function: "number_div" }, - } + core_identify) + } + core_identify), + dirty_atoms: [], + fallback: null }; def repl(env): diff --git a/jq/step5_tco.jq b/jq/step5_tco.jq index 24e636fa..8851056f 100644 --- a/jq/step5_tco.jq +++ b/jq/step5_tco.jq @@ -8,45 +8,11 @@ include "core"; def read_line: . as $in | label $top - | input; + | _readline; def READ: read_str | read_form | .value; -# def eval_ast(env): -# (select(.kind == "symbol") | .value | env_get(env) | addEnv(env)) // -# (select(.kind == "list") | reduce .value[] as $elem ( -# {value: [], env: env}; -# . as $dot | $elem | EVAL($dot.env) as $eval_env | -# { -# value: ($dot.value + [$eval_env.expr]), -# env: $eval_env.env -# } -# ) | { expr: .value, env: .env }) // (addEnv(env)); - -# def patch_with_env(env): -# . as $dot | (reduce .[] as $fnv ( -# []; -# . + [$fnv | setpath([1, "free_referencess"]; ($fnv[1].free_referencess + $dot) | unique)] -# )) as $functions | reduce $functions[] as $function ( -# env; -# env_set(.; $function[0]; $function[1]) -# ) | { functions: $functions, env: . }; - -def find_free_references(keys): - def _refs: - . as $dot - | if .kind == "symbol" then - if keys | contains([$dot.value]) then [] else [$dot.value] end - else if "list" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) - else if "vector" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + ["if", "def!", "let*", "fn*"])) - else - [] - end end end; - _refs | unique; - def recurseflip(x; y): recurse(y; x); @@ -233,7 +199,9 @@ def replEnv: inputs: 2, function: "number_div" }, - } + core_identify) + } + core_identify), + dirty_atoms: [], + fallback: null }; def repl(env): diff --git a/jq/step6_file.jq b/jq/step6_file.jq index dd5356e7..b5d9c568 100644 --- a/jq/step6_file.jq +++ b/jq/step6_file.jq @@ -8,28 +8,11 @@ include "core"; def read_line: . as $in | label $top - | input; + | _readline; def READ: read_str | read_form | .value; -def special_forms: - [ "if", "def!", "let*", "fn*", "do" ]; - -def find_free_references(keys): - def _refs: - . as $dot - | if .kind == "symbol" then - if keys | contains([$dot.value]) then [] else [$dot.value] end - else if "list" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) - else if "vector" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) - else - [] - end end end; - _refs | unique; - def recurseflip(x; y): recurse(y; x); @@ -130,13 +113,14 @@ def EVAL(env): # we can't do what the guide says, so we'll skip over this # and ues the later implementation # (fn* args body) - $value[1].value | map(.value) as $binds | { + $value[1].value | map(.value) as $binds | + ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { kind: "function", binds: $binds, - env: env, + env: (env | env_remove_references($free_referencess)), body: $value[2], names: [], # we can't do that circular reference this - free_referencess: $value[2] | find_free_references($currentEnv | env_dump_keys + $binds) # for dynamically scoped variables + free_referencess: $free_referencess # for dynamically scoped variables } | TCOWrap($_menv; $_orig_retenv; false) ) // ( @@ -231,7 +215,9 @@ def replEnv: inputs: 1, function: "eval" } - } + core_identify) + } + core_identify), + dirty_atoms: [], + fallback: null, }; def repl(env): @@ -259,8 +245,8 @@ def getEnv: def main: if $ARGS.positional|length > 0 then getEnv as $env | - env_set_($env; "*ARGV*"; $ARGS.positional[1:] | wrap("list")) | - eval_val("(load-file \($ARGS.positional[0] | tojson))") + env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | + eval_val("(load-file \($ARGS.positional[0] | tojson))") else repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; diff --git a/jq/step7_quote.jq b/jq/step7_quote.jq index 53ccf0ab..67d3d706 100644 --- a/jq/step7_quote.jq +++ b/jq/step7_quote.jq @@ -8,28 +8,11 @@ include "core"; def read_line: . as $in | label $top - | input; + | _readline; def READ: read_str | read_form | .value; -def special_forms: - [ "if", "def!", "let*", "fn*", "do" ]; - -def find_free_references(keys): - def _refs: - . as $dot - | if .kind == "symbol" then - if keys | contains([$dot.value]) then [] else [$dot.value] end - else if "list" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) - else if "vector" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) - else - [] - end end end; - _refs | unique; - def recurseflip(x; y): recurse(y; x); @@ -164,13 +147,14 @@ def EVAL(env): # we can't do what the guide says, so we'll skip over this # and ues the later implementation # (fn* args body) - $value[1].value | map(.value) as $binds | { + $value[1].value | map(.value) as $binds | + ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { kind: "function", binds: $binds, - env: env, + env: (env | env_remove_references($free_referencess)), body: $value[2], names: [], # we can't do that circular reference this - free_referencess: $value[2] | find_free_references($currentEnv | env_dump_keys + $binds) # for dynamically scoped variables + free_referencess: $free_referencess # for dynamically scoped variables } | TCOWrap($_menv; $_orig_retenv; false) ) // ( @@ -273,7 +257,9 @@ def replEnv: inputs: 1, function: "eval" } - } + core_identify) + } + core_identify), + dirty_atoms: [], + fallback: null }; def repl(env): @@ -301,10 +287,12 @@ def getEnv: def main: if $ARGS.positional|length > 0 then getEnv as $env | - env_set_($env; "*ARGV*"; $ARGS.positional[1:] | wrap("list")) | + env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | eval_val("(load-file \($ARGS.positional[0] | tojson))") else repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -main \ No newline at end of file +main + +# ( ( (fn* (a) (fn* (b) (+ a b))) 5) 7) \ No newline at end of file diff --git a/jq/step8_macros.jq b/jq/step8_macros.jq index b1678c2e..de912d57 100644 --- a/jq/step8_macros.jq +++ b/jq/step8_macros.jq @@ -8,28 +8,11 @@ include "core"; def read_line: . as $in | label $top - | input; + | _readline; def READ: read_str | read_form | .value; -def special_forms: - [ "if", "def!", "defmacro!", "let*", "fn*", "do", "quote", "unquote", "splice-unquote", "macroexpand" ]; - -def find_free_references(keys): - def _refs: - . as $dot - | if .kind == "symbol" then - if keys | contains([$dot.value]) then [] else [$dot.value] end - else if "list" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) - else if "vector" == $dot.kind then - ($dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ($dot.value[0] | find_free_references(keys + special_forms)) - else - [] - end end end; - _refs | unique; - def recurseflip(x; y): recurse(y; x); @@ -244,15 +227,16 @@ def EVAL(env): # we can't do what the guide says, so we'll skip over this # and ues the later implementation # (fn* args body) - $value[1].value | map(.value) as $binds | { - kind: "function", - binds: $binds, - env: env, - body: $value[2], - names: [], # we can't do that circular reference this - free_referencess: $value[2] | find_free_references($currentEnv | env_dump_keys + $binds), # for dynamically scoped variables - is_macro: false - } | TCOWrap($_menv; $_orig_retenv; false) + $value[1].value | map(.value) as $binds | + ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { + kind: "function", + binds: $binds, + env: (env | env_remove_references($free_referencess)), + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $free_referencess, # for dynamically scoped variables + is_macro: false + } | TCOWrap($_menv; $_orig_retenv; false) ) // ( .value | select(.[0].value == "quote") as $value | @@ -328,7 +312,9 @@ def replEnv: inputs: 1, function: "eval" } - } + core_identify) + } + core_identify), + dirty_atoms: [], + fallback: null }; def repl(env): @@ -358,7 +344,7 @@ def getEnv: def main: if $ARGS.positional|length > 0 then getEnv as $env | - env_set_($env; "*ARGV*"; $ARGS.positional[1:] | wrap("list")) | + env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | eval_val("(load-file \($ARGS.positional[0] | tojson))") else repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) diff --git a/jq/utils.jq b/jq/utils.jq index 666c11bf..45c14ad3 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -35,6 +35,47 @@ def isPair: def isPair(x): x | isPair; +def find_free_references(keys): + def _refs: + if . == null then [] else + . as $dot + | if .kind == "symbol" then + if keys | contains([$dot.value]) then [] else [$dot.value] end + else if "list" == $dot.kind then + # if - scan args + # def! - scan body + # let* - add keys sequentially, scan body + # fn* - add keys, scan body + # quote - [] + # quasiquote - ??? + $dot.value[0] as $head + | if $head.kind == "symbol" then + ( + select($head.value == "if") | $dot.value[1:] | map(_refs) | reduce .[] as $x ([]; . + $x) + ) // ( + select($head.value == "def!") | $dot.value[2] | _refs + ) // ( + select($head.value == "let*") | $dot.value[2] | find_free_references(($dot.value[1].value | map(.value[0].value)) + keys) + ) // ( + select($head.value == "fn*") | $dot.value[2] | find_free_references(($dot.value[1].value | map(.value)) + keys) + ) // ( + select($head.value == "quote") | [] + ) // ( + select($head.value == "quasiquote") | [] + ) // ($dot.value | map(_refs) | reduce .[] as $x ([]; . + $x)) + else + [ $dot.values[1:][] | _refs ] + end + else if "vector" == $dot.kind then + ($dot.value | map(_refs) | reduce .[] as $x ([]; . + $x)) + else if "hashmap" == $dot.kind then + ([$dot.value | from_entries | map({kind: .value.kkind, value: .key}, .value.value)] | map(_refs) | reduce .[] as $x ([]; . + $x)) + else + [] + end end end end + end; + _refs | unique; + def tomal: ( select(type == "array") | ( @@ -80,6 +121,11 @@ def _debug(ex): def _print: debug; +def _readline: + [] + | issue_extern("readline"; {nowait: false}) + ; + def _write_to_file(name): . as $value | [(name|tojson), (.|tojson), (false|tojson)] From 4db6de123015508718d25640321a0851a0e61df0 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Wed, 8 Jan 2020 20:04:12 +0330 Subject: [PATCH 18/40] add step9 and fix "symbol not found" exception format --- jq/core.jq | 192 +++++++++++++++++++++++- jq/env.jq | 4 +- jq/interp.jq | 30 +++- jq/step2_eval.jq | 2 +- jq/step9_try.jq | 382 +++++++++++++++++++++++++++++++++++++++++++++++ jq/utils.jq | 7 +- 6 files changed, 601 insertions(+), 16 deletions(-) create mode 100644 jq/step9_try.jq diff --git a/jq/core.jq b/jq/core.jq index 94364c2e..75e8ae60 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -107,7 +107,7 @@ def core_identify: "swap!": { # defined in interp kind: "fn", function: "swap!", - inputs: -1 + inputs: -3 }, "cons": { kind: "fn", @@ -133,6 +133,111 @@ def core_identify: kind: "fn", function: "rest", inputs: 1 + }, + "throw": { + kind: "fn", + function: "throw", + inputs: 1 + }, + "apply": { # defined in interp + kind: "fn", + function: "apply", + inputs: -3 + }, + "map": { # defined in interp + kind: "fn", + function: "map", + inputs: 2 + }, + "nil?": { + kind: "fn", + function: "nil?", + inputs: 1 + }, + "true?": { + kind: "fn", + function: "true?", + inputs: 1 + }, + "false?": { + kind: "fn", + function: "false?", + inputs: 1 + }, + "symbol": { + kind: "fn", + function: "symbol", + inputs: 1 + }, + "symbol?": { + kind: "fn", + function: "symbol?", + inputs: 1 + }, + "keyword": { + kind: "fn", + function: "keyword", + inputs: 1 + }, + "keyword?": { + kind: "fn", + function: "keyword?", + inputs: 1 + }, + "vector": { + kind: "fn", + function: "vector", + inputs: -1 + }, + "vector?": { + kind: "fn", + function: "vector?", + inputs: 1 + }, + "sequential?": { + kind: "fn", + function: "sequential?", + inputs: 1 + }, + "hash-map": { + kind: "fn", + function: "hash-map", + inputs: -1 + }, + "map?": { + kind: "fn", + function: "map?", + inputs: 1 + }, + "assoc": { + kind: "fn", + function: "assoc", + inputs: -2 + }, + "dissoc": { + kind: "fn", + function: "dissoc", + inputs: -2 + }, + "get": { + kind: "fn", + function: "get", + inputs: 2 + }, + "contains?": { + kind: "fn", + function: "contains?", + inputs: 2 + }, + "keys": { + kind: "fn", + function: "keys", + inputs: 1 + }, + "vals": { + kind: "fn", + function: "vals", + inputs: 1 } }; @@ -140,11 +245,16 @@ def vec2list(obj): if obj.kind == "list" then obj.value | map(vec2list(.)) | wrap("list") else - if obj.kind == "vector" then - obj.value | map(vec2list(.)) | wrap("list") - else - obj - end end; + if obj.kind == "vector" then + obj.value | map(vec2list(.)) | wrap("list") + else + if obj.kind == "hashmap" then + obj.value | map_values(.value |= vec2list(.)) | wrap("hashmap") + else + obj + end + end + end; def core_interp(arguments; env): ( @@ -220,4 +330,72 @@ def core_interp(arguments; env): select(.function == "first") | arguments[0].value | first // {kind:"nil"} ) // ( select(.function == "rest") | arguments[0]?.value?[1:]? // [] | wrap("list") - ) // jqmal_error("Unknown native function \(.function)"); + ) // ( + select(.function == "throw") | jqmal_error(arguments[0] | tojson) + ) // ( + select(.function == "nil?") | null | wrap((arguments[0].kind == "nil") | tostring) + ) // ( + select(.function == "true?") | null | wrap((arguments[0].kind == "true") | tostring) + ) // ( + select(.function == "false?") | null | wrap((arguments[0].kind == "false") | tostring) + ) // ( + select(.function == "symbol?") | null | wrap((arguments[0].kind == "symbol") | tostring) + ) // ( + select(.function == "symbol") | arguments[0].value | wrap("symbol") + ) // ( + select(.function == "keyword") | arguments[0].value | wrap("keyword") + ) // ( + select(.function == "keyword?") | null | wrap((arguments[0].kind == "keyword") | tostring) + ) // ( + select(.function == "vector") | arguments | wrap("vector") + ) // ( + select(.function == "vector?") | null | wrap((arguments[0].kind == "vector") | tostring) + ) // ( + select(.function == "sequential?") | null | wrap((arguments[0].kind == "vector" or arguments[0].kind == "list") | tostring) + ) // ( + select(.function == "hash-map") | + if (arguments|length) % 2 == 1 then + jqmal_error("Odd number of arguments to hash-map") + else + [ arguments | + nwise(2) | + try { + key: (.[0] | extract_string), + value: { + kkind: .[0].kind, + value: .[1] + } + } + ] | from_entries | wrap("hashmap") + end + ) // ( + select(.function == "map?") | null | wrap((arguments[0].kind == "hashmap") | tostring) + ) // ( + select(.function == "assoc") | + if (arguments|length) % 2 == 0 then + jqmal_error("Odd number of key-values to assoc") + else + arguments[0].value + ([ arguments[1:] | + nwise(2) | + try { + key: (.[0] | extract_string), + value: { + kkind: .[0].kind, + value: .[1] + } + } + ] | from_entries) | wrap("hashmap") + end + ) // ( + select(.function == "dissoc") | + arguments[1:] | map(.value) as $keynames | + arguments[0].value | with_entries(select(.key as $k | $keynames | contains([$k]) | not)) | wrap("hashmap") + ) // ( + select(.function == "get") | arguments[0].value[arguments[1].value].value // {kind:"nil"} + ) // ( + select(.function == "contains?") | null | wrap((arguments[0].value | has(arguments[1].value)) | tostring) + ) // ( + select(.function == "keys") | arguments[0].value | with_entries(.value as $v | .key as $k | {key: $k, value: {value: $k, kind: $v.kkind}}) | to_entries | map(.value) | wrap("list") + ) // ( + select(.function == "vals") | arguments[0].value | map(.value) | to_entries | map(.value) | wrap("list") + ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/env.jq b/jq/env.jq index 6882fda3..5dc03388 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -147,7 +147,7 @@ def env_setfallback(env; fallback): }; def env_get(env): - . as $key | env_find(env).environment[$key] // jqmal_error("Symbol \($key) not found"); + . as $key | env_find(env).environment[$key] // jqmal_error("'\($key)' not found"); def env_get(env; key): key | env_get(env); @@ -247,5 +247,5 @@ def lookup(env): if env.parent then lookup(env.parent) else - jqmal_error("Symbol \(.) not found") + jqmal_error("'\(.)' not found") end; \ No newline at end of file diff --git a/jq/interp.jq b/jq/interp.jq index 6a95d858..949337c4 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -4,10 +4,14 @@ include "env"; include "printer"; def arg_check(args): - if .inputs == -1 then - . + if .inputs < 0 then + if (abs(.inputs) - 1) > (args | length) then + jqmal_error("Invalid number of arguments (expected at least \(abs(.inputs) - 1), got \(args|length): \(args | wrap("vector") | pr_str))") + else + . + end else if .inputs != (args|length) then - jqmal_error("Invalid number of arguments (expected \(.inputs) got \(args|length): \(args))") + jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length): \(args | wrap("vector") | pr_str))") else . end end; @@ -139,7 +143,25 @@ def interpret(arguments; env; _eval): . as $env | env_set_($env; $name; $newValue) )) as $newEnv | $newValue.value | addEnv($newEnv | setpath(["currentEnv", "dirty_atoms"]; ($newEnv.currentEnv.dirty_atoms + [$newValue])|unique)) - )// + ) // + (select(.function == "apply") | + # (apply F ...T A) -> (F ...T ...A) + arguments as $args + | ($args|first) as $F + | ($args|last.value) as $A + | $args[1:-1] as $T + | $F | interpret([$T[], $A[]]; env; _eval) + ) // + (select(.function == "map") | + arguments + | first as $F + | last.value as $L + | (reduce $L[] as $elem ( + []; + . + [($F | interpret([$elem]; env; _eval).expr)] + )) as $ex + | $ex | wrap("list") | addEnv(env) + ) // (core_interp(arguments; env) | addEnv(env)) ) // (select(.kind == "function") as $fn | diff --git a/jq/step2_eval.jq b/jq/step2_eval.jq index 7f706571..0eec9d19 100644 --- a/jq/step2_eval.jq +++ b/jq/step2_eval.jq @@ -13,7 +13,7 @@ def READ: def lookup(env): env[.] // - jqmal_error("Symbol \(.) not found"); + jqmal_error("'\(.)' not found"); def EVAL(env): def eval_ast: diff --git a/jq/step9_try.jq b/jq/step9_try.jq new file mode 100644 index 00000000..e005b83d --- /dev/null +++ b/jq/step9_try.jq @@ -0,0 +1,382 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; +include "env"; +include "core"; + +def read_line: + . as $in + | label $top + | _readline; + +def READ: + read_str | read_form | .value; + +def recurseflip(x; y): + recurse(y; x); + +def TCOWrap(env; retenv; continue): + { + ast: ., + env: env, + ret_env: retenv, + finish: (continue | not), + cont: true # set inside + }; + +def _symbol(name): + { + kind: "symbol", + value: name + }; + +def _symbol_v(name): + if .kind == "symbol" then + .value == name + else + false + end; + +def quasiquote: + if isPair then + .value as $value | null | + if ($value[0] | _symbol_v("unquote")) then + $value[1] + else + if isPair($value[0]) and ($value[0].value[0] | _symbol_v("splice-unquote")) then + [_symbol("concat")] + + [$value[0].value[1]] + + [($value[1:] | wrap("list") | quasiquote)] | wrap("list") + else + [_symbol("cons")] + + [($value[0] | quasiquote)] + + [($value[1:] | wrap("list") | quasiquote)] | wrap("list") + end + end + else + [_symbol("quote")] + + [.] | wrap("list") + end; + +def set_macro_function: + if .kind != "function" then + jqmal_error("expected a function to be defined by defmacro!") + else + .is_macro |= true + end; + +def is_macro_call(env): + if .kind != "list" then + false + else + if (.value|first.kind == "symbol") then + env_req(env; .value|first.value) + | if .kind != "function" then + false + else + .is_macro + end + else + false + end + end; + +def EVAL(env): + def _eval_here: + .env as $env | .expr | EVAL($env); + + def _interpret($_menv): + reduce .value[] as $elem ( + []; + . as $dot | $elem | EVAL($_menv) as $eval_env | + ($dot + [$eval_env.expr]) + ) | . as $expr | first | + interpret($expr[1:]; $_menv; _eval_here); + + def macroexpand(env): + [ while(is_macro_call(env | unwrapCurrentEnv); + _interpret(env).expr) // . ] + | first + | if is_macro_call(env | unwrapCurrentEnv) then + _interpret(env).expr + else + . + end; + + def hmap_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem.value.value | EVAL($env) as $resv | + { + value: { + key: $elem.key, + value: { kkind: $elem.value.kkind, value: $resv.expr } + }, + env: env + }, + ({env: $resv.env, list: $rest} | hmap_with_env) + end; + def map_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem | EVAL($env) as $resv | + { value: $resv.expr, env: env }, + ({env: $resv.env, list: $rest} | map_with_env) + end; + def eval_ast(env): + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } + else + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } + end + ) // + (select(.kind == "hashmap") | + [ { env: env, list: (.value | to_entries) } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } + ) // + (select(.kind == "function") | + .# return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get(env | unwrapCurrentEnv) + ) // .; + + . as $ast + | { env: env, ast: ., cont: true, finish: false, ret_env: null } + | [ recurseflip(.cont; + .env as $_menv + | if .finish then + .cont |= false + else + (.ret_env//.env) as $_retenv + | .ret_env as $_orig_retenv + | .ast + | . as $init + | $_menv | unwrapCurrentEnv as $currentEnv # unwrap env "package" + | $_menv | unwrapReplEnv as $replEnv # - + | $init + | + (select(.kind == "list") | + macroexpand($_menv) | + if .kind != "list" then + eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false) + else + if .value | length == 0 then + . | TCOWrap($_menv; $_orig_retenv; false) + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL($_menv)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "defmacro!") as $value | + ($value[2] | EVAL($_menv) | (.expr |= set_macro_function)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | TCOWrap($env; $_retenv; true) + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: $_menv, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "try*") as $value | + try ( + $value[1] | EVAL($_menv) as $exp | $exp.expr | TCOWrap($exp.env; $_orig_retenv; false) + ) catch ( . as $exc | + if $value[2] then + if ($value[2].value[0] | _symbol_v("catch*")) then + (if ($exc | is_jqmal_error) then + $exc[19:] as $ex | + try ( + $ex + | fromjson + ) catch ( + $ex | + wrap("string") + ) + else + $exc|wrap("string") + end) as $exc | + $value[2].value[2] | EVAL($currentEnv | childEnv([$value[2].value[1].value]; [$exc]) | wrapEnv($replEnv)) as $ex | + $ex.expr | TCOWrap($ex.env; $_retenv; false) + else + error($exc) + end + else + error($exc) + end + ) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL($_menv) as $condenv | + (if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) + else + $value[2] + end) | TCOWrap($condenv.env; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + $value[1].value | map(.value) as $binds | + ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { + kind: "function", + binds: $binds, + env: (env | env_remove_references($free_referencess)), + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $free_referencess, # for dynamically scoped variables + is_macro: false + } | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "quote") as $value | + $value[1] | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "quasiquote") as $value | + $value[1] | quasiquote | TCOWrap($_menv; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "macroexpand") as $value | + $value[1] | macroexpand(env) | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + . as $dot | _interpret($_menv) as $exprenv | + $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false) + ) // + TCOWrap($_menv; $_orig_retenv; false) + ) + end + end + ) // + (eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false)) + end + ) ] + | last as $result + | ($result.ret_env // $result.env) as $env + | $result.ast + | addEnv($env); + +def PRINT: + pr_str; + +def rep(env): + READ | EVAL(env) as $expenv | + if $expenv.expr != null then + $expenv.expr | PRINT + else + null + end | addEnv($expenv.env); + +def repl_(env): + ("user> " | _print) | + (read_line | rep(env)); + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: ({ + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, + "eval": { + kind: "fn", + inputs: 1, + function: "eval" + } + } + core_identify), + dirty_atoms: [], + fallback: null + }; + +def repl(env): + def xrepl: + (.env as $env | try repl_($env) catch addEnv($env)) as $expenv | + { + value: $expenv.expr, + stop: false, + env: ($expenv.env // .env) + } | ., xrepl; + {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + +def eval_ign(expr): + . as $env | expr | rep($env) | .env; + +def eval_val(expr): + . as $env | expr | rep($env) | .expr; + +def getEnv: + replEnv + | wrapEnv + | eval_ign("(def! not (fn* (a) (if a false true)))") + | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") + | eval_ign("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + ; + +def main: + if $ARGS.positional|length > 0 then + getEnv as $env | + env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | + eval_val("(load-file \($ARGS.positional[0] | tojson))") + else + repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) + end; + +main \ No newline at end of file diff --git a/jq/utils.jq b/jq/utils.jq index 45c14ad3..0186264f 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -7,11 +7,14 @@ def nwise(n): end; _nwise; +def abs(x): + if x < 0 then 0 - x else x end; + def jqmal_error(e): - error("JqMAL :: " + e); + error("JqMAL Exception :: " + e); def is_jqmal_error: - startswith("JqMAL :: "); + startswith("JqMAL Exception :: "); def wrap(kind): { From b103f95e5a9ed9f06aefec8a3d7092f25cb06693 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Thu, 9 Jan 2020 19:37:24 +0330 Subject: [PATCH 19/40] fix weird interaction between let* and fn* and atoms --- jq/core.jq | 69 ++++++++- jq/env.jq | 124 +++++++++++----- jq/interp.jq | 15 +- jq/printer.jq | 4 +- jq/run | 5 +- jq/stepA_mal.jq | 388 ++++++++++++++++++++++++++++++++++++++++++++++++ jq/utils.jq | 29 ++-- 7 files changed, 583 insertions(+), 51 deletions(-) create mode 100644 jq/stepA_mal.jq diff --git a/jq/core.jq b/jq/core.jq index 75e8ae60..ab216afd 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -238,6 +238,56 @@ def core_identify: kind: "fn", function: "vals", inputs: 1 + }, + "string?": { + kind: "fn", + function: "string?", + inputs: 1 + }, + "fn?": { + kind: "fn", + function: "fn?", + inputs: 1 + }, + "number?": { + kind: "fn", + function: "number?", + inputs: 1 + }, + "macro?": { + kind: "fn", + function: "macro?", + inputs: 1 + }, + "readline": { + kind: "fn", + function: "readline", + inputs: 1 + }, + "time-ms": { + kind: "fn", + function: "time-ms", + inputs: 0 + }, + "meta": { + kind: "fn", + function: "meta", + inputs: 1 + }, + "with-meta": { + kind: "fn", + function: "with-meta", + inputs: 2 + }, + "seq": { + kind: "fn", + function: "seq", + inputs: 1 + }, + "conj": { + kind: "fn", + function: "conj", + inputs: -3 } }; @@ -308,7 +358,7 @@ def core_interp(arguments; env): ) // ( select(.function == "read-string") | arguments | first.value | read_str | read_form.value ) // ( - select(.function == "atom") | arguments | first | wrap2("atom"; {names: []}) + select(.function == "atom") | arguments | first | wrap2("atom"; {names: [], identity: now, last_modified: now}) ) // ( select(.function == "atom?") | null | wrap(arguments | first.kind == "atom" | tostring) ) // ( @@ -319,6 +369,7 @@ def core_interp(arguments; env): select(.function == "concat") | arguments | map(.value) | (add//[]) | wrap("list") ) // ( select(.function == "nth") + | _debug(arguments) | arguments[0].value as $lst | arguments[1].value as $idx | if ($lst|length < $idx) or ($idx < 0) then @@ -398,4 +449,20 @@ def core_interp(arguments; env): select(.function == "keys") | arguments[0].value | with_entries(.value as $v | .key as $k | {key: $k, value: {value: $k, kind: $v.kkind}}) | to_entries | map(.value) | wrap("list") ) // ( select(.function == "vals") | arguments[0].value | map(.value) | to_entries | map(.value) | wrap("list") + ) // ( + select(.function == "string?") | null | wrap((arguments[0].kind == "string") | tostring) + ) // ( + select(.function == "fn?") | null | wrap((arguments[0].kind == "fn" or arguments[0].kind == "function") | tostring) + ) // ( + select(.function == "number?") | null | wrap((arguments[0].kind == "number") | tostring) + ) // ( + select(.function == "macro?") | null | wrap((arguments[0].is_macro == true) | tostring) + ) // ( + select(.function == "readline") | arguments[0].value | __readline | wrap("string") + ) // ( + select(.function == "time-ms") | now * 1000 | wrap("number") + ) // ( + select(.function == "meta") | arguments[0].meta // {kind:"nil"} + ) // ( + select(.function == "with-meta") | arguments[0] | .meta |= arguments[1] ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/env.jq b/jq/env.jq index 5dc03388..97ceea2b 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -69,7 +69,7 @@ def inform_function_multi(names): ); def env_multiset(keys; value): - (if value.kind == "function" then + (if value.kind == "function" then # multiset not allowed on atoms value | inform_function_multi(keys) else value @@ -87,8 +87,20 @@ def env_multiset(env; keys; value): def env_set($key; $value): (if $value.kind == "function" or $value.kind == "atom" then - # inform the function of its names - $value | inform_function($key) + # inform the function/atom of its names + ($value | + if $value.kind == "atom" then + # check if the one we have is newer + env_req(env; key) as $ours | + if $ours.last_modified > $value.last_modified then + $ours + else + # update modification timestamp + $value | .last_modified |= now + end + else + . + end) | inform_function($key) else $value end) as $value | { @@ -98,35 +110,24 @@ def env_set($key; $value): dirty_atoms: .dirty_atoms }; -def env_dump_keys(atoms): - def _dump0: - [ .environment // {} | to_entries[] | select(.value.kind != "atom") | .key ]; +def env_dump_keys: def _dump1: .environment // {} | keys; if . == null then [] else if .parent == null then - (if atoms then _dump1 else _dump0 end + (.fallback | env_dump_keys(atoms))) | unique + ( + _dump1 + + (.fallback | env_dump_keys) + ) else - (.parent | env_dump_keys(atoms) + (if atoms then _dump1 else _dump0 end) + (.fallback | env_dump_keys(atoms))) | unique - end + ( + _dump1 + + (.parent | env_dump_keys) + + (.fallback | env_dump_keys) + ) + end | unique end; -def env_dump_keys: - env_dump_keys(false); - -def env_set(env; $key; $value): - (if $value.kind == "function" or $value.kind == "atom" then - # inform the function/atom of its names - $value | (.names += [$key]) | (.names |= unique) - else - $value - end) as $value | { - parent: env.parent, - environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)), # merge together, as env.environment[key] |= value does not work - fallback: env.fallback, - dirty_atoms: env.dirty_atoms - }; - def env_find(env): if env.environment[.] == null then if env.parent then @@ -138,6 +139,70 @@ def env_find(env): env end; +def env_get(env): + . as $key | $key | env_find(env).environment[$key] as $value | + if $value == null then + jqmal_error("'\($key)' not found") + else + if $value.kind == "atom" then + $value.identity as $id | + $key | env_find(env.parent).environment[$key] as $possibly_newer | + if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then + $possibly_newer + else + $value + end + else + $value + end + end; + +def env_get(env; key): + key | env_get(env); + +def env_req(env; key): + key as $key | key | env_find(env).environment[$key] as $value | + if $value == null then + null + else + if $value.kind == "atom" then + $value.identity as $id | + $key | env_find(env.parent).environment[$key] as $possibly_newer | + if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then + $possibly_newer + else + $value + end + else + $value + end + end; + +def env_set(env; $key; $value): + (if $value.kind == "function" or $value.kind == "atom" then + # inform the function/atom of its names + $value | (.names += [$key]) | (.names |= unique) | + if $value.kind == "atom" then + # check if the one we have is newer + env_req(env; $key) as $ours | + if $ours.last_modified > $value.last_modified then + $ours + else + # update modification timestamp + $value | .last_modified |= now + end + else + . + end + else + $value + end) as $value | { + parent: env.parent, + environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)), # merge together, as env.environment[key] |= value does not work + fallback: env.fallback, + dirty_atoms: env.dirty_atoms + }; + def env_setfallback(env; fallback): { parent: env.parent, @@ -145,15 +210,6 @@ def env_setfallback(env; fallback): environment: env.environment, dirty_atoms: env.dirty_atoms }; - -def env_get(env): - . as $key | env_find(env).environment[$key] // jqmal_error("'\($key)' not found"); - -def env_get(env; key): - key | env_get(env); - -def env_req(env; key): - key as $key | key | env_find(env).environment[$key] // null; def addEnv(env): { diff --git a/jq/interp.jq b/jq/interp.jq index 949337c4..ddac8e5a 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -6,7 +6,7 @@ include "printer"; def arg_check(args): if .inputs < 0 then if (abs(.inputs) - 1) > (args | length) then - jqmal_error("Invalid number of arguments (expected at least \(abs(.inputs) - 1), got \(args|length): \(args | wrap("vector") | pr_str))") + jqmal_error("Invalid number of arguments (expected at least \(abs(.inputs) - 1), got \(args|length))") else . end @@ -107,6 +107,7 @@ def addFrees(newEnv; frees): def interpret(arguments; env; _eval): extractReplEnv(env) as $replEnv | hasReplEnv(env) as $hasReplEnv | + (if $DEBUG then _debug("INTERP: \(. | pr_str)") else . end) | (select(.kind == "fn") | arg_check(arguments) | (select(.function == "eval") | @@ -122,7 +123,11 @@ def interpret(arguments; env; _eval): (select(.function == "reset!") | # env modifying function arguments[0].names as $names | - arguments[1]|wrap2("atom"; {names: $names}) as $value | + arguments[1]|wrap2("atom"; { + names: $names, + identity: arguments[0].identity, + last_modified: now + }) as $value | (reduce $names[] as $name ( env; . as $env | env_set_($env; $name; $value) @@ -136,7 +141,11 @@ def interpret(arguments; env; _eval): arguments[1] as $function | ([$initValue] + arguments[2:]) as $args | ($function | interpret($args; env; _eval)) as $newEnvValue | - $newEnvValue.expr|wrap2("atom"; {names: $names}) as $newValue | + $newEnvValue.expr | wrap2("atom"; { + names: $names, + identity: arguments[0].identity, + last_modified: now + }) as $newValue | $newEnvValue.env as $newEnv | (reduce $names[] as $name ( $newEnv; diff --git a/jq/printer.jq b/jq/printer.jq index ffadf5a8..88f6cad5 100644 --- a/jq/printer.jq +++ b/jq/printer.jq @@ -17,9 +17,9 @@ def pr_str(opt): (select(.kind == "nil") | "nil") // (select(.kind == "true") | "true") // (select(.kind == "false") | "false") // - (select(.kind == "fn") | "#") // + (select(.kind == "fn") | "#") // (select(.kind == "function")| "#") // - (select(.kind == "atom")| "(atom \(.value | pr_str(opt)))") // + (select(.kind == "atom") | "(atom \(.value | pr_str(opt)))") // "#"; def pr_str: diff --git a/jq/run b/jq/run index 40019290..04e53774 100755 --- a/jq/run +++ b/jq/run @@ -1,6 +1,8 @@ #!/bin/bash # let's do some sorcery to bestow IO upon jq +XDEBUG=${DEBUG:-false} +SELFDEBUG=${RUNDEBUG:-false} runjq() { pipe_name=$(mktemp) rm -f $pipe_name $ipipe_name @@ -12,9 +14,10 @@ runjq() { while [[ -e $pipe_name ]]; do timeout 1 cat $pipe_name done& - ) | jq -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" --args "${@}" |&\ + ) | jq --argjson DEBUG $XDEBUG -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" --args "${@}" |&\ tee \ >(jq -Rr 'try fromjson[1]|if type == "string" then . else empty end') \ + >(jq -Rr "if $SELFDEBUG then . else empty end") \ >(while read -r line; do command=$(echo $line | jq -c 'try if .[1] | has("command") then .[1].command else empty end' 2>/dev/null) if [[ $command ]]; then diff --git a/jq/stepA_mal.jq b/jq/stepA_mal.jq new file mode 100644 index 00000000..11506677 --- /dev/null +++ b/jq/stepA_mal.jq @@ -0,0 +1,388 @@ +include "reader"; +include "printer"; +include "utils"; +include "interp"; +include "env"; +include "core"; + +def read_line: + . as $in + | label $top + | _readline; + +def READ: + read_str | read_form | .value; + +def recurseflip(x; y): + recurse(y; x); + +def TCOWrap(env; retenv; continue): + { + ast: ., + env: env, + ret_env: retenv, + finish: (continue | not), + cont: true # set inside + }; + +def _symbol(name): + { + kind: "symbol", + value: name + }; + +def _symbol_v(name): + if .kind == "symbol" then + .value == name + else + false + end; + +def quasiquote: + if isPair then + .value as $value | null | + if ($value[0] | _symbol_v("unquote")) then + $value[1] + else + if isPair($value[0]) and ($value[0].value[0] | _symbol_v("splice-unquote")) then + [_symbol("concat")] + + [$value[0].value[1]] + + [($value[1:] | wrap("list") | quasiquote)] | wrap("list") + else + [_symbol("cons")] + + [($value[0] | quasiquote)] + + [($value[1:] | wrap("list") | quasiquote)] | wrap("list") + end + end + else + [_symbol("quote")] + + [.] | wrap("list") + end; + +def set_macro_function: + if .kind != "function" then + jqmal_error("expected a function to be defined by defmacro!") + else + .is_macro |= true + end; + +def is_macro_call(env): + if .kind != "list" then + false + else + if (.value|first.kind == "symbol") then + env_req(env; .value|first.value) + | if .kind != "function" then + false + else + .is_macro + end + else + false + end + end; + +def EVAL(env): + def _eval_here: + .env as $env | .expr | EVAL($env); + + def _interpret($_menv): + reduce .value[] as $elem ( + []; + . as $dot | $elem | EVAL($_menv) as $eval_env | + ($dot + [$eval_env.expr]) + ) | . as $expr | first | + interpret($expr[1:]; $_menv; _eval_here); + + def macroexpand(env): + [ while(is_macro_call(env | unwrapCurrentEnv); + _interpret(env).expr) // . ] + | first + | if is_macro_call(env | unwrapCurrentEnv) then + _interpret(env).expr + else + . + end; + + def hmap_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem.value.value | EVAL($env) as $resv | + { + value: { + key: $elem.key, + value: { kkind: $elem.value.kkind, value: $resv.expr } + }, + env: env + }, + ({env: $resv.env, list: $rest} | hmap_with_env) + end; + def map_with_env: + .env as $env | .list as $list | + if $list|length == 0 then + empty + else + $list[0] as $elem | + $list[1:] as $rest | + $elem | EVAL($env) as $resv | + { value: $resv.expr, env: env }, + ({env: $resv.env, list: $rest} | map_with_env) + end; + def eval_ast(env): + (select(.kind == "vector") | + if .value|length == 0 then + { + kind: "vector", + value: [] + } + else + [ { env: env, list: .value } | map_with_env ] as $res | + { + kind: "vector", + value: $res | map(.value) + } + end + ) // + (select(.kind == "hashmap") | + [ { env: env, list: (.value | to_entries) } | hmap_with_env ] as $res | + { + kind: "hashmap", + value: $res | map(.value) | from_entries + } + ) // + (select(.kind == "function") | + .# return this unchanged, since it can only be applied to + ) // + (select(.kind == "symbol") | + .value | env_get(env | unwrapCurrentEnv) + ) // .; + + . as $ast + | { env: env, ast: ., cont: true, finish: false, ret_env: null } + | [ recurseflip(.cont; + .env as $_menv + | (if $DEBUG then _debug("EVAL: \($ast | pr_str($_menv))") else . end) + | if .finish then + .cont |= false + else + (.ret_env//.env) as $_retenv + | .ret_env as $_orig_retenv + | .ast + | . as $init + | $_menv | unwrapCurrentEnv as $currentEnv # unwrap env "package" + | $_menv | unwrapReplEnv as $replEnv # - + | $init + | + (select(.kind == "list") | + macroexpand($_menv) | + if .kind != "list" then + eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false) + else + if .value | length == 0 then + . | TCOWrap($_menv; $_orig_retenv; false) + else + ( + ( + .value | select(.[0].value == "def!") as $value | + ($value[2] | EVAL($_menv)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "defmacro!") as $value | + ($value[2] | EVAL($_menv) | (.expr |= set_macro_function)) as $evval | + addToEnv($evval; $value[1].value) as $val | + $val.expr | TCOWrap($val.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | + (reduce ($value[1].value | nwise(2)) as $xvalue ( + $subenv; + . as $env | $xvalue[1] | EVAL($env) as $expenv | + env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env + | $value[2] | TCOWrap($env; $_retenv; true) + ) // + ( + .value | select(.[0].value == "do") as $value | + (reduce ($value[1:][]) as $xvalue ( + { env: $_menv, expr: {kind:"nil"} }; + .env as $env | $xvalue | EVAL($env) + )) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "try*") as $value | + try ( + $value[1] | EVAL($_menv) as $exp | $exp.expr | TCOWrap($exp.env; $_orig_retenv; false) + ) catch ( . as $exc | + if $value[2] then + if ($value[2].value[0] | _symbol_v("catch*")) then + (if ($exc | is_jqmal_error) then + $exc[19:] as $ex | + try ( + $ex + | fromjson + ) catch ( + $ex | + wrap("string") + ) + else + $exc|wrap("string") + end) as $exc | + $value[2].value[2] | EVAL($currentEnv | childEnv([$value[2].value[1].value]; [$exc]) | wrapEnv($replEnv)) as $ex | + $ex.expr | TCOWrap($ex.env; $_retenv; false) + else + error($exc) + end + else + error($exc) + end + ) + ) // + ( + .value | select(.[0].value == "if") as $value | + $value[1] | EVAL($_menv) as $condenv | + (if (["false", "nil"] | contains([$condenv.expr.kind])) then + ($value[3] // {kind:"nil"}) + else + $value[2] + end) | TCOWrap($condenv.env; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "fn*") as $value | + # we can't do what the guide says, so we'll skip over this + # and ues the later implementation + # (fn* args body) + $value[1].value | map(.value) as $binds | + ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { + kind: "function", + binds: $binds, + env: $_menv, + body: $value[2], + names: [], # we can't do that circular reference this + free_referencess: $free_referencess, # for dynamically scoped variables + is_macro: false + } | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "quote") as $value | + $value[1] | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + .value | select(.[0].value == "quasiquote") as $value | + $value[1] | quasiquote | TCOWrap($_menv; $_orig_retenv; true) + ) // + ( + .value | select(.[0].value == "macroexpand") as $value | + $value[1] | macroexpand(env) | TCOWrap($_menv; $_orig_retenv; false) + ) // + ( + . as $dot | _interpret($_menv) as $exprenv | + $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false) + ) // + TCOWrap($_menv; $_orig_retenv; false) + ) + end + end + ) // + (eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false)) + end + ) ] + | last as $result + | ($result.ret_env // $result.env) as $env + | $result.ast + | addEnv($env); + +def PRINT(env): + pr_str(env); + +def rep(env): + READ | EVAL(env) as $expenv | + if $expenv.expr != null then + $expenv.expr | PRINT($expenv.env) + else + null + end | addEnv($expenv.env); + +def repl_(env): + ("user> " | _print) | + (read_line | rep(env)); + +# we don't have no indirect functions, so we'll have to interpret the old way +def replEnv: + { + parent: null, + environment: ({ + "+": { + kind: "fn", # native function + inputs: 2, + function: "number_add" + }, + "-": { + kind: "fn", # native function + inputs: 2, + function: "number_sub" + }, + "*": { + kind: "fn", # native function + inputs: 2, + function: "number_mul" + }, + "/": { + kind: "fn", # native function + inputs: 2, + function: "number_div" + }, + "eval": { + kind: "fn", + inputs: 1, + function: "eval" + } + } + core_identify), + dirty_atoms: [], + fallback: null + }; + +def repl(env): + def xrepl: + (.env as $env | try repl_($env) catch addEnv($env)) as $expenv | + { + value: $expenv.expr, + stop: false, + env: ($expenv.env // .env) + } | ., xrepl; + {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + +def eval_ign(expr): + . as $env | expr | rep($env) | .env; + +def eval_val(expr): + . as $env | expr | rep($env) | .expr; + +def getEnv: + replEnv + | wrapEnv + | eval_ign("(def! *host-language* \"jq\")") + | eval_ign("(def! not (fn* (a) (if a false true)))") + | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") + | eval_ign("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + ; + +def main: + if $ARGS.positional|length > 0 then + try ( + getEnv as $env | + env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | + eval_val("(load-file \($ARGS.positional[0] | tojson))") + ) catch ( + _print + ) + else + repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) + end; + +main \ No newline at end of file diff --git a/jq/utils.jq b/jq/utils.jq index 0186264f..552f63e2 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -1,3 +1,12 @@ +def _debug(ex): + . as $top + | ex + | debug + | $top; + +def _print: + debug; + def nwise(n): def _nwise: if length <= n then @@ -72,7 +81,7 @@ def find_free_references(keys): else if "vector" == $dot.kind then ($dot.value | map(_refs) | reduce .[] as $x ([]; . + $x)) else if "hashmap" == $dot.kind then - ([$dot.value | from_entries | map({kind: .value.kkind, value: .key}, .value.value)] | map(_refs) | reduce .[] as $x ([]; . + $x)) + ([$dot.value | to_entries[] | ({kind: .value.kkind, value: .key}, .value.value) ] | map(_refs) | reduce .[] as $x ([]; . + $x)) else [] end end end end @@ -115,20 +124,20 @@ def issue_extern(cmd; options): def issue_extern(cmd): issue_extern(cmd; {}); -def _debug(ex): - . as $top - | ex - | debug - | $top; - -def _print: - debug; - def _readline: [] | issue_extern("readline"; {nowait: false}) ; +def __readline(prompt): + . as $top + | prompt + | _print + | _readline; + +def __readline: + __readline(.); + def _write_to_file(name): . as $value | [(name|tojson), (.|tojson), (false|tojson)] From f370b4c245e6f4b3a9f8a036be4212029c6d9fc7 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 04:42:39 +0330 Subject: [PATCH 20/40] fix printing strings --- jq/printer.jq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jq/printer.jq b/jq/printer.jq index 88f6cad5..c4ce3d95 100644 --- a/jq/printer.jq +++ b/jq/printer.jq @@ -8,7 +8,7 @@ def _reconstruct_hash: def pr_str(opt): (select(.kind == "symbol") | .value) // - (select(.kind == "string") | .value | if opt.readable then tojson else . end) // + (select(.kind == "string") | .value | if opt.readable then tojson|tojson else tojson end) // (select(.kind == "keyword") | ":\(.value)") // (select(.kind == "number") | .value | tostring) // (select(.kind == "list") | .value | map(pr_str(opt)) | join(" ") | "(\(.))") // From 832abfbdfb4ce163aec07d5124e39d75565e1262 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 04:43:54 +0330 Subject: [PATCH 21/40] fix macroexpand --- jq/step8_macros.jq | 19 +++++++++++++++---- jq/step9_try.jq | 19 +++++++++++++++---- jq/stepA_mal.jq | 22 +++++++++++++++++----- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/jq/step8_macros.jq b/jq/step8_macros.jq index de912d57..c124b032 100644 --- a/jq/step8_macros.jq +++ b/jq/step8_macros.jq @@ -95,14 +95,25 @@ def EVAL(env): interpret($expr[1:]; $_menv; _eval_here); def macroexpand(env): + . as $dot | + $dot | [ while(is_macro_call(env | unwrapCurrentEnv); - _interpret(env).expr) // . ] - | first + . as $dot + | ($dot.value[0] | EVAL(env).expr) as $fn + | $dot.value[1:] as $args + | $fn + | interpret($args; env; _eval_here).expr) // . ] + | last | if is_macro_call(env | unwrapCurrentEnv) then - _interpret(env).expr + . as $dot + | ($dot.value[0] | EVAL(env).expr) as $fn + | $dot.value[1:] as $args + | $fn + | interpret($args; env; _eval_here).expr else . - end; + end + ; def hmap_with_env: .env as $env | .list as $list | diff --git a/jq/step9_try.jq b/jq/step9_try.jq index e005b83d..f2f998a6 100644 --- a/jq/step9_try.jq +++ b/jq/step9_try.jq @@ -95,14 +95,25 @@ def EVAL(env): interpret($expr[1:]; $_menv; _eval_here); def macroexpand(env): + . as $dot | + $dot | [ while(is_macro_call(env | unwrapCurrentEnv); - _interpret(env).expr) // . ] - | first + . as $dot + | ($dot.value[0] | EVAL(env).expr) as $fn + | $dot.value[1:] as $args + | $fn + | interpret($args; env; _eval_here).expr) // . ] + | last | if is_macro_call(env | unwrapCurrentEnv) then - _interpret(env).expr + . as $dot + | ($dot.value[0] | EVAL(env).expr) as $fn + | $dot.value[1:] as $args + | $fn + | interpret($args; env; _eval_here).expr else . - end; + end + ; def hmap_with_env: .env as $env | .list as $list | diff --git a/jq/stepA_mal.jq b/jq/stepA_mal.jq index 11506677..a1aebec8 100644 --- a/jq/stepA_mal.jq +++ b/jq/stepA_mal.jq @@ -95,14 +95,25 @@ def EVAL(env): interpret($expr[1:]; $_menv; _eval_here); def macroexpand(env): + . as $dot | + $dot | [ while(is_macro_call(env | unwrapCurrentEnv); - _interpret(env).expr) // . ] - | first + . as $dot + | ($dot.value[0] | EVAL(env).expr) as $fn + | $dot.value[1:] as $args + | $fn + | interpret($args; env; _eval_here).expr) // . ] + | last | if is_macro_call(env | unwrapCurrentEnv) then - _interpret(env).expr + . as $dot + | ($dot.value[0] | EVAL(env).expr) as $fn + | $dot.value[1:] as $args + | $fn + | interpret($args; env; _eval_here).expr else . - end; + end + ; def hmap_with_env: .env as $env | .list as $list | @@ -278,7 +289,7 @@ def EVAL(env): ) // ( .value | select(.[0].value == "macroexpand") as $value | - $value[1] | macroexpand(env) | TCOWrap($_menv; $_orig_retenv; false) + $value[1] | macroexpand($_menv) | TCOWrap($_menv; $_orig_retenv; false) ) // ( . as $dot | _interpret($_menv) as $exprenv | @@ -291,6 +302,7 @@ def EVAL(env): ) // (eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false)) end + | (if $DEBUG then _debug("POSTEVAL: \($ast | pr_str($_menv)) = \(.ast | pr_str($_menv))") else . end) ) ] | last as $result | ($result.ret_env // $result.env) as $env From fed3ca508d68ba88315704b9d5b86cd733ed4b77 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 09:31:00 +0330 Subject: [PATCH 22/40] implement conj/seq now we're self-hosting --- jq/core.jq | 35 +++++++++++++----- jq/env.jq | 36 ++++++++---------- jq/interp.jq | 87 +++++++++++++++++--------------------------- jq/printer.jq | 17 +++++---- jq/step4_if_fn_do.jq | 1 - jq/step5_tco.jq | 1 - jq/step6_file.jq | 24 ++++++------ jq/step7_quote.jq | 25 ++++++------- jq/step8_macros.jq | 25 ++++++------- jq/step9_try.jq | 27 +++++++------- jq/stepA_mal.jq | 26 +++++++------ jq/utils.jq | 2 +- 12 files changed, 150 insertions(+), 156 deletions(-) diff --git a/jq/core.jq b/jq/core.jq index ab216afd..916558a6 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -306,6 +306,16 @@ def vec2list(obj): end end; +def make_sequence: + . as $dot + | if .value|length == 0 then null | wrap("nil") else + ( + select(.kind == "string") | .value | split("") | map(wrap("string")) + ) // ( + select(.kind == "list" or .kind == "vector") | .value + ) // jqmal_error("cannot make sequence from \(.kind)") | wrap("list") + end; + def core_interp(arguments; env): ( select(.function == "number_add") | @@ -324,16 +334,16 @@ def core_interp(arguments; env): env | tojson | wrap("string") ) // ( select(.function == "prn") | - arguments | map(pr_str({readable: true})) | join(" ") | _print | null | wrap("nil") + arguments | map(pr_str(env; {readable: true})) | join(" ") | _print | null | wrap("nil") ) // ( select(.function == "pr-str") | - arguments | map(pr_str({readable: true})) | join(" ") | wrap("string") + arguments | map(pr_str(env; {readable: true})) | join(" ") | wrap("string") ) // ( select(.function == "str") | - arguments | map(pr_str({readable: false})) | join("") | wrap("string") + arguments | map(pr_str(env; {readable: false})) | join("") | wrap("string") ) // ( select(.function == "println") | - arguments | map(pr_str({readable: false})) | join(" ") | _print | null | wrap("nil") + arguments | map(pr_str(env; {readable: false})) | join(" ") | _print | null | wrap("nil") ) // ( select(.function == "list") | arguments | wrap("list") @@ -357,12 +367,8 @@ def core_interp(arguments; env): select(.function == "slurp") | arguments | map(.value) | issue_extern("read") | wrap("string") ) // ( select(.function == "read-string") | arguments | first.value | read_str | read_form.value - ) // ( - select(.function == "atom") | arguments | first | wrap2("atom"; {names: [], identity: now, last_modified: now}) ) // ( select(.function == "atom?") | null | wrap(arguments | first.kind == "atom" | tostring) - ) // ( - select(.function == "deref") | arguments | first.value ) // ( select(.function == "cons") | ([arguments[0]] + arguments[1].value) | wrap("list") ) // ( @@ -452,7 +458,7 @@ def core_interp(arguments; env): ) // ( select(.function == "string?") | null | wrap((arguments[0].kind == "string") | tostring) ) // ( - select(.function == "fn?") | null | wrap((arguments[0].kind == "fn" or arguments[0].kind == "function") | tostring) + select(.function == "fn?") | null | wrap((arguments[0].kind == "fn" or (arguments[0].kind == "function" and (arguments[0].is_macro|not))) | tostring) ) // ( select(.function == "number?") | null | wrap((arguments[0].kind == "number") | tostring) ) // ( @@ -465,4 +471,15 @@ def core_interp(arguments; env): select(.function == "meta") | arguments[0].meta // {kind:"nil"} ) // ( select(.function == "with-meta") | arguments[0] | .meta |= arguments[1] + ) // ( + select(.function == "seq") | arguments[0] | make_sequence + ) // ( + select(.function == "conj") + | arguments[0] as $orig + | arguments[1:] as $stuff + | if $orig.kind == "list" then + [ $stuff|reverse[], $orig.value[] ] | wrap("list") + else + [ $orig.value[], $stuff[] ] | wrap("vector") + end ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file diff --git a/jq/env.jq b/jq/env.jq index 97ceea2b..8c51b190 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -4,7 +4,6 @@ def childEnv(binds; exprs): { parent: ., fallback: null, - dirty_atoms: .dirty_atoms, environment: [binds, exprs] | transpose | ( . as $dot | reduce .[] as $item ( { value: [], seen: false, name: null, idx: 0 }; @@ -47,16 +46,14 @@ def pureChildEnv: { parent: ., environment: {}, - fallback: null, - dirty_atoms: .dirty_atoms + fallback: null }; def rootEnv: { parent: null, fallback: null, - environment: {}, - dirty_atoms: [] + environment: {} }; def inform_function(name): @@ -78,8 +75,7 @@ def env_multiset(keys; value): environment: ( .environment + (reduce keys[] as $key(.environment; .[$key] |= value)) ), - fallback: .fallback, - dirty_atoms: .dirty_atoms + fallback: .fallback }; def env_multiset(env; keys; value): @@ -106,8 +102,7 @@ def env_set($key; $value): end) as $value | { parent: .parent, environment: (.environment + (.environment | .[$key] |= $value)), # merge together, as .environment[key] |= value does not work - fallback: .fallback, - dirty_atoms: .dirty_atoms + fallback: .fallback }; def env_dump_keys: @@ -199,16 +194,14 @@ def env_set(env; $key; $value): end) as $value | { parent: env.parent, environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)), # merge together, as env.environment[key] |= value does not work - fallback: env.fallback, - dirty_atoms: env.dirty_atoms + fallback: env.fallback }; def env_setfallback(env; fallback): { parent: env.parent, fallback: fallback, - environment: env.environment, - dirty_atoms: env.dirty_atoms + environment: env.environment }; def addEnv(env): @@ -224,17 +217,19 @@ def addToEnv(env; name; expr): }; -def wrapEnv: +def wrapEnv(atoms): { replEnv: ., currentEnv: ., + atoms: atoms, isReplEnv: true }; -def wrapEnv(replEnv): +def wrapEnv(replEnv; atoms): { replEnv: replEnv, currentEnv: ., + atoms: atoms, # id -> value isReplEnv: (replEnv == .) # should we allow separate copies? }; @@ -246,9 +241,9 @@ def unwrapCurrentEnv: def env_set6(env; key; value): if env.isReplEnv then - env_set(env.currentEnv; key; value) | wrapEnv + env_set(env.currentEnv; key; value) | wrapEnv(env.atoms) else - env_set(env.currentEnv; key; value) | wrapEnv(env.replEnv) + env_set(env.currentEnv; key; value) | wrapEnv(env.replEnv; env.atoms) end; def env_set_(env; key; value): @@ -262,9 +257,9 @@ def addToEnv6(envexp; name): envexp.expr as $value | envexp.env as $rawEnv | (if $rawEnv.isReplEnv then - env_set_($rawEnv.currentEnv; name; $value) | wrapEnv + env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.atoms) else - env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv) + env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv; $rawEnv.atoms) end) as $newEnv | { expr: $value, @@ -284,8 +279,7 @@ def _env_remove_references(refs): { environment: (.environment | to_entries | map(select(.key as $key | refs | contains([$key]) | not)) | from_entries), parent: (.parent | _env_remove_references(refs)), - fallback: (.fallback | _env_remove_references(refs)), - dirty_atoms: (.dirty_atoms | map(select(. as $dot | refs | contains([$dot]) | not))) + fallback: (.fallback | _env_remove_references(refs)) } else . end; diff --git a/jq/interp.jq b/jq/interp.jq index ddac8e5a..1610e9b9 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -11,7 +11,7 @@ def arg_check(args): . end else if .inputs != (args|length) then - jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length): \(args | wrap("vector") | pr_str))") + jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length))") else . end end; @@ -33,9 +33,9 @@ def extractEnv(env): def hasReplEnv(env): env | has("replEnv"); -def cWrapEnv(renv; cond): +def cWrapEnv(renv; atoms; cond): if cond then - wrapEnv(renv) + wrapEnv(renv; atoms) else . end; @@ -72,22 +72,8 @@ def extractCurrentReplEnv(env): env end; -def cWithReplEnv(renv; cond): - if cond then - extractEnv(.) | wrapEnv(renv) - else - . - end; - -def cUpdateAtoms(newEnv; cond): - . as $env - | (reduce (extractEnv(newEnv)|.dirty_atoms)[] as $atom ( - $env; - . as $e | reduce $atom.names[] as $name ( - $e; - . as $env | env_set_($env; $name; $atom)))) as $resEnv - | $resEnv | if cond then setpath(["currentEnv", "dirty_atoms"]; []) else . end - ; +def extractAtoms(env): + env.atoms // {}; def addFrees(newEnv; frees): . as $env @@ -106,13 +92,14 @@ def addFrees(newEnv; frees): def interpret(arguments; env; _eval): extractReplEnv(env) as $replEnv | + extractAtoms(env) as $envAtoms | hasReplEnv(env) as $hasReplEnv | - (if $DEBUG then _debug("INTERP: \(. | pr_str)") else . end) | + (if $DEBUG then _debug("INTERP: \(. | pr_str(env))") else . end) | (select(.kind == "fn") | arg_check(arguments) | (select(.function == "eval") | # special function - { expr: arguments[0], env: $replEnv|cWrapEnv($replEnv; $hasReplEnv) } + { expr: arguments[0], env: $replEnv|cWrapEnv($replEnv; $envAtoms; $hasReplEnv) } | _eval | .env as $xenv | extractReplEnv($xenv) as $xreplenv @@ -122,37 +109,27 @@ def interpret(arguments; env; _eval): ) // (select(.function == "reset!") | # env modifying function - arguments[0].names as $names | - arguments[1]|wrap2("atom"; { - names: $names, - identity: arguments[0].identity, - last_modified: now - }) as $value | - (reduce $names[] as $name ( - env; - . as $env | env_set_($env; $name; $value) - )) as $env | - $value.value | addEnv($env | setpath(["currentEnv", "dirty_atoms"]; ($env.currentEnv.dirty_atoms + [$value])|unique)) + arguments[0].identity as $id | + ($envAtoms | setpath([$id]; arguments[1])) as $envAtoms | + arguments[1] | addEnv(env | setpath(["atoms"]; $envAtoms)) ) // (select(.function == "swap!") | # env modifying function - arguments[0].names as $names | - arguments[0].value as $initValue | + arguments[0].identity as $id | + $envAtoms[$id] as $initValue | arguments[1] as $function | ([$initValue] + arguments[2:]) as $args | ($function | interpret($args; env; _eval)) as $newEnvValue | - $newEnvValue.expr | wrap2("atom"; { - names: $names, - identity: arguments[0].identity, - last_modified: now - }) as $newValue | - $newEnvValue.env as $newEnv | - (reduce $names[] as $name ( - $newEnv; - . as $env | env_set_($env; $name; $newValue) - )) as $newEnv | - $newValue.value | addEnv($newEnv | setpath(["currentEnv", "dirty_atoms"]; ($newEnv.currentEnv.dirty_atoms + [$newValue])|unique)) - ) // + ($envAtoms | setpath([$id]; $newEnvValue.expr)) as $envAtoms | + $newEnvValue.expr | addEnv(env | setpath(["atoms"]; $envAtoms)) + ) // (select(.function == "atom") | + (now|tostring) as $id | + {kind: "atom", identity: $id} as $value | + ($envAtoms | setpath([$id]; arguments[0])) as $envAtoms | + $value | addEnv(env | setpath(["atoms"]; $envAtoms)) + ) // (select(.function == "deref") | + $envAtoms[arguments[0].identity] | addEnv(env) + ) // (select(.function == "apply") | # (apply F ...T A) -> (F ...T ...A) arguments as $args @@ -166,16 +143,21 @@ def interpret(arguments; env; _eval): | first as $F | last.value as $L | (reduce $L[] as $elem ( - []; - . + [($F | interpret([$elem]; env; _eval).expr)] + {env: env, val: []}; + . as $dot | + ($F | interpret([$elem]; $dot.env; _eval)) as $val | + { + val: (.val + [$val.expr]), + env: (.env | setpath(["atoms"]; $val.env.atoms)) + } )) as $ex - | $ex | wrap("list") | addEnv(env) + | $ex.val | wrap("list") | addEnv($ex.env) ) // (core_interp(arguments; env) | addEnv(env)) ) // (select(.kind == "function") as $fn | # todo: arg_check - (.body | pr_str) as $src | + (.body | pr_str(env)) as $src | # _debug("INTERP " + $src) | # _debug("FREES " + ($fn.free_referencess | tostring)) | env_setfallback(extractEnv(.env | addFrees(env; $fn.free_referencess)); extractEnv(env)) | childEnv($fn.binds; arguments) as $fnEnv | @@ -196,7 +178,7 @@ def interpret(arguments; env; _eval): env_multiset($fnEnv; $fn.names; $fn) as $fnEnv | { env: env_multiset($fnEnv; $fn.names; $fn) - | cWrapEnv($replEnv; $hasReplEnv), + | cWrapEnv($replEnv; $envAtoms; $hasReplEnv), expr: $fn.body } | . as $dot @@ -209,8 +191,7 @@ def interpret(arguments; env; _eval): expr: .expr, env: extractEnv(env) | cUpdateReplEnv($xreplenv; $hasReplEnv) - | cWrapEnv($xreplenv; $hasReplEnv) - | cUpdateAtoms(extractEnv($envexp.env); $hasReplEnv) + | cWrapEnv($xreplenv; $envexp.env.atoms; $hasReplEnv) } # | . as $dot # | _debug("FNPOST " + (.expr | pr_str) + " " + (env_req($dot.expr.env; $fn.binds[0]) | pr_str)) diff --git a/jq/printer.jq b/jq/printer.jq index c4ce3d95..703eb650 100644 --- a/jq/printer.jq +++ b/jq/printer.jq @@ -6,21 +6,24 @@ def _reconstruct_hash: }, .value.value]); -def pr_str(opt): +def pr_str(env; opt): (select(.kind == "symbol") | .value) // - (select(.kind == "string") | .value | if opt.readable then tojson|tojson else tojson end) // + (select(.kind == "string") | .value | if opt.readable then tojson else . end) // (select(.kind == "keyword") | ":\(.value)") // (select(.kind == "number") | .value | tostring) // - (select(.kind == "list") | .value | map(pr_str(opt)) | join(" ") | "(\(.))") // - (select(.kind == "vector") | .value | map(pr_str(opt)) | join(" ") | "[\(.)]") // - (select(.kind == "hashmap") | .value | to_entries | _reconstruct_hash | add // [] | map(pr_str(opt)) | join(" ") | "{\(.)}") // + (select(.kind == "list") | .value | map(pr_str(env; opt)) | join(" ") | "(\(.))") // + (select(.kind == "vector") | .value | map(pr_str(env; opt)) | join(" ") | "[\(.)]") // + (select(.kind == "hashmap") | .value | to_entries | _reconstruct_hash | add // [] | map(pr_str(env; opt)) | join(" ") | "{\(.)}") // (select(.kind == "nil") | "nil") // (select(.kind == "true") | "true") // (select(.kind == "false") | "false") // (select(.kind == "fn") | "#") // (select(.kind == "function")| "#") // - (select(.kind == "atom") | "(atom \(.value | pr_str(opt)))") // + (select(.kind == "atom") | "(atom \(env.atoms[.identity] | pr_str(env; opt)))") // "#"; +def pr_str(env): + pr_str(env; {readable: true}); + def pr_str: - pr_str({readable: true}); \ No newline at end of file + pr_str(null); # for stepX where X<6 \ No newline at end of file diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index 7051cd10..00f8ff81 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -202,7 +202,6 @@ def replEnv: function: "number_div" }, } + core_identify), - dirty_atoms: [], fallback: null }; diff --git a/jq/step5_tco.jq b/jq/step5_tco.jq index 8851056f..b345d814 100644 --- a/jq/step5_tco.jq +++ b/jq/step5_tco.jq @@ -200,7 +200,6 @@ def replEnv: function: "number_div" }, } + core_identify), - dirty_atoms: [], fallback: null }; diff --git a/jq/step6_file.jq b/jq/step6_file.jq index b5d9c568..0e9f521a 100644 --- a/jq/step6_file.jq +++ b/jq/step6_file.jq @@ -20,7 +20,7 @@ def TCOWrap(env; retenv; continue): { ast: ., env: env, - ret_env: retenv, + ret_env: (if retenv != null then (retenv | setpath(["atoms"]; env.atoms)) else retenv end), finish: (continue | not), cont: true # set inside }; @@ -85,7 +85,7 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | - ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | + ($currentEnv | pureChildEnv | wrapEnv($replEnv; $_menv.atoms)) as $subenv | (reduce ($value[1].value | nwise(2)) as $xvalue ( $subenv; . as $env | $xvalue[1] | EVAL($env) as $expenv | @@ -125,11 +125,12 @@ def EVAL(env): ) // ( reduce .value[] as $elem ( - []; - . as $dot | $elem | EVAL($_menv) as $eval_env | - ($dot + [$eval_env.expr]) - ) | . as $expr | first | - interpret($expr[1:]; $_menv; _eval_here) as $exprenv | + {env: $_menv, val: []}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + ($dot.env | setpath(["atoms"]; $eval_env.env.atoms)) as $_menv | + {env: $_menv, val: ($dot.val + [$eval_env.expr])} + ) | . as $expr | $expr.val | first | + interpret($expr.val[1:]; $expr.env; _eval_here) as $exprenv | $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false) ) // TCOWrap($_menv; $_orig_retenv; false) @@ -170,13 +171,13 @@ def EVAL(env): | $result.ast | addEnv($env); -def PRINT: - pr_str; +def PRINT(env): + pr_str(env); def rep(env): READ | EVAL(env) as $expenv | if $expenv.expr != null then - $expenv.expr | PRINT + $expenv.expr | PRINT($expenv.env) else null end | addEnv($expenv.env); @@ -216,7 +217,6 @@ def replEnv: function: "eval" } } + core_identify), - dirty_atoms: [], fallback: null, }; @@ -238,7 +238,7 @@ def eval_val(expr): def getEnv: replEnv - | wrapEnv + | wrapEnv({}) | eval_ign("(def! not (fn* (a) (if a false true)))") | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))"); diff --git a/jq/step7_quote.jq b/jq/step7_quote.jq index 67d3d706..293b8541 100644 --- a/jq/step7_quote.jq +++ b/jq/step7_quote.jq @@ -20,7 +20,7 @@ def TCOWrap(env; retenv; continue): { ast: ., env: env, - ret_env: retenv, + ret_env: (if retenv != null then (retenv | setpath(["atoms"]; env.atoms)) else retenv end), finish: (continue | not), cont: true # set inside }; @@ -119,9 +119,8 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | - ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | (reduce ($value[1].value | nwise(2)) as $xvalue ( - $subenv; + $_menv; . as $env | $xvalue[1] | EVAL($env) as $expenv | env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env | $value[2] | TCOWrap($env; $_retenv; true) @@ -167,11 +166,12 @@ def EVAL(env): ) // ( reduce .value[] as $elem ( - []; - . as $dot | $elem | EVAL($_menv) as $eval_env | - ($dot + [$eval_env.expr]) - ) | . as $expr | first | - interpret($expr[1:]; $_menv; _eval_here) as $exprenv | + {env: $_menv, val: []}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + ($dot.env | setpath(["atoms"]; $eval_env.env.atoms)) as $_menv | + {env: $_menv, val: ($dot.val + [$eval_env.expr])} + ) | . as $expr | $expr.val | first | + interpret($expr.val[1:]; $expr.env; _eval_here) as $exprenv | $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false) ) // TCOWrap($_menv; $_orig_retenv; false) @@ -212,13 +212,13 @@ def EVAL(env): | $result.ast | addEnv($env); -def PRINT: - pr_str; +def PRINT(env): + pr_str(env); def rep(env): READ | EVAL(env) as $expenv | if $expenv.expr != null then - $expenv.expr | PRINT + $expenv.expr | PRINT($expenv.env) else null end | addEnv($expenv.env); @@ -258,7 +258,6 @@ def replEnv: function: "eval" } } + core_identify), - dirty_atoms: [], fallback: null }; @@ -280,7 +279,7 @@ def eval_val(expr): def getEnv: replEnv - | wrapEnv + | wrapEnv({}) | eval_ign("(def! not (fn* (a) (if a false true)))") | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))"); diff --git a/jq/step8_macros.jq b/jq/step8_macros.jq index c124b032..aead5481 100644 --- a/jq/step8_macros.jq +++ b/jq/step8_macros.jq @@ -20,7 +20,7 @@ def TCOWrap(env; retenv; continue): { ast: ., env: env, - ret_env: retenv, + ret_env: (if retenv != null then (retenv | setpath(["atoms"]; env.atoms)) else retenv end), finish: (continue | not), cont: true # set inside }; @@ -88,11 +88,12 @@ def EVAL(env): def _interpret($_menv): reduce .value[] as $elem ( - []; - . as $dot | $elem | EVAL($_menv) as $eval_env | - ($dot + [$eval_env.expr]) - ) | . as $expr | first | - interpret($expr[1:]; $_menv; _eval_here); + {env: $_menv, val: []}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + ($dot.env | setpath(["atoms"]; $eval_env.env.atoms)) as $_menv | + {env: $_menv, val: ($dot.val + [$eval_env.expr])} + ) | . as $expr | $expr.val | first | + interpret($expr.val[1:]; $expr.env; _eval_here); def macroexpand(env): . as $dot | @@ -210,9 +211,8 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | - ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | (reduce ($value[1].value | nwise(2)) as $xvalue ( - $subenv; + $_menv; . as $env | $xvalue[1] | EVAL($env) as $expenv | env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env | $value[2] | TCOWrap($env; $_retenv; true) @@ -278,13 +278,13 @@ def EVAL(env): | $result.ast | addEnv($env); -def PRINT: - pr_str; +def PRINT(env): + pr_str(env); def rep(env): READ | EVAL(env) as $expenv | if $expenv.expr != null then - $expenv.expr | PRINT + $expenv.expr | PRINT($expenv.env) else null end | addEnv($expenv.env); @@ -324,7 +324,6 @@ def replEnv: function: "eval" } } + core_identify), - dirty_atoms: [], fallback: null }; @@ -346,7 +345,7 @@ def eval_val(expr): def getEnv: replEnv - | wrapEnv + | wrapEnv({}) | eval_ign("(def! not (fn* (a) (if a false true)))") | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") | eval_ign("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") diff --git a/jq/step9_try.jq b/jq/step9_try.jq index f2f998a6..4e3dc24b 100644 --- a/jq/step9_try.jq +++ b/jq/step9_try.jq @@ -20,7 +20,7 @@ def TCOWrap(env; retenv; continue): { ast: ., env: env, - ret_env: retenv, + ret_env: (if retenv != null then (retenv | setpath(["atoms"]; env.atoms)) else retenv end), finish: (continue | not), cont: true # set inside }; @@ -88,11 +88,12 @@ def EVAL(env): def _interpret($_menv): reduce .value[] as $elem ( - []; - . as $dot | $elem | EVAL($_menv) as $eval_env | - ($dot + [$eval_env.expr]) - ) | . as $expr | first | - interpret($expr[1:]; $_menv; _eval_here); + {env: $_menv, val: []}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + ($dot.env | setpath(["atoms"]; $eval_env.env.atoms)) as $_menv | + {env: $_menv, val: ($dot.val + [$eval_env.expr])} + ) | . as $expr | $expr.val | first | + interpret($expr.val[1:]; $expr.env; _eval_here); def macroexpand(env): . as $dot | @@ -210,9 +211,8 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | - ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | (reduce ($value[1].value | nwise(2)) as $xvalue ( - $subenv; + $_menv; . as $env | $xvalue[1] | EVAL($env) as $expenv | env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env | $value[2] | TCOWrap($env; $_retenv; true) @@ -243,7 +243,7 @@ def EVAL(env): else $exc|wrap("string") end) as $exc | - $value[2].value[2] | EVAL($currentEnv | childEnv([$value[2].value[1].value]; [$exc]) | wrapEnv($replEnv)) as $ex | + $value[2].value[2] | EVAL($currentEnv | childEnv([$value[2].value[1].value]; [$exc]) | wrapEnv($replEnv; $_menv.atoms)) as $ex | $ex.expr | TCOWrap($ex.env; $_retenv; false) else error($exc) @@ -307,13 +307,13 @@ def EVAL(env): | $result.ast | addEnv($env); -def PRINT: - pr_str; +def PRINT(env): + pr_str(env); def rep(env): READ | EVAL(env) as $expenv | if $expenv.expr != null then - $expenv.expr | PRINT + $expenv.expr | PRINT($expenv.env) else null end | addEnv($expenv.env); @@ -353,7 +353,6 @@ def replEnv: function: "eval" } } + core_identify), - dirty_atoms: [], fallback: null }; @@ -375,7 +374,7 @@ def eval_val(expr): def getEnv: replEnv - | wrapEnv + | wrapEnv({}) | eval_ign("(def! not (fn* (a) (if a false true)))") | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") | eval_ign("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") diff --git a/jq/stepA_mal.jq b/jq/stepA_mal.jq index a1aebec8..77d1ee5c 100644 --- a/jq/stepA_mal.jq +++ b/jq/stepA_mal.jq @@ -20,7 +20,7 @@ def TCOWrap(env; retenv; continue): { ast: ., env: env, - ret_env: retenv, + ret_env: (if retenv != null then (retenv | setpath(["atoms"]; env.atoms)) else retenv end), finish: (continue | not), cont: true # set inside }; @@ -88,11 +88,12 @@ def EVAL(env): def _interpret($_menv): reduce .value[] as $elem ( - []; - . as $dot | $elem | EVAL($_menv) as $eval_env | - ($dot + [$eval_env.expr]) - ) | . as $expr | first | - interpret($expr[1:]; $_menv; _eval_here); + {env: $_menv, val: []}; + . as $dot | $elem | EVAL($dot.env) as $eval_env | + ($dot.env | setpath(["atoms"]; $eval_env.env.atoms)) as $_menv | + {env: $_menv, val: ($dot.val + [$eval_env.expr])} + ) | . as $expr | $expr.val | first | + interpret($expr.val[1:]; $expr.env; _eval_here); def macroexpand(env): . as $dot | @@ -177,6 +178,7 @@ def EVAL(env): | [ recurseflip(.cont; .env as $_menv | (if $DEBUG then _debug("EVAL: \($ast | pr_str($_menv))") else . end) + | (if $DEBUG then _debug("ATOMS: \($_menv.atoms)") else . end) | if .finish then .cont |= false else @@ -197,6 +199,10 @@ def EVAL(env): . | TCOWrap($_menv; $_orig_retenv; false) else ( + ( + .value | select(.[0].value == "atoms??") as $value | + $_menv.atoms | keys | map(wrap("string")) | wrap("list") | TCOWrap($_menv; $_orig_retenv; false) + ) // ( .value | select(.[0].value == "def!") as $value | ($value[2] | EVAL($_menv)) as $evval | @@ -211,9 +217,8 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | - ($currentEnv | pureChildEnv | wrapEnv($replEnv)) as $subenv | (reduce ($value[1].value | nwise(2)) as $xvalue ( - $subenv; + $_menv; . as $env | $xvalue[1] | EVAL($env) as $expenv | env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env | $value[2] | TCOWrap($env; $_retenv; true) @@ -244,7 +249,7 @@ def EVAL(env): else $exc|wrap("string") end) as $exc | - $value[2].value[2] | EVAL($currentEnv | childEnv([$value[2].value[1].value]; [$exc]) | wrapEnv($replEnv)) as $ex | + $value[2].value[2] | EVAL($currentEnv | childEnv([$value[2].value[1].value]; [$exc]) | wrapEnv($replEnv; $_menv.atoms)) as $ex | $ex.expr | TCOWrap($ex.env; $_retenv; false) else error($exc) @@ -355,7 +360,6 @@ def replEnv: function: "eval" } } + core_identify), - dirty_atoms: [], fallback: null }; @@ -377,7 +381,7 @@ def eval_val(expr): def getEnv: replEnv - | wrapEnv + | wrapEnv({}) | eval_ign("(def! *host-language* \"jq\")") | eval_ign("(def! not (fn* (a) (if a false true)))") | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))") diff --git a/jq/utils.jq b/jq/utils.jq index 552f63e2..dccbde6b 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -67,7 +67,7 @@ def find_free_references(keys): ) // ( select($head.value == "def!") | $dot.value[2] | _refs ) // ( - select($head.value == "let*") | $dot.value[2] | find_free_references(($dot.value[1].value | map(.value[0].value)) + keys) + select($head.value == "let*") | $dot.value[2] | find_free_references(($dot.value[1].value as $value | ([ range(0; $value|length; 2) ] | map(select(. % 2 == 0) | $value[.].value))) + keys) ) // ( select($head.value == "fn*") | $dot.value[2] | find_free_references(($dot.value[1].value | map(.value)) + keys) ) // ( From b12e9c7fdf1002581d2f4331cba2131246a9a7aa Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 09:38:30 +0330 Subject: [PATCH 23/40] add Dockerfile --- jq/Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 jq/Dockerfile diff --git a/jq/Dockerfile b/jq/Dockerfile new file mode 100644 index 00000000..d15420a9 --- /dev/null +++ b/jq/Dockerfile @@ -0,0 +1,13 @@ +FROM alpine +MAINTAINER Ali MohammadPur + +# General test requirements +RUN apk update +RUN apk add python make +RUN apk add curl + +RUN mkdir -p /mal +WORKDIR /mal + +# test requirements +RUN apk add jq bash pv timeout From 29328121a3d3f499867add30b785051b0bdb0706 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 09:41:13 +0330 Subject: [PATCH 24/40] busybox has its own timeout --- jq/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jq/Dockerfile b/jq/Dockerfile index d15420a9..66173032 100644 --- a/jq/Dockerfile +++ b/jq/Dockerfile @@ -10,4 +10,4 @@ RUN mkdir -p /mal WORKDIR /mal # test requirements -RUN apk add jq bash pv timeout +RUN apk add jq bash pv From 720fc8ff2f5bd37abbd5c78606ae02a34256e6ec Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 09:55:03 +0330 Subject: [PATCH 25/40] change python version, py2 segfaults --- jq/Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jq/Dockerfile b/jq/Dockerfile index 66173032..73b01770 100644 --- a/jq/Dockerfile +++ b/jq/Dockerfile @@ -3,11 +3,13 @@ MAINTAINER Ali MohammadPur # General test requirements RUN apk update -RUN apk add python make +RUN apk add python3 make RUN apk add curl +RUN rm -f `which python` && ln -s `which python3` /usr/bin/python + RUN mkdir -p /mal WORKDIR /mal # test requirements -RUN apk add jq bash pv +RUN apk add jq bash pv coreutils From cf9ef8a081b679d7ca5c204fbccdb595dd0fdd63 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 10:04:13 +0330 Subject: [PATCH 26/40] switch to ubuntu :shrug: --- jq/Dockerfile | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/jq/Dockerfile b/jq/Dockerfile index 73b01770..c65d5d15 100644 --- a/jq/Dockerfile +++ b/jq/Dockerfile @@ -1,15 +1,13 @@ -FROM alpine +FROM ubuntu:vivid MAINTAINER Ali MohammadPur # General test requirements -RUN apk update -RUN apk add python3 make -RUN apk add curl - -RUN rm -f `which python` && ln -s `which python3` /usr/bin/python +RUN apt-get -y update +RUN apt-get -y install python make +RUN apt-get -y curl RUN mkdir -p /mal WORKDIR /mal # test requirements -RUN apk add jq bash pv coreutils +RUN apt-get -y install jq pv From a44efb51e7edc17e678f6c254a7bfae254c6cded Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 10:05:34 +0330 Subject: [PATCH 27/40] xenial is the latest LTS apparently --- jq/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jq/Dockerfile b/jq/Dockerfile index c65d5d15..19766744 100644 --- a/jq/Dockerfile +++ b/jq/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Ali MohammadPur # General test requirements From ce08cdc57bbe2e10d2ca570af8c1f51c92579b3d Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 10:06:46 +0330 Subject: [PATCH 28/40] yes --- jq/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jq/Dockerfile b/jq/Dockerfile index 19766744..c03eed4a 100644 --- a/jq/Dockerfile +++ b/jq/Dockerfile @@ -4,7 +4,7 @@ MAINTAINER Ali MohammadPur # General test requirements RUN apt-get -y update RUN apt-get -y install python make -RUN apt-get -y curl +RUN apt-get -y install curl RUN mkdir -p /mal WORKDIR /mal From 4737022694f1f2d146c7e0674fbe1e65bd3e98e0 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 10:08:47 +0330 Subject: [PATCH 29/40] coreutils --- jq/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jq/Dockerfile b/jq/Dockerfile index c03eed4a..31491a7e 100644 --- a/jq/Dockerfile +++ b/jq/Dockerfile @@ -10,4 +10,4 @@ RUN mkdir -p /mal WORKDIR /mal # test requirements -RUN apt-get -y install jq pv +RUN apt-get -y install jq pv coreutils From acfccccc3027c61bdc23b696e56fda81e050703e Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 10:44:35 +0330 Subject: [PATCH 30/40] switch to tail -f circular pipes --- jq/Dockerfile | 11 ++++++----- jq/run | 13 +++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/jq/Dockerfile b/jq/Dockerfile index 31491a7e..d37c7d78 100644 --- a/jq/Dockerfile +++ b/jq/Dockerfile @@ -1,13 +1,14 @@ -FROM ubuntu:xenial +FROM archlinux/base MAINTAINER Ali MohammadPur # General test requirements -RUN apt-get -y update -RUN apt-get -y install python make -RUN apt-get -y install curl +RUN pacman -Sy +RUN pacman -S --noconfirm python make +RUN pacman -S --noconfirm curl RUN mkdir -p /mal WORKDIR /mal # test requirements -RUN apt-get -y install jq pv coreutils +RUN pacman -S --noconfirm jq pv coreutils + diff --git a/jq/run b/jq/run index 04e53774..ad500d1a 100755 --- a/jq/run +++ b/jq/run @@ -5,16 +5,13 @@ XDEBUG=${DEBUG:-false} SELFDEBUG=${RUNDEBUG:-false} runjq() { pipe_name=$(mktemp) - rm -f $pipe_name $ipipe_name - mkfifo $pipe_name || true + rm -f $pipe_name + echo -n "" > $pipe_name xstdout=$(readlink /proc/self/fd/1) stdin=$(readlink /proc/self/fd/0) trap "rm -f $pipe_name" EXIT SIGINT SIGHUP - ( - while [[ -e $pipe_name ]]; do - timeout 1 cat $pipe_name - done& - ) | jq --argjson DEBUG $XDEBUG -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" --args "${@}" |&\ + tail -f $pipe_name 2>/dev/null |\ + jq --argjson DEBUG $XDEBUG -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" --args "${@}" |&\ tee \ >(jq -Rr 'try fromjson[1]|if type == "string" then . else empty end') \ >(jq -Rr "if $SELFDEBUG then . else empty end") \ @@ -63,4 +60,4 @@ runjq() { } my_pid=$$ trap 'kill -INT $my_pid' EXIT SIGINT -runjq "${@}" \ No newline at end of file +runjq "${@}" From 83b974c5f52ecf1dd71938b30df9df66d8be06c5 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 17:43:51 +0330 Subject: [PATCH 31/40] switch run to a python script that gives io to the program --- jq/core.jq | 4 +- jq/rts.py | 112 +++++++++++++++++++++++++++++++++++++++++ jq/run | 64 +---------------------- jq/step0_repl.jq | 2 +- jq/step1_read_print.jq | 2 +- jq/step2_eval.jq | 2 +- jq/step3_env.jq | 2 +- jq/step4_if_fn_do.jq | 2 +- jq/step5_tco.jq | 2 +- jq/step6_file.jq | 7 +-- jq/step7_quote.jq | 7 +-- jq/step8_macros.jq | 7 +-- jq/step9_try.jq | 7 +-- jq/stepA_mal.jq | 7 +-- jq/utils.jq | 15 ++++-- 15 files changed, 153 insertions(+), 89 deletions(-) create mode 100644 jq/rts.py diff --git a/jq/core.jq b/jq/core.jq index 916558a6..b4b0db91 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -334,7 +334,7 @@ def core_interp(arguments; env): env | tojson | wrap("string") ) // ( select(.function == "prn") | - arguments | map(pr_str(env; {readable: true})) | join(" ") | _print | null | wrap("nil") + arguments | map(pr_str(env; {readable: true})) | join(" ") | _display | null | wrap("nil") ) // ( select(.function == "pr-str") | arguments | map(pr_str(env; {readable: true})) | join(" ") | wrap("string") @@ -343,7 +343,7 @@ def core_interp(arguments; env): arguments | map(pr_str(env; {readable: false})) | join("") | wrap("string") ) // ( select(.function == "println") | - arguments | map(pr_str(env; {readable: false})) | join(" ") | _print | null | wrap("nil") + arguments | map(pr_str(env; {readable: false})) | join(" ") | _display | null | wrap("nil") ) // ( select(.function == "list") | arguments | wrap("list") diff --git a/jq/rts.py b/jq/rts.py new file mode 100644 index 00000000..853c3fe7 --- /dev/null +++ b/jq/rts.py @@ -0,0 +1,112 @@ +import os +from os import fork, execv, pipe, close, dup2, kill, read, write +from select import select +import json +from os.path import dirname, realpath +from os import environ +import signal +from sys import argv +import fcntl + +DEBUG = False +HALT = False + +# Bestow IO upon jq + +def _read(fname, out=None): + with open(fname, "r") as f: + data = json.dumps(f.read()) + "\n" + # print("data =", data) + write(out, bytes(data, 'utf-8')) + +def _readline(prompt="", out=None): + data = json.dumps(input(prompt)) + "\n" + # print("data =", data) + write(out, bytes(data, 'utf-8')) + +def _fwrite(fname, data, out=None): + return + +def _halt(out=None): + global HALT + HALT = True + +def stub(*args, out=None): + raise Exception("command not understood") + +rts = { + "read": _read, + "readline": _readline, + "fwrite": _fwrite, + "halt": _halt, +} + +def process(cmd, fout): + if type(cmd) == str: + print(cmd, end="") + elif type(cmd) == dict: + cmd = cmd['command'] + command = cmd['cmd'] + args = cmd['args'] + fn = rts.get(command, stub) + fn(*args, out=fout) + +def get_one(fd): + s = b"" + while True: + x = read(fd, 1) + if x == b'\n': + break + if x == b'': + break + s += x + if s == "": + return None + return s.decode('utf-8') + + +def main(args): + args = [ + "jq", "--argjson", "DEBUG", json.dumps(DEBUG), "-nrRM", + "-f", + dirname(realpath(__file__)) + "/" + environ.get("STEP", "stepA_mal") + ".jq", + "--args", + *args + ] + # print(args) + sin_pipe = pipe() + sout_pipe = pipe() + + pid = fork() + if pid == 0: + # jq + close(sin_pipe[1]) + close(sout_pipe[0]) + + dup2(sin_pipe[0], 0) + dup2(sout_pipe[1], 2) # bind to stderr, as we write there + dup2(sout_pipe[1], 1) + + execv("/usr/bin/jq", args) + else: + close(sin_pipe[0]) + close(sout_pipe[1]) + + msout = sin_pipe[1] + msin = sout_pipe[0] + + while True: + try: + if HALT: + break + cmd = get_one(msin) + # print(cmd) + if cmd: + process(json.loads(cmd)[1], msout) + except KeyboardInterrupt: + exit() + except Exception as e: + print("RTS Error:", e) + + +main(argv[1:]) \ No newline at end of file diff --git a/jq/run b/jq/run index ad500d1a..dc7744e8 100755 --- a/jq/run +++ b/jq/run @@ -1,63 +1,3 @@ -#!/bin/bash +#!/bin/env bash -# let's do some sorcery to bestow IO upon jq -XDEBUG=${DEBUG:-false} -SELFDEBUG=${RUNDEBUG:-false} -runjq() { - pipe_name=$(mktemp) - rm -f $pipe_name - echo -n "" > $pipe_name - xstdout=$(readlink /proc/self/fd/1) - stdin=$(readlink /proc/self/fd/0) - trap "rm -f $pipe_name" EXIT SIGINT SIGHUP - tail -f $pipe_name 2>/dev/null |\ - jq --argjson DEBUG $XDEBUG -nrRM -f "$(dirname "$0")/${STEP:-stepA_mal}.jq" --args "${@}" |&\ - tee \ - >(jq -Rr 'try fromjson[1]|if type == "string" then . else empty end') \ - >(jq -Rr "if $SELFDEBUG then . else empty end") \ - >(while read -r line; do - command=$(echo $line | jq -c 'try if .[1] | has("command") then .[1].command else empty end' 2>/dev/null) - if [[ $command ]]; then - # echo ">>> " $command - cmd=$(echo "$command" | jq -rMc 'try .cmd catch "ignore"') - case "$cmd" in - readline) - data=$(jq -nR input < $stdin) - size=${#data} - # echo "read $size bytes '$data'" - echo "$data" | pv -q -B $size > $pipe_name - ;; - read) - filename=$(echo "$command" | jq -Mrc '.args[0]') - tmp=$(mktemp) - # echo "Read $filename into $tmp" - jq -rRnc --rawfile content "$filename" '$content|tojson' > $tmp - # echo "dump $tmp to pipe" - size=$(du -k $tmp) - cat $tmp | pv -q -B $size > $pipe_name #>/dev/null 2>&1 - rm $tmp - ;; - fwrite) - tmp=$(mktemp) - echo "$command" > $tmp - filename=$(cat $tmp | jq -Mrc ".args[0]|fromjson") - content=$(cat $tmp | jq -Mrc ".args[1]|fromjson") - app=$(cat $tmp | jq -Mrc ".args[2]|fromjson") - echo "'$app': Writing stuff to $filename" - if [[ $res == false ]]; then - echo "$content" > "$filename" - else - echo "$content" >> "$filename" - fi - ;; - *) - echo $cmd - ;; - esac - fi - done) > /dev/null - rm -f $pipe_name -} -my_pid=$$ -trap 'kill -INT $my_pid' EXIT SIGINT -runjq "${@}" +exec python rts.py "${@}" diff --git a/jq/step0_repl.jq b/jq/step0_repl.jq index 39f88d6c..46c5a5ea 100644 --- a/jq/step0_repl.jq +++ b/jq/step0_repl.jq @@ -15,7 +15,7 @@ def PRINT: .; def rep: - READ | EVAL | PRINT | _print; + READ | EVAL | PRINT | _display; def repl_: ("user> " | _print) | diff --git a/jq/step1_read_print.jq b/jq/step1_read_print.jq index d943165e..d0069c27 100644 --- a/jq/step1_read_print.jq +++ b/jq/step1_read_print.jq @@ -37,6 +37,6 @@ def repl: {value: "Error: \(.)", continue: true} else {value: ., continue: false} - end) | if .value then .value|_print else empty end; + end) | if .value then .value|_display else empty end; repl diff --git a/jq/step2_eval.jq b/jq/step2_eval.jq index 0eec9d19..23965e30 100644 --- a/jq/step2_eval.jq +++ b/jq/step2_eval.jq @@ -91,6 +91,6 @@ def repl(env): {value: "Error: \(.)", continue: true} else {value: ., continue: false} - end) | if .value then .value|_print else empty end; + end) | if .value then .value|_display else empty end; repl(replEnv) \ No newline at end of file diff --git a/jq/step3_env.jq b/jq/step3_env.jq index 71e87207..17ec3a5f 100644 --- a/jq/step3_env.jq +++ b/jq/step3_env.jq @@ -144,6 +144,6 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end; repl(replEnv) \ No newline at end of file diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index 00f8ff81..5ecbacd2 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -213,7 +213,7 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end; repl( "(def! not (fn* (a) (if a false true)))" | rep(replEnv) | .env diff --git a/jq/step5_tco.jq b/jq/step5_tco.jq index b345d814..a857849c 100644 --- a/jq/step5_tco.jq +++ b/jq/step5_tco.jq @@ -211,7 +211,7 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end; repl( "(def! not (fn* (a) (if a false true)))" | rep(replEnv) | .env diff --git a/jq/step6_file.jq b/jq/step6_file.jq index 0e9f521a..3381f3c8 100644 --- a/jq/step6_file.jq +++ b/jq/step6_file.jq @@ -228,7 +228,7 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end; def eval_ign(expr): . as $env | expr | rep($env) | .env; @@ -246,9 +246,10 @@ def main: if $ARGS.positional|length > 0 then getEnv as $env | env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | - eval_val("(load-file \($ARGS.positional[0] | tojson))") + eval_val("(load-file \($ARGS.positional[0] | tojson))") | + "" else repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -main \ No newline at end of file +[ main ] | _halt \ No newline at end of file diff --git a/jq/step7_quote.jq b/jq/step7_quote.jq index 293b8541..ba0554cb 100644 --- a/jq/step7_quote.jq +++ b/jq/step7_quote.jq @@ -269,7 +269,7 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end; def eval_ign(expr): . as $env | expr | rep($env) | .env; @@ -287,11 +287,12 @@ def main: if $ARGS.positional|length > 0 then getEnv as $env | env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | - eval_val("(load-file \($ARGS.positional[0] | tojson))") + eval_val("(load-file \($ARGS.positional[0] | tojson))") | + "" else repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -main +[ main ] | _halt # ( ( (fn* (a) (fn* (b) (+ a b))) 5) 7) \ No newline at end of file diff --git a/jq/step8_macros.jq b/jq/step8_macros.jq index aead5481..08747e55 100644 --- a/jq/step8_macros.jq +++ b/jq/step8_macros.jq @@ -335,7 +335,7 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end; def eval_ign(expr): . as $env | expr | rep($env) | .env; @@ -355,9 +355,10 @@ def main: if $ARGS.positional|length > 0 then getEnv as $env | env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | - eval_val("(load-file \($ARGS.positional[0] | tojson))") + eval_val("(load-file \($ARGS.positional[0] | tojson))") | + "" else repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -main \ No newline at end of file +[ main ] | _halt \ No newline at end of file diff --git a/jq/step9_try.jq b/jq/step9_try.jq index 4e3dc24b..0d8a35ed 100644 --- a/jq/step9_try.jq +++ b/jq/step9_try.jq @@ -364,7 +364,7 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end; def eval_ign(expr): . as $env | expr | rep($env) | .env; @@ -384,9 +384,10 @@ def main: if $ARGS.positional|length > 0 then getEnv as $env | env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | - eval_val("(load-file \($ARGS.positional[0] | tojson))") + eval_val("(load-file \($ARGS.positional[0] | tojson))") | + "" else repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -main \ No newline at end of file +[ main ] | _halt \ No newline at end of file diff --git a/jq/stepA_mal.jq b/jq/stepA_mal.jq index 77d1ee5c..85d9fee8 100644 --- a/jq/stepA_mal.jq +++ b/jq/stepA_mal.jq @@ -371,7 +371,7 @@ def repl(env): stop: false, env: ($expenv.env // .env) } | ., xrepl; - {stop: false, env: env} | xrepl | if .value then (.value | _print) else empty end; + {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end; def eval_ign(expr): . as $env | expr | rep($env) | .env; @@ -393,7 +393,8 @@ def main: try ( getEnv as $env | env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) | - eval_val("(load-file \($ARGS.positional[0] | tojson))") + eval_val("(load-file \($ARGS.positional[0] | tojson))") | + "" ) catch ( _print ) @@ -401,4 +402,4 @@ def main: repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -main \ No newline at end of file +[ main ] | _halt \ No newline at end of file diff --git a/jq/utils.jq b/jq/utils.jq index dccbde6b..a1585bed 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -5,7 +5,7 @@ def _debug(ex): | $top; def _print: - debug; + tostring; def nwise(n): def _nwise: @@ -110,7 +110,7 @@ def tomal: def _extern(options): {command: .} | debug - | if options.nowait | not then + | if (options.nowait | not) then input | fromjson else null @@ -125,19 +125,21 @@ def issue_extern(cmd): issue_extern(cmd; {}); def _readline: - [] + [.] | issue_extern("readline"; {nowait: false}) ; def __readline(prompt): . as $top | prompt - | _print | _readline; def __readline: __readline(.); +def _display: + tostring | .+"\n" | debug; + def _write_to_file(name): . as $value | [(name|tojson), (.|tojson), (false|tojson)] @@ -150,5 +152,10 @@ def _append_to_file(name): | issue_extern("fwrite"; {nowait: true}) | $value; +def _halt: + [] + | issue_extern("halt"; {nowait: true}) + | halt; + def trap: _write_to_file("trap_reason.json") | jqmal_error("trap"); \ No newline at end of file From f93123b4703fc6d089f239d79afd848192df6bd7 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 17:52:44 +0330 Subject: [PATCH 32/40] add jq to travis matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4b64719f..df389e3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,7 @@ matrix: - {env: IMPL=hy, services: [docker]} - {env: IMPL=io NO_SELF_HOST_PERF=1, services: [docker]} # perf OOM - {env: IMPL=java, services: [docker]} + - {env: IMPL=jq, services: [docker]} - {env: IMPL=js, services: [docker]} - {env: IMPL=julia, services: [docker]} - {env: IMPL=kotlin, services: [docker]} From 2ce3c78e545f6111201bdfc74983a9a59c2e0af6 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 10 Jan 2020 17:57:51 +0330 Subject: [PATCH 33/40] bring the let* child env to newwer steps --- jq/step7_quote.jq | 1 + jq/step8_macros.jq | 1 + jq/step9_try.jq | 1 + jq/stepA_mal.jq | 1 + 4 files changed, 4 insertions(+) diff --git a/jq/step7_quote.jq b/jq/step7_quote.jq index ba0554cb..5aab6b97 100644 --- a/jq/step7_quote.jq +++ b/jq/step7_quote.jq @@ -119,6 +119,7 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv; $_menv.atoms)) as $_menv | (reduce ($value[1].value | nwise(2)) as $xvalue ( $_menv; . as $env | $xvalue[1] | EVAL($env) as $expenv | diff --git a/jq/step8_macros.jq b/jq/step8_macros.jq index 08747e55..679ada1c 100644 --- a/jq/step8_macros.jq +++ b/jq/step8_macros.jq @@ -211,6 +211,7 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv; $_menv.atoms)) as $_menv | (reduce ($value[1].value | nwise(2)) as $xvalue ( $_menv; . as $env | $xvalue[1] | EVAL($env) as $expenv | diff --git a/jq/step9_try.jq b/jq/step9_try.jq index 0d8a35ed..b976ebc5 100644 --- a/jq/step9_try.jq +++ b/jq/step9_try.jq @@ -211,6 +211,7 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv; $_menv.atoms)) as $_menv | (reduce ($value[1].value | nwise(2)) as $xvalue ( $_menv; . as $env | $xvalue[1] | EVAL($env) as $expenv | diff --git a/jq/stepA_mal.jq b/jq/stepA_mal.jq index 85d9fee8..06458b65 100644 --- a/jq/stepA_mal.jq +++ b/jq/stepA_mal.jq @@ -217,6 +217,7 @@ def EVAL(env): ) // ( .value | select(.[0].value == "let*") as $value | + ($currentEnv | pureChildEnv | wrapEnv($replEnv; $_menv.atoms)) as $_menv | (reduce ($value[1].value | nwise(2)) as $xvalue ( $_menv; . as $env | $xvalue[1] | EVAL($env) as $expenv | From a1f8561c582cef961dd7088c2fe5204046dd9d85 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Fri, 10 Jan 2020 18:07:10 +0330 Subject: [PATCH 34/40] add jq info to readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 195d822f..8771db5f 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ | [Hy](#hy) | [Joel Martin](https://github.com/kanaka) | | [Io](#io) | [Dov Murik](https://github.com/dubek) | | [Java](#java-17) | [Joel Martin](https://github.com/kanaka) | +| [Jq](#jq) | [Ali MohammadPur](https://github.com/alimpfard) | | [JavaScript](#javascriptnode) ([Demo](http://kanaka.github.io/mal)) | [Joel Martin](https://github.com/kanaka) | | [Julia](#julia) | [Joel Martin](https://github.com/kanaka) | | [Kotlin](#kotlin) | [Javier Fernandez-Ivern](https://github.com/ivern) | @@ -587,6 +588,17 @@ mvn -quiet exec:java -Dexec.mainClass=mal.stepX_YYY mvn -quiet exec:java -Dexec.mainClass=mal.stepX_YYY -Dexec.args="CMDLINE_ARGS" ``` +### Jq + +Tested against version 1.6, with a lot of cheating in the IO department + +``` +cd jq +STEP=stepA_YYY ./run + # with Debug +DEBUG=true STEP=stepA_YYY ./run +``` + ### JavaScript/Node ``` From 989b1e6e00c6a829f7ef1fc0b4f0a54e16ecf450 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 12 Jan 2020 15:03:08 +0330 Subject: [PATCH 35/40] change Dockerfile to build on top of ubuntu:bionic --- jq/Dockerfile | 34 ++++++++++++++++++++++++++-------- jq/run | 2 +- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/jq/Dockerfile b/jq/Dockerfile index d37c7d78..80d2c08d 100644 --- a/jq/Dockerfile +++ b/jq/Dockerfile @@ -1,14 +1,32 @@ -FROM archlinux/base -MAINTAINER Ali MohammadPur +FROM ubuntu:bionic +MAINTAINER Joel Martin -# General test requirements -RUN pacman -Sy -RUN pacman -S --noconfirm python make -RUN pacman -S --noconfirm curl +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev libpcre3-dev RUN mkdir -p /mal WORKDIR /mal -# test requirements -RUN pacman -S --noconfirm jq pv coreutils +######################################################### +# Specific implementation requirements +######################################################### +RUN apt-get -y install python3.8 wget +RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.8 10 + +# grab jq 1.6 from github releases +RUN wget https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 + +RUN chmod +x jq-linux64 +# a bit ugly, but it'll do? +RUN mv jq-linux64 /usr/bin/jq diff --git a/jq/run b/jq/run index dc7744e8..02e476e4 100755 --- a/jq/run +++ b/jq/run @@ -1,3 +1,3 @@ -#!/bin/env bash +#!/bin/sh exec python rts.py "${@}" From 597522fa1d213b2688d936bc01cebdefb804dcfa Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 12 Jan 2020 15:09:52 +0330 Subject: [PATCH 36/40] clean up comments --- jq/core.jq | 2 +- jq/step4_if_fn_do.jq | 24 ++---------------------- jq/step5_tco.jq | 6 ++---- jq/step6_file.jq | 6 ++---- jq/step7_quote.jq | 6 +----- jq/step8_macros.jq | 6 ++---- jq/step9_try.jq | 6 ++---- jq/stepA_mal.jq | 6 ++---- jq/utils.jq | 5 ++--- 9 files changed, 16 insertions(+), 51 deletions(-) diff --git a/jq/core.jq b/jq/core.jq index b4b0db91..2ba743a9 100644 --- a/jq/core.jq +++ b/jq/core.jq @@ -482,4 +482,4 @@ def core_interp(arguments; env): else [ $orig.value[], $stuff[] ] | wrap("vector") end - ) // jqmal_error("Unknown native function \(.function)"); \ No newline at end of file + ) // jqmal_error("Unknown native function \(.function)"); diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index 5ecbacd2..e5a4297f 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -13,26 +13,6 @@ def read_line: def READ: read_str | read_form | .value; -# def eval_ast(env): -# (select(.kind == "symbol") | .value | env_get(env) | addEnv(env)) // -# (select(.kind == "list") | reduce .value[] as $elem ( -# {value: [], env: env}; -# . as $dot | $elem | EVAL($dot.env) as $eval_env | -# { -# value: ($dot.value + [$eval_env.expr]), -# env: $eval_env.env -# } -# ) | { expr: .value, env: .env }) // (addEnv(env)); - -# def patch_with_env(env): -# . as $dot | (reduce .[] as $fnv ( -# []; -# . + [$fnv | setpath([1, "free_referencess"]; ($fnv[1].free_referencess + $dot) | unique)] -# )) as $functions | reduce $functions[] as $function ( -# env; -# env_set(.; $function[0]; $function[1]) -# ) | { functions: $functions, env: . }; - def recurseflip(x; y): recurse(y; x); @@ -116,7 +96,7 @@ def EVAL(env): binds: $binds, env: env, body: $value[2], - names: [], # we can't do that circular reference this + names: [], # we can't do that circular reference thing free_referencess: $value[2] | find_free_references(env | env_dump_keys + $binds) # for dynamically scoped variables } | addEnv(env) ) // @@ -217,4 +197,4 @@ def repl(env): repl( "(def! not (fn* (a) (if a false true)))" | rep(replEnv) | .env -) \ No newline at end of file +) diff --git a/jq/step5_tco.jq b/jq/step5_tco.jq index a857849c..0def038d 100644 --- a/jq/step5_tco.jq +++ b/jq/step5_tco.jq @@ -100,15 +100,13 @@ def EVAL(env): ) // ( .value | select(.[0].value == "fn*") as $value | - # we can't do what the guide says, so we'll skip over this - # and ues the later implementation # (fn* args body) $value[1].value | map(.value) as $binds | { kind: "function", binds: $binds, env: $_menv, body: $value[2], - names: [], # we can't do that circular reference this + names: [], # we can't do that circular reference thing free_referencess: $value[2] | find_free_references($_menv | env_dump_keys + $binds) # for dynamically scoped variables } | TCOWrap($_menv; $_orig_retenv; false) ) // @@ -215,4 +213,4 @@ def repl(env): repl( "(def! not (fn* (a) (if a false true)))" | rep(replEnv) | .env -) \ No newline at end of file +) diff --git a/jq/step6_file.jq b/jq/step6_file.jq index 3381f3c8..f58e5931 100644 --- a/jq/step6_file.jq +++ b/jq/step6_file.jq @@ -110,8 +110,6 @@ def EVAL(env): ) // ( .value | select(.[0].value == "fn*") as $value | - # we can't do what the guide says, so we'll skip over this - # and ues the later implementation # (fn* args body) $value[1].value | map(.value) as $binds | ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { @@ -119,7 +117,7 @@ def EVAL(env): binds: $binds, env: (env | env_remove_references($free_referencess)), body: $value[2], - names: [], # we can't do that circular reference this + names: [], # we can't do that circular reference thing free_referencess: $free_referencess # for dynamically scoped variables } | TCOWrap($_menv; $_orig_retenv; false) ) // @@ -252,4 +250,4 @@ def main: repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -[ main ] | _halt \ No newline at end of file +[ main ] | _halt diff --git a/jq/step7_quote.jq b/jq/step7_quote.jq index 5aab6b97..123f2cc5 100644 --- a/jq/step7_quote.jq +++ b/jq/step7_quote.jq @@ -144,8 +144,6 @@ def EVAL(env): ) // ( .value | select(.[0].value == "fn*") as $value | - # we can't do what the guide says, so we'll skip over this - # and ues the later implementation # (fn* args body) $value[1].value | map(.value) as $binds | ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { @@ -153,7 +151,7 @@ def EVAL(env): binds: $binds, env: (env | env_remove_references($free_referencess)), body: $value[2], - names: [], # we can't do that circular reference this + names: [], # we can't do that circular reference thing free_referencess: $free_referencess # for dynamically scoped variables } | TCOWrap($_menv; $_orig_retenv; false) ) // @@ -295,5 +293,3 @@ def main: end; [ main ] | _halt - -# ( ( (fn* (a) (fn* (b) (+ a b))) 5) 7) \ No newline at end of file diff --git a/jq/step8_macros.jq b/jq/step8_macros.jq index 679ada1c..46031c93 100644 --- a/jq/step8_macros.jq +++ b/jq/step8_macros.jq @@ -236,8 +236,6 @@ def EVAL(env): ) // ( .value | select(.[0].value == "fn*") as $value | - # we can't do what the guide says, so we'll skip over this - # and ues the later implementation # (fn* args body) $value[1].value | map(.value) as $binds | ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { @@ -245,7 +243,7 @@ def EVAL(env): binds: $binds, env: (env | env_remove_references($free_referencess)), body: $value[2], - names: [], # we can't do that circular reference this + names: [], # we can't do that circular reference thing free_referencess: $free_referencess, # for dynamically scoped variables is_macro: false } | TCOWrap($_menv; $_orig_retenv; false) @@ -362,4 +360,4 @@ def main: repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -[ main ] | _halt \ No newline at end of file +[ main ] | _halt diff --git a/jq/step9_try.jq b/jq/step9_try.jq index b976ebc5..6d1c509a 100644 --- a/jq/step9_try.jq +++ b/jq/step9_try.jq @@ -265,8 +265,6 @@ def EVAL(env): ) // ( .value | select(.[0].value == "fn*") as $value | - # we can't do what the guide says, so we'll skip over this - # and ues the later implementation # (fn* args body) $value[1].value | map(.value) as $binds | ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { @@ -274,7 +272,7 @@ def EVAL(env): binds: $binds, env: (env | env_remove_references($free_referencess)), body: $value[2], - names: [], # we can't do that circular reference this + names: [], # we can't do that circular reference thing free_referencess: $free_referencess, # for dynamically scoped variables is_macro: false } | TCOWrap($_menv; $_orig_retenv; false) @@ -391,4 +389,4 @@ def main: repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -[ main ] | _halt \ No newline at end of file +[ main ] | _halt diff --git a/jq/stepA_mal.jq b/jq/stepA_mal.jq index 06458b65..c39c787a 100644 --- a/jq/stepA_mal.jq +++ b/jq/stepA_mal.jq @@ -271,8 +271,6 @@ def EVAL(env): ) // ( .value | select(.[0].value == "fn*") as $value | - # we can't do what the guide says, so we'll skip over this - # and ues the later implementation # (fn* args body) $value[1].value | map(.value) as $binds | ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | { @@ -280,7 +278,7 @@ def EVAL(env): binds: $binds, env: $_menv, body: $value[2], - names: [], # we can't do that circular reference this + names: [], # we can't do that circular reference thing free_referencess: $free_referencess, # for dynamically scoped variables is_macro: false } | TCOWrap($_menv; $_orig_retenv; false) @@ -403,4 +401,4 @@ def main: repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) ) end; -[ main ] | _halt \ No newline at end of file +[ main ] | _halt diff --git a/jq/utils.jq b/jq/utils.jq index a1585bed..c55a07e9 100644 --- a/jq/utils.jq +++ b/jq/utils.jq @@ -114,11 +114,10 @@ def _extern(options): input | fromjson else null - end; # oof + end; def issue_extern(cmd; options): {cmd: cmd, args: .} - # | (tostring | debug) as $ignore | _extern(options); def issue_extern(cmd): @@ -158,4 +157,4 @@ def _halt: | halt; def trap: - _write_to_file("trap_reason.json") | jqmal_error("trap"); \ No newline at end of file + _write_to_file("trap_reason.json") | jqmal_error("trap"); From 0b25243bbc2db9df548ed8a9bda9f358bd9f4a5c Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 12 Jan 2020 15:11:47 +0330 Subject: [PATCH 37/40] order alphabetically --- Makefile | 4 ++-- README.md | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index a6a1c1e0..fbaaf6cc 100644 --- a/Makefile +++ b/Makefile @@ -91,10 +91,10 @@ DOCKERIZE = IMPLS = ada ada.2 awk bash basic bbc-basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gnu-smalltalk \ - guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \ + guile haskell haxe hy io java js jq julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ plsql powershell ps python python.2 r racket rexx rpython ruby rust scala scheme skew \ - swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick zig jq + swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick zig EXTENSION = .mal diff --git a/README.md b/README.md index 8771db5f..35056d17 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **1. Mal is a Clojure inspired Lisp interpreter** -**2. Mal is implemented in 80 languages (83 different implementations and 103 runtime modes)** +**2. Mal is implemented in 81 languages (84 different implementations and 104 runtime modes)** | Language | Creator | | -------- | ------- | @@ -44,8 +44,8 @@ | [Hy](#hy) | [Joel Martin](https://github.com/kanaka) | | [Io](#io) | [Dov Murik](https://github.com/dubek) | | [Java](#java-17) | [Joel Martin](https://github.com/kanaka) | -| [Jq](#jq) | [Ali MohammadPur](https://github.com/alimpfard) | | [JavaScript](#javascriptnode) ([Demo](http://kanaka.github.io/mal)) | [Joel Martin](https://github.com/kanaka) | +| [Jq](#jq) | [Ali MohammadPur](https://github.com/alimpfard) | | [Julia](#julia) | [Joel Martin](https://github.com/kanaka) | | [Kotlin](#kotlin) | [Javier Fernandez-Ivern](https://github.com/ivern) | | [LiveScript](#livescript) | [Jos van Bakel](https://github.com/c0deaddict) | @@ -588,17 +588,6 @@ mvn -quiet exec:java -Dexec.mainClass=mal.stepX_YYY mvn -quiet exec:java -Dexec.mainClass=mal.stepX_YYY -Dexec.args="CMDLINE_ARGS" ``` -### Jq - -Tested against version 1.6, with a lot of cheating in the IO department - -``` -cd jq -STEP=stepA_YYY ./run - # with Debug -DEBUG=true STEP=stepA_YYY ./run -``` - ### JavaScript/Node ``` @@ -616,6 +605,17 @@ cd julia julia stepX_YYY.jl ``` +### Jq + +Tested against version 1.6, with a lot of cheating in the IO department + +``` +cd jq +STEP=stepA_YYY ./run + # with Debug +DEBUG=true STEP=stepA_YYY ./run +``` + ### Kotlin The Kotlin implementation of mal has been tested with Kotlin 1.0. From d2e940f0fbf92a4735414ace92ac60c5053ab864 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Mon, 13 Jan 2020 22:36:15 +0330 Subject: [PATCH 38/40] add stub makefile --- jq/Makefile | 1 + 1 file changed, 1 insertion(+) create mode 100644 jq/Makefile diff --git a/jq/Makefile b/jq/Makefile new file mode 100644 index 00000000..1263948f --- /dev/null +++ b/jq/Makefile @@ -0,0 +1 @@ +all: From c7714aca17d75c57f822b88de05bb12d6f90272c Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Tue, 14 Jan 2020 01:20:01 +0330 Subject: [PATCH 39/40] refactor some stuff to adhere to mal guidelines --- jq/env.jq | 11 +------ jq/interp.jq | 8 ----- jq/step2_eval.jq | 27 ++++++++++++++++- jq/step3_env.jq | 79 +++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 101 insertions(+), 24 deletions(-) diff --git a/jq/env.jq b/jq/env.jq index 8c51b190..9fbcb1dc 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -289,13 +289,4 @@ def env_remove_references(refs): .currentEnv |= _env_remove_references(refs) else _env_remove_references(refs) - end; - -# for step2 -def lookup(env): - env.environment[.] // - if env.parent then - lookup(env.parent) - else - jqmal_error("'\(.)' not found") - end; \ No newline at end of file + end; \ No newline at end of file diff --git a/jq/interp.jq b/jq/interp.jq index 1610e9b9..f38dd35a 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -16,14 +16,6 @@ def arg_check(args): . end end; - -def interpret(arguments; env): - (select(.kind == "fn") | - arg_check(arguments) | core_interp(arguments; env) - ) // - jqmal_error("Unsupported native function kind \(.kind)"); - - def extractReplEnv(env): env | .replEnv // .; diff --git a/jq/step2_eval.jq b/jq/step2_eval.jq index 23965e30..e04ce81d 100644 --- a/jq/step2_eval.jq +++ b/jq/step2_eval.jq @@ -1,7 +1,6 @@ include "reader"; include "printer"; include "utils"; -include "interp"; def read_line: . as $in @@ -15,6 +14,32 @@ def lookup(env): env[.] // jqmal_error("'\(.)' not found"); +def arg_check(args): + if .inputs != (args|length) then + jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length))") + else + . + end; + +def interpret(arguments; env): + (select(.kind == "fn") | + arg_check(arguments) | + ( + select(.function == "number_add") | + arguments | map(.value) | .[0] + .[1] | wrap("number") + ) // ( + select(.function == "number_sub") | + arguments | map(.value) | .[0] - .[1] | wrap("number") + ) // ( + select(.function == "number_mul") | + arguments | map(.value) | .[0] * .[1] | wrap("number") + ) // ( + select(.function == "number_div") | + arguments | map(.value) | .[0] / .[1] | wrap("number") + ) + ) // + jqmal_error("Unsupported native function kind \(.kind)"); + def EVAL(env): def eval_ast: (select(.kind == "symbol") | .value | lookup(env)) // diff --git a/jq/step3_env.jq b/jq/step3_env.jq index 17ec3a5f..49ae998e 100644 --- a/jq/step3_env.jq +++ b/jq/step3_env.jq @@ -1,8 +1,6 @@ include "reader"; include "printer"; include "utils"; -include "interp"; -include "env"; def read_line: . as $in @@ -12,9 +10,80 @@ def read_line: def READ: read_str | read_form | .value; +# Environment functions + +def pureChildEnv: + { + parent: ., + environment: {} + }; + +def env_set(env; $key; $value): + { + parent: env.parent, + environment: (env.environment + (env.environment | .[$key] |= $value)) # merge together, as .environment[key] |= value does not work + }; + +def env_find(env): + if env.environment[.] == null then + if env.parent then + env_find(env.parent) + else + null + end + else + env + end; + +def addToEnv(envexp; name): + { + expr: envexp.expr, + env: env_set(envexp.env; name; envexp.expr) + }; + +def env_get(env): + . as $key | $key | env_find(env).environment[$key] as $value | + if $value == null then + jqmal_error("'\($key)' not found") + else + $value + end; + +def addEnv(env): + { + expr: ., + env: env + }; + +# Evaluation + +def arg_check(args): + if .inputs != (args|length) then + jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length))") + else + . + end; + +def interpret(arguments; env): + (select(.kind == "fn") | + arg_check(arguments) | + ( + select(.function == "number_add") | + arguments | map(.value) | .[0] + .[1] | wrap("number") + ) // ( + select(.function == "number_sub") | + arguments | map(.value) | .[0] - .[1] | wrap("number") + ) // ( + select(.function == "number_mul") | + arguments | map(.value) | .[0] * .[1] | wrap("number") + ) // ( + select(.function == "number_div") | + arguments | map(.value) | .[0] / .[1] | wrap("number") + ) + ) | addEnv(env) // + jqmal_error("Unsupported native function kind \(.kind)"); + def EVAL(env): - def _eval_here: - .env as $env | .expr | EVAL($env); def hmap_with_env: .env as $env | .list as $list | if $list|length == 0 then @@ -63,7 +132,7 @@ def EVAL(env): ($dot + [$eval_env.expr]) ) | { expr: ., env: env } as $ev | $ev.expr | first | - interpret($ev.expr[1:]; $ev.env; _eval_here) + interpret($ev.expr[1:]; $ev.env) ) // addEnv(env) ) From 413107d1e6ffa030476110a5853924350f5cc9cc Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Tue, 14 Jan 2020 01:55:39 +0330 Subject: [PATCH 40/40] refactor atoms out of step4 and step5 also inline step4-and-5-specific versions of env.jq and interp.jq --- jq/env.jq | 10 +- jq/interp.jq | 31 +--- jq/step4_if_fn_do.jq | 370 ++++++++++++++++++++++++++++++++++++++++++- jq/step5_tco.jq | 370 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 745 insertions(+), 36 deletions(-) diff --git a/jq/env.jq b/jq/env.jq index 9fbcb1dc..bea8eb1b 100644 --- a/jq/env.jq +++ b/jq/env.jq @@ -253,7 +253,7 @@ def env_set_(env; key; value): env_set(env; key; value) end; -def addToEnv6(envexp; name): +def addToEnv(envexp; name): envexp.expr as $value | envexp.env as $rawEnv | (if $rawEnv.isReplEnv then @@ -266,14 +266,6 @@ def addToEnv6(envexp; name): env: $newEnv }; -def addToEnv(envexp; name): - if envexp.env.replEnv != null then - addToEnv6(envexp; name) - else { - expr: envexp.expr, - env: env_set_(envexp.env; name; envexp.expr) - } end; - def _env_remove_references(refs): if . != null then { diff --git a/jq/interp.jq b/jq/interp.jq index f38dd35a..a60693f4 100644 --- a/jq/interp.jq +++ b/jq/interp.jq @@ -22,17 +22,7 @@ def extractReplEnv(env): def extractEnv(env): env | .currentEnv // .; -def hasReplEnv(env): - env | has("replEnv"); - -def cWrapEnv(renv; atoms; cond): - if cond then - wrapEnv(renv; atoms) - else - . - end; - -def cUpdateReplEnv(renv; cond): +def updateReplEnv(renv): def findpath: if .env.parent then .path += ["parent"] | @@ -41,12 +31,8 @@ def cUpdateReplEnv(renv; cond): else .path end; - if cond then - ({ env: ., path: [] } | findpath) as $path | - setpath($path; renv) - else - . - end; + ({ env: ., path: [] } | findpath) as $path | + setpath($path; renv); def extractCurrentReplEnv(env): def findpath: @@ -85,19 +71,18 @@ def addFrees(newEnv; frees): def interpret(arguments; env; _eval): extractReplEnv(env) as $replEnv | extractAtoms(env) as $envAtoms | - hasReplEnv(env) as $hasReplEnv | (if $DEBUG then _debug("INTERP: \(. | pr_str(env))") else . end) | (select(.kind == "fn") | arg_check(arguments) | (select(.function == "eval") | # special function - { expr: arguments[0], env: $replEnv|cWrapEnv($replEnv; $envAtoms; $hasReplEnv) } + { expr: arguments[0], env: $replEnv|wrapEnv($replEnv; $envAtoms) } | _eval | .env as $xenv | extractReplEnv($xenv) as $xreplenv | setpath( ["env", "currentEnv"]; - extractEnv(env) | cUpdateReplEnv($xreplenv; $hasReplEnv)) + extractEnv(env) | updateReplEnv($xreplenv)) ) // (select(.function == "reset!") | # env modifying function @@ -170,7 +155,7 @@ def interpret(arguments; env; _eval): env_multiset($fnEnv; $fn.names; $fn) as $fnEnv | { env: env_multiset($fnEnv; $fn.names; $fn) - | cWrapEnv($replEnv; $envAtoms; $hasReplEnv), + | wrapEnv($replEnv; $envAtoms), expr: $fn.body } | . as $dot @@ -182,8 +167,8 @@ def interpret(arguments; env; _eval): { expr: .expr, env: extractEnv(env) - | cUpdateReplEnv($xreplenv; $hasReplEnv) - | cWrapEnv($xreplenv; $envexp.env.atoms; $hasReplEnv) + | updateReplEnv($xreplenv) + | wrapEnv($xreplenv; $envexp.env.atoms) } # | . as $dot # | _debug("FNPOST " + (.expr | pr_str) + " " + (env_req($dot.expr.env; $fn.binds[0]) | pr_str)) diff --git a/jq/step4_if_fn_do.jq b/jq/step4_if_fn_do.jq index e5a4297f..3fcfcfe0 100644 --- a/jq/step4_if_fn_do.jq +++ b/jq/step4_if_fn_do.jq @@ -1,8 +1,6 @@ include "reader"; include "printer"; include "utils"; -include "interp"; -include "env"; include "core"; def read_line: @@ -13,6 +11,374 @@ def read_line: def READ: read_str | read_form | .value; +# Environment Functions + +def childEnv(binds; exprs): + { + parent: ., + fallback: null, + environment: [binds, exprs] | transpose | ( + . as $dot | reduce .[] as $item ( + { value: [], seen: false, name: null, idx: 0 }; + if $item[1] != null then + if .seen then + { + value: (.value[1:-1] + (.value|last[1].value += [$item[1]])), + seen: true, + name: .name + } + else + if $item[0] == "&" then + $dot[.idx+1][0] as $name | { + value: (.value + [[$name, {kind:"list", value: [$item[1]]}]]), + seen: true, + name: $name + } + else + { + value: (.value + [$item]), + seen: false, + name: null + } + end + end | (.idx |= .idx + 1) + else + if $item[0] == "&" then + $dot[.idx+1][0] as $name | { + value: (.value + [[$name, {kind:"list", value: []}]]), + seen: true, + name: $name + } + else . end + end + ) + ) | .value | map({(.[0]): .[1]}) | add + }; + +def pureChildEnv: + { + parent: ., + environment: {}, + fallback: null + }; + +def rootEnv: + { + parent: null, + fallback: null, + environment: {} + }; + +def inform_function(name): + (.names += [name]) | (.names |= unique); + +def inform_function_multi(names): + . as $dot | reduce names[] as $name( + $dot; + inform_function($name) + ); + +def env_multiset(keys; value): + (if value.kind == "function" then # multiset not allowed on atoms + value | inform_function_multi(keys) + else + value + end) as $value | { + parent: .parent, + environment: ( + .environment + (reduce keys[] as $key(.environment; .[$key] |= value)) + ), + fallback: .fallback + }; + +def env_multiset(env; keys; value): + env | env_multiset(keys; value); + +def env_set($key; $value): + (if $value.kind == "function" or $value.kind == "atom" then + # inform the function/atom of its names + ($value | + if $value.kind == "atom" then + # check if the one we have is newer + env_req(env; key) as $ours | + if $ours.last_modified > $value.last_modified then + $ours + else + # update modification timestamp + $value | .last_modified |= now + end + else + . + end) | inform_function($key) + else + $value + end) as $value | { + parent: .parent, + environment: (.environment + (.environment | .[$key] |= $value)), # merge together, as .environment[key] |= value does not work + fallback: .fallback + }; + +def env_dump_keys: + def _dump1: + .environment // {} | keys; + if . == null then [] else + if .parent == null then + ( + _dump1 + + (.fallback | env_dump_keys) + ) + else + ( + _dump1 + + (.parent | env_dump_keys) + + (.fallback | env_dump_keys) + ) + end | unique + end; + +def env_find(env): + if env.environment[.] == null then + if env.parent then + env_find(env.parent) // if env.fallback then env_find(env.fallback) else null end + else + null + end + else + env + end; + +def env_get(env): + . as $key | $key | env_find(env).environment[$key] as $value | + if $value == null then + jqmal_error("'\($key)' not found") + else + if $value.kind == "atom" then + $value.identity as $id | + $key | env_find(env.parent).environment[$key] as $possibly_newer | + if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then + $possibly_newer + else + $value + end + else + $value + end + end; + +def env_get(env; key): + key | env_get(env); + +def env_req(env; key): + key as $key | key | env_find(env).environment[$key] as $value | + if $value == null then + null + else + if $value.kind == "atom" then + $value.identity as $id | + $key | env_find(env.parent).environment[$key] as $possibly_newer | + if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then + $possibly_newer + else + $value + end + else + $value + end + end; + +def env_set(env; $key; $value): + (if $value.kind == "function" or $value.kind == "atom" then + # inform the function/atom of its names + $value | (.names += [$key]) | (.names |= unique) | + if $value.kind == "atom" then + # check if the one we have is newer + env_req(env; $key) as $ours | + if $ours.last_modified > $value.last_modified then + $ours + else + # update modification timestamp + $value | .last_modified |= now + end + else + . + end + else + $value + end) as $value | { + parent: env.parent, + environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)), # merge together, as env.environment[key] |= value does not work + fallback: env.fallback + }; + +def env_setfallback(env; fallback): + { + parent: env.parent, + fallback: fallback, + environment: env.environment + }; + +def addEnv(env): + { + expr: ., + env: env + }; + +def addToEnv(env; name; expr): + { + expr: expr, + env: env_set(env; name; expr) + }; + + +def wrapEnv(atoms): + { + replEnv: ., + currentEnv: ., + atoms: atoms, + isReplEnv: true + }; + +def wrapEnv(replEnv; atoms): + { + replEnv: replEnv, + currentEnv: ., + atoms: atoms, # id -> value + isReplEnv: (replEnv == .) # should we allow separate copies? + }; + +def unwrapReplEnv: + .replEnv; + +def unwrapCurrentEnv: + .currentEnv; + +def env_set6(env; key; value): + if env.isReplEnv then + env_set(env.currentEnv; key; value) | wrapEnv(env.atoms) + else + env_set(env.currentEnv; key; value) | wrapEnv(env.replEnv; env.atoms) + end; + +def env_set_(env; key; value): + if env.currentEnv != null then + env_set6(env; key; value) + else + env_set(env; key; value) + end; + +def addToEnv6(envexp; name): + envexp.expr as $value + | envexp.env as $rawEnv + | (if $rawEnv.isReplEnv then + env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.atoms) + else + env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv; $rawEnv.atoms) + end) as $newEnv + | { + expr: $value, + env: $newEnv + }; + +def addToEnv(envexp; name): + if envexp.env.replEnv != null then + addToEnv6(envexp; name) + else { + expr: envexp.expr, + env: env_set_(envexp.env; name; envexp.expr) + } end; + +def _env_remove_references(refs): + if . != null then + { + environment: (.environment | to_entries | map(select(.key as $key | refs | contains([$key]) | not)) | from_entries), + parent: (.parent | _env_remove_references(refs)), + fallback: (.fallback | _env_remove_references(refs)) + } + else . end; + +def env_remove_references(refs): + . as $env + | if has("replEnv") then + .currentEnv |= _env_remove_references(refs) + else + _env_remove_references(refs) + end; + +# Evaluation + +def arg_check(args): + if .inputs < 0 then + if (abs(.inputs) - 1) > (args | length) then + jqmal_error("Invalid number of arguments (expected at least \(abs(.inputs) - 1), got \(args|length))") + else + . + end + else if .inputs != (args|length) then + jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length))") + else + . + end end; + +def addFrees(newEnv; frees): + . as $env + | reduce frees[] as $free ( + $env; + . as $dot + | env_req(newEnv; $free) as $lookup + | if $lookup != null then + env_set_(.; $free; $lookup) + else + . + end) + | . as $env + | $env; + +def interpret(arguments; env; _eval): + (if $DEBUG then _debug("INTERP: \(. | pr_str(env))") else . end) | + (select(.kind == "fn") | + arg_check(arguments) | + (core_interp(arguments; env) | addEnv(env)) + ) // + (select(.kind == "function") as $fn | + # todo: arg_check + (.body | pr_str(env)) as $src | + # _debug("INTERP " + $src) | + # _debug("FREES " + ($fn.free_referencess | tostring)) | + env_setfallback((.env | addFrees(env; $fn.free_referencess)); env) | childEnv($fn.binds; arguments) as $fnEnv | + # tell it about its surroundings + (reduce $fn.free_referencess[] as $name ( + $fnEnv; + . as $env | try env_set( + .; + $name; + $name | env_get(env) | . as $xvalue + | if $xvalue.kind == "function" then + setpath(["free_referencess"]; $fn.free_referencess) + else + $xvalue + end + ) catch $env)) as $fnEnv | + # tell it about itself + env_multiset($fnEnv; $fn.names; $fn) as $fnEnv | + { + env: env_multiset($fnEnv; $fn.names; $fn), + expr: $fn.body + } + | . as $dot + # | _debug("FNEXEC " + (.expr | pr_str) + " " + (env_req($dot.env; $fn.binds[0]) | pr_str)) + | _eval + | . as $envexp + | + { + expr: .expr, + env: env + } + # | . as $dot + # | _debug("FNPOST " + (.expr | pr_str) + " " + (env_req($dot.expr.env; $fn.binds[0]) | pr_str)) + # | _debug("INTERP " + $src + " = " + (.expr|pr_str)) + ) // + jqmal_error("Unsupported function kind \(.kind)"); + def recurseflip(x; y): recurse(y; x); diff --git a/jq/step5_tco.jq b/jq/step5_tco.jq index 0def038d..c2053e30 100644 --- a/jq/step5_tco.jq +++ b/jq/step5_tco.jq @@ -1,8 +1,6 @@ include "reader"; include "printer"; include "utils"; -include "interp"; -include "env"; include "core"; def read_line: @@ -13,6 +11,374 @@ def read_line: def READ: read_str | read_form | .value; +# Environment Functions + +def childEnv(binds; exprs): + { + parent: ., + fallback: null, + environment: [binds, exprs] | transpose | ( + . as $dot | reduce .[] as $item ( + { value: [], seen: false, name: null, idx: 0 }; + if $item[1] != null then + if .seen then + { + value: (.value[1:-1] + (.value|last[1].value += [$item[1]])), + seen: true, + name: .name + } + else + if $item[0] == "&" then + $dot[.idx+1][0] as $name | { + value: (.value + [[$name, {kind:"list", value: [$item[1]]}]]), + seen: true, + name: $name + } + else + { + value: (.value + [$item]), + seen: false, + name: null + } + end + end | (.idx |= .idx + 1) + else + if $item[0] == "&" then + $dot[.idx+1][0] as $name | { + value: (.value + [[$name, {kind:"list", value: []}]]), + seen: true, + name: $name + } + else . end + end + ) + ) | .value | map({(.[0]): .[1]}) | add + }; + +def pureChildEnv: + { + parent: ., + environment: {}, + fallback: null + }; + +def rootEnv: + { + parent: null, + fallback: null, + environment: {} + }; + +def inform_function(name): + (.names += [name]) | (.names |= unique); + +def inform_function_multi(names): + . as $dot | reduce names[] as $name( + $dot; + inform_function($name) + ); + +def env_multiset(keys; value): + (if value.kind == "function" then # multiset not allowed on atoms + value | inform_function_multi(keys) + else + value + end) as $value | { + parent: .parent, + environment: ( + .environment + (reduce keys[] as $key(.environment; .[$key] |= value)) + ), + fallback: .fallback + }; + +def env_multiset(env; keys; value): + env | env_multiset(keys; value); + +def env_set($key; $value): + (if $value.kind == "function" or $value.kind == "atom" then + # inform the function/atom of its names + ($value | + if $value.kind == "atom" then + # check if the one we have is newer + env_req(env; key) as $ours | + if $ours.last_modified > $value.last_modified then + $ours + else + # update modification timestamp + $value | .last_modified |= now + end + else + . + end) | inform_function($key) + else + $value + end) as $value | { + parent: .parent, + environment: (.environment + (.environment | .[$key] |= $value)), # merge together, as .environment[key] |= value does not work + fallback: .fallback + }; + +def env_dump_keys: + def _dump1: + .environment // {} | keys; + if . == null then [] else + if .parent == null then + ( + _dump1 + + (.fallback | env_dump_keys) + ) + else + ( + _dump1 + + (.parent | env_dump_keys) + + (.fallback | env_dump_keys) + ) + end | unique + end; + +def env_find(env): + if env.environment[.] == null then + if env.parent then + env_find(env.parent) // if env.fallback then env_find(env.fallback) else null end + else + null + end + else + env + end; + +def env_get(env): + . as $key | $key | env_find(env).environment[$key] as $value | + if $value == null then + jqmal_error("'\($key)' not found") + else + if $value.kind == "atom" then + $value.identity as $id | + $key | env_find(env.parent).environment[$key] as $possibly_newer | + if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then + $possibly_newer + else + $value + end + else + $value + end + end; + +def env_get(env; key): + key | env_get(env); + +def env_req(env; key): + key as $key | key | env_find(env).environment[$key] as $value | + if $value == null then + null + else + if $value.kind == "atom" then + $value.identity as $id | + $key | env_find(env.parent).environment[$key] as $possibly_newer | + if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then + $possibly_newer + else + $value + end + else + $value + end + end; + +def env_set(env; $key; $value): + (if $value.kind == "function" or $value.kind == "atom" then + # inform the function/atom of its names + $value | (.names += [$key]) | (.names |= unique) | + if $value.kind == "atom" then + # check if the one we have is newer + env_req(env; $key) as $ours | + if $ours.last_modified > $value.last_modified then + $ours + else + # update modification timestamp + $value | .last_modified |= now + end + else + . + end + else + $value + end) as $value | { + parent: env.parent, + environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)), # merge together, as env.environment[key] |= value does not work + fallback: env.fallback + }; + +def env_setfallback(env; fallback): + { + parent: env.parent, + fallback: fallback, + environment: env.environment + }; + +def addEnv(env): + { + expr: ., + env: env + }; + +def addToEnv(env; name; expr): + { + expr: expr, + env: env_set(env; name; expr) + }; + + +def wrapEnv(atoms): + { + replEnv: ., + currentEnv: ., + atoms: atoms, + isReplEnv: true + }; + +def wrapEnv(replEnv; atoms): + { + replEnv: replEnv, + currentEnv: ., + atoms: atoms, # id -> value + isReplEnv: (replEnv == .) # should we allow separate copies? + }; + +def unwrapReplEnv: + .replEnv; + +def unwrapCurrentEnv: + .currentEnv; + +def env_set6(env; key; value): + if env.isReplEnv then + env_set(env.currentEnv; key; value) | wrapEnv(env.atoms) + else + env_set(env.currentEnv; key; value) | wrapEnv(env.replEnv; env.atoms) + end; + +def env_set_(env; key; value): + if env.currentEnv != null then + env_set6(env; key; value) + else + env_set(env; key; value) + end; + +def addToEnv6(envexp; name): + envexp.expr as $value + | envexp.env as $rawEnv + | (if $rawEnv.isReplEnv then + env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.atoms) + else + env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv; $rawEnv.atoms) + end) as $newEnv + | { + expr: $value, + env: $newEnv + }; + +def addToEnv(envexp; name): + if envexp.env.replEnv != null then + addToEnv6(envexp; name) + else { + expr: envexp.expr, + env: env_set_(envexp.env; name; envexp.expr) + } end; + +def _env_remove_references(refs): + if . != null then + { + environment: (.environment | to_entries | map(select(.key as $key | refs | contains([$key]) | not)) | from_entries), + parent: (.parent | _env_remove_references(refs)), + fallback: (.fallback | _env_remove_references(refs)) + } + else . end; + +def env_remove_references(refs): + . as $env + | if has("replEnv") then + .currentEnv |= _env_remove_references(refs) + else + _env_remove_references(refs) + end; + +# Evaluation + +def arg_check(args): + if .inputs < 0 then + if (abs(.inputs) - 1) > (args | length) then + jqmal_error("Invalid number of arguments (expected at least \(abs(.inputs) - 1), got \(args|length))") + else + . + end + else if .inputs != (args|length) then + jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length))") + else + . + end end; + +def addFrees(newEnv; frees): + . as $env + | reduce frees[] as $free ( + $env; + . as $dot + | env_req(newEnv; $free) as $lookup + | if $lookup != null then + env_set_(.; $free; $lookup) + else + . + end) + | . as $env + | $env; + +def interpret(arguments; env; _eval): + (if $DEBUG then _debug("INTERP: \(. | pr_str(env))") else . end) | + (select(.kind == "fn") | + arg_check(arguments) | + (core_interp(arguments; env) | addEnv(env)) + ) // + (select(.kind == "function") as $fn | + # todo: arg_check + (.body | pr_str(env)) as $src | + # _debug("INTERP " + $src) | + # _debug("FREES " + ($fn.free_referencess | tostring)) | + env_setfallback((.env | addFrees(env; $fn.free_referencess)); env) | childEnv($fn.binds; arguments) as $fnEnv | + # tell it about its surroundings + (reduce $fn.free_referencess[] as $name ( + $fnEnv; + . as $env | try env_set( + .; + $name; + $name | env_get(env) | . as $xvalue + | if $xvalue.kind == "function" then + setpath(["free_referencess"]; $fn.free_referencess) + else + $xvalue + end + ) catch $env)) as $fnEnv | + # tell it about itself + env_multiset($fnEnv; $fn.names; $fn) as $fnEnv | + { + env: env_multiset($fnEnv; $fn.names; $fn), + expr: $fn.body + } + | . as $dot + # | _debug("FNEXEC " + (.expr | pr_str) + " " + (env_req($dot.env; $fn.binds[0]) | pr_str)) + | _eval + | . as $envexp + | + { + expr: .expr, + env: env + } + # | . as $dot + # | _debug("FNPOST " + (.expr | pr_str) + " " + (env_req($dot.expr.env; $fn.binds[0]) | pr_str)) + # | _debug("INTERP " + $src + " = " + (.expr|pr_str)) + ) // + jqmal_error("Unsupported function kind \(.kind)"); + def recurseflip(x; y): recurse(y; x);