DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / jq / env.jq
1 include "utils";
2
3 def childEnv(binds; exprs):
4 {
5 parent: .,
6 fallback: null,
7 environment: [binds, exprs] | transpose | (
8 . as $dot | reduce .[] as $item (
9 { value: [], seen: false, name: null, idx: 0 };
10 if $item[1] != null then
11 if .seen then
12 {
13 value: (.value[1:-1] + (.value|last[1].value += [$item[1]])),
14 seen: true,
15 name: .name
16 }
17 else
18 if $item[0] == "&" then
19 $dot[.idx+1][0] as $name | {
20 value: (.value + [[$name, {kind:"list", value: [$item[1]]}]]),
21 seen: true,
22 name: $name
23 }
24 else
25 {
26 value: (.value + [$item]),
27 seen: false,
28 name: null
29 }
30 end
31 end | (.idx |= .idx + 1)
32 else
33 if $item[0] == "&" then
34 $dot[.idx+1][0] as $name | {
35 value: (.value + [[$name, {kind:"list", value: []}]]),
36 seen: true,
37 name: $name
38 }
39 else . end
40 end
41 )
42 ) | .value | map({(.[0]): .[1]}) | add
43 };
44
45 def pureChildEnv:
46 {
47 parent: .,
48 environment: {},
49 fallback: null
50 };
51
52 def rootEnv:
53 {
54 parent: null,
55 fallback: null,
56 environment: {}
57 };
58
59 def inform_function(name):
60 (.names += [name]) | (.names |= unique);
61
62 def inform_function_multi(names):
63 . as $dot | reduce names[] as $name(
64 $dot;
65 inform_function($name)
66 );
67
68 def env_multiset(keys; value):
69 (if value.kind == "function" then # multiset not allowed on atoms
70 value | inform_function_multi(keys)
71 else
72 value
73 end) as $value | {
74 parent: .parent,
75 environment: (
76 .environment + (reduce keys[] as $key(.environment; .[$key] |= value))
77 ),
78 fallback: .fallback
79 };
80
81 def env_multiset(env; keys; value):
82 env | env_multiset(keys; value);
83
84 def env_set($key; $value):
85 (if $value.kind == "function" or $value.kind == "atom" then
86 # inform the function/atom of its names
87 ($value |
88 if $value.kind == "atom" then
89 # check if the one we have is newer
90 env_req(env; key) as $ours |
91 if $ours.last_modified > $value.last_modified then
92 $ours
93 else
94 # update modification timestamp
95 $value | .last_modified |= now
96 end
97 else
98 .
99 end) | inform_function($key)
100 else
101 $value
102 end) as $value | {
103 parent: .parent,
104 environment: (.environment + (.environment | .[$key] |= $value)), # merge together, as .environment[key] |= value does not work
105 fallback: .fallback
106 };
107
108 def env_dump_keys:
109 def _dump1:
110 .environment // {} | keys;
111 if . == null then [] else
112 if .parent == null then
113 (
114 _dump1 +
115 (.fallback | env_dump_keys)
116 )
117 else
118 (
119 _dump1 +
120 (.parent | env_dump_keys) +
121 (.fallback | env_dump_keys)
122 )
123 end | unique
124 end;
125
126 def env_find(env):
127 if env.environment[.] == null then
128 if env.parent then
129 env_find(env.parent) // if env.fallback then env_find(env.fallback) else null end
130 else
131 null
132 end
133 else
134 env
135 end;
136
137 def env_get(env):
138 . as $key | $key | env_find(env).environment[$key] as $value |
139 if $value == null then
140 jqmal_error("'\($key)' not found")
141 else
142 if $value.kind == "atom" then
143 $value.identity as $id |
144 $key | env_find(env.parent).environment[$key] as $possibly_newer |
145 if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then
146 $possibly_newer
147 else
148 $value
149 end
150 else
151 $value
152 end
153 end;
154
155 def env_get(env; key):
156 key | env_get(env);
157
158 def env_req(env; key):
159 key as $key | key | env_find(env).environment[$key] as $value |
160 if $value == null then
161 null
162 else
163 if $value.kind == "atom" then
164 $value.identity as $id |
165 $key | env_find(env.parent).environment[$key] as $possibly_newer |
166 if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then
167 $possibly_newer
168 else
169 $value
170 end
171 else
172 $value
173 end
174 end;
175
176 def env_set(env; $key; $value):
177 (if $value.kind == "function" then
178 # inform the function/atom of its names
179 $value | (.names += [$key]) | (.names |= unique)
180 else
181 $value
182 end) as $value | {
183 parent: env.parent,
184 environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)), # merge together, as env.environment[key] |= value does not work
185 fallback: env.fallback
186 };
187
188 def env_setfallback(env; fallback):
189 {
190 parent: env.parent,
191 fallback: fallback,
192 environment: env.environment
193 };
194
195 def addEnv(env):
196 {
197 expr: .,
198 env: env
199 };
200
201 def addToEnv(env; name; expr):
202 {
203 expr: expr,
204 env: env_set(env; name; expr)
205 };
206
207
208 def wrapEnv(atoms):
209 {
210 replEnv: .,
211 currentEnv: .,
212 atoms: atoms,
213 isReplEnv: true
214 };
215
216 def wrapEnv(replEnv; atoms):
217 {
218 replEnv: replEnv,
219 currentEnv: .,
220 atoms: atoms, # id -> value
221 isReplEnv: (replEnv == .) # should we allow separate copies?
222 };
223
224 def unwrapReplEnv:
225 .replEnv;
226
227 def unwrapCurrentEnv:
228 .currentEnv;
229
230 def env_set6(env; key; value):
231 if env.isReplEnv then
232 env_set(env.currentEnv; key; value) | wrapEnv(env.atoms)
233 else
234 env_set(env.currentEnv; key; value) | wrapEnv(env.replEnv; env.atoms)
235 end;
236
237 def env_set_(env; key; value):
238 if env.currentEnv != null then
239 env_set6(env; key; value)
240 else
241 env_set(env; key; value)
242 end;
243
244 def addToEnv(envexp; name):
245 envexp.expr as $value
246 | envexp.env as $rawEnv
247 | (if $rawEnv.isReplEnv then
248 env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.atoms)
249 else
250 env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv; $rawEnv.atoms)
251 end) as $newEnv
252 | {
253 expr: $value,
254 env: $newEnv
255 };
256
257 def _env_remove_references(refs):
258 if . != null then
259 if .environment == null then
260 _debug("This one broke the rules, officer: \(.)")
261 else
262 {
263 environment: (.environment | to_entries | map(select(.key as $key | refs | contains([$key]) | not)) | from_entries),
264 parent: (.parent | _env_remove_references(refs)),
265 fallback: (.fallback | _env_remove_references(refs))
266 }
267 end
268 else . end;
269
270 def env_remove_references(refs):
271 . as $env
272 | if (refs|length == 0) then
273 # optimisation: most functions are purely lexical
274 $env
275 else
276 if has("replEnv") then
277 .currentEnv |= _env_remove_references(refs)
278 else
279 _env_remove_references(refs)
280 end
281 end;