* Started integrating the new evaluator.
This commit is contained in:
		
							parent
							
								
									52090d2418
								
							
						
					
					
						commit
						31428c3a06
					
				
					 8 changed files with 745 additions and 709 deletions
				
			
		|  | @ -6,6 +6,7 @@ | |||
| namespace nix { | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| bool isAttrs(EvalState & state, Expr e, ATermMap & attrs) | ||||
| { | ||||
|     e = evalExpr(state, e); | ||||
|  | @ -77,6 +78,7 @@ Expr findAlongAttrPath(EvalState & state, const string & attrPath, | |||
|      | ||||
|     return e; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
|   | ||||
| } | ||||
|  |  | |||
|  | @ -10,668 +10,13 @@ | |||
| using namespace nix; | ||||
| 
 | ||||
| 
 | ||||
| struct Env; | ||||
| struct Value; | ||||
| 
 | ||||
| typedef ATerm Sym; | ||||
| 
 | ||||
| typedef std::map<Sym, Value> Bindings; | ||||
| 
 | ||||
| 
 | ||||
| struct Env | ||||
| { | ||||
|     Env * up; | ||||
|     Bindings bindings; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| typedef enum { | ||||
|     tInt = 1, | ||||
|     tBool, | ||||
|     tString, | ||||
|     tPath, | ||||
|     tNull, | ||||
|     tAttrs, | ||||
|     tList, | ||||
|     tThunk, | ||||
|     tLambda, | ||||
|     tCopy, | ||||
|     tBlackhole, | ||||
|     tPrimOp, | ||||
|     tPrimOpApp, | ||||
| } ValueType; | ||||
| 
 | ||||
| 
 | ||||
| typedef void (* PrimOp_) (Value * * args, Value & v); | ||||
| 
 | ||||
| 
 | ||||
| struct Value | ||||
| { | ||||
|     ValueType type; | ||||
|     union  | ||||
|     { | ||||
|         int integer; | ||||
|         bool boolean; | ||||
|         struct { | ||||
|             const char * s; | ||||
|             const char * * context; | ||||
|         } string; | ||||
|         Bindings * attrs; | ||||
|         struct { | ||||
|             unsigned int length; | ||||
|             Value * elems; | ||||
|         } list; | ||||
|         struct { | ||||
|             Env * env; | ||||
|             Expr expr; | ||||
|         } thunk; | ||||
|         struct { | ||||
|             Env * env; | ||||
|             Pattern pat; | ||||
|             Expr body; | ||||
|         } lambda; | ||||
|         Value * val; | ||||
|         struct { | ||||
|             PrimOp_ fun; | ||||
|             unsigned int arity; | ||||
|         } primOp; | ||||
|         struct { | ||||
|             Value * left, * right; | ||||
|             unsigned int argsLeft; | ||||
|         } primOpApp; | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| static void mkThunk(Value & v, Env & env, Expr expr) | ||||
| { | ||||
|     v.type = tThunk; | ||||
|     v.thunk.env = &env; | ||||
|     v.thunk.expr = expr; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void mkInt(Value & v, int n) | ||||
| { | ||||
|     v.type = tInt; | ||||
|     v.integer = n; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void mkBool(Value & v, bool b) | ||||
| { | ||||
|     v.type = tBool; | ||||
|     v.boolean = b; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void mkString(Value & v, const char * s) | ||||
| { | ||||
|     v.type = tString; | ||||
|     v.string.s = s; | ||||
|     v.string.context = 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| std::ostream & operator << (std::ostream & str, Value & v) | ||||
| { | ||||
|     switch (v.type) { | ||||
|     case tInt: | ||||
|         str << v.integer; | ||||
|         break; | ||||
|     case tBool: | ||||
|         str << (v.boolean ? "true" : "false"); | ||||
|         break; | ||||
|     case tString: | ||||
|         str << "\"" << v.string.s << "\""; // !!! escaping
 | ||||
|         break; | ||||
|     case tNull: | ||||
|         str << "true"; | ||||
|         break; | ||||
|     case tAttrs: | ||||
|         str << "{ "; | ||||
|         foreach (Bindings::iterator, i, *v.attrs) | ||||
|             str << aterm2String(i->first) << " = " << i->second << "; "; | ||||
|         str << "}"; | ||||
|         break; | ||||
|     case tList: | ||||
|         str << "[ "; | ||||
|         for (unsigned int n = 0; n < v.list.length; ++n) | ||||
|             str << v.list.elems[n] << " "; | ||||
|         str << "]"; | ||||
|         break; | ||||
|     case tThunk: | ||||
|         str << "<CODE>"; | ||||
|         break; | ||||
|     case tLambda: | ||||
|         str << "<LAMBDA>"; | ||||
|         break; | ||||
|     case tPrimOp: | ||||
|         str << "<PRIMOP>"; | ||||
|         break; | ||||
|     case tPrimOpApp: | ||||
|         str << "<PRIMOP-APP>"; | ||||
|         break; | ||||
|     default: | ||||
|         abort(); | ||||
|     } | ||||
|     return str; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void eval(Env & env, Expr e, Value & v); | ||||
| 
 | ||||
| 
 | ||||
| string showType(Value & v) | ||||
| { | ||||
|     switch (v.type) { | ||||
|         case tString: return "a string"; | ||||
|         case tPath: return "a path"; | ||||
|         case tNull: return "null"; | ||||
|         case tInt: return "an integer"; | ||||
|         case tBool: return "a boolean"; | ||||
|         case tLambda: return "a function"; | ||||
|         case tAttrs: return "an attribute set"; | ||||
|         case tList: return "a list"; | ||||
|         case tPrimOpApp: return "a partially applied built-in function"; | ||||
|         default: throw Error("unknown type"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void forceValue(Value & v) | ||||
| { | ||||
|     if (v.type == tThunk) { | ||||
|         v.type = tBlackhole; | ||||
|         eval(*v.thunk.env, v.thunk.expr, v); | ||||
|     } | ||||
|     else if (v.type == tCopy) { | ||||
|         forceValue(*v.val); | ||||
|         v = *v.val; | ||||
|     } | ||||
|     else if (v.type == tBlackhole) | ||||
|         throw EvalError("infinite recursion encountered"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void forceInt(Value & v) | ||||
| { | ||||
|     forceValue(v); | ||||
|     if (v.type != tInt) | ||||
|         throw TypeError(format("value is %1% while an integer was expected") % showType(v)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void forceAttrs(Value & v) | ||||
| { | ||||
|     forceValue(v); | ||||
|     if (v.type != tAttrs) | ||||
|         throw TypeError(format("value is %1% while an attribute set was expected") % showType(v)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void forceList(Value & v) | ||||
| { | ||||
|     forceValue(v); | ||||
|     if (v.type != tList) | ||||
|         throw TypeError(format("value is %1% while a list was expected") % showType(v)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static Value * lookupWith(Env * env, Sym name) | ||||
| { | ||||
|     if (!env) return 0; | ||||
|     Value * v = lookupWith(env->up, name); | ||||
|     if (v) return v; | ||||
|     Bindings::iterator i = env->bindings.find(sWith); | ||||
|     if (i == env->bindings.end()) return 0; | ||||
|     Bindings::iterator j = i->second.attrs->find(name); | ||||
|     if (j != i->second.attrs->end()) return &j->second; | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static Value * lookupVar(Env * env, Sym name) | ||||
| { | ||||
|     /* First look for a regular variable binding for `name'. */ | ||||
|     for (Env * env2 = env; env2; env2 = env2->up) { | ||||
|         Bindings::iterator i = env2->bindings.find(name); | ||||
|         if (i != env2->bindings.end()) return &i->second; | ||||
|     } | ||||
| 
 | ||||
|     /* Otherwise, look for a `with' attribute set containing `name'.
 | ||||
|        Outer `withs' take precedence (i.e. `with {x=1;}; with {x=2;}; | ||||
|        x' evaluates to 1).  */ | ||||
|     Value * v = lookupWith(env, name); | ||||
|     if (v) return v; | ||||
| 
 | ||||
|     /* Alternative implementation where the inner `withs' take
 | ||||
|        precedence (i.e. `with {x=1;}; with {x=2;}; x' evaluates to | ||||
|        2). */ | ||||
| #if 0 | ||||
|     for (Env * env2 = env; env2; env2 = env2->up) { | ||||
|         Bindings::iterator i = env2->bindings.find(sWith); | ||||
|         if (i == env2->bindings.end()) continue; | ||||
|         Bindings::iterator j = i->second.attrs->find(name); | ||||
|         if (j != i->second.attrs->end()) return &j->second; | ||||
|     } | ||||
| #endif | ||||
|      | ||||
|     throw Error("undefined variable"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static bool eqValues(Value & v1, Value & v2) | ||||
| { | ||||
|     forceValue(v1); | ||||
|     forceValue(v2); | ||||
|     switch (v1.type) { | ||||
| 
 | ||||
|         case tInt: | ||||
|             return v2.type == tInt && v1.integer == v2.integer; | ||||
| 
 | ||||
|         case tBool: | ||||
|             return v2.type == tBool && v1.boolean == v2.boolean; | ||||
| 
 | ||||
|         case tList: | ||||
|             if (v2.type != tList || v1.list.length != v2.list.length) return false; | ||||
|             for (unsigned int n = 0; n < v1.list.length; ++n) | ||||
|                 if (!eqValues(v1.list.elems[n], v2.list.elems[n])) return false; | ||||
|             return true; | ||||
| 
 | ||||
|         case tAttrs: { | ||||
|             if (v2.type != tAttrs || v1.attrs->size() != v2.attrs->size()) return false; | ||||
|             Bindings::iterator i, j; | ||||
|             for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j) | ||||
|                 if (!eqValues(i->second, j->second)) return false; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         default: | ||||
|             throw Error("cannot compare given values"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| unsigned long nrValues = 0, nrEnvs = 0, nrEvaluated = 0; | ||||
| 
 | ||||
| static Value * allocValues(unsigned int count) | ||||
| { | ||||
|     nrValues += count; | ||||
|     return new Value[count]; // !!! check destructor
 | ||||
| } | ||||
| 
 | ||||
| static Env & allocEnv() | ||||
| { | ||||
|     nrEnvs++; | ||||
|     return *(new Env); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| char * p1 = 0, * p2 = 0; | ||||
| 
 | ||||
| 
 | ||||
| static bool evalBool(Env & env, Expr e) | ||||
| { | ||||
|     Value v; | ||||
|     eval(env, e, v); | ||||
|     if (v.type != tBool) | ||||
|         throw TypeError(format("value is %1% while a Boolean was expected") % showType(v)); | ||||
|     return v.boolean; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void eval(Env & env, Expr e, Value & v) | ||||
| { | ||||
|     char c; | ||||
|     if (!p1) p1 = &c; else if (!p2) p2 = &c; | ||||
| 
 | ||||
|     printMsg(lvlError, format("eval: %1%") % e); | ||||
| 
 | ||||
|     nrEvaluated++; | ||||
| 
 | ||||
|     Sym name; | ||||
|     if (matchVar(e, name)) { | ||||
|         Value * v2 = lookupVar(&env, name); | ||||
|         forceValue(*v2); | ||||
|         v = *v2; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     int n; | ||||
|     if (matchInt(e, n)) { | ||||
|         mkInt(v, n); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ATerm s; ATermList context; | ||||
|     if (matchStr(e, s, context)) { | ||||
|         assert(context == ATempty); | ||||
|         mkString(v, ATgetName(ATgetAFun(s))); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ATermList es; | ||||
|     if (matchAttrs(e, es)) { | ||||
|         v.type = tAttrs; | ||||
|         v.attrs = new Bindings; | ||||
|         ATerm e2, pos; | ||||
|         for (ATermIterator i(es); i; ++i) { | ||||
|             if (!matchBind(*i, name, e2, pos)) abort(); /* can't happen */ | ||||
|             Value & v2 = (*v.attrs)[name]; | ||||
|             nrValues++; | ||||
|             mkThunk(v2, env, e2); | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ATermList rbnds, nrbnds; | ||||
|     if (matchRec(e, rbnds, nrbnds)) { | ||||
|         Env & env2(allocEnv()); | ||||
|         env2.up = &env; | ||||
|          | ||||
|         v.type = tAttrs; | ||||
|         v.attrs = &env2.bindings; | ||||
|         ATerm name, e2, pos; | ||||
|         for (ATermIterator i(rbnds); i; ++i) { | ||||
|             if (!matchBind(*i, name, e2, pos)) abort(); /* can't happen */ | ||||
|             Value & v2 = env2.bindings[name]; | ||||
|             nrValues++; | ||||
|             mkThunk(v2, env2, e2); | ||||
|         } | ||||
|          | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Expr e1, e2; | ||||
|     if (matchSelect(e, e2, name)) { | ||||
|         eval(env, e2, v); | ||||
|         forceAttrs(v); // !!! eval followed by force is slightly inefficient
 | ||||
|         Bindings::iterator i = v.attrs->find(name); | ||||
|         if (i == v.attrs->end()) throw TypeError("attribute not found"); | ||||
|         forceValue(i->second); | ||||
|         v = i->second; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Pattern pat; Expr body; Pos pos; | ||||
|     if (matchFunction(e, pat, body, pos)) { | ||||
|         v.type = tLambda; | ||||
|         v.lambda.env = &env; | ||||
|         v.lambda.pat = pat; | ||||
|         v.lambda.body = body; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Expr fun, arg; | ||||
|     if (matchCall(e, fun, arg)) { | ||||
|         eval(env, fun, v); | ||||
| 
 | ||||
|         if (v.type == tPrimOp || v.type == tPrimOpApp) { | ||||
|             unsigned int argsLeft = | ||||
|                 v.type == tPrimOp ? v.primOp.arity : v.primOpApp.argsLeft; | ||||
|             if (argsLeft == 1) { | ||||
|                 /* We have all the arguments, so call the primop.
 | ||||
|                    First find the primop. */ | ||||
|                 Value * primOp = &v; | ||||
|                 while (primOp->type == tPrimOpApp) primOp = primOp->primOpApp.left; | ||||
|                 assert(primOp->type == tPrimOp); | ||||
|                 unsigned int arity = primOp->primOp.arity; | ||||
|                  | ||||
|                 Value vLastArg; | ||||
|                 mkThunk(vLastArg, env, arg); | ||||
| 
 | ||||
|                 /* Put all the arguments in an array. */ | ||||
|                 Value * vArgs[arity]; | ||||
|                 unsigned int n = arity - 1; | ||||
|                 vArgs[n--] = &vLastArg; | ||||
|                 for (Value * arg = &v; arg->type == tPrimOpApp; arg = arg->primOpApp.left) | ||||
|                     vArgs[n--] = arg->primOpApp.right; | ||||
| 
 | ||||
|                 /* And call the primop. */ | ||||
|                 primOp->primOp.fun(vArgs, v); | ||||
|             } else { | ||||
|                 Value * v2 = allocValues(2); | ||||
|                 v2[0] = v; | ||||
|                 mkThunk(v2[1], env, arg); | ||||
|                 v.type = tPrimOpApp; | ||||
|                 v.primOpApp.left = &v2[0]; | ||||
|                 v.primOpApp.right = &v2[1]; | ||||
|                 v.primOpApp.argsLeft = argsLeft - 1; | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         if (v.type != tLambda) throw TypeError("expected function"); | ||||
| 
 | ||||
|         Env & env2(allocEnv()); | ||||
|         env2.up = &env; | ||||
| 
 | ||||
|         ATermList formals; ATerm ellipsis; | ||||
| 
 | ||||
|         if (matchVarPat(v.lambda.pat, name)) { | ||||
|             Value & vArg = env2.bindings[name]; | ||||
|             nrValues++; | ||||
|             mkThunk(vArg, env, arg); | ||||
|         } | ||||
| 
 | ||||
|         else if (matchAttrsPat(v.lambda.pat, formals, ellipsis, name)) { | ||||
|             Value * vArg; | ||||
|             Value vArg_; | ||||
| 
 | ||||
|             if (name == sNoAlias) | ||||
|                 vArg = &vArg_; | ||||
|             else { | ||||
|                 vArg = &env2.bindings[name]; | ||||
|                 nrValues++; | ||||
|             }                 | ||||
| 
 | ||||
|             eval(env, arg, *vArg); | ||||
|             forceAttrs(*vArg); | ||||
|              | ||||
|             /* For each formal argument, get the actual argument.  If
 | ||||
|                there is no matching actual argument but the formal | ||||
|                argument has a default, use the default. */ | ||||
|             unsigned int attrsUsed = 0; | ||||
|             for (ATermIterator i(formals); i; ++i) { | ||||
|                 Expr def; Sym name; | ||||
|                 DefaultValue def2; | ||||
|                 if (!matchFormal(*i, name, def2)) abort(); /* can't happen */ | ||||
| 
 | ||||
|                 Bindings::iterator j = vArg->attrs->find(name); | ||||
|                  | ||||
|                 Value & v = env2.bindings[name]; | ||||
|                 nrValues++; | ||||
|                  | ||||
|                 if (j == vArg->attrs->end()) { | ||||
|                     if (!matchDefaultValue(def2, def)) def = 0; | ||||
|                     if (def == 0) throw TypeError(format("the argument named `%1%' required by the function is missing") | ||||
|                         % aterm2String(name)); | ||||
|                     mkThunk(v, env2, def); | ||||
|                 } else { | ||||
|                     attrsUsed++; | ||||
|                     v.type = tCopy; | ||||
|                     v.val = &j->second; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /* Check that each actual argument is listed as a formal
 | ||||
|                argument (unless the attribute match specifies a | ||||
|                `...').  TODO: show the names of the | ||||
|                expected/unexpected arguments. */ | ||||
|             if (ellipsis == eFalse && attrsUsed != vArg->attrs->size()) | ||||
|                 throw TypeError("function called with unexpected argument"); | ||||
|         } | ||||
| 
 | ||||
|         else abort(); | ||||
|          | ||||
|         eval(env2, v.lambda.body, v); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Expr attrs; | ||||
|     if (matchWith(e, attrs, body, pos)) { | ||||
|         Env & env2(allocEnv()); | ||||
|         env2.up = &env; | ||||
| 
 | ||||
|         Value & vAttrs = env2.bindings[sWith]; | ||||
|         nrValues++; | ||||
|         eval(env, attrs, vAttrs); | ||||
|         forceAttrs(vAttrs); | ||||
|          | ||||
|         eval(env2, body, v); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchList(e, es)) { | ||||
|         v.type = tList; | ||||
|         v.list.length = ATgetLength(es); | ||||
|         v.list.elems = allocValues(v.list.length); | ||||
|         for (unsigned int n = 0; n < v.list.length; ++n, es = ATgetNext(es)) | ||||
|             mkThunk(v.list.elems[n], env, ATgetFirst(es)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchOpEq(e, e1, e2)) { | ||||
|         Value v1; eval(env, e1, v1); | ||||
|         Value v2; eval(env, e2, v2); | ||||
|         mkBool(v, eqValues(v1, v2)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchOpNEq(e, e1, e2)) { | ||||
|         Value v1; eval(env, e1, v1); | ||||
|         Value v2; eval(env, e2, v2); | ||||
|         mkBool(v, !eqValues(v1, v2)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchOpConcat(e, e1, e2)) { | ||||
|         Value v1; eval(env, e1, v1); | ||||
|         forceList(v1); | ||||
|         Value v2; eval(env, e2, v2); | ||||
|         forceList(v2); | ||||
|         v.type = tList; | ||||
|         v.list.length = v1.list.length + v2.list.length; | ||||
|         v.list.elems = allocValues(v.list.length); | ||||
|         /* !!! This loses sharing with the original lists.  We could
 | ||||
|            use a tCopy node, but that would use more memory. */ | ||||
|         for (unsigned int n = 0; n < v1.list.length; ++n) | ||||
|             v.list.elems[n] = v1.list.elems[n]; | ||||
|         for (unsigned int n = 0; n < v2.list.length; ++n) | ||||
|             v.list.elems[n + v1.list.length] = v2.list.elems[n]; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchConcatStrings(e, es)) { | ||||
|         unsigned int n = ATgetLength(es), j = 0; | ||||
|         Value vs[n]; | ||||
|         unsigned int len = 0; | ||||
|         for (ATermIterator i(es); i; ++i, ++j) { | ||||
|             eval(env, *i, vs[j]); | ||||
|             if (vs[j].type != tString) throw TypeError("string expected"); | ||||
|             len += strlen(vs[j].string.s); | ||||
|         } | ||||
|         char * s = new char[len + 1], * t = s; | ||||
|         for (unsigned int i = 0; i < j; ++i) { | ||||
|             strcpy(t, vs[i].string.s); | ||||
|             t += strlen(vs[i].string.s); | ||||
|         } | ||||
|         *t = 0; | ||||
|         mkString(v, s); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Expr e3; | ||||
|     if (matchIf(e, e1, e2, e3)) { | ||||
|         eval(env, evalBool(env, e1) ? e2 : e3, v); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchOpOr(e, e1, e2)) { | ||||
|         mkBool(v, evalBool(env, e1) || evalBool(env, e2)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     throw Error("unsupported term"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void strictEval(Env & env, Expr e, Value & v) | ||||
| { | ||||
|     eval(env, e, v); | ||||
|      | ||||
|     if (v.type == tAttrs) { | ||||
|         foreach (Bindings::iterator, i, *v.attrs) | ||||
|             forceValue(i->second); | ||||
|     } | ||||
|      | ||||
|     else if (v.type == tList) { | ||||
|         for (unsigned int n = 0; n < v.list.length; ++n) | ||||
|             forceValue(v.list.elems[n]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void prim_head(Value * * args, Value & v) | ||||
| { | ||||
|     forceList(*args[0]); | ||||
|     if (args[0]->list.length == 0) | ||||
|         throw Error("`head' called on an empty list"); | ||||
|     forceValue(args[0]->list.elems[0]); | ||||
|     v = args[0]->list.elems[0]; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void prim_add(Value * * args, Value & v) | ||||
| { | ||||
|     forceInt(*args[0]); | ||||
|     forceInt(*args[1]); | ||||
|     mkInt(v, args[0]->integer + args[1]->integer); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void addPrimOp(Env & env, const string & name, unsigned int arity, PrimOp_ fun) | ||||
| { | ||||
|     Value & v = env.bindings[toATerm(name)]; | ||||
|     nrValues++; | ||||
|     v.type = tPrimOp; | ||||
|     v.primOp.arity = arity; | ||||
|     v.primOp.fun = fun; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void doTest(string s) | ||||
| { | ||||
|     Env baseEnv; | ||||
|     baseEnv.up = 0; | ||||
| 
 | ||||
|     /* Add global constants such as `true' to the base environment. */ | ||||
|     { | ||||
|         Value & v = baseEnv.bindings[toATerm("true")]; | ||||
|         v.type = tBool; | ||||
|         v.boolean = true; | ||||
|     } | ||||
|     { | ||||
|         Value & v = baseEnv.bindings[toATerm("false")]; | ||||
|         v.type = tBool; | ||||
|         v.boolean = false; | ||||
|     } | ||||
|     { | ||||
|         Value & v = baseEnv.bindings[toATerm("null")]; | ||||
|         v.type = tNull; | ||||
|     } | ||||
| 
 | ||||
|     /* Add primops to the base environment. */ | ||||
|     addPrimOp(baseEnv, "__head", 1, prim_head); | ||||
|     addPrimOp(baseEnv, "__add", 2, prim_add); | ||||
|      | ||||
|     p1 = p2 = 0; | ||||
|     EvalState state; | ||||
|     Expr e = parseExprFromString(state, s, "/"); | ||||
|     printMsg(lvlError, format(">>>>> %1%") % e); | ||||
|     Value v; | ||||
|     strictEval(baseEnv, e, v); | ||||
|     state.strictEval(e, v); | ||||
|     printMsg(lvlError, format("result: %1%") % v); | ||||
| } | ||||
| 
 | ||||
|  | @ -721,11 +66,6 @@ void run(Strings args) | |||
|     doTest("if false then 1 else 2"); | ||||
|     doTest("if false || true then 1 else 2"); | ||||
|     doTest("let x = x; in if true || x then 1 else 2"); | ||||
|      | ||||
|     printMsg(lvlError, format("alloced %1% values") % nrValues); | ||||
|     printMsg(lvlError, format("alloced %1% environments") % nrEnvs); | ||||
|     printMsg(lvlError, format("evaluated %1% expressions") % nrEvaluated); | ||||
|     printMsg(lvlError, format("each eval() uses %1% bytes of stack space") % (p1 - p2)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,8 @@ | |||
| #include "nixexpr-ast.hh" | ||||
| #include "globals.hh" | ||||
| 
 | ||||
| #include <cstring> | ||||
| 
 | ||||
| 
 | ||||
| #define LocalNoInline(f) static f __attribute__((noinline)); f | ||||
| #define LocalNoInlineNoReturn(f) static f __attribute__((noinline, noreturn)); f | ||||
|  | @ -15,14 +17,76 @@ | |||
| namespace nix { | ||||
|      | ||||
| 
 | ||||
| EvalState::EvalState() | ||||
|     : normalForms(32768), primOps(128) | ||||
| std::ostream & operator << (std::ostream & str, Value & v) | ||||
| { | ||||
|     nrEvaluated = nrCached = 0; | ||||
|     switch (v.type) { | ||||
|     case tInt: | ||||
|         str << v.integer; | ||||
|         break; | ||||
|     case tBool: | ||||
|         str << (v.boolean ? "true" : "false"); | ||||
|         break; | ||||
|     case tString: | ||||
|         str << "\"" << v.string.s << "\""; // !!! escaping
 | ||||
|         break; | ||||
|     case tNull: | ||||
|         str << "true"; | ||||
|         break; | ||||
|     case tAttrs: | ||||
|         str << "{ "; | ||||
|         foreach (Bindings::iterator, i, *v.attrs) | ||||
|             str << aterm2String(i->first) << " = " << i->second << "; "; | ||||
|         str << "}"; | ||||
|         break; | ||||
|     case tList: | ||||
|         str << "[ "; | ||||
|         for (unsigned int n = 0; n < v.list.length; ++n) | ||||
|             str << v.list.elems[n] << " "; | ||||
|         str << "]"; | ||||
|         break; | ||||
|     case tThunk: | ||||
|         str << "<CODE>"; | ||||
|         break; | ||||
|     case tLambda: | ||||
|         str << "<LAMBDA>"; | ||||
|         break; | ||||
|     case tPrimOp: | ||||
|         str << "<PRIMOP>"; | ||||
|         break; | ||||
|     case tPrimOpApp: | ||||
|         str << "<PRIMOP-APP>"; | ||||
|         break; | ||||
|     default: | ||||
|         throw Error("invalid value"); | ||||
|     } | ||||
|     return str; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| string showType(Value & v) | ||||
| { | ||||
|     switch (v.type) { | ||||
|         case tString: return "a string"; | ||||
|         case tPath: return "a path"; | ||||
|         case tNull: return "null"; | ||||
|         case tInt: return "an integer"; | ||||
|         case tBool: return "a boolean"; | ||||
|         case tLambda: return "a function"; | ||||
|         case tAttrs: return "an attribute set"; | ||||
|         case tList: return "a list"; | ||||
|         case tPrimOpApp: return "a partially applied built-in function"; | ||||
|         default: throw Error("unknown type"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| EvalState::EvalState() : baseEnv(allocEnv()) | ||||
| { | ||||
|     nrValues = nrEnvs = nrEvaluated = 0; | ||||
| 
 | ||||
|     initNixExprHelpers(); | ||||
| 
 | ||||
|     addPrimOps(); | ||||
|     createBaseEnv(); | ||||
|      | ||||
|     allowUnsafeEquality = getEnv("NIX_NO_UNSAFE_EQ", "") == ""; | ||||
| } | ||||
|  | @ -31,7 +95,11 @@ EvalState::EvalState() | |||
| void EvalState::addPrimOp(const string & name, | ||||
|     unsigned int arity, PrimOp primOp) | ||||
| { | ||||
|     primOps.set(toATerm(name), makePrimOpDef(arity, ATmakeBlob(0, (void *) primOp))); | ||||
|     Value & v = baseEnv.bindings[toATerm(name)]; | ||||
|     nrValues++; | ||||
|     v.type = tPrimOp; | ||||
|     v.primOp.arity = arity; | ||||
|     v.primOp.fun = primOp; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -76,6 +144,471 @@ LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2, | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void mkThunk(Value & v, Env & env, Expr expr) | ||||
| { | ||||
|     v.type = tThunk; | ||||
|     v.thunk.env = &env; | ||||
|     v.thunk.expr = expr; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static Value * lookupWith(Env * env, Sym name) | ||||
| { | ||||
|     if (!env) return 0; | ||||
|     Value * v = lookupWith(env->up, name); | ||||
|     if (v) return v; | ||||
|     Bindings::iterator i = env->bindings.find(sWith); | ||||
|     if (i == env->bindings.end()) return 0; | ||||
|     Bindings::iterator j = i->second.attrs->find(name); | ||||
|     if (j != i->second.attrs->end()) return &j->second; | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static Value * lookupVar(Env * env, Sym name) | ||||
| { | ||||
|     /* First look for a regular variable binding for `name'. */ | ||||
|     for (Env * env2 = env; env2; env2 = env2->up) { | ||||
|         Bindings::iterator i = env2->bindings.find(name); | ||||
|         if (i != env2->bindings.end()) return &i->second; | ||||
|     } | ||||
| 
 | ||||
|     /* Otherwise, look for a `with' attribute set containing `name'.
 | ||||
|        Outer `withs' take precedence (i.e. `with {x=1;}; with {x=2;}; | ||||
|        x' evaluates to 1).  */ | ||||
|     Value * v = lookupWith(env, name); | ||||
|     if (v) return v; | ||||
| 
 | ||||
|     /* Alternative implementation where the inner `withs' take
 | ||||
|        precedence (i.e. `with {x=1;}; with {x=2;}; x' evaluates to | ||||
|        2). */ | ||||
| #if 0 | ||||
|     for (Env * env2 = env; env2; env2 = env2->up) { | ||||
|         Bindings::iterator i = env2->bindings.find(sWith); | ||||
|         if (i == env2->bindings.end()) continue; | ||||
|         Bindings::iterator j = i->second.attrs->find(name); | ||||
|         if (j != i->second.attrs->end()) return &j->second; | ||||
|     } | ||||
| #endif | ||||
|      | ||||
|     throw Error("undefined variable"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Value * EvalState::allocValues(unsigned int count) | ||||
| { | ||||
|     nrValues += count; | ||||
|     return new Value[count]; // !!! check destructor
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Env & EvalState::allocEnv() | ||||
| { | ||||
|     nrEnvs++; | ||||
|     return *(new Env); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static char * deepestStack = (char *) -1; /* for measuring stack usage */ | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::eval(Env & env, Expr e, Value & v) | ||||
| { | ||||
|     /* When changing this function, make sure that you don't cause a
 | ||||
|        (large) increase in stack consumption! */ | ||||
|      | ||||
|     char x; | ||||
|     if (&x < deepestStack) deepestStack = &x; | ||||
|      | ||||
|     printMsg(lvlError, format("eval: %1%") % e); | ||||
| 
 | ||||
|     nrEvaluated++; | ||||
| 
 | ||||
|     Sym name; | ||||
|     if (matchVar(e, name)) { | ||||
|         Value * v2 = lookupVar(&env, name); | ||||
|         forceValue(*v2); | ||||
|         v = *v2; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     int n; | ||||
|     if (matchInt(e, n)) { | ||||
|         mkInt(v, n); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ATerm s; ATermList context; | ||||
|     if (matchStr(e, s, context)) { | ||||
|         assert(context == ATempty); | ||||
|         mkString(v, ATgetName(ATgetAFun(s))); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ATermList es; | ||||
|     if (matchAttrs(e, es)) { | ||||
|         v.type = tAttrs; | ||||
|         v.attrs = new Bindings; | ||||
|         ATerm e2, pos; | ||||
|         for (ATermIterator i(es); i; ++i) { | ||||
|             if (!matchBind(*i, name, e2, pos)) abort(); /* can't happen */ | ||||
|             Value & v2 = (*v.attrs)[name]; | ||||
|             nrValues++; | ||||
|             mkThunk(v2, env, e2); | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ATermList rbnds, nrbnds; | ||||
|     if (matchRec(e, rbnds, nrbnds)) { | ||||
|         Env & env2(allocEnv()); | ||||
|         env2.up = &env; | ||||
|          | ||||
|         v.type = tAttrs; | ||||
|         v.attrs = &env2.bindings; | ||||
|         ATerm name, e2, pos; | ||||
|         for (ATermIterator i(rbnds); i; ++i) { | ||||
|             if (!matchBind(*i, name, e2, pos)) abort(); /* can't happen */ | ||||
|             Value & v2 = env2.bindings[name]; | ||||
|             nrValues++; | ||||
|             mkThunk(v2, env2, e2); | ||||
|         } | ||||
|          | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Expr e1, e2; | ||||
|     if (matchSelect(e, e2, name)) { | ||||
|         eval(env, e2, v); | ||||
|         forceAttrs(v); // !!! eval followed by force is slightly inefficient
 | ||||
|         Bindings::iterator i = v.attrs->find(name); | ||||
|         if (i == v.attrs->end()) throw TypeError("attribute not found"); | ||||
|         forceValue(i->second); | ||||
|         v = i->second; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Pattern pat; Expr body; Pos pos; | ||||
|     if (matchFunction(e, pat, body, pos)) { | ||||
|         v.type = tLambda; | ||||
|         v.lambda.env = &env; | ||||
|         v.lambda.pat = pat; | ||||
|         v.lambda.body = body; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Expr fun, arg; | ||||
|     if (matchCall(e, fun, arg)) { | ||||
|         eval(env, fun, v); | ||||
| 
 | ||||
|         if (v.type == tPrimOp || v.type == tPrimOpApp) { | ||||
|             unsigned int argsLeft = | ||||
|                 v.type == tPrimOp ? v.primOp.arity : v.primOpApp.argsLeft; | ||||
|             if (argsLeft == 1) { | ||||
|                 /* We have all the arguments, so call the primop.
 | ||||
|                    First find the primop. */ | ||||
|                 Value * primOp = &v; | ||||
|                 while (primOp->type == tPrimOpApp) primOp = primOp->primOpApp.left; | ||||
|                 assert(primOp->type == tPrimOp); | ||||
|                 unsigned int arity = primOp->primOp.arity; | ||||
|                  | ||||
|                 Value vLastArg; | ||||
|                 mkThunk(vLastArg, env, arg); | ||||
| 
 | ||||
|                 /* Put all the arguments in an array. */ | ||||
|                 Value * vArgs[arity]; | ||||
|                 unsigned int n = arity - 1; | ||||
|                 vArgs[n--] = &vLastArg; | ||||
|                 for (Value * arg = &v; arg->type == tPrimOpApp; arg = arg->primOpApp.left) | ||||
|                     vArgs[n--] = arg->primOpApp.right; | ||||
| 
 | ||||
|                 /* And call the primop. */ | ||||
|                 primOp->primOp.fun(*this, vArgs, v); | ||||
|             } else { | ||||
|                 Value * v2 = allocValues(2); | ||||
|                 v2[0] = v; | ||||
|                 mkThunk(v2[1], env, arg); | ||||
|                 v.type = tPrimOpApp; | ||||
|                 v.primOpApp.left = &v2[0]; | ||||
|                 v.primOpApp.right = &v2[1]; | ||||
|                 v.primOpApp.argsLeft = argsLeft - 1; | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         if (v.type != tLambda) throw TypeError("expected function"); | ||||
| 
 | ||||
|         Env & env2(allocEnv()); | ||||
|         env2.up = &env; | ||||
| 
 | ||||
|         ATermList formals; ATerm ellipsis; | ||||
| 
 | ||||
|         if (matchVarPat(v.lambda.pat, name)) { | ||||
|             Value & vArg = env2.bindings[name]; | ||||
|             nrValues++; | ||||
|             mkThunk(vArg, env, arg); | ||||
|         } | ||||
| 
 | ||||
|         else if (matchAttrsPat(v.lambda.pat, formals, ellipsis, name)) { | ||||
|             Value * vArg; | ||||
|             Value vArg_; | ||||
| 
 | ||||
|             if (name == sNoAlias) | ||||
|                 vArg = &vArg_; | ||||
|             else { | ||||
|                 vArg = &env2.bindings[name]; | ||||
|                 nrValues++; | ||||
|             }                 | ||||
| 
 | ||||
|             eval(env, arg, *vArg); | ||||
|             forceAttrs(*vArg); | ||||
|              | ||||
|             /* For each formal argument, get the actual argument.  If
 | ||||
|                there is no matching actual argument but the formal | ||||
|                argument has a default, use the default. */ | ||||
|             unsigned int attrsUsed = 0; | ||||
|             for (ATermIterator i(formals); i; ++i) { | ||||
|                 Expr def; Sym name; | ||||
|                 DefaultValue def2; | ||||
|                 if (!matchFormal(*i, name, def2)) abort(); /* can't happen */ | ||||
| 
 | ||||
|                 Bindings::iterator j = vArg->attrs->find(name); | ||||
|                  | ||||
|                 Value & v = env2.bindings[name]; | ||||
|                 nrValues++; | ||||
|                  | ||||
|                 if (j == vArg->attrs->end()) { | ||||
|                     if (!matchDefaultValue(def2, def)) def = 0; | ||||
|                     if (def == 0) throw TypeError(format("the argument named `%1%' required by the function is missing") | ||||
|                         % aterm2String(name)); | ||||
|                     mkThunk(v, env2, def); | ||||
|                 } else { | ||||
|                     attrsUsed++; | ||||
|                     v.type = tCopy; | ||||
|                     v.val = &j->second; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /* Check that each actual argument is listed as a formal
 | ||||
|                argument (unless the attribute match specifies a | ||||
|                `...').  TODO: show the names of the | ||||
|                expected/unexpected arguments. */ | ||||
|             if (ellipsis == eFalse && attrsUsed != vArg->attrs->size()) | ||||
|                 throw TypeError("function called with unexpected argument"); | ||||
|         } | ||||
| 
 | ||||
|         else abort(); | ||||
|          | ||||
|         eval(env2, v.lambda.body, v); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Expr attrs; | ||||
|     if (matchWith(e, attrs, body, pos)) { | ||||
|         Env & env2(allocEnv()); | ||||
|         env2.up = &env; | ||||
| 
 | ||||
|         Value & vAttrs = env2.bindings[sWith]; | ||||
|         nrValues++; | ||||
|         eval(env, attrs, vAttrs); | ||||
|         forceAttrs(vAttrs); | ||||
|          | ||||
|         eval(env2, body, v); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchList(e, es)) { | ||||
|         v.type = tList; | ||||
|         v.list.length = ATgetLength(es); | ||||
|         v.list.elems = allocValues(v.list.length); | ||||
|         for (unsigned int n = 0; n < v.list.length; ++n, es = ATgetNext(es)) | ||||
|             mkThunk(v.list.elems[n], env, ATgetFirst(es)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchOpEq(e, e1, e2)) { | ||||
|         Value v1; eval(env, e1, v1); | ||||
|         Value v2; eval(env, e2, v2); | ||||
|         mkBool(v, eqValues(v1, v2)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchOpNEq(e, e1, e2)) { | ||||
|         Value v1; eval(env, e1, v1); | ||||
|         Value v2; eval(env, e2, v2); | ||||
|         mkBool(v, !eqValues(v1, v2)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchOpConcat(e, e1, e2)) { | ||||
|         Value v1; eval(env, e1, v1); | ||||
|         forceList(v1); | ||||
|         Value v2; eval(env, e2, v2); | ||||
|         forceList(v2); | ||||
|         v.type = tList; | ||||
|         v.list.length = v1.list.length + v2.list.length; | ||||
|         v.list.elems = allocValues(v.list.length); | ||||
|         /* !!! This loses sharing with the original lists.  We could
 | ||||
|            use a tCopy node, but that would use more memory. */ | ||||
|         for (unsigned int n = 0; n < v1.list.length; ++n) | ||||
|             v.list.elems[n] = v1.list.elems[n]; | ||||
|         for (unsigned int n = 0; n < v2.list.length; ++n) | ||||
|             v.list.elems[n + v1.list.length] = v2.list.elems[n]; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchConcatStrings(e, es)) { | ||||
|         unsigned int n = ATgetLength(es), j = 0; | ||||
|         Value vs[n]; | ||||
|         unsigned int len = 0; | ||||
|         for (ATermIterator i(es); i; ++i, ++j) { | ||||
|             eval(env, *i, vs[j]); | ||||
|             if (vs[j].type != tString) throw TypeError("string expected"); | ||||
|             len += strlen(vs[j].string.s); | ||||
|         } | ||||
|         char * s = new char[len + 1], * t = s; | ||||
|         for (unsigned int i = 0; i < j; ++i) { | ||||
|             strcpy(t, vs[i].string.s); | ||||
|             t += strlen(vs[i].string.s); | ||||
|         } | ||||
|         *t = 0; | ||||
|         mkString(v, s); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Expr e3; | ||||
|     if (matchIf(e, e1, e2, e3)) { | ||||
|         eval(env, evalBool(env, e1) ? e2 : e3, v); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (matchOpOr(e, e1, e2)) { | ||||
|         mkBool(v, evalBool(env, e1) || evalBool(env, e2)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     throw Error("unsupported term"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::eval(Expr e, Value & v) | ||||
| { | ||||
|     eval(baseEnv, e, v); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool EvalState::evalBool(Env & env, Expr e) | ||||
| { | ||||
|     Value v; | ||||
|     eval(env, e, v); | ||||
|     if (v.type != tBool) | ||||
|         throw TypeError(format("value is %1% while a Boolean was expected") % showType(v)); | ||||
|     return v.boolean; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::strictEval(Env & env, Expr e, Value & v) | ||||
| { | ||||
|     eval(env, e, v); | ||||
|      | ||||
|     if (v.type == tAttrs) { | ||||
|         foreach (Bindings::iterator, i, *v.attrs) | ||||
|             forceValue(i->second); | ||||
|     } | ||||
|      | ||||
|     else if (v.type == tList) { | ||||
|         for (unsigned int n = 0; n < v.list.length; ++n) | ||||
|             forceValue(v.list.elems[n]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::strictEval(Expr e, Value & v) | ||||
| { | ||||
|     strictEval(baseEnv, e, v); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::forceValue(Value & v) | ||||
| { | ||||
|     if (v.type == tThunk) { | ||||
|         v.type = tBlackhole; | ||||
|         eval(*v.thunk.env, v.thunk.expr, v); | ||||
|     } | ||||
|     else if (v.type == tCopy) { | ||||
|         forceValue(*v.val); | ||||
|         v = *v.val; | ||||
|     } | ||||
|     else if (v.type == tBlackhole) | ||||
|         throw EvalError("infinite recursion encountered"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| int EvalState::forceInt(Value & v) | ||||
| { | ||||
|     forceValue(v); | ||||
|     if (v.type != tInt) | ||||
|         throw TypeError(format("value is %1% while an integer was expected") % showType(v)); | ||||
|     return v.integer; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::forceAttrs(Value & v) | ||||
| { | ||||
|     forceValue(v); | ||||
|     if (v.type != tAttrs) | ||||
|         throw TypeError(format("value is %1% while an attribute set was expected") % showType(v)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::forceList(Value & v) | ||||
| { | ||||
|     forceValue(v); | ||||
|     if (v.type != tList) | ||||
|         throw TypeError(format("value is %1% while a list was expected") % showType(v)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool EvalState::eqValues(Value & v1, Value & v2) | ||||
| { | ||||
|     forceValue(v1); | ||||
|     forceValue(v2); | ||||
| 
 | ||||
|     if (v1.type != v2.type) return false; | ||||
|      | ||||
|     switch (v1.type) { | ||||
| 
 | ||||
|         case tInt: | ||||
|             return v1.integer == v2.integer; | ||||
| 
 | ||||
|         case tBool: | ||||
|             return v1.boolean == v2.boolean; | ||||
| 
 | ||||
|         case tString: | ||||
|             /* !!! contexts */ | ||||
|             return strcmp(v1.string.s, v2.string.s) == 0; | ||||
| 
 | ||||
|         case tList: | ||||
|             if (v2.type != tList || v1.list.length != v2.list.length) return false; | ||||
|             for (unsigned int n = 0; n < v1.list.length; ++n) | ||||
|                 if (!eqValues(v1.list.elems[n], v2.list.elems[n])) return false; | ||||
|             return true; | ||||
| 
 | ||||
|         case tAttrs: { | ||||
|             if (v2.type != tAttrs || v1.attrs->size() != v2.attrs->size()) return false; | ||||
|             Bindings::iterator i, j; | ||||
|             for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j) | ||||
|                 if (!eqValues(i->second, j->second)) return false; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         default: | ||||
|             throw Error("cannot compare given values"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| /* Pattern-match `pat' against `arg'.  The result is a set of
 | ||||
|    substitutions (`subs') and a set of recursive substitutions | ||||
|    (`subsRecursive').  The latter can refer to the variables bound by | ||||
|  | @ -683,9 +1216,6 @@ LocalNoInline(bool areEqual(EvalState & state, Expr e1, Expr e2)) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| static char * deepestStack = (char *) -1; /* for measuring stack usage */ | ||||
| 
 | ||||
| 
 | ||||
| Expr evalExpr2(EvalState & state, Expr e) | ||||
| { | ||||
|     /* When changing this function, make sure that you don't cause a
 | ||||
|  | @ -884,23 +1414,19 @@ Expr strictEvalExpr(EvalState & state, Expr e) | |||
|     ATermMap strictNormalForms; | ||||
|     return strictEvalExpr(state, e, strictNormalForms); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| /* Yes, this is a really bad idea... */ | ||||
| extern "C" { | ||||
|     unsigned long AT_calcAllocatedSize(); | ||||
| } | ||||
| 
 | ||||
| void printEvalStats(EvalState & state) | ||||
| { | ||||
|     char x; | ||||
|     bool showStats = getEnv("NIX_SHOW_STATS", "0") != "0"; | ||||
|     printMsg(showStats ? lvlInfo : lvlDebug, | ||||
|         format("evaluated %1% expressions, %2% cache hits, %3%%% efficiency, used %4% ATerm bytes, used %5% bytes of stack space") | ||||
|         % state.nrEvaluated % state.nrCached | ||||
|         % ((float) state.nrCached / (float) state.nrEvaluated * 100) | ||||
|         % AT_calcAllocatedSize() | ||||
|         % (&x - deepestStack)); | ||||
|         format("evaluated %1% expressions, used %2% bytes of stack space, allocated %3% values, allocated %4% environments") | ||||
|         % state.nrEvaluated | ||||
|         % (&x - deepestStack) | ||||
|         % state.nrValues | ||||
|         % state.nrEnvs); | ||||
|     if (showStats) | ||||
|         printATermMapStats(); | ||||
| } | ||||
|  |  | |||
|  | @ -11,6 +11,100 @@ namespace nix { | |||
| 
 | ||||
| 
 | ||||
| class Hash; | ||||
| class EvalState; | ||||
| struct Env; | ||||
| struct Value; | ||||
| 
 | ||||
| typedef ATerm Sym; | ||||
| 
 | ||||
| typedef std::map<Sym, Value> Bindings; | ||||
| 
 | ||||
| 
 | ||||
| struct Env | ||||
| { | ||||
|     Env * up; | ||||
|     Bindings bindings; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| typedef enum { | ||||
|     tInt = 1, | ||||
|     tBool, | ||||
|     tString, | ||||
|     tPath, | ||||
|     tNull, | ||||
|     tAttrs, | ||||
|     tList, | ||||
|     tThunk, | ||||
|     tLambda, | ||||
|     tCopy, | ||||
|     tBlackhole, | ||||
|     tPrimOp, | ||||
|     tPrimOpApp, | ||||
| } ValueType; | ||||
| 
 | ||||
| 
 | ||||
| typedef void (* PrimOp) (EvalState & state, Value * * args, Value & v); | ||||
| 
 | ||||
| 
 | ||||
| struct Value | ||||
| { | ||||
|     ValueType type; | ||||
|     union  | ||||
|     { | ||||
|         int integer; | ||||
|         bool boolean; | ||||
|         struct { | ||||
|             const char * s; | ||||
|             const char * * context; | ||||
|         } string; | ||||
|         Bindings * attrs; | ||||
|         struct { | ||||
|             unsigned int length; | ||||
|             Value * elems; | ||||
|         } list; | ||||
|         struct { | ||||
|             Env * env; | ||||
|             Expr expr; | ||||
|         } thunk; | ||||
|         struct { | ||||
|             Env * env; | ||||
|             Pattern pat; | ||||
|             Expr body; | ||||
|         } lambda; | ||||
|         Value * val; | ||||
|         struct { | ||||
|             PrimOp fun; | ||||
|             unsigned int arity; | ||||
|         } primOp; | ||||
|         struct { | ||||
|             Value * left, * right; | ||||
|             unsigned int argsLeft; | ||||
|         } primOpApp; | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| static inline void mkInt(Value & v, int n) | ||||
| { | ||||
|     v.type = tInt; | ||||
|     v.integer = n; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static inline void mkBool(Value & v, bool b) | ||||
| { | ||||
|     v.type = tBool; | ||||
|     v.boolean = b; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static inline void mkString(Value & v, const char * s) | ||||
| { | ||||
|     v.type = tString; | ||||
|     v.string.s = s; | ||||
|     v.string.context = 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| typedef std::map<Path, PathSet> DrvRoots; | ||||
|  | @ -22,32 +116,69 @@ typedef std::map<Path, Path> SrcToStore; | |||
| 
 | ||||
| struct EvalState; | ||||
| 
 | ||||
| /* Note: using a ATermVector is safe here, since when we call a primop
 | ||||
|    we also have an ATermList on the stack. */ | ||||
| typedef Expr (* PrimOp) (EvalState &, const ATermVector & args); | ||||
| 
 | ||||
| std::ostream & operator << (std::ostream & str, Value & v); | ||||
| 
 | ||||
| 
 | ||||
| struct EvalState  | ||||
| { | ||||
|     ATermMap normalForms; | ||||
|     ATermMap primOps; | ||||
|     DrvRoots drvRoots; | ||||
|     DrvHashes drvHashes; /* normalised derivation hashes */ | ||||
|     SrcToStore srcToStore;  | ||||
| 
 | ||||
|     unsigned int nrEvaluated; | ||||
|     unsigned int nrCached; | ||||
|     unsigned long nrValues; | ||||
|     unsigned long nrEnvs; | ||||
|     unsigned long nrEvaluated; | ||||
| 
 | ||||
|     bool allowUnsafeEquality; | ||||
| 
 | ||||
|     EvalState(); | ||||
| 
 | ||||
|     void addPrimOps(); | ||||
|     /* Evaluate an expression to normal form, storing the result in
 | ||||
|        value `v'. */ | ||||
|     void eval(Expr e, Value & v); | ||||
|     void eval(Env & env, Expr e, Value & v); | ||||
| 
 | ||||
|     /* Evaluation the expression, then verify that it has the expected
 | ||||
|        type. */ | ||||
|     bool evalBool(Env & env, Expr e); | ||||
| 
 | ||||
|     /* Evaluate an expression, and recursively evaluate list elements
 | ||||
|        and attributes. */ | ||||
|     void strictEval(Expr e, Value & v); | ||||
|     void strictEval(Env & env, Expr e, Value & v); | ||||
| 
 | ||||
|     /* If `v' is a thunk, enter it and overwrite `v' with the result
 | ||||
|        of the evaluation of the thunk.  Otherwise, this is a no-op. */ | ||||
|     void forceValue(Value & v); | ||||
| 
 | ||||
|     /* Force `v', and then verify that it has the expected type. */ | ||||
|     int forceInt(Value & v); | ||||
|     void forceAttrs(Value & v); | ||||
|     void forceList(Value & v); | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     /* The base environment, containing the builtin functions and
 | ||||
|        values. */ | ||||
|     Env & baseEnv; | ||||
| 
 | ||||
|     void createBaseEnv(); | ||||
|      | ||||
|     void addPrimOp(const string & name, | ||||
|         unsigned int arity, PrimOp primOp); | ||||
| 
 | ||||
|     /* Do a deep equality test between two values.  That is, list
 | ||||
|        elements and attributes are compared recursively. */ | ||||
|     bool eqValues(Value & v1, Value & v2); | ||||
| 
 | ||||
|     /* Allocation primitives. */ | ||||
|     Value * allocValues(unsigned int count); | ||||
|     Env & allocEnv(); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| /* Evaluate an expression to normal form. */ | ||||
| Expr evalExpr(EvalState & state, Expr e); | ||||
| 
 | ||||
|  | @ -86,6 +217,7 @@ Path coerceToPath(EvalState & state, Expr e, PathSet & context); | |||
|    value or has a binding in the `args' map.  Note: result is a call, | ||||
|    not a normal form; it should be evaluated by calling evalExpr(). */ | ||||
| Expr autoCallFunction(Expr e, const ATermMap & args); | ||||
| #endif | ||||
| 
 | ||||
| /* Print statistics. */ | ||||
| void printEvalStats(EvalState & state); | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| namespace nix { | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| string DrvInfo::queryDrvPath(EvalState & state) const | ||||
| { | ||||
|     if (drvPath == "") { | ||||
|  | @ -256,6 +257,7 @@ void getDerivations(EvalState & state, Expr e, const string & pathPrefix, | |||
|     Exprs doneExprs; | ||||
|     getDerivations(state, e, pathPrefix, autoArgs, drvs, doneExprs); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
|   | ||||
| } | ||||
|  |  | |||
|  | @ -375,7 +375,7 @@ expr_op | |||
|   | expr_op UPDATE expr_op { $$ = makeOpUpdate($1, $3); } | ||||
|   | expr_op '~' expr_op { $$ = makeSubPath($1, $3); } | ||||
|   | expr_op '?' ID { $$ = makeOpHasAttr($1, $3); } | ||||
|   | expr_op '+' expr_op { $$ = makeOpPlus($1, $3); } | ||||
|   | expr_op '+' expr_op { $$ = makeConcatStrings(ATmakeList2($1, $3)); } | ||||
|   | expr_op CONCAT expr_op { $$ = makeOpConcat($1, $3); } | ||||
|   | expr_app | ||||
|   ; | ||||
|  | @ -513,7 +513,7 @@ static Expr parse(EvalState & state, | |||
|     if (res) throw ParseError(data.error); | ||||
| 
 | ||||
|     try { | ||||
|         checkVarDefs(state.primOps, data.result); | ||||
|         // !!! checkVarDefs(state.primOps, data.result); | ||||
|     } catch (Error & e) { | ||||
|         throw ParseError(format("%1%, in `%2%'") % e.msg() % path); | ||||
|     } | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ | |||
| namespace nix { | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| /*************************************************************
 | ||||
|  * Constants | ||||
|  *************************************************************/ | ||||
|  | @ -895,18 +896,21 @@ static Expr prim_isList(EvalState & state, const ATermVector & args) | |||
|     ATermList list; | ||||
|     return makeBool(matchList(evalExpr(state, args[0]), list)); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| /* Return the first element of a list. */ | ||||
| static Expr prim_head(EvalState & state, const ATermVector & args) | ||||
| static void prim_head(EvalState & state, Value * * args, Value & v) | ||||
| { | ||||
|     ATermList list = evalList(state, args[0]); | ||||
|     if (ATisEmpty(list)) | ||||
|     state.forceList(*args[0]); | ||||
|     if (args[0]->list.length == 0) | ||||
|         throw Error("`head' called on an empty list"); | ||||
|     return evalExpr(state, ATgetFirst(list)); | ||||
|     state.forceValue(args[0]->list.elems[0]); | ||||
|     v = args[0]->list.elems[0]; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| /* Return a list consisting of everything but the the first element of
 | ||||
|    a list. */ | ||||
| static Expr prim_tail(EvalState & state, const ATermVector & args) | ||||
|  | @ -938,6 +942,7 @@ static Expr prim_length(EvalState & state, const ATermVector & args) | |||
|     ATermList list = evalList(state, args[0]); | ||||
|     return makeInt(ATgetLength(list)); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| /*************************************************************
 | ||||
|  | @ -945,14 +950,13 @@ static Expr prim_length(EvalState & state, const ATermVector & args) | |||
|  *************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| static Expr prim_add(EvalState & state, const ATermVector & args) | ||||
| static void prim_add(EvalState & state, Value * * args, Value & v) | ||||
| { | ||||
|     int i1 = evalInt(state, args[0]); | ||||
|     int i2 = evalInt(state, args[1]); | ||||
|     return makeInt(i1 + i2); | ||||
|     mkInt(v, state.forceInt(*args[0]) + state.forceInt(*args[1])); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| static Expr prim_sub(EvalState & state, const ATermVector & args) | ||||
| { | ||||
|     int i1 = evalInt(state, args[0]); | ||||
|  | @ -1102,6 +1106,7 @@ static Expr prim_compareVersions(EvalState & state, const ATermVector & args) | |||
|     int d = compareVersions(version1, version2); | ||||
|     return makeInt(d); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| /*************************************************************
 | ||||
|  | @ -1109,14 +1114,31 @@ static Expr prim_compareVersions(EvalState & state, const ATermVector & args) | |||
|  *************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::addPrimOps() | ||||
| void EvalState::createBaseEnv() | ||||
| { | ||||
|     addPrimOp("builtins", 0, prim_builtins); | ||||
|     baseEnv.up = 0; | ||||
| 
 | ||||
|     {   Value & v = baseEnv.bindings[toATerm("builtins")]; | ||||
|         v.type = tAttrs; | ||||
|         v.attrs = new Bindings; | ||||
|     } | ||||
|      | ||||
|     /* Add global constants such as `true' to the base environment. */ | ||||
|     {   Value & v = baseEnv.bindings[toATerm("true")]; | ||||
|         mkBool(v, true); | ||||
|     } | ||||
|     {   Value & v = baseEnv.bindings[toATerm("false")]; | ||||
|         mkBool(v, false); | ||||
|     } | ||||
|     {   Value & v = baseEnv.bindings[toATerm("null")]; | ||||
|         v.type = tNull; | ||||
|     } | ||||
|     {   Value & v = (*baseEnv.bindings[toATerm("builtins")].attrs)[toATerm("currentSystem")]; | ||||
|         mkString(v, thisSystem.c_str()); // !!! copy string
 | ||||
|     } | ||||
| 
 | ||||
| #if 0    
 | ||||
|     // Constants
 | ||||
|     addPrimOp("true", 0, prim_true); | ||||
|     addPrimOp("false", 0, prim_false); | ||||
|     addPrimOp("null", 0, prim_null); | ||||
|     addPrimOp("__currentSystem", 0, prim_currentSystem); | ||||
|     addPrimOp("__currentTime", 0, prim_currentTime); | ||||
| 
 | ||||
|  | @ -1135,7 +1157,6 @@ void EvalState::addPrimOps() | |||
|     addPrimOp("__getEnv", 1, prim_getEnv); | ||||
|     addPrimOp("__trace", 2, prim_trace); | ||||
|      | ||||
|      | ||||
|     // Expr <-> String
 | ||||
|     addPrimOp("__exprToString", 1, prim_exprToString); | ||||
|     addPrimOp("__stringToExpr", 1, prim_stringToExpr); | ||||
|  | @ -1169,13 +1190,17 @@ void EvalState::addPrimOps() | |||
| 
 | ||||
|     // Lists
 | ||||
|     addPrimOp("__isList", 1, prim_isList); | ||||
| #endif | ||||
|     addPrimOp("__head", 1, prim_head); | ||||
| #if 0 | ||||
|     addPrimOp("__tail", 1, prim_tail); | ||||
|     addPrimOp("map", 2, prim_map); | ||||
|     addPrimOp("__length", 1, prim_length); | ||||
| #endif | ||||
|      | ||||
|     // Integer arithmetic
 | ||||
|     addPrimOp("__add", 2, prim_add); | ||||
| #if 0 | ||||
|     addPrimOp("__sub", 2, prim_sub); | ||||
|     addPrimOp("__mul", 2, prim_mul); | ||||
|     addPrimOp("__div", 2, prim_div); | ||||
|  | @ -1191,6 +1216,7 @@ void EvalState::addPrimOps() | |||
|     // Versions
 | ||||
|     addPrimOp("__parseDrvName", 1, prim_parseDrvName); | ||||
|     addPrimOp("__compareVersions", 2, prim_compareVersions); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -37,6 +37,7 @@ static int rootNr = 0; | |||
| static bool indirectRoot = false; | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| static void printResult(EvalState & state, Expr e, | ||||
|     bool evalOnly, bool xmlOutput, const ATermMap & autoArgs) | ||||
| { | ||||
|  | @ -63,21 +64,28 @@ static void printResult(EvalState & state, Expr e, | |||
|         } | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| void processExpr(EvalState & state, const Strings & attrPaths, | ||||
|     bool parseOnly, bool strict, const ATermMap & autoArgs, | ||||
|     bool evalOnly, bool xmlOutput, Expr e) | ||||
| { | ||||
|     Value v; | ||||
|     state.strictEval(e, v); | ||||
|     std::cout << v << std::endl; | ||||
|      | ||||
| #if 0 | ||||
|     for (Strings::const_iterator i = attrPaths.begin(); i != attrPaths.end(); ++i) { | ||||
|         Expr e2 = findAlongAttrPath(state, *i, autoArgs, e); | ||||
|         if (!parseOnly) | ||||
|             if (strict) | ||||
|                 e2 = strictEvalExpr(state, e2); | ||||
|                 e2 = state.strictEval(e2); | ||||
|             else | ||||
|                 e2 = evalExpr(state, e2); | ||||
|         printResult(state, e2, evalOnly, xmlOutput, autoArgs); | ||||
|     } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue