From d407d572fdc72f4eb14cc0f37d7d61446425b663 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 15 May 2009 12:35:23 +0000 Subject: * 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'. --- src/libexpr/parser.y | 105 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 25 deletions(-) (limited to 'src/libexpr/parser.y') diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 9cdda3ee9662..2ee3833fe61f 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -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 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; + Tree attrs; - ATermMap used; - 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 */ + } + + 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 */ } - return recursive? makeRec(bs, cs) : makeAttrs(bs); + 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 start expr expr_function expr_if expr_op %type expr_app expr_select expr_simple bind inheritsrc formal %type pattern pattern2 -%type binds ids expr_list string_parts ind_string_parts +%type binds ids attrpath expr_list string_parts ind_string_parts %type formals %token 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(""), $4, CUR_POS))), toATerm("")); } + { $$ = makeSelect(fixAttrs(true, ATinsert($2, makeBindAttrPath(ATmakeList1(toATerm("")), $4, CUR_POS))), toATerm("")); } | 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; -- cgit 1.4.1