Optimize primop calls
We now parse function applications as a vector of arguments rather than as a chain of binary applications, e.g. 'substring 1 2 "foo"' is parsed as ExprCall { .fun = <substring>, .args = [ <1>, <2>, <"foo"> ] } rather than ExprApp (ExprApp (ExprApp <substring> <1>) <2>) <"foo"> This allows primops to be called immediately (if enough arguments are supplied) without having to allocate intermediate tPrimOpApp values. On $ nix-instantiate --dry-run '<nixpkgs/nixos/release-combined.nix>' -A nixos.tests.simple.x86_64-linux this gives a substantial performance improvement: user CPU time: median = 0.9209 mean = 0.9218 stddev = 0.0073 min = 0.9086 max = 0.9340 [rejected, p=0.00000, Δ=-0.21433±0.00677] elapsed time: median = 1.0585 mean = 1.0584 stddev = 0.0024 min = 1.0523 max = 1.0623 [rejected, p=0.00000, Δ=-0.20594±0.00236] because it reduces the number of tPrimOpApp allocations from 551990 to 42534 (i.e. only small minority of primop calls are partially applied) which in turn reduces time spent in the garbage collector.
This commit is contained in:
parent
ab35cbd675
commit
81e7c40264
|
@ -594,6 +594,8 @@ Value * EvalState::addConstant(const string & name, Value & v)
|
||||||
Value * EvalState::addPrimOp(const string & name,
|
Value * EvalState::addPrimOp(const string & name,
|
||||||
size_t arity, PrimOpFun primOp)
|
size_t arity, PrimOpFun primOp)
|
||||||
{
|
{
|
||||||
|
assert(arity <= maxPrimOpArity);
|
||||||
|
|
||||||
auto name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
|
auto name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
|
||||||
Symbol sym = symbols.create(name2);
|
Symbol sym = symbols.create(name2);
|
||||||
|
|
||||||
|
@ -1251,144 +1253,182 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprApp::eval(EvalState & state, Env & env, Value & v)
|
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos)
|
||||||
{
|
|
||||||
/* FIXME: vFun prevents GCC from doing tail call optimisation. */
|
|
||||||
Value vFun;
|
|
||||||
e1->eval(state, env, vFun);
|
|
||||||
state.callFunction(vFun, *(e2->maybeThunk(state, env)), v, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
|
|
||||||
{
|
|
||||||
/* Figure out the number of arguments still needed. */
|
|
||||||
size_t argsDone = 0;
|
|
||||||
Value * primOp = &fun;
|
|
||||||
while (primOp->isPrimOpApp()) {
|
|
||||||
argsDone++;
|
|
||||||
primOp = primOp->primOpApp.left;
|
|
||||||
}
|
|
||||||
assert(primOp->isPrimOp());
|
|
||||||
auto arity = primOp->primOp->arity;
|
|
||||||
auto argsLeft = arity - argsDone;
|
|
||||||
|
|
||||||
if (argsLeft == 1) {
|
|
||||||
/* We have all the arguments, so call the primop. */
|
|
||||||
|
|
||||||
/* Put all the arguments in an array. */
|
|
||||||
Value * vArgs[arity];
|
|
||||||
auto n = arity - 1;
|
|
||||||
vArgs[n--] = &arg;
|
|
||||||
for (Value * arg = &fun; arg->isPrimOpApp(); arg = arg->primOpApp.left)
|
|
||||||
vArgs[n--] = arg->primOpApp.right;
|
|
||||||
|
|
||||||
/* And call the primop. */
|
|
||||||
nrPrimOpCalls++;
|
|
||||||
if (countCalls) primOpCalls[primOp->primOp->name]++;
|
|
||||||
primOp->primOp->fun(*this, pos, vArgs, v);
|
|
||||||
} else {
|
|
||||||
Value * fun2 = allocValue();
|
|
||||||
*fun2 = fun;
|
|
||||||
v.mkPrimOpApp(fun2, &arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & pos)
|
|
||||||
{
|
{
|
||||||
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
|
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
|
||||||
|
|
||||||
forceValue(fun, pos);
|
forceValue(fun, pos);
|
||||||
|
|
||||||
if (fun.isPrimOp() || fun.isPrimOpApp()) {
|
Value vCur(fun);
|
||||||
callPrimOp(fun, arg, v, pos);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fun.type() == nAttrs) {
|
auto makeAppChain = [&]()
|
||||||
auto found = fun.attrs->find(sFunctor);
|
{
|
||||||
if (found != fun.attrs->end()) {
|
vRes = vCur;
|
||||||
/* fun may be allocated on the stack of the calling function,
|
for (size_t i = 0; i < nrArgs; ++i) {
|
||||||
* but for functors we may keep a reference, so heap-allocate
|
auto fun2 = allocValue();
|
||||||
* a copy and use that instead.
|
*fun2 = vRes;
|
||||||
*/
|
vRes.mkPrimOpApp(fun2, args[i]);
|
||||||
auto & fun2 = *allocValue();
|
}
|
||||||
fun2 = fun;
|
};
|
||||||
/* !!! Should we use the attr pos here? */
|
|
||||||
Value v2;
|
|
||||||
callFunction(*found->value, fun2, v2, pos);
|
|
||||||
return callFunction(v2, arg, v, pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fun.isLambda())
|
while (nrArgs > 0) {
|
||||||
throwTypeError(pos, "attempt to call something which is not a function but %1%", fun);
|
|
||||||
|
|
||||||
ExprLambda & lambda(*fun.lambda.fun);
|
if (vCur.isLambda()) {
|
||||||
|
|
||||||
auto size =
|
ExprLambda & lambda(*vCur.lambda.fun);
|
||||||
(lambda.arg.empty() ? 0 : 1) +
|
|
||||||
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
|
||||||
Env & env2(allocEnv(size));
|
|
||||||
env2.up = fun.lambda.env;
|
|
||||||
|
|
||||||
size_t displ = 0;
|
auto size =
|
||||||
|
(lambda.arg.empty() ? 0 : 1) +
|
||||||
|
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
||||||
|
Env & env2(allocEnv(size));
|
||||||
|
env2.up = vCur.lambda.env;
|
||||||
|
|
||||||
if (!lambda.hasFormals())
|
size_t displ = 0;
|
||||||
env2.values[displ++] = &arg;
|
|
||||||
|
|
||||||
else {
|
if (!lambda.hasFormals())
|
||||||
forceAttrs(arg, pos);
|
env2.values[displ++] = args[0];
|
||||||
|
|
||||||
if (!lambda.arg.empty())
|
else {
|
||||||
env2.values[displ++] = &arg;
|
forceAttrs(*args[0], pos);
|
||||||
|
|
||||||
/* For each formal argument, get the actual argument. If
|
if (!lambda.arg.empty())
|
||||||
there is no matching actual argument but the formal
|
env2.values[displ++] = args[0];
|
||||||
argument has a default, use the default. */
|
|
||||||
size_t attrsUsed = 0;
|
/* For each formal argument, get the actual argument. If
|
||||||
for (auto & i : lambda.formals->formals) {
|
there is no matching actual argument but the formal
|
||||||
Bindings::iterator j = arg.attrs->find(i.name);
|
argument has a default, use the default. */
|
||||||
if (j == arg.attrs->end()) {
|
size_t attrsUsed = 0;
|
||||||
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
|
for (auto & i : lambda.formals->formals) {
|
||||||
lambda, i.name);
|
auto j = args[0]->attrs->get(i.name);
|
||||||
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
if (!j) {
|
||||||
|
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
|
||||||
|
lambda, i.name);
|
||||||
|
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
||||||
|
} else {
|
||||||
|
attrsUsed++;
|
||||||
|
env2.values[displ++] = j->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that each actual argument is listed as a formal
|
||||||
|
argument (unless the attribute match specifies a `...'). */
|
||||||
|
if (!lambda.formals->ellipsis && attrsUsed != args[0]->attrs->size()) {
|
||||||
|
/* Nope, so show the first unexpected argument to the
|
||||||
|
user. */
|
||||||
|
for (auto & i : *args[0]->attrs)
|
||||||
|
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
|
||||||
|
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
|
||||||
|
abort(); // can't happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nrFunctionCalls++;
|
||||||
|
if (countCalls) incrFunctionCall(&lambda);
|
||||||
|
|
||||||
|
/* Evaluate the body. */
|
||||||
|
try {
|
||||||
|
lambda.body->eval(*this, env2, vCur);
|
||||||
|
} catch (Error & e) {
|
||||||
|
if (loggerSettings.showTrace.get()) {
|
||||||
|
addErrorTrace(e, lambda.pos, "while evaluating %s",
|
||||||
|
(lambda.name.set()
|
||||||
|
? "'" + (string) lambda.name + "'"
|
||||||
|
: "anonymous lambda"));
|
||||||
|
addErrorTrace(e, pos, "from call site%s", "");
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
nrArgs--;
|
||||||
|
args += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (vCur.isPrimOp()) {
|
||||||
|
|
||||||
|
size_t argsLeft = vCur.primOp->arity;
|
||||||
|
|
||||||
|
if (nrArgs < argsLeft) {
|
||||||
|
/* We don't have enough arguments, so create a tPrimOpApp chain. */
|
||||||
|
makeAppChain();
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
attrsUsed++;
|
/* We have all the arguments, so call the primop. */
|
||||||
env2.values[displ++] = j->value;
|
nrPrimOpCalls++;
|
||||||
|
if (countCalls) primOpCalls[vCur.primOp->name]++;
|
||||||
|
vCur.primOp->fun(*this, pos, args, vCur);
|
||||||
|
|
||||||
|
nrArgs -= argsLeft;
|
||||||
|
args += argsLeft;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check that each actual argument is listed as a formal
|
else if (vCur.isPrimOpApp()) {
|
||||||
argument (unless the attribute match specifies a `...'). */
|
/* Figure out the number of arguments still needed. */
|
||||||
if (!lambda.formals->ellipsis && attrsUsed != arg.attrs->size()) {
|
size_t argsDone = 0;
|
||||||
/* Nope, so show the first unexpected argument to the
|
Value * primOp = &vCur;
|
||||||
user. */
|
while (primOp->isPrimOpApp()) {
|
||||||
for (auto & i : *arg.attrs)
|
argsDone++;
|
||||||
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
|
primOp = primOp->primOpApp.left;
|
||||||
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
|
}
|
||||||
abort(); // can't happen
|
assert(primOp->isPrimOp());
|
||||||
|
auto arity = primOp->primOp->arity;
|
||||||
|
auto argsLeft = arity - argsDone;
|
||||||
|
|
||||||
|
if (nrArgs < argsLeft) {
|
||||||
|
/* We still don't have enough arguments, so extend the tPrimOpApp chain. */
|
||||||
|
makeAppChain();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
/* We have all the arguments, so call the primop with
|
||||||
|
the previous and new arguments. */
|
||||||
|
|
||||||
|
Value * vArgs[arity];
|
||||||
|
auto n = argsDone;
|
||||||
|
for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left)
|
||||||
|
vArgs[--n] = arg->primOpApp.right;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < argsLeft; ++i)
|
||||||
|
vArgs[argsDone + i] = args[i];
|
||||||
|
|
||||||
|
nrPrimOpCalls++;
|
||||||
|
if (countCalls) primOpCalls[primOp->primOp->name]++;
|
||||||
|
primOp->primOp->fun(*this, pos, vArgs, vCur);
|
||||||
|
|
||||||
|
nrArgs -= argsLeft;
|
||||||
|
args += argsLeft;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (vCur.type() == nAttrs) {
|
||||||
|
if (auto functor = vCur.attrs->get(sFunctor)) {
|
||||||
|
/* 'vCur" may be allocated on the stack of the calling
|
||||||
|
function, but for functors we may keep a reference,
|
||||||
|
so heap-allocate a copy and use that instead. */
|
||||||
|
Value * args2[] = {allocValue()};
|
||||||
|
*args2[0] = vCur;
|
||||||
|
/* !!! Should we use the attr pos here? */
|
||||||
|
callFunction(*functor->value, 1, args2, vCur, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur);
|
||||||
}
|
}
|
||||||
|
|
||||||
nrFunctionCalls++;
|
vRes = vCur;
|
||||||
if (countCalls) incrFunctionCall(&lambda);
|
}
|
||||||
|
|
||||||
/* Evaluate the body. This is conditional on showTrace, because
|
|
||||||
catching exceptions makes this function not tail-recursive. */
|
void ExprCall::eval(EvalState & state, Env & env, Value & v)
|
||||||
if (loggerSettings.showTrace.get())
|
{
|
||||||
try {
|
Value vFun;
|
||||||
lambda.body->eval(*this, env2, v);
|
fun->eval(state, env, vFun);
|
||||||
} catch (Error & e) {
|
|
||||||
addErrorTrace(e, lambda.pos, "while evaluating %s",
|
Value * vArgs[args.size()];
|
||||||
(lambda.name.set()
|
for (size_t i = 0; i < args.size(); ++i)
|
||||||
? "'" + (string) lambda.name + "'"
|
vArgs[i] = args[i]->maybeThunk(state, env);
|
||||||
: "anonymous lambda"));
|
|
||||||
addErrorTrace(e, pos, "from call site%s", "");
|
state.callFunction(vFun, args.size(), vArgs, v, pos);
|
||||||
throw;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
fun.lambda.fun->body->eval(*this, env2, v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -277,6 +277,8 @@ private:
|
||||||
|
|
||||||
Value * addConstant(const string & name, Value & v);
|
Value * addConstant(const string & name, Value & v);
|
||||||
|
|
||||||
|
constexpr static size_t maxPrimOpArity = 3;
|
||||||
|
|
||||||
Value * addPrimOp(const string & name,
|
Value * addPrimOp(const string & name,
|
||||||
size_t arity, PrimOpFun primOp);
|
size_t arity, PrimOpFun primOp);
|
||||||
|
|
||||||
|
@ -316,8 +318,14 @@ public:
|
||||||
|
|
||||||
bool isFunctor(Value & fun);
|
bool isFunctor(Value & fun);
|
||||||
|
|
||||||
void callFunction(Value & fun, Value & arg, Value & v, const Pos & pos);
|
// FIXME: use std::span
|
||||||
void callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos);
|
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos);
|
||||||
|
|
||||||
|
void callFunction(Value & fun, Value & arg, Value & vRes, const Pos & pos)
|
||||||
|
{
|
||||||
|
Value * args[] = {&arg};
|
||||||
|
callFunction(fun, 1, args, vRes, pos);
|
||||||
|
}
|
||||||
|
|
||||||
/* Automatically call a function for which each argument has a
|
/* Automatically call a function for which each argument has a
|
||||||
default value or has a binding in the `args' map. */
|
default value or has a binding in the `args' map. */
|
||||||
|
|
|
@ -64,6 +64,7 @@ static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: optimize
|
||||||
static Expr * unescapeStr(SymbolTable & symbols, const char * s, size_t length)
|
static Expr * unescapeStr(SymbolTable & symbols, const char * s, size_t length)
|
||||||
{
|
{
|
||||||
string t;
|
string t;
|
||||||
|
|
|
@ -143,6 +143,16 @@ void ExprLambda::show(std::ostream & str) const
|
||||||
str << ": " << *body << ")";
|
str << ": " << *body << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExprCall::show(std::ostream & str) const
|
||||||
|
{
|
||||||
|
str << '(' << *fun;
|
||||||
|
for (auto e : args) {
|
||||||
|
str << ' ';
|
||||||
|
str << *e;
|
||||||
|
}
|
||||||
|
str << ')';
|
||||||
|
}
|
||||||
|
|
||||||
void ExprLet::show(std::ostream & str) const
|
void ExprLet::show(std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "(let ";
|
str << "(let ";
|
||||||
|
@ -366,6 +376,13 @@ void ExprLambda::bindVars(const StaticEnv & env)
|
||||||
body->bindVars(newEnv);
|
body->bindVars(newEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExprCall::bindVars(const StaticEnv & env)
|
||||||
|
{
|
||||||
|
fun->bindVars(env);
|
||||||
|
for (auto e : args)
|
||||||
|
e->bindVars(env);
|
||||||
|
}
|
||||||
|
|
||||||
void ExprLet::bindVars(const StaticEnv & env)
|
void ExprLet::bindVars(const StaticEnv & env)
|
||||||
{
|
{
|
||||||
StaticEnv newEnv(false, &env, attrs->attrs.size());
|
StaticEnv newEnv(false, &env, attrs->attrs.size());
|
||||||
|
@ -461,5 +478,4 @@ size_t SymbolTable::totalSize() const
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,6 +248,17 @@ struct ExprLambda : Expr
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ExprCall : Expr
|
||||||
|
{
|
||||||
|
Expr * fun;
|
||||||
|
std::vector<Expr *> args;
|
||||||
|
Pos pos;
|
||||||
|
ExprCall(const Pos & pos, Expr * fun, std::vector<Expr *> && args)
|
||||||
|
: fun(fun), args(args), pos(pos)
|
||||||
|
{ }
|
||||||
|
COMMON_METHODS
|
||||||
|
};
|
||||||
|
|
||||||
struct ExprLet : Expr
|
struct ExprLet : Expr
|
||||||
{
|
{
|
||||||
ExprAttrs * attrs;
|
ExprAttrs * attrs;
|
||||||
|
@ -306,7 +317,6 @@ struct ExprOpNot : Expr
|
||||||
void eval(EvalState & state, Env & env, Value & v); \
|
void eval(EvalState & state, Env & env, Value & v); \
|
||||||
};
|
};
|
||||||
|
|
||||||
MakeBinOp(ExprApp, "")
|
|
||||||
MakeBinOp(ExprOpEq, "==")
|
MakeBinOp(ExprOpEq, "==")
|
||||||
MakeBinOp(ExprOpNEq, "!=")
|
MakeBinOp(ExprOpNEq, "!=")
|
||||||
MakeBinOp(ExprOpAnd, "&&")
|
MakeBinOp(ExprOpAnd, "&&")
|
||||||
|
|
|
@ -41,6 +41,12 @@ namespace nix {
|
||||||
{ };
|
{ };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper to prevent an expensive dynamic_cast call in expr_app.
|
||||||
|
struct App
|
||||||
|
{
|
||||||
|
Expr * e;
|
||||||
|
bool isCall;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#define YY_DECL int yylex \
|
#define YY_DECL int yylex \
|
||||||
|
@ -280,10 +286,12 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
|
||||||
char * uri;
|
char * uri;
|
||||||
std::vector<nix::AttrName> * attrNames;
|
std::vector<nix::AttrName> * attrNames;
|
||||||
std::vector<nix::Expr *> * string_parts;
|
std::vector<nix::Expr *> * string_parts;
|
||||||
|
nix::App app; // bool == whether this is an ExprCall
|
||||||
}
|
}
|
||||||
|
|
||||||
%type <e> start expr expr_function expr_if expr_op
|
%type <e> start expr expr_function expr_if expr_op
|
||||||
%type <e> expr_app expr_select expr_simple
|
%type <e> expr_select expr_simple
|
||||||
|
%type <app> expr_app
|
||||||
%type <list> expr_list
|
%type <list> expr_list
|
||||||
%type <attrs> binds
|
%type <attrs> binds
|
||||||
%type <formals> formals
|
%type <formals> formals
|
||||||
|
@ -353,13 +361,13 @@ expr_if
|
||||||
|
|
||||||
expr_op
|
expr_op
|
||||||
: '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
|
: '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
|
||||||
| '-' expr_op %prec NEGATE { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__sub")), new ExprInt(0)), $2); }
|
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); }
|
||||||
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
|
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
|
||||||
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
|
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
|
||||||
| expr_op '<' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__lessThan")), $1), $3); }
|
| expr_op '<' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
|
||||||
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__lessThan")), $3), $1)); }
|
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
|
||||||
| expr_op '>' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__lessThan")), $3), $1); }
|
| expr_op '>' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
|
||||||
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__lessThan")), $1), $3)); }
|
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
|
||||||
| expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); }
|
| expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); }
|
||||||
| expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); }
|
| expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); }
|
||||||
| expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); }
|
| expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); }
|
||||||
|
@ -367,17 +375,24 @@ expr_op
|
||||||
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
|
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
|
||||||
| expr_op '+' expr_op
|
| expr_op '+' expr_op
|
||||||
{ $$ = new ExprConcatStrings(CUR_POS, false, new vector<Expr *>({$1, $3})); }
|
{ $$ = new ExprConcatStrings(CUR_POS, false, new vector<Expr *>({$1, $3})); }
|
||||||
| expr_op '-' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__sub")), $1), $3); }
|
| expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
|
||||||
| expr_op '*' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__mul")), $1), $3); }
|
| expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
|
||||||
| expr_op '/' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.create("__div")), $1), $3); }
|
| expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); }
|
||||||
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); }
|
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); }
|
||||||
| expr_app
|
| expr_app { $$ = $1.e; }
|
||||||
;
|
;
|
||||||
|
|
||||||
expr_app
|
expr_app
|
||||||
: expr_app expr_select
|
: expr_app expr_select {
|
||||||
{ $$ = new ExprApp(CUR_POS, $1, $2); }
|
if ($1.isCall) {
|
||||||
| expr_select { $$ = $1; }
|
((ExprCall *) $1.e)->args.push_back($2);
|
||||||
|
$$ = $1;
|
||||||
|
} else {
|
||||||
|
$$.e = new ExprCall(CUR_POS, $1.e, {$2});
|
||||||
|
$$.isCall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| expr_select { $$.e = $1; $$.isCall = false; }
|
||||||
;
|
;
|
||||||
|
|
||||||
expr_select
|
expr_select
|
||||||
|
@ -388,7 +403,7 @@ expr_select
|
||||||
| /* Backwards compatibility: because Nixpkgs has a rarely used
|
| /* Backwards compatibility: because Nixpkgs has a rarely used
|
||||||
function named ‘or’, allow stuff like ‘map or [...]’. */
|
function named ‘or’, allow stuff like ‘map or [...]’. */
|
||||||
expr_simple OR_KW
|
expr_simple OR_KW
|
||||||
{ $$ = new ExprApp(CUR_POS, $1, new ExprVar(CUR_POS, data->symbols.create("or"))); }
|
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, data->symbols.create("or"))}); }
|
||||||
| expr_simple { $$ = $1; }
|
| expr_simple { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -412,10 +427,10 @@ expr_simple
|
||||||
}
|
}
|
||||||
| SPATH {
|
| SPATH {
|
||||||
string path($1 + 1, strlen($1) - 2);
|
string path($1 + 1, strlen($1) - 2);
|
||||||
$$ = new ExprApp(CUR_POS,
|
$$ = new ExprCall(CUR_POS,
|
||||||
new ExprApp(new ExprVar(data->symbols.create("__findFile")),
|
new ExprVar(data->symbols.create("__findFile")),
|
||||||
new ExprVar(data->symbols.create("__nixPath"))),
|
{new ExprVar(data->symbols.create("__nixPath")),
|
||||||
new ExprString(data->symbols.create(path)));
|
new ExprString(data->symbols.create(path))});
|
||||||
}
|
}
|
||||||
| URI {
|
| URI {
|
||||||
static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals);
|
static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals);
|
||||||
|
|
Loading…
Reference in a new issue