* Some syntactic sugar for attribute sets: allow {x.y.z = ...;} as a
shorthand for {x = {y = {z = ...;};};}. This is especially useful for NixOS configuration files, e.g. { services = { sshd = { enable = true; port = 2022; }; }; } can now be written as { services.sshd.enable = true; services.sshd.port = 2022; } However, it is currently not permitted to write { services.sshd = {enable = true;}; services.sshd.port = 2022; } as this is considered a duplicate definition of `services.sshd'.
This commit is contained in:
parent
e42975490f
commit
d407d572fd
9 changed files with 118 additions and 26 deletions
|
@ -70,6 +70,7 @@ Bool | ATermBool | Expr |
|
|||
Null | | Expr |
|
||||
|
||||
Bind | string Expr Pos | ATerm |
|
||||
BindAttrPath | ATermList Expr Pos | ATerm | # desugared during parsing
|
||||
Bind | string Expr | ATerm | ObsoleteBind
|
||||
Inherit | Expr ATermList Pos | ATerm |
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ string showPos(ATerm pos)
|
|||
if (matchNoPos(pos)) return "undefined position";
|
||||
if (!matchPos(pos, path, line, column))
|
||||
throw badTerm("position expected", pos);
|
||||
return (format("`%1%', line %2%") % aterm2String(path) % line).str();
|
||||
return (format("`%1%:%2%:%3%'") % aterm2String(path) % line % column).str();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace nix {
|
|||
|
||||
|
||||
MakeError(EvalError, Error)
|
||||
MakeError(ParseError, Error)
|
||||
MakeError(AssertionError, EvalError)
|
||||
MakeError(ThrownError, AssertionError)
|
||||
MakeError(Abort, EvalError)
|
||||
|
|
|
@ -46,38 +46,88 @@ struct ParseData
|
|||
};
|
||||
|
||||
|
||||
static void duplicateAttr(ATerm name, ATerm pos, ATerm prevPos)
|
||||
static string showAttrPath(ATermList attrPath)
|
||||
{
|
||||
throw EvalError(format("duplicate attribute `%1%' at %2% (previously defined at %3%)")
|
||||
% aterm2String(name) % showPos(pos) % showPos (prevPos));
|
||||
string s;
|
||||
for (ATermIterator i(attrPath); i; ++i) {
|
||||
if (!s.empty()) s += '.';
|
||||
s += aterm2String(*i);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
struct Tree
|
||||
{
|
||||
Expr leaf; ATerm pos; bool recursive;
|
||||
typedef std::map<ATerm, Tree> Children;
|
||||
Children children;
|
||||
Tree() { leaf = 0; recursive = true; }
|
||||
};
|
||||
|
||||
|
||||
static ATermList buildAttrs(const Tree & t, ATermList & nonrec)
|
||||
{
|
||||
ATermList res = ATempty;
|
||||
for (Tree::Children::const_reverse_iterator i = t.children.rbegin();
|
||||
i != t.children.rend(); ++i)
|
||||
if (!i->second.recursive)
|
||||
nonrec = ATinsert(nonrec, makeBind(i->first, i->second.leaf, i->second.pos));
|
||||
else
|
||||
res = ATinsert(res, i->second.leaf
|
||||
? makeBind(i->first, i->second.leaf, i->second.pos)
|
||||
: makeBind(i->first, makeAttrs(buildAttrs(i->second, nonrec)), makeNoPos()));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static Expr fixAttrs(bool recursive, ATermList as)
|
||||
{
|
||||
ATermList bs = ATempty, cs = ATempty;
|
||||
ATermList * is = recursive ? &cs : &bs;
|
||||
|
||||
ATermMap used;
|
||||
Tree attrs;
|
||||
|
||||
for (ATermIterator i(as); i; ++i) {
|
||||
ATermList names; Expr src, e; ATerm name, pos;
|
||||
ATermList names, attrPath; Expr src, e; ATerm name, pos;
|
||||
|
||||
if (matchInherit(*i, src, names, pos)) {
|
||||
bool fromScope = matchScope(src);
|
||||
for (ATermIterator j(names); j; ++j) {
|
||||
Expr rhs = fromScope ? makeVar(*j) : makeSelect(src, *j);
|
||||
if (used.get(*j)) duplicateAttr(*j, pos, used[*j]);
|
||||
used.set(*j, pos);
|
||||
*is = ATinsert(*is, makeBind(*j, rhs, pos));
|
||||
if (attrs.children.find(*j) != attrs.children.end())
|
||||
throw ParseError(format("duplicate definition of attribute `%1%' at %2%")
|
||||
% showAttrPath(ATmakeList1(*j)) % showPos(pos));
|
||||
Tree & t(attrs.children[*j]);
|
||||
t.leaf = rhs; t.pos = pos; if (recursive) t.recursive = false;
|
||||
}
|
||||
} else if (matchBind(*i, name, e, pos)) {
|
||||
if (used.get(name)) duplicateAttr(name, pos, used[name]);
|
||||
used.set(name, pos);
|
||||
bs = ATinsert(bs, *i);
|
||||
} else abort(); /* can't happen */
|
||||
}
|
||||
|
||||
return recursive? makeRec(bs, cs) : makeAttrs(bs);
|
||||
else if (matchBindAttrPath(*i, attrPath, e, pos)) {
|
||||
|
||||
Tree * t(&attrs);
|
||||
|
||||
for (ATermIterator j(attrPath); j; ) {
|
||||
name = *j; ++j;
|
||||
if (t->leaf) throw ParseError(format("attribute set containing `%1%' at %2% already defined at %3%")
|
||||
% showAttrPath(attrPath) % showPos(pos) % showPos (t->pos));
|
||||
t = &(t->children[name]);
|
||||
}
|
||||
|
||||
if (t->leaf)
|
||||
throw ParseError(format("duplicate definition of attribute `%1%' at %2% and %3%")
|
||||
% showAttrPath(attrPath) % showPos(pos) % showPos (t->pos));
|
||||
if (!t->children.empty())
|
||||
throw ParseError(format("duplicate definition of attribute `%1%' at %2%")
|
||||
% showAttrPath(attrPath) % showPos(pos));
|
||||
|
||||
t->leaf = e; t->pos = pos;
|
||||
}
|
||||
|
||||
else abort(); /* can't happen */
|
||||
}
|
||||
|
||||
ATermList nonrec = ATempty;
|
||||
ATermList rec = buildAttrs(attrs, nonrec);
|
||||
|
||||
return recursive ? makeRec(rec, nonrec) : makeAttrs(rec);
|
||||
}
|
||||
|
||||
|
||||
|
@ -89,7 +139,7 @@ static void checkPatternVars(ATerm pos, ATermMap & map, Pattern pat)
|
|||
ATermBool ellipsis;
|
||||
if (matchVarPat(pat, name)) {
|
||||
if (map.get(name))
|
||||
throw EvalError(format("duplicate formal function argument `%1%' at %2%")
|
||||
throw ParseError(format("duplicate formal function argument `%1%' at %2%")
|
||||
% aterm2String(name) % showPos(pos));
|
||||
map.set(name, name);
|
||||
}
|
||||
|
@ -98,7 +148,7 @@ static void checkPatternVars(ATerm pos, ATermMap & map, Pattern pat)
|
|||
ATerm d1;
|
||||
if (!matchFormal(*i, name, d1)) abort();
|
||||
if (map.get(name))
|
||||
throw EvalError(format("duplicate formal function argument `%1%' at %2%")
|
||||
throw ParseError(format("duplicate formal function argument `%1%' at %2%")
|
||||
% aterm2String(name) % showPos(pos));
|
||||
map.set(name, name);
|
||||
}
|
||||
|
@ -267,7 +317,7 @@ static void freeAndUnprotect(void * p)
|
|||
%type <t> start expr expr_function expr_if expr_op
|
||||
%type <t> expr_app expr_select expr_simple bind inheritsrc formal
|
||||
%type <t> pattern pattern2
|
||||
%type <ts> binds ids expr_list string_parts ind_string_parts
|
||||
%type <ts> binds ids attrpath expr_list string_parts ind_string_parts
|
||||
%type <formals> formals
|
||||
%token <t> ID INT STR IND_STR PATH URI
|
||||
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL
|
||||
|
@ -300,7 +350,7 @@ expr_function
|
|||
| WITH expr ';' expr_function
|
||||
{ $$ = makeWith($2, $4, CUR_POS); }
|
||||
| LET binds IN expr_function
|
||||
{ $$ = makeSelect(fixAttrs(true, ATinsert($2, makeBind(toATerm("<let-body>"), $4, CUR_POS))), toATerm("<let-body>")); }
|
||||
{ $$ = makeSelect(fixAttrs(true, ATinsert($2, makeBindAttrPath(ATmakeList1(toATerm("<let-body>")), $4, CUR_POS))), toATerm("<let-body>")); }
|
||||
| expr_if
|
||||
;
|
||||
|
||||
|
@ -391,8 +441,8 @@ binds
|
|||
;
|
||||
|
||||
bind
|
||||
: ID '=' expr ';'
|
||||
{ $$ = makeBind($1, $3, CUR_POS); }
|
||||
: attrpath '=' expr ';'
|
||||
{ $$ = makeBindAttrPath(ATreverse($1), $3, CUR_POS); }
|
||||
| INHERIT inheritsrc ids ';'
|
||||
{ $$ = makeInherit($2, $3, CUR_POS); }
|
||||
;
|
||||
|
@ -404,6 +454,11 @@ inheritsrc
|
|||
|
||||
ids: ids ID { $$ = ATinsert($1, $2); } | { $$ = ATempty; };
|
||||
|
||||
attrpath
|
||||
: attrpath '.' ID { $$ = ATinsert($1, $3); }
|
||||
| ID { $$ = ATmakeList1($1); }
|
||||
;
|
||||
|
||||
expr_list
|
||||
: expr_list expr_select { $$ = ATinsert($1, $2); }
|
||||
| { $$ = ATempty; }
|
||||
|
@ -453,12 +508,12 @@ static Expr parse(EvalState & state,
|
|||
int res = yyparse(scanner, &data);
|
||||
yylex_destroy(scanner);
|
||||
|
||||
if (res) throw EvalError(data.error);
|
||||
if (res) throw ParseError(data.error);
|
||||
|
||||
try {
|
||||
checkVarDefs(state.primOps, data.result);
|
||||
} catch (Error & e) {
|
||||
throw EvalError(format("%1%, in `%2%'") % e.msg() % path);
|
||||
throw ParseError(format("%1%, in `%2%'") % e.msg() % path);
|
||||
}
|
||||
|
||||
return data.result;
|
||||
|
|
1
tests/lang/eval-okay-attrs3.exp
Normal file
1
tests/lang/eval-okay-attrs3.exp
Normal file
|
@ -0,0 +1 @@
|
|||
Str("foo 22 80 itchyxac",[])
|
22
tests/lang/eval-okay-attrs3.nix
Normal file
22
tests/lang/eval-okay-attrs3.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
let
|
||||
|
||||
config =
|
||||
{
|
||||
services.sshd.enable = true;
|
||||
services.sshd.port = 22;
|
||||
services.httpd.port = 80;
|
||||
hostName = "itchy";
|
||||
a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z = "x";
|
||||
foo = {
|
||||
a = "a";
|
||||
b.c = "c";
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
if config.services.sshd.enable
|
||||
then "foo ${toString config.services.sshd.port} ${toString config.services.httpd.port} ${config.hostName}"
|
||||
+ "${config.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z}"
|
||||
+ "${config.foo.a}"
|
||||
+ "${config.foo.b.c}"
|
||||
else "bar"
|
4
tests/lang/parse-fail-dup-attrs-4.nix
Normal file
4
tests/lang/parse-fail-dup-attrs-4.nix
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
services.ssh.port = 22;
|
||||
services.ssh.port = 23;
|
||||
}
|
4
tests/lang/parse-fail-dup-attrs-5.nix
Normal file
4
tests/lang/parse-fail-dup-attrs-5.nix
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
services.ssh = { enable = true; };
|
||||
services.ssh.port = 23;
|
||||
}
|
4
tests/lang/parse-fail-dup-attrs-6.nix
Normal file
4
tests/lang/parse-fail-dup-attrs-6.nix
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
services.ssh.port = 23;
|
||||
services.ssh = { enable = true; };
|
||||
}
|
Loading…
Reference in a new issue