From d4c3edfaba91a0e5e1e9528749e5b1e178511a6d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 27 Jun 2003 09:55:31 +0000 Subject: [PATCH] * Normalisation. --- src/eval.cc | 142 +++++++++++++++++++++++++++++++++++++++++++++------- src/eval.hh | 71 +++++++++++++++++++------- src/test.cc | 66 ++++++++++++++++++++---- 3 files changed, 233 insertions(+), 46 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index dc1fe3157..831464c18 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include "eval.hh" #include "globals.hh" @@ -91,7 +92,7 @@ static Hash computeDerived(Hash sourceHash, string targetName, } #endif - build: +// build: /* Fill in the environment. We don't bother freeing the strings, since we'll exec or die soon @@ -210,13 +211,14 @@ Hash hashExpr(Expr e) /* Evaluate an expression; the result must be a string. */ static string evalString(Expr e) { - e = evalValue(e); + e = whNormalise(e); char * s; if (ATmatch(e, "Str()", &s)) return s; else throw badTerm("string value expected", e); } +#if 0 /* Evaluate an expression; the result must be a value reference. */ static Hash evalHash(Expr e) { @@ -225,8 +227,10 @@ static Hash evalHash(Expr e) if (ATmatch(e, "Hash()", &s)) return parseHash(s); else throw badTerm("value reference expected", e); } +#endif +#if 0 /* Evaluate a list of arguments into normal form. */ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) { @@ -255,6 +259,7 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) argsNF = ATreverse(argsNF); } +#endif Expr substExpr(string x, Expr rep, Expr e) @@ -302,19 +307,13 @@ Expr substExpr(string x, Expr rep, Expr e) } +#if 0 Expr evalValue(Expr e) { char * s; Expr eBuildPlatform, eProg, e2, e3, e4; ATermList args; - /* Normal forms. */ - if (ATmatch(e, "Str()", &s) || - ATmatch(e, "Bool(True)") || - ATmatch(e, "Bool(False)") || - ATmatch(e, "Lam(, )", &s, &e2)) - return e; - /* Value references. */ if (ATmatch(e, "Hash()", &s)) { parseHash(s); /* i.e., throw exception if not valid */ @@ -329,14 +328,6 @@ Expr evalValue(Expr e) return evalValue(e3); } - /* Application. */ - if (ATmatch(e, "App(, )", &e2, &e3)) { - e2 = evalValue(e2); - if (!ATmatch(e2, "Lam(, )", &s, &e4)) - throw badTerm("expecting lambda", e2); - return evalValue(substExpr(s, e3, e4)); - } - /* Execution primitive. */ if (ATmatch(e, "Exec(, , [])", @@ -376,3 +367,120 @@ Expr evalValue(Expr e) /* Barf. */ throw badTerm("invalid expression", e); } +#endif + + +Expr whNormalise(Expr e) +{ + char * s; + Expr e2, e3, e4, e5; + + /* Normal forms. */ + if (ATmatch(e, "Str()", &s) || + ATmatch(e, "Bool(True)") || + ATmatch(e, "Bool(False)") || + ATmatch(e, "Lam(, )", &s, &e2) || + ATmatch(e, "File(, , )", &s, &e2, &e3) || + ATmatch(e, "Derive(, , , )", &e2, &e3, &e4, &e5)) + return e; + + /* Application. */ + if (ATmatch(e, "App(, )", &e2, &e3)) { + e2 = whNormalise(e2); + if (!ATmatch(e2, "Lam(, )", &s, &e4)) + throw badTerm("expecting lambda", e2); + return whNormalise(substExpr(s, e3, e4)); + } + + throw badTerm("invalid expression", e); +} + + +Expr dNormalise(Expr e) +{ + e = whNormalise(e); + /* !!! todo */ + return e; +} + + +Expr fNormalise(Expr e) +{ + e = dNormalise(e); + + char * s; + Expr e2, e3; + + if (ATmatch(e, "File(, , [])", &s, &e2, &e3)) { + + ATermList refs = (ATermList) e3, refs2 = ATempty; + while (!ATisEmpty(refs)) { + ATerm ref = ATgetFirst(refs); + refs2 = ATinsert(refs2, fNormalise(ref)); + refs = ATgetNext(refs); + } + refs2 = ATreverse(refs2); + + return ATmake("File(, , )", s, e2, refs2); + + } + + else return e; +} + + +void writeContent(string path, Content content) +{ + char * s; + + if (ATmatch(content, "Regular()", &s)) { + + int fd; /* !!! close on exception */ + fd = open(path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd == -1) + throw SysError("creating file " + path); + + int len = strlen(s); + if (write(fd, s, len) != len) + throw SysError("writing file " + path); + + close(fd); + } + + else throw badTerm("ill-formed content", content); +} + + +struct RStatus +{ + /* !!! the comparator of this hash should match the semantics of + the file system */ + map paths; +}; + + +static void realise2(RStatus & status, Expr e) +{ + char * s; + Content content; + ATermList refs; + + if (!ATmatch(e, "File(, , [])", &s, &content, &refs)) + throw badTerm("not f-normalised", e); + + string path(s); + + while (!ATisEmpty(refs)) { + realise2(status, ATgetFirst(refs)); + refs = ATgetNext(refs); + } + + writeContent(path, content); +} + + +void realise(Expr e) +{ + RStatus status; + realise2(status, e); +} diff --git a/src/eval.hh b/src/eval.hh index c1b2f2139..807f98f85 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -10,14 +10,14 @@ extern "C" { using namespace std; -/* Abstract syntax of Nix expressions. +/* \section{Abstract syntax of Nix expressions} An expression describes a (partial) state of the file system in a referentially transparent way. The operational effect of evaluating an expression is that the state described by the expression is realised. - File : String * Content * [Expr] -> Expr + File : Path * Content * [Expr] -> Expr File(path, content, refs) specifies a file object (its full path and contents), along with all file objects referenced by it (that @@ -25,12 +25,13 @@ using namespace std; self-referential. This prevents us from having to deal with cycles. - Derive : String * Expr * [Expr] * [String] -> Expr + Derive : String * Path * [Expr] * [Expr] * [Expr] -> Expr - Derive(platform, builder, ins, outs) specifies the creation of new - file objects (in paths declared by `outs') by the execution of a - program `builder' on a platform `platform'. This execution takes - place in a file system state and in an environment given by `ins'. + Derive(platform, builder, ins, outs, env) specifies the creation of + new file objects (in paths declared by `outs') by the execution of + a program `builder' on a platform `platform'. This execution takes + place in a file system state given by `ins'. `env' specifies a + mapping of strings to strings. Str : String -> Expr @@ -40,39 +41,73 @@ using namespace std; Tuples of expressions. - Regular : String -> Content - Directory : [(String, Content)] -> Content - Hash : String -> Content + [ !!! NOT IMPLEMENTED + Regular : String -> Content + Directory : [(String, Content)] -> Content + (this complicates unambiguous normalisation) + ] + CHash : Hash -> Content - File content, given either explicitly or implicitly through a cryptographic hash. + File content, given either in situ, or through an external reference + to the file system or url-space decorated with a hash to preserve purity. - The set of expressions in {\em $f$-normal form} is as follows: + DISCUSSION: the idea is that a Regular/Directory is interchangeable + with its CHash. This would appear to break referential + transparency, e.g., Derive(..., ..., [...CHash(h)...], ...) can + only be reduced in a context were the Regular/Directory equivalent + of Hash(h) is known. However, CHash should be viewed strictly as a + shorthand; that is, when we export an expression containing a + CHash, we should also export the file object referenced by that + CHash. - File : String * Content * [FExpr] -> FExpr - These are completely evaluated Nix expressions. + \section{Reduction rules} - The set of expressions in {\em $d$-normal form} is as follows: + ... + + + \section{Normals forms} + + An expression is in {\em weak head normal form} if it is a lambda, + a string or boolean value, or a File or Derive value. + + An expression is in {\em $d$-normal form} if it matches the + signature FExpr: File : String * Content * [DExpr] -> DExpr - Derive : String * DExpr * [Tup] * [String] -> DExpr + Derive : String * Path * [Tup] * [Tup2] -> DExpr Tup : Str * DExpr -> Tup Tup : Str * Str -> Tup + Tup : Str * Str -> Tup2 + Str : String -> Str These are Nix expressions in which the file system result of Derive expressions has not yet been computed. This is useful for, e.g., querying dependencies. + An expression is in {\em $f$-normal form} if it matches the + signature FExpr: + + File : String * Content * [FExpr] -> FExpr + + These are completely evaluated Nix expressions. + */ typedef ATerm Expr; +typedef ATerm Content; -/* Evaluate an expression. */ -Expr evalValue(Expr e); +/* Expression normalisation. */ +Expr whNormalise(Expr e); +Expr dNormalise(Expr e); +Expr fNormalise(Expr e); + +/* Realise a $f$-normalised expression in the file system. */ +void realise(Expr e); /* Return a canonical textual representation of an expression. */ string printExpr(Expr e); diff --git a/src/test.cc b/src/test.cc index 268b35d89..d912eaa6a 100644 --- a/src/test.cc +++ b/src/test.cc @@ -11,13 +11,27 @@ #include "globals.hh" -void evalTest(Expr e) +typedef Expr (* Normaliser) (Expr); + + +void eval(Normaliser n, Expr e) { - e = evalValue(e); + e = n(e); cout << (string) hashExpr(e) << ": " << printExpr(e) << endl; } +void evalFail(Normaliser n, Expr e) +{ + try { + e = n(e); + abort(); + } catch (Error e) { + cout << "error (expected): " << e.what() << endl; + } +} + + struct MySink : DumpSink { virtual void operator () (const unsigned char * data, unsigned int len) @@ -90,19 +104,48 @@ void runTests() /* Expression evaluation. */ - evalTest(ATmake("Str(\"Hello World\")")); - evalTest(ATmake("Bool(True)")); - evalTest(ATmake("Bool(False)")); - evalTest(ATmake("App(Lam(\"x\", Var(\"x\")), Str(\"Hello World\"))")); - evalTest(ATmake("App(App(Lam(\"x\", Lam(\"y\", Var(\"x\"))), Str(\"Hello World\")), Str(\"Hallo Wereld\"))")); - evalTest(ATmake("App(Lam(\"sys\", Lam(\"x\", [Var(\"x\"), Var(\"sys\")])), Str(\"i686-suse-linux\"))")); + eval(whNormalise, + ATmake("Str(\"Hello World\")")); + eval(whNormalise, + ATmake("Bool(True)")); + eval(whNormalise, + ATmake("Bool(False)")); + eval(whNormalise, + ATmake("App(Lam(\"x\", Var(\"x\")), Str(\"Hello World\"))")); + eval(whNormalise, + ATmake("App(App(Lam(\"x\", Lam(\"y\", Var(\"x\"))), Str(\"Hello World\")), Str(\"Hallo Wereld\"))")); + eval(whNormalise, + ATmake("App(Lam(\"sys\", Lam(\"x\", [Var(\"x\"), Var(\"sys\")])), Str(\"i686-suse-linux\"))")); + evalFail(whNormalise, + ATmake("Foo(123)")); + + string builder1fn = absPath("./test-builder-1.sh"); + Hash builder1h = hashFile(builder1fn); + + string fn1 = nixValues + "/builder-1.sh"; + Expr e1 = ATmake("File(, ExtFile(, ), [])", + fn1.c_str(), + builder1h.c_str(), + builder1fn.c_str()); + eval(fNormalise, e1); + + string fn2 = nixValues + "/refer.txt"; + Expr e2 = ATmake("File(, Regular(), [])", + fn2.c_str(), + ("I refer to " + fn1).c_str(), + e1); + eval(fNormalise, e2); + + realise(e2); + +#if 0 Hash builder1 = addValue("./test-builder-1.sh"); Expr e1 = ATmake("Exec(Str(), Hash(), [])", thisSystem.c_str(), ((string) builder1).c_str()); - evalTest(e1); + eval(e1); Hash builder2 = addValue("./test-builder-2.sh"); @@ -110,14 +153,15 @@ void runTests() "Exec(Str(), Hash(), [Tup(Str(\"src\"), )])", thisSystem.c_str(), ((string) builder2).c_str(), e1); - evalTest(e2); + eval(e2); Hash h3 = addValue("./test-expr-1.nix"); Expr e3 = ATmake("Deref(Hash())", ((string) h3).c_str()); - evalTest(e3); + eval(e3); deleteValue(h3); +#endif }