add step9 and fix "symbol not found" exception format
authorAnotherTest <ali.mpfard@gmail.com>
Wed, 8 Jan 2020 16:34:12 +0000 (20:04 +0330)
committerAnotherTest <ali.mpfard@gmail.com>
Wed, 8 Jan 2020 16:34:12 +0000 (20:04 +0330)
jq/core.jq
jq/env.jq
jq/interp.jq
jq/step2_eval.jq
jq/step9_try.jq [new file with mode: 0644]
jq/utils.jq

index 94364c2..75e8ae6 100644 (file)
@@ -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
index 6882fda..5dc0338 100644 (file)
--- 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
index 6a95d85..949337c 100644 (file)
@@ -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 |
index 7f70657..0eec9d1 100644 (file)
@@ -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 (file)
index 0000000..e005b83
--- /dev/null
@@ -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
index 45c14ad..0186264 100644 (file)
@@ -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):
     {