* Normalisation.

This commit is contained in:
Eelco Dolstra 2003-06-27 09:55:31 +00:00
parent 3ec5252582
commit d4c3edfaba
3 changed files with 233 additions and 46 deletions

View file

@ -5,6 +5,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h>
#include "eval.hh" #include "eval.hh"
#include "globals.hh" #include "globals.hh"
@ -91,7 +92,7 @@ static Hash computeDerived(Hash sourceHash, string targetName,
} }
#endif #endif
build: // build:
/* Fill in the environment. We don't bother freeing /* Fill in the environment. We don't bother freeing
the strings, since we'll exec or die soon 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. */ /* Evaluate an expression; the result must be a string. */
static string evalString(Expr e) static string evalString(Expr e)
{ {
e = evalValue(e); e = whNormalise(e);
char * s; char * s;
if (ATmatch(e, "Str(<str>)", &s)) return s; if (ATmatch(e, "Str(<str>)", &s)) return s;
else throw badTerm("string value expected", e); else throw badTerm("string value expected", e);
} }
#if 0
/* Evaluate an expression; the result must be a value reference. */ /* Evaluate an expression; the result must be a value reference. */
static Hash evalHash(Expr e) static Hash evalHash(Expr e)
{ {
@ -225,8 +227,10 @@ static Hash evalHash(Expr e)
if (ATmatch(e, "Hash(<str>)", &s)) return parseHash(s); if (ATmatch(e, "Hash(<str>)", &s)) return parseHash(s);
else throw badTerm("value reference expected", e); else throw badTerm("value reference expected", e);
} }
#endif
#if 0
/* Evaluate a list of arguments into normal form. */ /* Evaluate a list of arguments into normal form. */
void evalArgs(ATermList args, ATermList & argsNF, Environment & env) void evalArgs(ATermList args, ATermList & argsNF, Environment & env)
{ {
@ -255,6 +259,7 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env)
argsNF = ATreverse(argsNF); argsNF = ATreverse(argsNF);
} }
#endif
Expr substExpr(string x, Expr rep, Expr e) 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) Expr evalValue(Expr e)
{ {
char * s; char * s;
Expr eBuildPlatform, eProg, e2, e3, e4; Expr eBuildPlatform, eProg, e2, e3, e4;
ATermList args; ATermList args;
/* Normal forms. */
if (ATmatch(e, "Str(<str>)", &s) ||
ATmatch(e, "Bool(True)") ||
ATmatch(e, "Bool(False)") ||
ATmatch(e, "Lam(<str>, <term>)", &s, &e2))
return e;
/* Value references. */ /* Value references. */
if (ATmatch(e, "Hash(<str>)", &s)) { if (ATmatch(e, "Hash(<str>)", &s)) {
parseHash(s); /* i.e., throw exception if not valid */ parseHash(s); /* i.e., throw exception if not valid */
@ -329,14 +328,6 @@ Expr evalValue(Expr e)
return evalValue(e3); return evalValue(e3);
} }
/* Application. */
if (ATmatch(e, "App(<term>, <term>)", &e2, &e3)) {
e2 = evalValue(e2);
if (!ATmatch(e2, "Lam(<str>, <term>)", &s, &e4))
throw badTerm("expecting lambda", e2);
return evalValue(substExpr(s, e3, e4));
}
/* Execution primitive. */ /* Execution primitive. */
if (ATmatch(e, "Exec(<term>, <term>, [<list>])", if (ATmatch(e, "Exec(<term>, <term>, [<list>])",
@ -376,3 +367,120 @@ Expr evalValue(Expr e)
/* Barf. */ /* Barf. */
throw badTerm("invalid expression", e); throw badTerm("invalid expression", e);
} }
#endif
Expr whNormalise(Expr e)
{
char * s;
Expr e2, e3, e4, e5;
/* Normal forms. */
if (ATmatch(e, "Str(<str>)", &s) ||
ATmatch(e, "Bool(True)") ||
ATmatch(e, "Bool(False)") ||
ATmatch(e, "Lam(<str>, <term>)", &s, &e2) ||
ATmatch(e, "File(<str>, <term>, <term>)", &s, &e2, &e3) ||
ATmatch(e, "Derive(<term>, <term>, <term>, <term>)", &e2, &e3, &e4, &e5))
return e;
/* Application. */
if (ATmatch(e, "App(<term>, <term>)", &e2, &e3)) {
e2 = whNormalise(e2);
if (!ATmatch(e2, "Lam(<str>, <term>)", &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(<str>, <term>, [<list>])", &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(<str>, <term>, <term>)", s, e2, refs2);
}
else return e;
}
void writeContent(string path, Content content)
{
char * s;
if (ATmatch(content, "Regular(<str>)", &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<string, Hash> paths;
};
static void realise2(RStatus & status, Expr e)
{
char * s;
Content content;
ATermList refs;
if (!ATmatch(e, "File(<str>, <term>, [<list>])", &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);
}

View file

@ -10,14 +10,14 @@ extern "C" {
using namespace std; 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 An expression describes a (partial) state of the file system in a
referentially transparent way. The operational effect of referentially transparent way. The operational effect of
evaluating an expression is that the state described by the evaluating an expression is that the state described by the
expression is realised. expression is realised.
File : String * Content * [Expr] -> Expr File : Path * Content * [Expr] -> Expr
File(path, content, refs) specifies a file object (its full path File(path, content, refs) specifies a file object (its full path
and contents), along with all file objects referenced by it (that 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 self-referential. This prevents us from having to deal with
cycles. cycles.
Derive : String * Expr * [Expr] * [String] -> Expr Derive : String * Path * [Expr] * [Expr] * [Expr] -> Expr
Derive(platform, builder, ins, outs) specifies the creation of new Derive(platform, builder, ins, outs, env) specifies the creation of
file objects (in paths declared by `outs') by the execution of a new file objects (in paths declared by `outs') by the execution of
program `builder' on a platform `platform'. This execution takes a program `builder' on a platform `platform'. This execution takes
place in a file system state and in an environment given by `ins'. place in a file system state given by `ins'. `env' specifies a
mapping of strings to strings.
Str : String -> Expr Str : String -> Expr
@ -40,39 +41,73 @@ using namespace std;
Tuples of expressions. Tuples of expressions.
Regular : String -> Content [ !!! NOT IMPLEMENTED
Directory : [(String, Content)] -> Content Regular : String -> Content
Hash : 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 File : String * Content * [DExpr] -> DExpr
Derive : String * DExpr * [Tup] * [String] -> DExpr Derive : String * Path * [Tup] * [Tup2] -> DExpr
Tup : Str * DExpr -> Tup Tup : Str * DExpr -> Tup
Tup : Str * Str -> Tup Tup : Str * Str -> Tup
Tup : Str * Str -> Tup2
Str : String -> Str Str : String -> Str
These are Nix expressions in which the file system result of Derive These are Nix expressions in which the file system result of Derive
expressions has not yet been computed. This is useful for, e.g., expressions has not yet been computed. This is useful for, e.g.,
querying dependencies. 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 Expr;
typedef ATerm Content;
/* Evaluate an expression. */ /* Expression normalisation. */
Expr evalValue(Expr e); 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. */ /* Return a canonical textual representation of an expression. */
string printExpr(Expr e); string printExpr(Expr e);

View file

@ -11,13 +11,27 @@
#include "globals.hh" #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; 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 struct MySink : DumpSink
{ {
virtual void operator () (const unsigned char * data, unsigned int len) virtual void operator () (const unsigned char * data, unsigned int len)
@ -90,19 +104,48 @@ void runTests()
/* Expression evaluation. */ /* Expression evaluation. */
evalTest(ATmake("Str(\"Hello World\")")); eval(whNormalise,
evalTest(ATmake("Bool(True)")); ATmake("Str(\"Hello World\")"));
evalTest(ATmake("Bool(False)")); eval(whNormalise,
evalTest(ATmake("App(Lam(\"x\", Var(\"x\")), Str(\"Hello World\"))")); ATmake("Bool(True)"));
evalTest(ATmake("App(App(Lam(\"x\", Lam(\"y\", Var(\"x\"))), Str(\"Hello World\")), Str(\"Hallo Wereld\"))")); eval(whNormalise,
evalTest(ATmake("App(Lam(\"sys\", Lam(\"x\", [Var(\"x\"), Var(\"sys\")])), Str(\"i686-suse-linux\"))")); 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(<str>, ExtFile(<str>, <str>), [])",
fn1.c_str(),
builder1h.c_str(),
builder1fn.c_str());
eval(fNormalise, e1);
string fn2 = nixValues + "/refer.txt";
Expr e2 = ATmake("File(<str>, Regular(<str>), [<term>])",
fn2.c_str(),
("I refer to " + fn1).c_str(),
e1);
eval(fNormalise, e2);
realise(e2);
#if 0
Hash builder1 = addValue("./test-builder-1.sh"); Hash builder1 = addValue("./test-builder-1.sh");
Expr e1 = ATmake("Exec(Str(<str>), Hash(<str>), [])", Expr e1 = ATmake("Exec(Str(<str>), Hash(<str>), [])",
thisSystem.c_str(), ((string) builder1).c_str()); thisSystem.c_str(), ((string) builder1).c_str());
evalTest(e1); eval(e1);
Hash builder2 = addValue("./test-builder-2.sh"); Hash builder2 = addValue("./test-builder-2.sh");
@ -110,14 +153,15 @@ void runTests()
"Exec(Str(<str>), Hash(<str>), [Tup(Str(\"src\"), <term>)])", "Exec(Str(<str>), Hash(<str>), [Tup(Str(\"src\"), <term>)])",
thisSystem.c_str(), ((string) builder2).c_str(), e1); thisSystem.c_str(), ((string) builder2).c_str(), e1);
evalTest(e2); eval(e2);
Hash h3 = addValue("./test-expr-1.nix"); Hash h3 = addValue("./test-expr-1.nix");
Expr e3 = ATmake("Deref(Hash(<str>))", ((string) h3).c_str()); Expr e3 = ATmake("Deref(Hash(<str>))", ((string) h3).c_str());
evalTest(e3); eval(e3);
deleteValue(h3); deleteValue(h3);
#endif
} }