DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / jq / step3_env.jq
1 include "reader";
2 include "printer";
3 include "utils";
4
5 def read_line:
6 . as $in
7 | label $top
8 | _readline;
9
10 def READ:
11 read_str | read_form | .value;
12
13 # Environment functions
14
15 def pureChildEnv:
16 {
17 parent: .,
18 environment: {}
19 };
20
21 def env_set(env; $key; $value):
22 {
23 parent: env.parent,
24 environment: (env.environment + (env.environment | .[$key] |= $value)) # merge together, as .environment[key] |= value does not work
25 };
26
27 def env_find(env):
28 if env.environment[.] == null then
29 if env.parent then
30 env_find(env.parent)
31 else
32 null
33 end
34 else
35 env
36 end;
37
38 def addToEnv(envexp; name):
39 {
40 expr: envexp.expr,
41 env: env_set(envexp.env; name; envexp.expr)
42 };
43
44 def env_get(env):
45 . as $key | $key | env_find(env).environment[$key] as $value |
46 if $value == null then
47 jqmal_error("'\($key)' not found")
48 else
49 $value
50 end;
51
52 def addEnv(env):
53 {
54 expr: .,
55 env: env
56 };
57
58 # Evaluation
59
60 def arg_check(args):
61 if .inputs != (args|length) then
62 jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length))")
63 else
64 .
65 end;
66
67 def interpret(arguments; env):
68 (select(.kind == "fn") |
69 arg_check(arguments) |
70 (
71 select(.function == "number_add") |
72 arguments | map(.value) | .[0] + .[1] | wrap("number")
73 ) // (
74 select(.function == "number_sub") |
75 arguments | map(.value) | .[0] - .[1] | wrap("number")
76 ) // (
77 select(.function == "number_mul") |
78 arguments | map(.value) | .[0] * .[1] | wrap("number")
79 ) // (
80 select(.function == "number_div") |
81 arguments | map(.value) | .[0] / .[1] | wrap("number")
82 )
83 ) | addEnv(env) //
84 jqmal_error("Unsupported native function kind \(.kind)");
85
86 def EVAL(env):
87 def hmap_with_env:
88 .env as $env | .list as $list |
89 if $list|length == 0 then
90 empty
91 else
92 $list[0] as $elem |
93 $list[1:] as $rest |
94 $elem[1] | EVAL($env) as $resv |
95 { value: [$elem[0], $resv.expr], env: env },
96 ({env: $resv.env, list: $rest} | hmap_with_env)
97 end;
98 def map_with_env:
99 .env as $env | .list as $list |
100 if $list|length == 0 then
101 empty
102 else
103 $list[0] as $elem |
104 $list[1:] as $rest |
105 $elem | EVAL($env) as $resv |
106 { value: $resv.expr, env: env },
107 ({env: $resv.env, list: $rest} | map_with_env)
108 end;
109 (select(.kind == "list") |
110 if .value | length == 0 then
111 .
112 else
113 (
114 (
115 .value | select(.[0].value == "def!") as $value |
116 ($value[2] | EVAL(env)) as $evval |
117 addToEnv($evval; $value[1].value)
118 ) //
119 (
120 .value | select(.[0].value == "let*") as $value |
121 (env | pureChildEnv) as $subenv |
122 (reduce ($value[1].value | nwise(2)) as $xvalue (
123 $subenv;
124 . as $env | $xvalue[1] | EVAL($env) as $expenv |
125 env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env
126 | $value[2] | { expr: EVAL($env).expr, env: env }
127 ) //
128 (
129 reduce .value[] as $elem (
130 [];
131 . as $dot | $elem | EVAL(env) as $eval_env |
132 ($dot + [$eval_env.expr])
133 ) | { expr: ., env: env } as $ev
134 | $ev.expr | first |
135 interpret($ev.expr[1:]; $ev.env)
136 ) //
137 addEnv(env)
138 )
139 end
140 ) //
141 (select(.kind == "vector") |
142 [ { env: env, list: .value } | map_with_env ] as $res |
143 {
144 kind: "vector",
145 value: $res | map(.value)
146 } | addEnv($res | last.env)
147 ) //
148 (select(.kind == "hashmap") |
149 [ { env: env, list: .value | to_entries } | hmap_with_env ] as $res |
150 {
151 kind: "hashmap",
152 value: $res | map(.value) | from_entries
153 } | addEnv($res | last.env)
154 ) //
155 (select(.kind == "symbol") |
156 .value | env_get(env) | addEnv(env)
157 ) // addEnv(env);
158
159 def PRINT:
160 pr_str;
161
162 def rep(env):
163 READ | EVAL(env) as $expenv |
164 if $expenv.expr != null then
165 $expenv.expr | PRINT
166 else
167 null
168 end | addEnv($expenv.env);
169
170 def repl_(env):
171 ("user> " | _print) |
172 (read_line | rep(env));
173
174 def childEnv(binds; value):
175 {
176 parent: .,
177 environment: [binds, value] | transpose | map({(.[0]): .[1]}) | from_entries
178 };
179
180 # we don't have no indirect functions, so we'll have to interpret the old way
181 def replEnv:
182 {
183 parent: null,
184 environment: {
185 "+": {
186 kind: "fn", # native function
187 inputs: 2,
188 function: "number_add"
189 },
190 "-": {
191 kind: "fn", # native function
192 inputs: 2,
193 function: "number_sub"
194 },
195 "*": {
196 kind: "fn", # native function
197 inputs: 2,
198 function: "number_mul"
199 },
200 "/": {
201 kind: "fn", # native function
202 inputs: 2,
203 function: "number_div"
204 },
205 }
206 };
207
208 def repl(env):
209 def xrepl:
210 (.env as $env | try repl_($env) catch addEnv($env)) as $expenv |
211 {
212 value: $expenv.expr,
213 stop: false,
214 env: ($expenv.env // .env)
215 } | ., xrepl;
216 {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end;
217
218 repl(replEnv)