lix/src/libexpr/eval.cc
Eelco Dolstra 447089a5f6 * Catch SIGINT to terminate cleanly when the user tries to interrupt
Nix.  This is to prevent Berkeley DB from becoming wedged.

  Unfortunately it is not possible to throw C++ exceptions from a
  signal handler.  In fact, you can't do much of anything except
  change variables of type `volatile sig_atomic_t'.  So we set an
  interrupt flag in the signal handler and check it at various
  strategic locations in the code (by calling checkInterrupt()).
  Since this is unlikely to cover all cases (e.g., (semi-)infinite
  loops), sometimes SIGTERM may now be required to kill Nix.
2004-01-15 20:23:55 +00:00

269 lines
7.5 KiB
C++

#include "eval.hh"
#include "parser.hh"
#include "primops.hh"
EvalState::EvalState()
: normalForms(32768, 75)
{
blackHole = ATmake("BlackHole()");
if (!blackHole) throw Error("cannot build black hole");
nrEvaluated = nrCached = 0;
}
/* Substitute an argument set into the body of a function. */
static Expr substArgs(Expr body, ATermList formals, Expr arg)
{
ATMatcher m;
ATermMap subs;
Expr undefined = ATmake("Undefined");
/* Get the formal arguments. */
for (ATermIterator i(formals); i; ++i) {
Expr name, def;
if (atMatch(m, *i) >> "NoDefFormal" >> name)
subs.set(name, undefined);
else if (atMatch(m, *i) >> "DefFormal" >> name >> def)
subs.set(name, def);
else abort(); /* can't happen */
}
/* Get the actual arguments, and check that they match with the
formals. */
ATermMap args;
queryAllAttrs(arg, args);
for (ATermIterator i(args.keys()); i; ++i) {
Expr key = *i;
Expr cur = subs.get(key);
if (!cur)
throw badTerm(format("function has no formal argument `%1%'")
% aterm2String(key), arg);
subs.set(key, args.get(key));
}
/* Check that all arguments are defined. */
for (ATermIterator i(subs.keys()); i; ++i)
if (subs.get(*i) == undefined)
throw badTerm(format("formal argument `%1%' missing")
% aterm2String(*i), arg);
return substitute(subs, body);
}
/* Transform a mutually recursive set into a non-recursive set. Each
attribute is transformed into an expression that has all references
to attributes substituted with selection expressions on the
original set. E.g., e = `rec {x = f x y, y = x}' becomes `{x = f
(e.x) (e.y), y = e.x}'. */
ATerm expandRec(ATerm e, ATermList bnds)
{
ATMatcher m;
/* Create the substitution list. */
ATermMap subs;
for (ATermIterator i(bnds); i; ++i) {
string s;
Expr e2;
if (!(atMatch(m, *i) >> "Bind" >> s >> e2))
abort(); /* can't happen */
subs.set(s, ATmake("Select(<term>, <str>)", e, s.c_str()));
}
/* Create the non-recursive set. */
ATermMap as;
for (ATermIterator i(bnds); i; ++i) {
string s;
Expr e2;
if (!(atMatch(m, *i) >> "Bind" >> s >> e2))
abort(); /* can't happen */
as.set(s, substitute(subs, e2));
}
return makeAttrs(as);
}
string evalString(EvalState & state, Expr e)
{
e = evalExpr(state, e);
ATMatcher m;
string s;
if (!(atMatch(m, e) >> "Str" >> s))
throw badTerm("string expected", e);
return s;
}
Path evalPath(EvalState & state, Expr e)
{
e = evalExpr(state, e);
ATMatcher m;
string s;
if (!(atMatch(m, e) >> "Path" >> s))
throw badTerm("path expected", e);
return s;
}
bool evalBool(EvalState & state, Expr e)
{
e = evalExpr(state, e);
ATMatcher m;
if (atMatch(m, e) >> "Bool" >> "True") return true;
else if (atMatch(m, e) >> "Bool" >> "False") return false;
else throw badTerm("expecting a boolean", e);
}
Expr evalExpr2(EvalState & state, Expr e)
{
ATMatcher m;
Expr e1, e2, e3, e4;
string s1;
/* Normal forms. */
if (atMatch(m, e) >> "Str" ||
atMatch(m, e) >> "Path" ||
atMatch(m, e) >> "Uri" ||
atMatch(m, e) >> "Int" ||
atMatch(m, e) >> "Bool" ||
atMatch(m, e) >> "Function" ||
atMatch(m, e) >> "Attrs" ||
atMatch(m, e) >> "List")
return e;
/* Any encountered variables must be undeclared or primops. */
if (atMatch(m, e) >> "Var" >> s1) {
if (s1 == "null") return primNull(state);
return e;
}
/* Function application. */
if (atMatch(m, e) >> "Call" >> e1 >> e2) {
ATermList formals;
/* Evaluate the left-hand side. */
e1 = evalExpr(state, e1);
/* Is it a primop or a function? */
if (atMatch(m, e1) >> "Var" >> s1) {
if (s1 == "import") return primImport(state, e2);
if (s1 == "derivation") return primDerivation(state, e2);
if (s1 == "toString") return primToString(state, e2);
if (s1 == "baseNameOf") return primBaseNameOf(state, e2);
if (s1 == "isNull") return primIsNull(state, e2);
else throw badTerm("undefined variable/primop", e1);
}
else if (atMatch(m, e1) >> "Function" >> formals >> e4)
return evalExpr(state,
substArgs(e4, formals, evalExpr(state, e2)));
else throw badTerm("expecting a function or primop", e1);
}
/* Attribute selection. */
if (atMatch(m, e) >> "Select" >> e1 >> s1) {
Expr a = queryAttr(evalExpr(state, e1), s1);
if (!a) throw badTerm(format("missing attribute `%1%'") % s1, e);
return evalExpr(state, a);
}
/* Mutually recursive sets. */
ATermList bnds;
if (atMatch(m, e) >> "Rec" >> bnds)
return expandRec(e, bnds);
/* Let expressions `let {..., body = ...}' are just desugared
into `(rec {..., body = ...}).body'. */
if (atMatch(m, e) >> "LetRec" >> bnds)
return evalExpr(state, ATmake("Select(Rec(<term>), \"body\")", bnds));
/* Conditionals. */
if (atMatch(m, e) >> "If" >> e1 >> e2 >> e3) {
if (evalBool(state, e1))
return evalExpr(state, e2);
else
return evalExpr(state, e3);
}
/* Assertions. */
if (atMatch(m, e) >> "Assert" >> e1 >> e2) {
if (!evalBool(state, e1)) throw badTerm("guard failed", e);
return evalExpr(state, e2);
}
/* Generic equality. */
if (atMatch(m, e) >> "OpEq" >> e1 >> e2)
return makeBool(evalExpr(state, e1) == evalExpr(state, e2));
/* Generic inequality. */
if (atMatch(m, e) >> "OpNEq" >> e1 >> e2)
return makeBool(evalExpr(state, e1) != evalExpr(state, e2));
/* Negation. */
if (atMatch(m, e) >> "OpNot" >> e1)
return makeBool(!evalBool(state, e1));
/* Implication. */
if (atMatch(m, e) >> "OpImpl" >> e1 >> e2)
return makeBool(!evalBool(state, e1) || evalBool(state, e2));
/* Conjunction (logical AND). */
if (atMatch(m, e) >> "OpAnd" >> e1 >> e2)
return makeBool(evalBool(state, e1) && evalBool(state, e2));
/* Disjunction (logical OR). */
if (atMatch(m, e) >> "OpOr" >> e1 >> e2)
return makeBool(evalBool(state, e1) || evalBool(state, e2));
/* Barf. */
throw badTerm("invalid expression", e);
}
Expr evalExpr(EvalState & state, Expr e)
{
checkInterrupt();
startNest(nest, lvlVomit,
format("evaluating expression: %1%") % e);
state.nrEvaluated++;
/* Consult the memo table to quickly get the normal form of
previously evaluated expressions. */
Expr nf = state.normalForms.get(e);
if (nf) {
if (nf == state.blackHole)
throw badTerm("infinite recursion", e);
state.nrCached++;
return nf;
}
/* Otherwise, evaluate and memoize. */
state.normalForms.set(e, state.blackHole);
nf = evalExpr2(state, e);
state.normalForms.set(e, nf);
return nf;
}
Expr evalFile(EvalState & state, const Path & path)
{
startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path);
Expr e = parseExprFromFile(path);
return evalExpr(state, e);
}
void printEvalStats(EvalState & state)
{
debug(format("evaluated %1% expressions, %2% cache hits, %3%%% efficiency")
% state.nrEvaluated % state.nrCached
% ((float) state.nrCached / (float) state.nrEvaluated * 100));
}