Dynamic attrs
This adds new syntax for attribute names: * attrs."${name}" => getAttr name attrs * attrs ? "${name}" => isAttrs attrs && hasAttr attrs name * attrs."${name}" or def => if attrs ? "${name}" then attrs."${name}" else def * { "${name}" = value; } => listToAttrs [{ inherit name value; }] Of course, it's a bit more complicated than that. The attribute chains can be arbitrarily long and contain combinations of static and dynamic parts (e.g. attrs."${foo}".bar."${baz}" or qux), which is relatively straightforward for the getAttrs/hasAttrs cases but is more complex for the listToAttrs case due to rules about duplicate attribute definitions. For attribute sets with dynamic attribute names, duplicate static attributes are detected at parse time while duplicate dynamic attributes are detected when the attribute set is forced. So, for example, { a = null; a.b = null; "${"c"}" = true; } will be a parse-time error, while { a = {}; "${"a"}".b = null; c = true; } will be an eval-time error (technically that case could theoretically be detected at parse time, but the general case would require full evaluation). Moreover, duplicate dynamic attributes are not allowed even in cases where they would be with static attributes ({ a.b.d = true; a.b.c = false; } is legal, but { a."${"b"}".d = true; a."${"b"}".c = false; } is not). This restriction might be relaxed in the future in cases where the static variant would not be an error, but it is not obvious that that is desirable. Finally, recursive attribute sets with dynamic attributes have the static attributes in scope but not the dynamic ones. So rec { a = true; "${"b"}" = a; } is equivalent to { a = true; b = true; } but rec { "${"a"}" = true; b = a; } would be an error or use a from the surrounding scope if it exists. Note that the getAttr, getAttr or default, and hasAttr are all implemented purely in the parser as syntactic sugar, while attribute sets with dynamic attribute names required changes to the AST to be implemented cleanly. This is an alternative solution to and closes #167 Signed-off-by: Shea Levy <shea@shealevy.com>
This commit is contained in:
parent
136f2f7046
commit
18fefacf7d
6 changed files with 256 additions and 23 deletions
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -163,6 +163,14 @@ struct ExprAttrs : Expr
|
|||
};
|
||||
typedef std::map<Symbol, AttrDef> 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<DynamicAttrDef> 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
|
||||
};
|
||||
|
||||
|
|
|
@ -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<AttrName> 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<nix::Symbol> * attrNames;
|
||||
std::vector<nix::AttrName> * attrpath;
|
||||
std::vector<nix::Symbol> * attrlist;
|
||||
std::vector<nix::Expr *> * string_parts;
|
||||
}
|
||||
|
||||
|
@ -253,8 +263,10 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
|
|||
%type <attrs> binds
|
||||
%type <formals> formals
|
||||
%type <formal> formal
|
||||
%type <attrNames> attrs attrpath
|
||||
%type <string_parts> string_parts ind_string_parts
|
||||
%type <attrpath> attrpath
|
||||
%type <attrlist> attrs
|
||||
%type <string_parts> string_parts_interpolated ind_string_parts
|
||||
%type <e> string_parts string_attr
|
||||
%type <id> attr
|
||||
%token <id> ID ATTRPATH
|
||||
%token <e> 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<AttrName>::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<Expr *> * l = new vector<Expr *>;
|
||||
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<AttrName>::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<AttrName>::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<ExprString *>($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<Expr *>; }
|
||||
: 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<Expr *>;
|
||||
$$->push_back($1);
|
||||
$$->push_back($3);
|
||||
}
|
||||
| DOLLAR_CURLY expr '}'
|
||||
{
|
||||
backToString(scanner);
|
||||
$$ = new vector<Expr *>;
|
||||
$$->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<AttrName>::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<ExprString *>($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<Symbol>; }
|
||||
;
|
||||
|
||||
attrpath
|
||||
: attrpath '.' attr { $$ = $1; $1->push_back(data->symbols.create($3)); }
|
||||
| attr { $$ = new vector<Symbol>; $$->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<ExprString *>($3);
|
||||
if (str) {
|
||||
$$->push_back(AttrName(str->s));
|
||||
delete str;
|
||||
} else
|
||||
$$->push_back(AttrName($3));
|
||||
}
|
||||
| attr { $$ = new vector<AttrName>; $$->push_back(AttrName(data->symbols.create($1))); }
|
||||
| string_attr
|
||||
{ $$ = new vector<AttrName>;
|
||||
ExprString *str = dynamic_cast<ExprString *>($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
|
||||
|
|
1
tests/lang/eval-okay-dynamic-attrs.exp
Normal file
1
tests/lang/eval-okay-dynamic-attrs.exp
Normal file
|
@ -0,0 +1 @@
|
|||
{ binds = true; hasAttrs = true; multiAttrs = true; recBinds = true; selectAttrs = true; selectOrAttrs = true; }
|
17
tests/lang/eval-okay-dynamic-attrs.nix
Normal file
17
tests/lang/eval-okay-dynamic-attrs.nix
Normal file
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue