about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2009-05-15T12·35+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2009-05-15T12·35+0000
commitd407d572fdc72f4eb14cc0f37d7d61446425b663 (patch)
tree039d1409f7bdd30c2e953cf894b06b858db3a6f3
parente42975490fa96e811c9fd80435ce20c26f8603f8 (diff)
* 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'.

-rw-r--r--src/libexpr/nixexpr-ast.def1
-rw-r--r--src/libexpr/nixexpr.cc2
-rw-r--r--src/libexpr/nixexpr.hh1
-rw-r--r--src/libexpr/parser.y105
-rw-r--r--tests/lang/eval-okay-attrs3.exp1
-rw-r--r--tests/lang/eval-okay-attrs3.nix22
-rw-r--r--tests/lang/parse-fail-dup-attrs-4.nix4
-rw-r--r--tests/lang/parse-fail-dup-attrs-5.nix4
-rw-r--r--tests/lang/parse-fail-dup-attrs-6.nix4
9 files changed, 118 insertions, 26 deletions
diff --git a/src/libexpr/nixexpr-ast.def b/src/libexpr/nixexpr-ast.def
index ba8162984156..00b5f5a13716 100644
--- a/src/libexpr/nixexpr-ast.def
+++ b/src/libexpr/nixexpr-ast.def
@@ -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 |
 
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index d96b5be97fb9..3675dcceaf66 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -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();
 }
     
 
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 43ac19f92a24..c7cdf46df974 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -11,6 +11,7 @@ namespace nix {
 
 
 MakeError(EvalError, Error)
+MakeError(ParseError, Error)
 MakeError(AssertionError, EvalError)
 MakeError(ThrownError, AssertionError)
 MakeError(Abort, EvalError)
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<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;
+    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 <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;
diff --git a/tests/lang/eval-okay-attrs3.exp b/tests/lang/eval-okay-attrs3.exp
new file mode 100644
index 000000000000..d2c7555c1f6c
--- /dev/null
+++ b/tests/lang/eval-okay-attrs3.exp
@@ -0,0 +1 @@
+Str("foo 22 80 itchyxac",[])
diff --git a/tests/lang/eval-okay-attrs3.nix b/tests/lang/eval-okay-attrs3.nix
new file mode 100644
index 000000000000..f29de11fe660
--- /dev/null
+++ b/tests/lang/eval-okay-attrs3.nix
@@ -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"
diff --git a/tests/lang/parse-fail-dup-attrs-4.nix b/tests/lang/parse-fail-dup-attrs-4.nix
new file mode 100644
index 000000000000..77417432b347
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-4.nix
@@ -0,0 +1,4 @@
+{
+  services.ssh.port = 22;
+  services.ssh.port = 23;
+}
diff --git a/tests/lang/parse-fail-dup-attrs-5.nix b/tests/lang/parse-fail-dup-attrs-5.nix
new file mode 100644
index 000000000000..f4b9efd0c596
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-5.nix
@@ -0,0 +1,4 @@
+{
+  services.ssh = { enable = true; };
+  services.ssh.port = 23;
+}
diff --git a/tests/lang/parse-fail-dup-attrs-6.nix b/tests/lang/parse-fail-dup-attrs-6.nix
new file mode 100644
index 000000000000..ae6d7a769305
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-6.nix
@@ -0,0 +1,4 @@
+{
+  services.ssh.port = 23;
+  services.ssh = { enable = true; };
+}