1 : #include "eval.hh"
2 : #include "parser.hh"
3 : #include "nixexpr-ast.hh"
4 :
5 :
6 : EvalState::EvalState()
7 23 : : normalForms(32768, 50)
8 23 : {
9 23 : blackHole = makeBlackHole();
10 :
11 23 : nrEvaluated = nrCached = 0;
12 :
13 23 : initNixExprHelpers();
14 :
15 23 : addPrimOps();
16 : }
17 :
18 :
19 : void EvalState::addPrimOp(const string & name,
20 : unsigned int arity, PrimOp primOp)
21 207 : {
22 207 : primOps.set(name, makePrimOpDef(arity, ATmakeBlob(0, (void *) primOp)));
23 : }
24 :
25 :
26 : /* Substitute an argument set into the body of a function. */
27 : static Expr substArgs(Expr body, ATermList formals, Expr arg)
28 3 : {
29 3 : ATermMap subs;
30 3 : Expr undefined = makeUndefined();
31 :
32 : /* Get the formal arguments. */
33 6 : for (ATermIterator i(formals); i; ++i) {
34 3 : Expr name, def;
35 3 : if (matchNoDefFormal(*i, name))
36 3 : subs.set(name, undefined);
37 0 : else if (matchDefFormal(*i, name, def))
38 0 : subs.set(name, def);
39 0 : else abort(); /* can't happen */
40 : }
41 :
42 : /* Get the actual arguments, and check that they match with the
43 : formals. */
44 3 : ATermMap args;
45 3 : queryAllAttrs(arg, args);
46 6 : for (ATermIterator i(args.keys()); i; ++i) {
47 3 : Expr key = *i;
48 3 : Expr cur = subs.get(key);
49 3 : if (!cur)
50 0 : throw Error(format("unexpected function argument `%1%'")
51 0 : % aterm2String(key));
52 3 : subs.set(key, args.get(key));
53 : }
54 :
55 : /* Check that all arguments are defined. */
56 6 : for (ATermIterator i(subs.keys()); i; ++i)
57 3 : if (subs.get(*i) == undefined)
58 0 : throw Error(format("required function argument `%1%' missing")
59 : % aterm2String(*i));
60 :
61 3 : return substitute(subs, body);
62 : }
63 :
64 :
65 : /* Transform a mutually recursive set into a non-recursive set. Each
66 : attribute is transformed into an expression that has all references
67 : to attributes substituted with selection expressions on the
68 : original set. E.g., e = `rec {x = f x y; y = x;}' becomes `{x = f
69 : (e.x) (e.y); y = e.x;}'. */
70 : ATerm expandRec(ATerm e, ATermList rbnds, ATermList nrbnds)
71 8 : {
72 8 : ATerm name;
73 8 : Expr e2;
74 8 : Pos pos;
75 :
76 : /* Create the substitution list. */
77 8 : ATermMap subs;
78 37 : for (ATermIterator i(rbnds); i; ++i) {
79 29 : if (!matchBind(*i, name, e2, pos)) abort(); /* can't happen */
80 29 : subs.set(name, makeSelect(e, name));
81 : }
82 9 : for (ATermIterator i(nrbnds); i; ++i) {
83 1 : if (!matchBind(*i, name, e2, pos)) abort(); /* can't happen */
84 1 : subs.set(name, e2);
85 : }
86 :
87 : /* Create the non-recursive set. */
88 8 : ATermMap as;
89 37 : for (ATermIterator i(rbnds); i; ++i) {
90 29 : if (!matchBind(*i, name, e2, pos)) abort(); /* can't happen */
91 29 : as.set(name, makeAttrRHS(substitute(subs, e2), pos));
92 : }
93 :
94 : /* Copy the non-recursive bindings. !!! inefficient */
95 9 : for (ATermIterator i(nrbnds); i; ++i) {
96 1 : if (!matchBind(*i, name, e2, pos)) abort(); /* can't happen */
97 1 : as.set(name, makeAttrRHS(e2, pos));
98 : }
99 :
100 8 : return makeAttrs(as);
101 : }
102 :
103 :
104 : static Expr updateAttrs(Expr e1, Expr e2)
105 0 : {
106 : /* Note: e1 and e2 should be in normal form. */
107 :
108 0 : ATermMap attrs;
109 0 : queryAllAttrs(e1, attrs, true);
110 0 : queryAllAttrs(e2, attrs, true);
111 :
112 0 : return makeAttrs(attrs);
113 : }
114 :
115 :
116 : string evalString(EvalState & state, Expr e)
117 43 : {
118 43 : e = evalExpr(state, e);
119 43 : ATerm s;
120 43 : if (!matchStr(e, s)) throw Error("string expected");
121 43 : return aterm2String(s);
122 : }
123 :
124 :
125 : Path evalPath(EvalState & state, Expr e)
126 43 : {
127 43 : e = evalExpr(state, e);
128 43 : ATerm s;
129 43 : if (!matchPath(e, s)) throw Error("path expected");
130 43 : return aterm2String(s);
131 : }
132 :
133 :
134 : bool evalBool(EvalState & state, Expr e)
135 0 : {
136 0 : e = evalExpr(state, e);
137 0 : if (e == eTrue) return true;
138 0 : else if (e == eFalse) return false;
139 0 : else throw Error("boolean expected");
140 : }
141 :
142 :
143 : Expr evalExpr2(EvalState & state, Expr e)
144 381 : {
145 381 : Expr e1, e2, e3, e4;
146 381 : ATerm name, pos;
147 381 : AFun sym = ATgetAFun(e);
148 :
149 : /* Normal forms. */
150 381 : if (sym == symStr ||
151 : sym == symPath ||
152 : sym == symSubPath ||
153 : sym == symUri ||
154 : sym == symNull ||
155 : sym == symInt ||
156 : sym == symBool ||
157 : sym == symFunction ||
158 : sym == symFunction1 ||
159 : sym == symAttrs ||
160 : sym == symList ||
161 : sym == symPrimOp)
162 231 : return e;
163 :
164 : /* The `Closed' constructor is just a way to prevent substitutions
165 : into expressions not containing free variables. */
166 150 : if (matchClosed(e, e1))
167 41 : return evalExpr(state, e1);
168 :
169 : /* Any encountered variables must be primops (since undefined
170 : variables are detected after parsing). */
171 109 : if (matchVar(e, name)) {
172 13 : ATerm primOp = state.primOps.get(name);
173 13 : if (!primOp)
174 0 : throw Error(format("impossible: undefined variable `%1%'") % name);
175 13 : int arity;
176 13 : ATermBlob fun;
177 13 : if (!matchPrimOpDef(primOp, arity, fun)) abort();
178 13 : if (arity == 0)
179 0 : return ((PrimOp) ATgetBlobData(fun)) (state, ATermVector());
180 : else
181 13 : return makePrimOp(arity, fun, ATempty);
182 : }
183 :
184 : /* Function application. */
185 96 : if (matchCall(e, e1, e2)) {
186 :
187 53 : ATermList formals;
188 53 : ATerm pos;
189 :
190 : /* Evaluate the left-hand side. */
191 53 : e1 = evalExpr(state, e1);
192 :
193 : /* Is it a primop or a function? */
194 53 : int arity;
195 53 : ATermBlob fun;
196 53 : ATermList args;
197 53 : if (matchPrimOp(e1, arity, fun, args)) {
198 26 : args = ATinsert(args, e2);
199 26 : if (ATgetLength(args) == arity) {
200 : /* Put the arguments in a vector in reverse (i.e.,
201 : actual) order. */
202 25 : ATermVector args2(arity);
203 51 : for (ATermIterator i(args); i; ++i)
204 26 : args2[--arity] = *i;
205 25 : return ((PrimOp) ATgetBlobData((ATermBlob) fun))
206 : (state, args2);
207 : } else
208 : /* Need more arguments, so propagate the primop. */
209 1 : return makePrimOp(arity, fun, args);
210 : }
211 :
212 27 : else if (matchFunction(e1, formals, e4, pos)) {
213 3 : e2 = evalExpr(state, e2);
214 3 : try {
215 3 : return evalExpr(state, substArgs(e4, formals, e2));
216 0 : } catch (Error & e) {
217 0 : throw Error(format("while evaluating the function at %1%:\n%2%")
218 : % showPos(pos) % e.msg());
219 : }
220 : }
221 :
222 24 : else if (matchFunction1(e1, name, e4, pos)) {
223 24 : try {
224 24 : ATermMap subs;
225 24 : subs.set(name, e2);
226 24 : return evalExpr(state, substitute(subs, e4));
227 0 : } catch (Error & e) {
228 0 : throw Error(format("while evaluating the function at %1%:\n%2%")
229 : % showPos(pos) % e.msg());
230 : }
231 : }
232 :
233 0 : else throw Error("function or primop expected in function call");
234 : }
235 :
236 : /* Attribute selection. */
237 43 : if (matchSelect(e, e1, name)) {
238 31 : ATerm pos;
239 31 : string s1 = aterm2String(name);
240 31 : Expr a = queryAttr(evalExpr(state, e1), s1, pos);
241 31 : if (!a) throw Error(format("attribute `%1%' missing") % s1);
242 31 : try {
243 31 : return evalExpr(state, a);
244 3 : } catch (Error & e) {
245 3 : throw Error(format("while evaluating the attribute `%1%' at %2%:\n%3%")
246 : % s1 % showPos(pos) % e.msg());
247 : }
248 : }
249 :
250 : /* Mutually recursive sets. */
251 12 : ATermList rbnds, nrbnds;
252 12 : if (matchRec(e, rbnds, nrbnds))
253 8 : return expandRec(e, rbnds, nrbnds);
254 :
255 : /* Conditionals. */
256 4 : if (matchIf(e, e1, e2, e3)) {
257 0 : if (evalBool(state, e1))
258 0 : return evalExpr(state, e2);
259 : else
260 0 : return evalExpr(state, e3);
261 : }
262 :
263 : /* Assertions. */
264 4 : if (matchAssert(e, e1, e2, pos)) {
265 0 : if (!evalBool(state, e1))
266 0 : throw Error(format("assertion failed at %1%") % showPos(pos));
267 0 : return evalExpr(state, e2);
268 : }
269 :
270 : /* Withs. */
271 4 : if (matchWith(e, e1, e2, pos)) {
272 0 : ATermMap attrs;
273 0 : try {
274 0 : e1 = evalExpr(state, e1);
275 0 : queryAllAttrs(e1, attrs);
276 0 : } catch (Error & e) {
277 0 : throw Error(format("while evaluating the `with' definitions at %1%:\n%2%")
278 : % showPos(pos) % e.msg());
279 : }
280 0 : try {
281 0 : e2 = substitute(attrs, e2);
282 0 : checkVarDefs(state.primOps, e2);
283 0 : return evalExpr(state, e2);
284 0 : } catch (Error & e) {
285 0 : throw Error(format("while evaluating the `with' body at %1%:\n%2%")
286 : % showPos(pos) % e.msg());
287 : }
288 : }
289 :
290 : /* Generic equality. */
291 4 : if (matchOpEq(e, e1, e2))
292 0 : return makeBool(evalExpr(state, e1) == evalExpr(state, e2));
293 :
294 : /* Generic inequality. */
295 4 : if (matchOpNEq(e, e1, e2))
296 0 : return makeBool(evalExpr(state, e1) != evalExpr(state, e2));
297 :
298 : /* Negation. */
299 4 : if (matchOpNot(e, e1))
300 0 : return makeBool(!evalBool(state, e1));
301 :
302 : /* Implication. */
303 4 : if (matchOpImpl(e, e1, e2))
304 0 : return makeBool(!evalBool(state, e1) || evalBool(state, e2));
305 :
306 : /* Conjunction (logical AND). */
307 4 : if (matchOpAnd(e, e1, e2))
308 0 : return makeBool(evalBool(state, e1) && evalBool(state, e2));
309 :
310 : /* Disjunction (logical OR). */
311 4 : if (matchOpOr(e, e1, e2))
312 0 : return makeBool(evalBool(state, e1) || evalBool(state, e2));
313 :
314 : /* Attribute set update (//). */
315 4 : if (matchOpUpdate(e, e1, e2))
316 0 : return updateAttrs(evalExpr(state, e1), evalExpr(state, e2));
317 :
318 : /* Attribute existence test (?). */
319 4 : if (matchOpHasAttr(e, e1, name)) {
320 0 : ATermMap attrs;
321 0 : queryAllAttrs(evalExpr(state, e1), attrs);
322 0 : return makeBool(attrs.get(name) != 0);
323 : }
324 :
325 : /* String or path concatenation. */
326 4 : if (matchOpPlus(e, e1, e2)) {
327 4 : e1 = evalExpr(state, e1);
328 4 : e2 = evalExpr(state, e2);
329 4 : ATerm s1, s2;
330 4 : if (matchStr(e1, s1) && matchStr(e2, s2))
331 3 : return makeStr(toATerm(
332 : (string) aterm2String(s1) + (string) aterm2String(s2)));
333 1 : else if (matchPath(e1, s1) && matchPath(e2, s2))
334 1 : return makePath(toATerm(canonPath(
335 : (string) aterm2String(s1) + "/" + (string) aterm2String(s2))));
336 0 : else throw Error("wrong argument types in `+' operator");
337 : }
338 :
339 : /* Barf. */
340 0 : throw badTerm("invalid expression", e);
341 : }
342 :
343 :
344 : Expr evalExpr(EvalState & state, Expr e)
345 607 : {
346 607 : checkInterrupt();
347 :
348 607 : startNest(nest, lvlVomit,
349 : format("evaluating expression: %1%") % e);
350 :
351 607 : state.nrEvaluated++;
352 :
353 : /* Consult the memo table to quickly get the normal form of
354 : previously evaluated expressions. */
355 607 : Expr nf = state.normalForms.get(e);
356 607 : if (nf) {
357 226 : if (nf == state.blackHole)
358 1 : throw Error("infinite recursion encountered");
359 225 : state.nrCached++;
360 225 : return nf;
361 : }
362 :
363 : /* Otherwise, evaluate and memoize. */
364 381 : state.normalForms.set(e, state.blackHole);
365 381 : nf = evalExpr2(state, e);
366 376 : state.normalForms.set(e, nf);
367 376 : return nf;
368 : }
369 :
370 :
371 : Expr evalFile(EvalState & state, const Path & path)
372 11 : {
373 11 : startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path);
374 11 : Expr e = parseExprFromFile(state, path);
375 11 : try {
376 11 : return evalExpr(state, e);
377 0 : } catch (Error & e) {
378 0 : throw Error(format("while evaluating the file `%1%':\n%2%")
379 : % path % e.msg());
380 : }
381 : }
382 :
383 :
384 : void printEvalStats(EvalState & state)
385 21 : {
386 21 : debug(format("evaluated %1% expressions, %2% cache hits, %3%%% efficiency")
387 : % state.nrEvaluated % state.nrCached
388 : % ((float) state.nrCached / (float) state.nrEvaluated * 100));
389 : }
|