forked from lix-project/lix
447089a5f6
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.
269 lines
7.5 KiB
C++
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));
|
|
}
|