* 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:
Eelco Dolstra 2009-05-15 12:35:23 +00:00
parent e42975490f
commit d407d572fd
9 changed files with 118 additions and 26 deletions

View file

@ -70,6 +70,7 @@ Bool | ATermBool | Expr |
Null | | Expr | Null | | Expr |
Bind | string Expr Pos | ATerm | Bind | string Expr Pos | ATerm |
BindAttrPath | ATermList Expr Pos | ATerm | # desugared during parsing
Bind | string Expr | ATerm | ObsoleteBind Bind | string Expr | ATerm | ObsoleteBind
Inherit | Expr ATermList Pos | ATerm | Inherit | Expr ATermList Pos | ATerm |

View file

@ -19,7 +19,7 @@ string showPos(ATerm pos)
if (matchNoPos(pos)) return "undefined position"; if (matchNoPos(pos)) return "undefined position";
if (!matchPos(pos, path, line, column)) if (!matchPos(pos, path, line, column))
throw badTerm("position expected", pos); throw badTerm("position expected", pos);
return (format("`%1%', line %2%") % aterm2String(path) % line).str(); return (format("`%1%:%2%:%3%'") % aterm2String(path) % line % column).str();
} }

View file

@ -11,6 +11,7 @@ namespace nix {
MakeError(EvalError, Error) MakeError(EvalError, Error)
MakeError(ParseError, Error)
MakeError(AssertionError, EvalError) MakeError(AssertionError, EvalError)
MakeError(ThrownError, AssertionError) MakeError(ThrownError, AssertionError)
MakeError(Abort, EvalError) MakeError(Abort, EvalError)

View file

@ -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%)") string s;
% aterm2String(name) % showPos(pos) % showPos (prevPos)); 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) static Expr fixAttrs(bool recursive, ATermList as)
{ {
ATermList bs = ATempty, cs = ATempty; Tree attrs;
ATermList * is = recursive ? &cs : &bs;
ATermMap used;
for (ATermIterator i(as); i; ++i) { 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)) { if (matchInherit(*i, src, names, pos)) {
bool fromScope = matchScope(src); bool fromScope = matchScope(src);
for (ATermIterator j(names); j; ++j) { for (ATermIterator j(names); j; ++j) {
Expr rhs = fromScope ? makeVar(*j) : makeSelect(src, *j); Expr rhs = fromScope ? makeVar(*j) : makeSelect(src, *j);
if (used.get(*j)) duplicateAttr(*j, pos, used[*j]); if (attrs.children.find(*j) != attrs.children.end())
used.set(*j, pos); throw ParseError(format("duplicate definition of attribute `%1%' at %2%")
*is = ATinsert(*is, makeBind(*j, rhs, pos)); % 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; ATermBool ellipsis;
if (matchVarPat(pat, name)) { if (matchVarPat(pat, name)) {
if (map.get(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)); % aterm2String(name) % showPos(pos));
map.set(name, name); map.set(name, name);
} }
@ -98,7 +148,7 @@ static void checkPatternVars(ATerm pos, ATermMap & map, Pattern pat)
ATerm d1; ATerm d1;
if (!matchFormal(*i, name, d1)) abort(); if (!matchFormal(*i, name, d1)) abort();
if (map.get(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)); % aterm2String(name) % showPos(pos));
map.set(name, name); 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> start expr expr_function expr_if expr_op
%type <t> expr_app expr_select expr_simple bind inheritsrc formal %type <t> expr_app expr_select expr_simple bind inheritsrc formal
%type <t> pattern pattern2 %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 %type <formals> formals
%token <t> ID INT STR IND_STR PATH URI %token <t> ID INT STR IND_STR PATH URI
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL %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 | WITH expr ';' expr_function
{ $$ = makeWith($2, $4, CUR_POS); } { $$ = makeWith($2, $4, CUR_POS); }
| LET binds IN expr_function | 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 | expr_if
; ;
@ -391,8 +441,8 @@ binds
; ;
bind bind
: ID '=' expr ';' : attrpath '=' expr ';'
{ $$ = makeBind($1, $3, CUR_POS); } { $$ = makeBindAttrPath(ATreverse($1), $3, CUR_POS); }
| INHERIT inheritsrc ids ';' | INHERIT inheritsrc ids ';'
{ $$ = makeInherit($2, $3, CUR_POS); } { $$ = makeInherit($2, $3, CUR_POS); }
; ;
@ -404,6 +454,11 @@ inheritsrc
ids: ids ID { $$ = ATinsert($1, $2); } | { $$ = ATempty; }; ids: ids ID { $$ = ATinsert($1, $2); } | { $$ = ATempty; };
attrpath
: attrpath '.' ID { $$ = ATinsert($1, $3); }
| ID { $$ = ATmakeList1($1); }
;
expr_list expr_list
: expr_list expr_select { $$ = ATinsert($1, $2); } : expr_list expr_select { $$ = ATinsert($1, $2); }
| { $$ = ATempty; } | { $$ = ATempty; }
@ -453,12 +508,12 @@ static Expr parse(EvalState & state,
int res = yyparse(scanner, &data); int res = yyparse(scanner, &data);
yylex_destroy(scanner); yylex_destroy(scanner);
if (res) throw EvalError(data.error); if (res) throw ParseError(data.error);
try { try {
checkVarDefs(state.primOps, data.result); checkVarDefs(state.primOps, data.result);
} catch (Error & e) { } catch (Error & e) {
throw EvalError(format("%1%, in `%2%'") % e.msg() % path); throw ParseError(format("%1%, in `%2%'") % e.msg() % path);
} }
return data.result; return data.result;

View file

@ -0,0 +1 @@
Str("foo 22 80 itchyxac",[])

View 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"

View file

@ -0,0 +1,4 @@
{
services.ssh.port = 22;
services.ssh.port = 23;
}

View file

@ -0,0 +1,4 @@
{
services.ssh = { enable = true; };
services.ssh.port = 23;
}

View file

@ -0,0 +1,4 @@
{
services.ssh.port = 23;
services.ssh = { enable = true; };
}