From 1b962fc7206bf3134b2a2097d3db0ee6d2863c47 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 Aug 2008 12:53:29 +0000 Subject: [PATCH] * @-patterns as in Haskell. For instance, in a function definition f = args @ {x, y, z}: ...; `args' refers to the argument as a whole, which is further pattern-matched against the attribute set pattern {x, y, z}. --- src/libexpr/eval.cc | 89 +++++++++++++++------------- src/libexpr/expr-to-xml.cc | 10 +++- src/libexpr/nixexpr-ast.def | 1 + src/libexpr/nixexpr.cc | 7 ++- src/libexpr/parser.y | 13 +++- tests/lang/eval-okay-patterns.exp | 1 + tests/lang/eval-okay-patterns.nix | 16 +++++ tests/lang/eval-okay-xml.exp.xml | 12 ++++ tests/lang/eval-okay-xml.nix | 2 + tests/lang/parse-fail-patterns-1.nix | 1 + 10 files changed, 106 insertions(+), 46 deletions(-) create mode 100644 tests/lang/eval-okay-patterns.exp create mode 100644 tests/lang/eval-okay-patterns.nix create mode 100644 tests/lang/parse-fail-patterns-1.nix diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 95a70ac27..ebcbac539 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -74,11 +74,16 @@ LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2, } +/* Pattern-match `pat' against `arg'. The result is a set of + substitutions (`subs') and a set of recursive substitutions + (`subsRecursive'). The latter can refer to the variables bound by + both `subs' and `subsRecursive'. */ static void patternMatch(EvalState & state, - Pattern pat, Expr arg, ATermMap & subs) + Pattern pat, Expr arg, ATermMap & subs, ATermMap & subsRecursive) { ATerm name; ATermList formals; + Pattern pat1, pat2; if (matchVarPat(pat, name)) subs.set(name, arg); @@ -87,60 +92,45 @@ static void patternMatch(EvalState & state, arg = evalExpr(state, arg); - unsigned int nrFormals = ATgetLength(formals); + /* Get the actual arguments. */ + ATermMap attrs; + queryAllAttrs(arg, attrs); + unsigned int nrAttrs = attrs.size(); - /* Get the actual arguments and put them in the substitution. - !!! shouldn't do this once we add `...'.*/ - ATermMap args; - queryAllAttrs(arg, args); - for (ATermMap::const_iterator i = args.begin(); i != args.end(); ++i) - subs.set(i->key, i->value); - - /* Get the formal arguments. */ - ATermVector defsUsed; - ATermList recAttrs = ATempty; + /* For each formal argument, get the actual argument. If + there is no matching actual argument but the formal + argument has a default, use the default. */ + unsigned int attrsUsed = 0; for (ATermIterator i(formals); i; ++i) { Expr name, def; DefaultValue def2; if (!matchFormal(*i, name, def2)) abort(); /* can't happen */ - Expr value = subs[name]; - + Expr value = attrs[name]; + if (value == 0) { if (!matchDefaultValue(def2, def)) def = 0; if (def == 0) throw TypeError(format("the argument named `%1%' required by the function is missing") % aterm2String(name)); - value = def; - defsUsed.push_back(name); - recAttrs = ATinsert(recAttrs, makeBind(name, def, makeNoPos())); + subsRecursive.set(name, def); + } else { + attrsUsed++; + attrs.remove(name); + subs.set(name, value); } } - - /* Make a recursive attribute set out of the (argument-name, - value) tuples. This is so that we can support default - parameters that refer to each other, e.g. ({x, y ? x + x}: - y) {x = "foo";} evaluates to "foofoo". */ - if (defsUsed.size() != 0) { - for (ATermMap::const_iterator i = args.begin(); i != args.end(); ++i) - recAttrs = ATinsert(recAttrs, makeBind(i->key, i->value, makeNoPos())); - Expr rec = makeRec(recAttrs, ATempty); - for (ATermVector::iterator i = defsUsed.begin(); i != defsUsed.end(); ++i) - subs.set(*i, makeSelect(rec, *i)); - } - - if (subs.size() != nrFormals) { - /* One or more actual arguments were not declared as - formal arguments. Find out which. */ - for (ATermIterator i(formals); i; ++i) { - Expr name; ATerm d1; - if (!matchFormal(*i, name, d1)) abort(); - subs.remove(name); - } + + /* Check that each actual argument is listed as a formal + argument. */ + if (attrsUsed != nrAttrs) throw TypeError(format("the function does not expect an argument named `%1%'") - % aterm2String(subs.begin()->key)); - } + % aterm2String(attrs.begin()->key)); + } + else if (matchAtPat(pat, pat1, pat2)) { + patternMatch(state, pat1, arg, subs, subsRecursive); + patternMatch(state, pat2, arg, subs, subsRecursive); } else abort(); @@ -151,9 +141,24 @@ static void patternMatch(EvalState & state, static Expr substArgs(EvalState & state, Expr body, Pattern pat, Expr arg) { - ATermMap subs(16); + ATermMap subs(16), subsRecursive(16); - patternMatch(state, pat, arg, subs); + patternMatch(state, pat, arg, subs, subsRecursive); + + /* If we used any default values, make a recursive attribute set + out of the (argument-name, value) tuples. This is so that we + can support default values that refer to each other, e.g. ({x, + y ? x + x}: y) {x = "foo";} evaluates to "foofoo". */ + if (subsRecursive.size() != 0) { + ATermList recAttrs = ATempty; + foreach (ATermMap::const_iterator, i, subs) + recAttrs = ATinsert(recAttrs, makeBind(i->key, i->value, makeNoPos())); + foreach (ATermMap::const_iterator, i, subsRecursive) + recAttrs = ATinsert(recAttrs, makeBind(i->key, i->value, makeNoPos())); + Expr rec = makeRec(recAttrs, ATempty); + foreach (ATermMap::const_iterator, i, subsRecursive) + subs.set(i->key, makeSelect(rec, i->key)); + } return substitute(Substitution(0, &subs), body); } diff --git a/src/libexpr/expr-to-xml.cc b/src/libexpr/expr-to-xml.cc index 5aa537b20..6ec906356 100644 --- a/src/libexpr/expr-to-xml.cc +++ b/src/libexpr/expr-to-xml.cc @@ -40,10 +40,11 @@ static void showAttrs(const ATermMap & attrs, XMLWriter & doc, } -static void printPatternAsXML(Pattern pat, XMLWriter & doc, PathSet & context) +static void printPatternAsXML(Pattern pat, XMLWriter & doc) { ATerm name; ATermList formals; + Pattern pat1, pat2; if (matchVarPat(pat, name)) doc.writeEmptyElement("varpat", singletonAttrs("name", aterm2String(name))); else if (matchAttrsPat(pat, formals)) { @@ -54,6 +55,11 @@ static void printPatternAsXML(Pattern pat, XMLWriter & doc, PathSet & context) doc.writeEmptyElement("attr", singletonAttrs("name", aterm2String(name))); } } + else if (matchAtPat(pat, pat1, pat2)) { + XMLOpenElement _(doc, "at"); + printPatternAsXML(pat1, doc); + printPatternAsXML(pat2, doc); + } } @@ -128,7 +134,7 @@ static void printTermAsXML(Expr e, XMLWriter & doc, PathSet & context, else if (matchFunction(e, pat, body, pos)) { XMLOpenElement _(doc, "function"); - printPatternAsXML(pat, doc, context); + printPatternAsXML(pat, doc); } else diff --git a/src/libexpr/nixexpr-ast.def b/src/libexpr/nixexpr-ast.def index 6b9cf95fa..670db1976 100644 --- a/src/libexpr/nixexpr-ast.def +++ b/src/libexpr/nixexpr-ast.def @@ -77,6 +77,7 @@ Scope | | Expr | VarPat | string | Pattern | AttrsPat | ATermList | Pattern | +AtPat | Pattern Pattern | Pattern | Formal | string DefaultValue | ATerm | diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 4744cdde3..b2d775abb 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -114,6 +114,7 @@ static void varsBoundByPattern(ATermMap & map, Pattern pat) { ATerm name; ATermList formals; + Pattern pat1, pat2; /* Use makeRemoved() so that it can be used directly in substitute(). */ if (matchVarPat(pat, name)) @@ -125,6 +126,10 @@ static void varsBoundByPattern(ATermMap & map, Pattern pat) map.set(name, makeRemoved()); } } + else if (matchAtPat(pat, pat1, pat2)) { + varsBoundByPattern(map, pat1); + varsBoundByPattern(map, pat2); + } else abort(); } @@ -354,7 +359,7 @@ Expr makeStr(const string & s, const PathSet & context) string showType(Expr e) { - ATerm t1, t2, t3; + ATerm t1, t2; ATermList l1; ATermBlob b1; int i1; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 7713021a9..c48aa34aa 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -211,7 +211,8 @@ static void freeAndUnprotect(void * p) } %type start expr expr_function expr_if expr_op -%type expr_app expr_select expr_simple bind inheritsrc formal pattern +%type expr_app expr_select expr_simple bind inheritsrc formal +%type pattern pattern2 %type binds ids expr_list formals string_parts ind_string_parts %token ID INT STR IND_STR PATH URI %token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL @@ -319,6 +320,11 @@ ind_string_parts ; pattern + : pattern2 '@' pattern { $$ = makeAtPat($1, $3); } + | pattern2 + ; + +pattern2 : ID { $$ = makeVarPat($1); } | '{' formals '}' { $$ = makeAttrsPat($2); } ; @@ -394,6 +400,7 @@ static void checkPatternVars(ATerm pos, ATermMap & map, Pattern pat) { ATerm name; ATermList formals; + Pattern pat1, pat2; if (matchVarPat(pat, name)) { if (map.get(name)) throw EvalError(format("duplicate formal function argument `%1%' at %2%") @@ -410,6 +417,10 @@ static void checkPatternVars(ATerm pos, ATermMap & map, Pattern pat) map.set(name, name); } } + else if (matchAtPat(pat, pat1, pat2)) { + checkPatternVars(pos, map, pat1); + checkPatternVars(pos, map, pat2); + } else abort(); } diff --git a/tests/lang/eval-okay-patterns.exp b/tests/lang/eval-okay-patterns.exp new file mode 100644 index 000000000..382a0bd26 --- /dev/null +++ b/tests/lang/eval-okay-patterns.exp @@ -0,0 +1 @@ +Str("abcxyzDDDDEFgh",[]) diff --git a/tests/lang/eval-okay-patterns.nix b/tests/lang/eval-okay-patterns.nix new file mode 100644 index 000000000..bcb9f3842 --- /dev/null +++ b/tests/lang/eval-okay-patterns.nix @@ -0,0 +1,16 @@ +let + + f = args@{x, y, z}: x + args.y + z; + + g = {x, y, z}@args: f args; + + h = {x ? "d", y ? x, z ? args.x}@args: x + y + z; + + i = args@args2: args.x + args2.y; + +in + f {x = "a"; y = "b"; z = "c";} + + g {x = "x"; y = "y"; z = "z";} + + h {x = "D";} + + h {x = "D"; y = "E"; z = "F";} + + i {x = "g"; y = "h";} diff --git a/tests/lang/eval-okay-xml.exp.xml b/tests/lang/eval-okay-xml.exp.xml index 4798e87f8..4f55c69a7 100644 --- a/tests/lang/eval-okay-xml.exp.xml +++ b/tests/lang/eval-okay-xml.exp.xml @@ -4,6 +4,18 @@ + + + + + + + + + + + + diff --git a/tests/lang/eval-okay-xml.nix b/tests/lang/eval-okay-xml.nix index 75420316d..44afbff1e 100644 --- a/tests/lang/eval-okay-xml.nix +++ b/tests/lang/eval-okay-xml.nix @@ -12,4 +12,6 @@ rec { id = x: x; + at = args@{x, y, z}: x; + } diff --git a/tests/lang/parse-fail-patterns-1.nix b/tests/lang/parse-fail-patterns-1.nix new file mode 100644 index 000000000..7b4061641 --- /dev/null +++ b/tests/lang/parse-fail-patterns-1.nix @@ -0,0 +1 @@ +args@{args, x, y, z}: x