fix scoping and backport print patch
authorAnotherTest <ali.mpfard@gmail.com>
Mon, 6 Jan 2020 07:30:26 +0000 (11:00 +0330)
committerAnotherTest <ali.mpfard@gmail.com>
Tue, 7 Jan 2020 20:35:33 +0000 (00:05 +0330)
add TCO file, no TCO though (seems ok?)

jq/env.jq
jq/interp.jq
jq/run
jq/step0_repl.jq
jq/step1_read_print.jq
jq/step2_eval.jq
jq/step3_env.jq
jq/step4_if_fn_do.jq
jq/step5_tco.jq [new file with mode: 0644]

index 11d590a..671203c 100644 (file)
--- 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");
 
index dca7b22..58c2ff7 100644 (file)
@@ -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 bb5218d..984d923 100755 (executable)
--- 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]'
index e2fb9b3..51d4b3a 100644 (file)
@@ -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:
index 7d416d4..2429383 100644 (file)
@@ -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
index f7dfa1d..cfc6ff6 100644 (file)
@@ -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
index 87250c8..e939635 100644 (file)
@@ -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
index ae9371f..a45e84c 100644 (file)
@@ -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 (file)
index 0000000..a45e84c
--- /dev/null
@@ -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