diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 538325601b..cd3edecaa7 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -247,6 +247,11 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, con throw EvalError(format(s) % s2 % s3); } +LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, const Pos & p1, const Pos & p2)) +{ + throw EvalError(format(s) % sym % p1 % p2); +} + LocalNoInlineNoReturn(void throwTypeError(const char * s)) { throw TypeError(s); @@ -557,12 +562,14 @@ void ExprPath::eval(EvalState & state, Env & env, Value & v) void ExprAttrs::eval(EvalState & state, Env & env, Value & v) { state.mkAttrs(v, attrs.size()); + Env *dynamicEnv = &env; if (recursive) { /* Create a new environment that contains the attributes in this `rec'. */ Env & env2(state.allocEnv(attrs.size())); env2.up = &env; + dynamicEnv = &env2; AttrDefs::iterator overrides = attrs.find(state.sOverrides); bool hasOverrides = overrides != attrs.end(); @@ -605,9 +612,24 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) } } - else { + else foreach (AttrDefs::iterator, i, attrs) v.attrs->push_back(Attr(i->first, i->second.e->maybeThunk(state, env), &i->second.pos)); + + /* dynamic attrs apply *after* rec and __overrides */ + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) { + Value nameVal; + i->nameExpr->eval(state, *dynamicEnv, nameVal); + state.forceStringNoCtx(nameVal); + Symbol nameSym = state.symbols.create(nameVal.string.s); + Bindings::iterator j = v.attrs->find(nameSym); + if (j != v.attrs->end()) + throwEvalError("dynamic attribute `%1%' at %2% already defined at %3%", nameSym, i->pos, *j->pos); + + i->valueExpr->setName(nameSym); + /* Keep sorted order so find can catch duplicates */ + v.attrs->insert(lower_bound(v.attrs->begin(), v.attrs->end(), Attr(nameSym, 0)), + Attr(nameSym, i->valueExpr->maybeThunk(state, *dynamicEnv), &i->pos)); } } diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index c7ac967968..a7ce58c4d9 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -61,6 +61,8 @@ void ExprAttrs::show(std::ostream & str) str << "inherit " << i->first << " " << "; "; else str << i->first << " = " << *i->second.e << "; "; + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) + str << "\"${" << *i->nameExpr << "}\" = " << *i->valueExpr << "; "; str << "}"; } @@ -227,8 +229,10 @@ void ExprOpHasAttr::bindVars(const StaticEnv & env) void ExprAttrs::bindVars(const StaticEnv & env) { + const StaticEnv *dynamicEnv = &env; if (recursive) { StaticEnv newEnv(false, &env); + dynamicEnv = &newEnv; unsigned int displ = 0; foreach (AttrDefs::iterator, i, attrs) @@ -241,6 +245,11 @@ void ExprAttrs::bindVars(const StaticEnv & env) else foreach (AttrDefs::iterator, i, attrs) i->second.e->bindVars(env); + + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) { + i->nameExpr->bindVars(*dynamicEnv); + i->valueExpr->bindVars(*dynamicEnv); + } } void ExprList::bindVars(const StaticEnv & env) diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 2ae2e2e96f..92c2ca8dc5 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -163,6 +163,14 @@ struct ExprAttrs : Expr }; typedef std::map AttrDefs; AttrDefs attrs; + struct DynamicAttrDef { + Expr * nameExpr; + Expr * valueExpr; + Pos pos; + DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos) : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { }; + }; + typedef std::vector DynamicAttrDefs; + DynamicAttrDefs dynamicAttrs; ExprAttrs() : recursive(false) { }; COMMON_METHODS }; @@ -251,7 +259,7 @@ struct ExprOpNot : Expr struct ExprBuiltin : Expr { Symbol name; - ExprBuiltin(Symbol name) : name(name) { }; + ExprBuiltin(const Symbol & name) : name(name) { }; COMMON_METHODS }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index d30882ac84..aa08e1a63e 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -39,6 +39,15 @@ namespace nix { { }; }; + struct AttrName + { + Symbol symbol; + Expr *expr; + AttrName(const Symbol & s) : symbol(s) {}; + AttrName(Expr *e) : expr(e) {}; + }; + + typedef std::vector AttrNames; } #define YY_DECL int yylex \ @@ -243,7 +252,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err char * id; // !!! -> Symbol char * path; char * uri; - std::vector * attrNames; + std::vector * attrpath; + std::vector * attrlist; std::vector * string_parts; } @@ -253,8 +263,10 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err %type binds %type formals %type formal -%type attrs attrpath -%type string_parts ind_string_parts +%type attrpath +%type attrs +%type string_parts_interpolated ind_string_parts +%type string_parts string_attr %type attr %token ID ATTRPATH %token STR IND_STR @@ -300,7 +312,11 @@ expr_function | WITH expr ';' expr_function { $$ = new ExprWith(CUR_POS, $2, $4); } | LET binds IN expr_function - { $$ = new ExprLet($2, $4); } + { if (!$2->dynamicAttrs.empty()) + throw ParseError(format("dynamic attributes not allowed in let at %1%") + % CUR_POS); + $$ = new ExprLet($2, $4); + } | expr_if ; @@ -322,7 +338,39 @@ expr_op | expr_op OR expr_op { $$ = new ExprOpOr($1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl($1, $3); } | expr_op UPDATE expr_op { $$ = new ExprOpUpdate($1, $3); } - | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } + | expr_op '?' attrpath + { AttrPath path; + vector::iterator i; + $$ = $1; + // All attrpaths have at least one attr + assert(!$3->empty()); + for (i = $3->begin(); i + 1 != $3->end(); i++) { + if (i->symbol.set()) { + path.push_back(i->symbol); + } else { + if (!path.empty()) { + $$ = new ExprSelect($$, path, new ExprAttrs()); + path.clear(); + } + $$ = new ExprIf( + new ExprOpAnd( + new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), + new ExprAttrs()); + } + } + if (i->symbol.set()) { + path.push_back(i->symbol); + $$ = new ExprOpHasAttr($$, path); + } else { + if (!path.empty()) + $$ = new ExprSelect($$, path, new ExprAttrs()); + $$ = new ExprOpAnd( + new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)); + } + } | expr_op '+' expr_op { vector * l = new vector; l->push_back($1); @@ -344,9 +392,58 @@ expr_app expr_select : expr_simple '.' attrpath - { $$ = new ExprSelect($1, *$3, 0); } + { AttrPath path; + $$ = $1; + foreach (vector::iterator, i, *$3) { + if (i->symbol.set()) { + path.push_back(i->symbol); + } else { + if (!path.empty()) { + $$ = new ExprSelect($$, path, 0); + path.clear(); + } + $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$); + } + } + if (!path.empty()) + $$ = new ExprSelect($$, path, 0); + } | expr_simple '.' attrpath OR_KW expr_select - { $$ = new ExprSelect($1, *$3, $5); } + { AttrPath path; + vector::iterator i; + $$ = $1; + // All attrpaths have at least one attr + assert(!$3->empty()); + for (i = $3->begin(); i + 1 != $3->end(); i++) { + if (i->symbol.set()) { + path.push_back(i->symbol); + } else { + if (!path.empty()) { + $$ = new ExprSelect($$, path, new ExprAttrs()); + path.clear(); + } + $$ = new ExprIf( + new ExprOpAnd( + new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), + new ExprAttrs()); + } + } + if (i->symbol.set()) { + path.push_back(i->symbol); + $$ = new ExprSelect($$, path, $5); + } else { + if (!path.empty()) + $$ = new ExprSelect($$, path, new ExprAttrs()); + $$ = new ExprIf( + new ExprOpAnd( + new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), + $5); + } + } | /* Backwards compatibility: because Nixpkgs has a rarely used function named ‘or’, allow stuff like ‘map or [...]’. */ expr_simple OR_KW @@ -362,12 +459,7 @@ expr_simple $$ = new ExprVar(CUR_POS, data->symbols.create($1)); } | INT { $$ = new ExprInt($1); } - | '"' string_parts '"' { - /* For efficiency, and to simplify parse trees a bit. */ - if ($2->empty()) $$ = new ExprString(data->symbols.create("")); - else if ($2->size() == 1 && dynamic_cast($2->front())) $$ = $2->front(); - else $$ = new ExprConcatStrings(true, $2); - } + | '"' string_parts '"' { $$ = $2; } | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE { $$ = stripIndentation(data->symbols, *$2); } @@ -400,9 +492,27 @@ expr_simple ; string_parts - : string_parts STR { $$ = $1; $1->push_back($2); } - | string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); } - | { $$ = new vector; } + : STR + | string_parts_interpolated { $$ = new ExprConcatStrings(true, $1); } + | { $$ = new ExprString(data->symbols.create("")) } + ; + +string_parts_interpolated + : string_parts_interpolated STR { $$ = $1; $1->push_back($2); } + | string_parts_interpolated DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); } + | STR DOLLAR_CURLY expr '}' + { + backToString(scanner); + $$ = new vector; + $$->push_back($1); + $$->push_back($3); + } + | DOLLAR_CURLY expr '}' + { + backToString(scanner); + $$ = new vector; + $$->push_back($2); + } ; ind_string_parts @@ -412,7 +522,43 @@ ind_string_parts ; binds - : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); } + : binds attrpath '=' expr ';' + { + ExprAttrs *curAttrs = $1; + AttrPath path; + vector::iterator i; + // All attrpaths have at least one attr + assert(!$2->empty()); + for (i = $2->begin(); i + 1 < $2->end(); i++) { + if (i->symbol.set()) { + path.push_back(i->symbol); + } else { + ExprAttrs *temp; + if (!path.empty()) { + temp = curAttrs; + curAttrs = new ExprAttrs; + addAttr(temp, path, curAttrs, makeCurPos(@2, data)); + } + path.clear(); + + temp = curAttrs; + curAttrs = new ExprAttrs; + temp->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, curAttrs, makeCurPos(@2, data))); + } + } + if (i->symbol.set()) { + path.push_back(i->symbol); + addAttr(curAttrs, path, $4, makeCurPos(@2, data)); + } else { + if (!path.empty()) { + ExprAttrs *temp = curAttrs; + curAttrs = new ExprAttrs; + addAttr(temp, path, curAttrs, makeCurPos(@2, data)); + } + curAttrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, $4, makeCurPos(@2, data))); + } + $$ = $1; + } | binds INHERIT attrs ';' { $$ = $1; foreach (AttrPath::iterator, i, *$3) { @@ -436,19 +582,49 @@ binds attrs : attrs attr { $$ = $1; $1->push_back(data->symbols.create($2)); /* !!! dangerous */ } + | attrs string_attr + { $$ = $1; + ExprString *str = dynamic_cast($2); + if (str) { + $$->push_back(str->s); + delete str; + } else + throw ParseError(format("dynamic attributes not allowed in inherit at %1%") + % makeCurPos(@2, data)); + } | { $$ = new vector; } ; attrpath - : attrpath '.' attr { $$ = $1; $1->push_back(data->symbols.create($3)); } - | attr { $$ = new vector; $$->push_back(data->symbols.create($1)); } + : attrpath '.' attr { $$ = $1; $1->push_back(AttrName(data->symbols.create($3))); } + | attrpath '.' string_attr + { $$ = $1; + ExprString *str = dynamic_cast($3); + if (str) { + $$->push_back(AttrName(str->s)); + delete str; + } else + $$->push_back(AttrName($3)); + } + | attr { $$ = new vector; $$->push_back(AttrName(data->symbols.create($1))); } + | string_attr + { $$ = new vector; + ExprString *str = dynamic_cast($1); + if (str) { + $$->push_back(AttrName(str->s)); + delete str; + } else + $$->push_back(AttrName($1)); + } ; attr : ID { $$ = $1; } | OR_KW { $$ = "or"; } - | '"' STR '"' - { $$ = strdup(((string) ((ExprString *) $2)->s).c_str()); delete $2; } + ; + +string_attr + : '"' string_parts '"' { $$ = $2; } ; expr_list diff --git a/tests/lang/eval-okay-dynamic-attrs.exp b/tests/lang/eval-okay-dynamic-attrs.exp new file mode 100644 index 0000000000..df8750afc0 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs.exp @@ -0,0 +1 @@ +{ binds = true; hasAttrs = true; multiAttrs = true; recBinds = true; selectAttrs = true; selectOrAttrs = true; } diff --git a/tests/lang/eval-okay-dynamic-attrs.nix b/tests/lang/eval-okay-dynamic-attrs.nix new file mode 100644 index 0000000000..ee02ac7e65 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs.nix @@ -0,0 +1,17 @@ +let + aString = "a"; + + bString = "b"; +in { + hasAttrs = { a.b = null; } ? "${aString}".b; + + selectAttrs = { a.b = true; }.a."${bString}"; + + selectOrAttrs = { }."${aString}" or true; + + binds = { "${aString}"."${bString}c" = true; }.a.bc; + + recBinds = rec { "${bString}" = a; a = true; }.b; + + multiAttrs = { "${aString}" = true; "${bString}" = false; }.a; +}