1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 10:41:16 +02:00

* Refactoring: put the Nix expression evaluator in its own library so

that it can be used by multiple programs.
This commit is contained in:
Eelco Dolstra 2003-11-19 11:35:41 +00:00
parent 2be8b5917a
commit ac68840e79
15 changed files with 3 additions and 2 deletions

22
src/libexpr/Makefile.am Normal file
View file

@ -0,0 +1,22 @@
bin_PROGRAMS = nix-instantiate
nix_instantiate_SOURCES = nixexpr.cc parser.cc eval.cc primops.cc main.cc
nix_instantiate_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \
../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \
-lsglr -lATB -lconversion -lasfix2 -lmept -lATerm
AM_CXXFLAGS = \
-I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain
# Parse table generation.
parser.o: parse-table.h
parse-table.h: nix.tbl
../bin2c/bin2c nixParseTable < $< > $@ || (rm $@ && exit 1)
%.tbl: %.sdf
../../externals/inst/bin/sdf2table -s -i $< -o $@
CLEANFILES = parse-table.h nix.tbl

265
src/libexpr/eval.cc Normal file
View file

@ -0,0 +1,265 @@
#include "eval.hh"
#include "parser.hh"
#include "primops.hh"
EvalState::EvalState()
: normalForms(32768, 75)
{
blackHole = ATmake("BlackHole()");
if (!blackHole) throw Error("cannot build black hole");
nrEvaluated = nrCached = 0;
}
/* Substitute an argument set into the body of a function. */
static Expr substArgs(Expr body, ATermList formals, Expr arg)
{
ATMatcher m;
ATermMap subs;
Expr undefined = ATmake("Undefined");
/* Get the formal arguments. */
for (ATermIterator i(formals); i; ++i) {
Expr name, def;
if (atMatch(m, *i) >> "NoDefFormal" >> name)
subs.set(name, undefined);
else if (atMatch(m, *i) >> "DefFormal" >> name >> def)
subs.set(name, def);
else abort(); /* can't happen */
}
/* Get the actual arguments, and check that they match with the
formals. */
ATermMap args;
queryAllAttrs(arg, args);
for (ATermIterator i(args.keys()); i; ++i) {
Expr key = *i;
Expr cur = subs.get(key);
if (!cur)
throw badTerm(format("function has no formal argument `%1%'")
% aterm2String(key), arg);
subs.set(key, args.get(key));
}
/* Check that all arguments are defined. */
for (ATermIterator i(subs.keys()); i; ++i)
if (subs.get(*i) == undefined)
throw badTerm(format("formal argument `%1%' missing")
% aterm2String(*i), arg);
return substitute(subs, body);
}
/* Transform a mutually recursive set into a non-recursive set. Each
attribute is transformed into an expression that has all references
to attributes substituted with selection expressions on the
original set. E.g., e = `rec {x = f x y, y = x}' becomes `{x = f
(e.x) (e.y), y = e.x}'. */
ATerm expandRec(ATerm e, ATermList bnds)
{
ATMatcher m;
/* Create the substitution list. */
ATermMap subs;
for (ATermIterator i(bnds); i; ++i) {
string s;
Expr e2;
if (!(atMatch(m, *i) >> "Bind" >> s >> e2))
abort(); /* can't happen */
subs.set(s, ATmake("Select(<term>, <str>)", e, s.c_str()));
}
/* Create the non-recursive set. */
ATermMap as;
for (ATermIterator i(bnds); i; ++i) {
string s;
Expr e2;
if (!(atMatch(m, *i) >> "Bind" >> s >> e2))
abort(); /* can't happen */
as.set(s, substitute(subs, e2));
}
return makeAttrs(as);
}
string evalString(EvalState & state, Expr e)
{
e = evalExpr(state, e);
ATMatcher m;
string s;
if (!(atMatch(m, e) >> "Str" >> s))
throw badTerm("string expected", e);
return s;
}
Path evalPath(EvalState & state, Expr e)
{
e = evalExpr(state, e);
ATMatcher m;
string s;
if (!(atMatch(m, e) >> "Path" >> s))
throw badTerm("path expected", e);
return s;
}
bool evalBool(EvalState & state, Expr e)
{
e = evalExpr(state, e);
ATMatcher m;
if (atMatch(m, e) >> "Bool" >> "True") return true;
else if (atMatch(m, e) >> "Bool" >> "False") return false;
else throw badTerm("expecting a boolean", e);
}
Expr evalExpr2(EvalState & state, Expr e)
{
ATMatcher m;
Expr e1, e2, e3, e4;
string s1;
/* Normal forms. */
if (atMatch(m, e) >> "Str" ||
atMatch(m, e) >> "Path" ||
atMatch(m, e) >> "Uri" ||
atMatch(m, e) >> "Bool" ||
atMatch(m, e) >> "Function" ||
atMatch(m, e) >> "Attrs" ||
atMatch(m, e) >> "List")
return e;
/* Any encountered variables must be undeclared or primops. */
if (atMatch(m, e) >> "Var" >> s1) {
if (s1 == "null") return primNull(state);
return e;
}
/* Function application. */
if (atMatch(m, e) >> "Call" >> e1 >> e2) {
ATermList formals;
/* Evaluate the left-hand side. */
e1 = evalExpr(state, e1);
/* Is it a primop or a function? */
if (atMatch(m, e1) >> "Var" >> s1) {
if (s1 == "import") return primImport(state, e2);
if (s1 == "derivation") return primDerivation(state, e2);
if (s1 == "toString") return primToString(state, e2);
if (s1 == "baseNameOf") return primBaseNameOf(state, e2);
if (s1 == "isNull") return primIsNull(state, e2);
else throw badTerm("undefined variable/primop", e1);
}
else if (atMatch(m, e1) >> "Function" >> formals >> e4)
return evalExpr(state,
substArgs(e4, formals, evalExpr(state, e2)));
else throw badTerm("expecting a function or primop", e1);
}
/* Attribute selection. */
if (atMatch(m, e) >> "Select" >> e1 >> s1) {
Expr a = queryAttr(evalExpr(state, e1), s1);
if (!a) throw badTerm(format("missing attribute `%1%'") % s1, e);
return evalExpr(state, a);
}
/* Mutually recursive sets. */
ATermList bnds;
if (atMatch(m, e) >> "Rec" >> bnds)
return expandRec(e, bnds);
/* Let expressions `let {..., body = ...}' are just desugared
into `(rec {..., body = ...}).body'. */
if (atMatch(m, e) >> "LetRec" >> bnds)
return evalExpr(state, ATmake("Select(Rec(<term>), \"body\")", bnds));
/* Conditionals. */
if (atMatch(m, e) >> "If" >> e1 >> e2 >> e3) {
if (evalBool(state, e1))
return evalExpr(state, e2);
else
return evalExpr(state, e3);
}
/* Assertions. */
if (atMatch(m, e) >> "Assert" >> e1 >> e2) {
if (!evalBool(state, e1)) throw badTerm("guard failed", e);
return evalExpr(state, e2);
}
/* Generic equality. */
if (atMatch(m, e) >> "OpEq" >> e1 >> e2)
return makeBool(evalExpr(state, e1) == evalExpr(state, e2));
/* Generic inequality. */
if (atMatch(m, e) >> "OpNEq" >> e1 >> e2)
return makeBool(evalExpr(state, e1) != evalExpr(state, e2));
/* Negation. */
if (atMatch(m, e) >> "OpNot" >> e1)
return makeBool(!evalBool(state, e1));
/* Implication. */
if (atMatch(m, e) >> "OpImpl" >> e1 >> e2)
return makeBool(!evalBool(state, e1) || evalBool(state, e2));
/* Conjunction (logical AND). */
if (atMatch(m, e) >> "OpAnd" >> e1 >> e2)
return makeBool(evalBool(state, e1) && evalBool(state, e2));
/* Disjunction (logical OR). */
if (atMatch(m, e) >> "OpOr" >> e1 >> e2)
return makeBool(evalBool(state, e1) || evalBool(state, e2));
/* Barf. */
throw badTerm("invalid expression", e);
}
Expr evalExpr(EvalState & state, Expr e)
{
startNest(nest, lvlVomit,
format("evaluating expression: %1%") % e);
state.nrEvaluated++;
/* Consult the memo table to quickly get the normal form of
previously evaluated expressions. */
Expr nf = state.normalForms.get(e);
if (nf) {
if (nf == state.blackHole)
throw badTerm("infinite recursion", e);
state.nrCached++;
return nf;
}
/* Otherwise, evaluate and memoize. */
state.normalForms.set(e, state.blackHole);
nf = evalExpr2(state, e);
state.normalForms.set(e, nf);
return nf;
}
Expr evalFile(EvalState & state, const Path & path)
{
startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path);
Expr e = parseExprFromFile(path);
return evalExpr(state, e);
}
void printEvalStats(EvalState & state)
{
debug(format("evaluated %1% expressions, %2% cache hits, %3%%% efficiency")
% state.nrEvaluated % state.nrCached
% ((float) state.nrCached / (float) state.nrEvaluated * 100));
}

42
src/libexpr/eval.hh Normal file
View file

@ -0,0 +1,42 @@
#ifndef __EVAL_H
#define __EVAL_H
#include <map>
#include "aterm.hh"
#include "hash.hh"
#include "nixexpr.hh"
typedef map<Path, PathSet> DrvPaths;
typedef map<Path, Hash> DrvHashes;
struct EvalState
{
ATermMap normalForms;
DrvPaths drvPaths;
DrvHashes drvHashes; /* normalised derivation hashes */
Expr blackHole;
unsigned int nrEvaluated;
unsigned int nrCached;
EvalState();
};
/* Evaluate an expression to normal form. */
Expr evalExpr(EvalState & state, Expr e);
/* Evaluate an expression read from the given file to normal form. */
Expr evalFile(EvalState & state, const Path & path);
/* Specific results. */
string evalString(EvalState & state, Expr e);
Path evalPath(EvalState & state, Expr e);
/* Print statistics. */
void printEvalStats(EvalState & state);
#endif /* !__EVAL_H */

215
src/libexpr/fix-expr.cc Normal file
View file

@ -0,0 +1,215 @@
#include "fix-expr.hh"
#include "expr.hh"
ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct)
{
table = ATtableCreate(initialSize, maxLoadPct);
if (!table) throw Error("cannot create ATerm table");
}
ATermMap::ATermMap(const ATermMap & map)
: table(0)
{
ATermList keys = map.keys();
/* !!! adjust allocation for load pct */
table = ATtableCreate(ATgetLength(keys), map.maxLoadPct);
if (!table) throw Error("cannot create ATerm table");
for (ATermIterator i(keys); i; ++i)
set(*i, map.get(*i));
}
ATermMap::~ATermMap()
{
if (table) ATtableDestroy(table);
}
void ATermMap::set(ATerm key, ATerm value)
{
return ATtablePut(table, key, value);
}
void ATermMap::set(const string & key, ATerm value)
{
set(string2ATerm(key), value);
}
ATerm ATermMap::get(ATerm key) const
{
return ATtableGet(table, key);
}
ATerm ATermMap::get(const string & key) const
{
return get(string2ATerm(key));
}
void ATermMap::remove(ATerm key)
{
ATtableRemove(table, key);
}
void ATermMap::remove(const string & key)
{
remove(string2ATerm(key));
}
ATermList ATermMap::keys() const
{
ATermList keys = ATtableKeys(table);
if (!keys) throw Error("cannot query aterm map keys");
return keys;
}
ATerm string2ATerm(const string & s)
{
return (ATerm) ATmakeAppl0(ATmakeAFun((char *) s.c_str(), 0, ATtrue));
}
string aterm2String(ATerm t)
{
return ATgetName(ATgetAFun(t));
}
ATerm bottomupRewrite(TermFun & f, ATerm e)
{
if (ATgetType(e) == AT_APPL) {
AFun fun = ATgetAFun(e);
int arity = ATgetArity(fun);
ATermList args = ATempty;
for (int i = arity - 1; i >= 0; i--)
args = ATinsert(args, bottomupRewrite(f, ATgetArgument(e, i)));
e = (ATerm) ATmakeApplList(fun, args);
}
else if (ATgetType(e) == AT_LIST) {
ATermList in = (ATermList) e;
ATermList out = ATempty;
for (ATermIterator i(in); i; ++i)
out = ATinsert(out, bottomupRewrite(f, *i));
e = (ATerm) ATreverse(out);
}
return f(e);
}
void queryAllAttrs(Expr e, ATermMap & attrs)
{
ATMatcher m;
ATermList bnds;
if (!(atMatch(m, e) >> "Attrs" >> bnds))
throw badTerm("expected attribute set", e);
for (ATermIterator i(bnds); i; ++i) {
string s;
Expr e;
if (!(atMatch(m, *i) >> "Bind" >> s >> e))
abort(); /* can't happen */
attrs.set(s, e);
}
}
Expr queryAttr(Expr e, const string & name)
{
ATermMap attrs;
queryAllAttrs(e, attrs);
return attrs.get(name);
}
Expr makeAttrs(const ATermMap & attrs)
{
ATermList bnds = ATempty;
for (ATermIterator i(attrs.keys()); i; ++i)
bnds = ATinsert(bnds,
ATmake("Bind(<term>, <term>)", *i, attrs.get(*i)));
return ATmake("Attrs(<term>)", ATreverse(bnds));
}
Expr substitute(const ATermMap & subs, Expr e)
{
ATMatcher m;
string s;
if (atMatch(m, e) >> "Var" >> s) {
Expr sub = subs.get(s);
return sub ? sub : e;
}
/* In case of a function, filter out all variables bound by this
function. */
ATermList formals;
ATerm body;
if (atMatch(m, e) >> "Function" >> formals >> body) {
ATermMap subs2(subs);
for (ATermIterator i(formals); i; ++i) {
Expr def;
if (!(atMatch(m, *i) >> "NoDefFormal" >> s) &&
!(atMatch(m, *i) >> "DefFormal" >> s >> def))
abort();
subs2.remove(s);
}
return ATmake("Function(<term>, <term>)", formals,
substitute(subs2, body));
}
/* Idem for a mutually recursive attribute set. */
ATermList bindings;
if (atMatch(m, e) >> "Rec" >> bindings) {
ATermMap subs2(subs);
for (ATermIterator i(bindings); i; ++i) {
Expr e;
if (!(atMatch(m, *i) >> "Bind" >> s >> e))
abort(); /* can't happen */
subs2.remove(s);
}
return ATmake("Rec(<term>)", substitute(subs2, (ATerm) bindings));
}
if (ATgetType(e) == AT_APPL) {
AFun fun = ATgetAFun(e);
int arity = ATgetArity(fun);
ATermList args = ATempty;
for (int i = arity - 1; i >= 0; i--)
args = ATinsert(args, substitute(subs, ATgetArgument(e, i)));
return (ATerm) ATmakeApplList(fun, args);
}
if (ATgetType(e) == AT_LIST) {
ATermList out = ATempty;
for (ATermIterator i((ATermList) e); i; ++i)
out = ATinsert(out, substitute(subs, *i));
return (ATerm) ATreverse(out);
}
return e;
}
Expr makeBool(bool b)
{
return b ? ATmake("Bool(True)") : ATmake("Bool(False)");
}

75
src/libexpr/fix-expr.hh Normal file
View file

@ -0,0 +1,75 @@
#ifndef __FIXEXPR_H
#define __FIXEXPR_H
#include <map>
#include <aterm2.h>
#include "util.hh"
/* Fix expressions are represented as ATerms. The maximal sharing
property of the ATerm library allows us to implement caching of
normals forms efficiently. */
typedef ATerm Expr;
/* Mappings from ATerms to ATerms. This is just a wrapper around
ATerm tables. */
class ATermMap
{
private:
unsigned int maxLoadPct;
ATermTable table;
public:
ATermMap(unsigned int initialSize = 16, unsigned int maxLoadPct = 75);
ATermMap(const ATermMap & map);
~ATermMap();
void set(ATerm key, ATerm value);
void set(const string & key, ATerm value);
ATerm get(ATerm key) const;
ATerm get(const string & key) const;
void remove(ATerm key);
void remove(const string & key);
ATermList keys() const;
};
/* Convert a string to an ATerm (i.e., a quoted nullary function
applicaton). */
ATerm string2ATerm(const string & s);
string aterm2String(ATerm t);
/* Generic bottomup traversal over ATerms. The traversal first
recursively descends into subterms, and then applies the given term
function to the resulting term. */
struct TermFun
{
virtual ATerm operator () (ATerm e) = 0;
};
ATerm bottomupRewrite(TermFun & f, ATerm e);
/* Query all attributes in an attribute set expression. The
expression must be in normal form. */
void queryAllAttrs(Expr e, ATermMap & attrs);
/* Query a specific attribute from an attribute set expression. The
expression must be in normal form. */
Expr queryAttr(Expr e, const string & name);
/* Create an attribute set expression from an Attrs value. */
Expr makeAttrs(const ATermMap & attrs);
/* Perform a set of substitutions on an expression. */
Expr substitute(const ATermMap & subs, Expr e);
/* Create an expression representing a boolean. */
Expr makeBool(bool b);
#endif /* !__FIXEXPR_H */

117
src/libexpr/main.cc Normal file
View file

@ -0,0 +1,117 @@
#include <map>
#include <iostream>
#include "globals.hh"
#include "normalise.hh"
#include "shared.hh"
#include "eval.hh"
#if 0
static Path searchPath(const Paths & searchDirs, const Path & relPath)
{
if (string(relPath, 0, 1) == "/") return relPath;
for (Paths::const_iterator i = searchDirs.begin();
i != searchDirs.end(); i++)
{
Path path = *i + "/" + relPath;
if (pathExists(path)) return path;
}
throw Error(
format("path `%1%' not found in any of the search directories")
% relPath);
}
#endif
static Expr evalStdin(EvalState & state)
{
startNest(nest, lvlTalkative, format("evaluating standard input"));
Expr e = ATreadFromFile(stdin);
if (!e)
throw Error(format("unable to read a term from stdin"));
return evalExpr(state, e);
}
static void printNixExpr(EvalState & state, Expr e)
{
ATMatcher m;
ATermList es;
if (atMatch(m, e) >> "Attrs" >> es) {
Expr a = queryAttr(e, "type");
if (a && evalString(state, a) == "derivation") {
a = queryAttr(e, "drvPath");
if (a) {
cout << format("%1%\n") % evalPath(state, a);
return;
}
}
}
if (ATgetType(e) == AT_LIST) {
for (ATermIterator i((ATermList) e); i; ++i)
printNixExpr(state, evalExpr(state, *i));
return;
}
throw badTerm("top level does not evaluate to one or more Nix expressions", e);
}
void run(Strings args)
{
EvalState state;
Strings files;
bool readStdin = false;
#if 0
state.searchDirs.push_back(".");
state.searchDirs.push_back(nixDataDir + "/nix");
#endif
for (Strings::iterator it = args.begin();
it != args.end(); )
{
string arg = *it++;
#if 0
if (arg == "--includedir" || arg == "-I") {
if (it == args.end())
throw UsageError(format("argument required in `%1%'") % arg);
state.searchDirs.push_back(*it++);
}
else
#endif
if (arg == "--verbose" || arg == "-v")
verbosity = (Verbosity) ((int) verbosity + 1);
else if (arg == "-")
readStdin = true;
else if (arg[0] == '-')
throw UsageError(format("unknown flag `%1%`") % arg);
else
files.push_back(arg);
}
openDB();
if (readStdin) {
Expr e = evalStdin(state);
printNixExpr(state, e);
}
for (Strings::iterator it = files.begin();
it != files.end(); it++)
{
Expr e = evalFile(state, absPath(*it));
printNixExpr(state, e);
}
printEvalStats(state);
}
string programId = "nix-instantiate";

209
src/libexpr/nix.sdf Normal file
View file

@ -0,0 +1,209 @@
definition
module Main
imports Fix
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Top level syntax.
module Fix
imports Fix-Exprs Fix-Layout
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Expressions.
module Fix-Exprs
imports Fix-Lexicals URI
exports
sorts Expr Formal Bind Binds BindSemi ExprList
context-free syntax
Id -> Expr {cons("Var")}
Int -> Expr {cons("Int")}
Str -> Expr {cons("Str")}
Uri -> Expr {cons("Uri")}
Path -> Expr {cons("Path")}
"(" Expr ")" -> Expr {bracket}
Expr Expr -> Expr {cons("Call"), left}
"{" {Formal ","}* "}" ":" Expr -> Expr {cons("Function")}
Id -> Formal {cons("NoDefFormal")}
Id "?" Expr -> Formal {cons("DefFormal")}
"assert" Expr ";" Expr -> Expr {cons("Assert")}
"rec" "{" Binds "}" -> Expr {cons("Rec")}
"let" "{" Binds "}" -> Expr {cons("LetRec")}
"{" Binds "}" -> Expr {cons("Attrs")}
Id "=" Expr -> Bind {cons("Bind")}
{Bind ";"}* -> Binds
Bind ";" -> BindSemi
BindSemi* -> Binds
"[" ExprList "]" -> Expr {cons("List")}
"" -> ExprList {cons("ExprNil")}
Expr ExprList -> ExprList {cons("ExprCons")}
Expr "." Id -> Expr {cons("Select")}
"if" Expr "then" Expr "else" Expr -> Expr {cons("If")}
Expr "==" Expr -> Expr {cons("OpEq"), non-assoc}
Expr "!=" Expr -> Expr {cons("OpNEq"), non-assoc}
"!" Expr -> Expr {cons("OpNot")}
Expr "&&" Expr -> Expr {cons("OpAnd"), right}
Expr "||" Expr -> Expr {cons("OpOr"), right}
Expr "->" Expr -> Expr {cons("OpImpl"), right}
Bool -> Expr {cons("Bool")}
context-free priorities
Expr "." Id -> Expr
> Expr ExprList -> ExprList
> Expr Expr -> Expr
> "!" Expr -> Expr
> Expr "==" Expr -> Expr
> Expr "!=" Expr -> Expr
> Expr "&&" Expr -> Expr
> Expr "||" Expr -> Expr
> Expr "->" Expr -> Expr
> "assert" Expr ";" Expr -> Expr
> "{" {Formal ","}* "}" ":" Expr -> Expr
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Lexical syntax.
module Fix-Lexicals
exports
sorts Id Int Str Path PathComp Bool
lexical syntax
[a-zA-Z\_][a-zA-Z0-9\_\']* -> Id
"rec" -> Id {reject}
"let" -> Id {reject}
"if" -> Id {reject}
"then" -> Id {reject}
"else" -> Id {reject}
"true" -> Id {reject}
"false" -> Id {reject}
"assert" -> Id {reject}
[0-9]+ -> Int
"\"" ~[\n\"]* "\"" -> Str
PathComp ("/" PathComp)+ -> Path
[a-zA-Z0-9\.\_\-\+]+ -> PathComp
"true" -> Bool
"false" -> Bool
lexical restrictions
Id -/- [a-zA-Z0-9\_\']
Int -/- [0-9]
Path -/- [a-zA-Z0-9\.\_\-\+\/]
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% URIs (RFC 2396, appendix A).
module URI
exports
sorts Uri Uhierpart Uopaquepart Uuricnoslash Unetpath Uabspath
Urelpath Urelsegment Uscheme Uauthority Uregname Userver
Uuserinfo Uhostport Uhost Uhostname Udomainlabel Utoplabel
UIPv4address Uport Upath Upathsegments Usegment Uparam
Upchar Uquery Ufragment Uuric Ureserved Uunreserved Umark
Uescaped Uhex Ualphanum Ualpha Ulowalpha Uupalpha Udigit
lexical syntax
Uscheme ":" (Uhierpart | Uopaquepart) -> Uri
(Unetpath | Uabspath) ("?" Uquery)? -> Uhierpart
Uuricnoslash Uuric* -> Uopaquepart
Uunreserved | Uescaped | [\;\?\:\@\&\=\+\$\,] -> Uuricnoslash
"//" Uauthority Uabspath? -> Unetpath
"/" Upathsegments -> Uabspath
"//" Uuric* -> Uabspath {reject}
Urelsegment Uabspath? -> Urelpath
(Uunreserved | Uescaped | [\;\@\&\=\+\$\,])+ -> Urelsegment
Ualpha (Ualpha | Udigit | [\+\-\.])* -> Uscheme
Userver | Uregname -> Uauthority
(Uunreserved | Uescaped | [\$\,\;\:\@\&\=\+])+ -> Uregname
((Uuserinfo "@") Uhostport) -> Userver
(Uunreserved | Uescaped | [\;\:\&\=\+\$\,])* -> Uuserinfo
Uhost (":" Uport)? -> Uhostport
Uhostname | UIPv4address -> Uhost
(Udomainlabel ".")+ Utoplabel "."? -> Uhostname
Ualphanum | Ualphanum (Ualphanum | "-")* Ualphanum -> Udomainlabel
Ualpha | Ualpha (Ualphanum | "-")* Ualphanum -> Utoplabel
Udigit+ "." Udigit+ "." Udigit+ "." Udigit+ -> UIPv4address
Udigit* -> Uport
Uabspath | Uopaquepart -> Upath
Usegment ("/" Usegment)* -> Upathsegments
Upchar* (";" Uparam)* -> Usegment
Upchar* -> Uparam
Uunreserved | Uescaped | [\:\@\&\=\+\$\,] -> Upchar
Uuric* -> Uquery
Uuric* -> Ufragment
Ureserved | Uunreserved | Uescaped -> Uuric
[\;\/\?\:\@\&\=\+\$\,] -> Ureserved
Ualphanum | Umark -> Uunreserved
[\-\_\.\!\~\*\'\(\)] -> Umark
"%" Uhex Uhex -> Uescaped
Udigit | [A-Fa-f] -> Uhex
Ualpha | Udigit -> Ualphanum
Ulowalpha | Uupalpha -> Ualpha
[a-z] -> Ulowalpha
[A-Z] -> Uupalpha
[0-9] -> Udigit
lexical restrictions
Uri -/- [a-zA-Z0-9\-\_\.\!\~\*\'\(\)]
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Layout.
module Fix-Layout
exports
sorts HashComment Asterisk Comment EOF
lexical syntax
[\ \t\n] -> LAYOUT
HashComment -> LAYOUT
Comment -> LAYOUT
"#" ~[\n]* ([\n] | EOF) -> HashComment
"//" ~[\n]* ([\n] | EOF) -> HashComment
"/*" ( ~[\*] | Asterisk )* "*/" -> Comment
[\*] -> Asterisk
"" -> EOF
lexical restrictions
Asterisk -/- [\/]
EOF -/- ~[]
context-free restrictions
LAYOUT? -/- [\ \t\n] | [\#]

215
src/libexpr/nixexpr.cc Normal file
View file

@ -0,0 +1,215 @@
#include "nixexpr.hh"
#include "storeexpr.hh"
ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct)
{
table = ATtableCreate(initialSize, maxLoadPct);
if (!table) throw Error("cannot create ATerm table");
}
ATermMap::ATermMap(const ATermMap & map)
: table(0)
{
ATermList keys = map.keys();
/* !!! adjust allocation for load pct */
table = ATtableCreate(ATgetLength(keys), map.maxLoadPct);
if (!table) throw Error("cannot create ATerm table");
for (ATermIterator i(keys); i; ++i)
set(*i, map.get(*i));
}
ATermMap::~ATermMap()
{
if (table) ATtableDestroy(table);
}
void ATermMap::set(ATerm key, ATerm value)
{
return ATtablePut(table, key, value);
}
void ATermMap::set(const string & key, ATerm value)
{
set(string2ATerm(key), value);
}
ATerm ATermMap::get(ATerm key) const
{
return ATtableGet(table, key);
}
ATerm ATermMap::get(const string & key) const
{
return get(string2ATerm(key));
}
void ATermMap::remove(ATerm key)
{
ATtableRemove(table, key);
}
void ATermMap::remove(const string & key)
{
remove(string2ATerm(key));
}
ATermList ATermMap::keys() const
{
ATermList keys = ATtableKeys(table);
if (!keys) throw Error("cannot query aterm map keys");
return keys;
}
ATerm string2ATerm(const string & s)
{
return (ATerm) ATmakeAppl0(ATmakeAFun((char *) s.c_str(), 0, ATtrue));
}
string aterm2String(ATerm t)
{
return ATgetName(ATgetAFun(t));
}
ATerm bottomupRewrite(TermFun & f, ATerm e)
{
if (ATgetType(e) == AT_APPL) {
AFun fun = ATgetAFun(e);
int arity = ATgetArity(fun);
ATermList args = ATempty;
for (int i = arity - 1; i >= 0; i--)
args = ATinsert(args, bottomupRewrite(f, ATgetArgument(e, i)));
e = (ATerm) ATmakeApplList(fun, args);
}
else if (ATgetType(e) == AT_LIST) {
ATermList in = (ATermList) e;
ATermList out = ATempty;
for (ATermIterator i(in); i; ++i)
out = ATinsert(out, bottomupRewrite(f, *i));
e = (ATerm) ATreverse(out);
}
return f(e);
}
void queryAllAttrs(Expr e, ATermMap & attrs)
{
ATMatcher m;
ATermList bnds;
if (!(atMatch(m, e) >> "Attrs" >> bnds))
throw badTerm("expected attribute set", e);
for (ATermIterator i(bnds); i; ++i) {
string s;
Expr e;
if (!(atMatch(m, *i) >> "Bind" >> s >> e))
abort(); /* can't happen */
attrs.set(s, e);
}
}
Expr queryAttr(Expr e, const string & name)
{
ATermMap attrs;
queryAllAttrs(e, attrs);
return attrs.get(name);
}
Expr makeAttrs(const ATermMap & attrs)
{
ATermList bnds = ATempty;
for (ATermIterator i(attrs.keys()); i; ++i)
bnds = ATinsert(bnds,
ATmake("Bind(<term>, <term>)", *i, attrs.get(*i)));
return ATmake("Attrs(<term>)", ATreverse(bnds));
}
Expr substitute(const ATermMap & subs, Expr e)
{
ATMatcher m;
string s;
if (atMatch(m, e) >> "Var" >> s) {
Expr sub = subs.get(s);
return sub ? sub : e;
}
/* In case of a function, filter out all variables bound by this
function. */
ATermList formals;
ATerm body;
if (atMatch(m, e) >> "Function" >> formals >> body) {
ATermMap subs2(subs);
for (ATermIterator i(formals); i; ++i) {
Expr def;
if (!(atMatch(m, *i) >> "NoDefFormal" >> s) &&
!(atMatch(m, *i) >> "DefFormal" >> s >> def))
abort();
subs2.remove(s);
}
return ATmake("Function(<term>, <term>)", formals,
substitute(subs2, body));
}
/* Idem for a mutually recursive attribute set. */
ATermList bindings;
if (atMatch(m, e) >> "Rec" >> bindings) {
ATermMap subs2(subs);
for (ATermIterator i(bindings); i; ++i) {
Expr e;
if (!(atMatch(m, *i) >> "Bind" >> s >> e))
abort(); /* can't happen */
subs2.remove(s);
}
return ATmake("Rec(<term>)", substitute(subs2, (ATerm) bindings));
}
if (ATgetType(e) == AT_APPL) {
AFun fun = ATgetAFun(e);
int arity = ATgetArity(fun);
ATermList args = ATempty;
for (int i = arity - 1; i >= 0; i--)
args = ATinsert(args, substitute(subs, ATgetArgument(e, i)));
return (ATerm) ATmakeApplList(fun, args);
}
if (ATgetType(e) == AT_LIST) {
ATermList out = ATempty;
for (ATermIterator i((ATermList) e); i; ++i)
out = ATinsert(out, substitute(subs, *i));
return (ATerm) ATreverse(out);
}
return e;
}
Expr makeBool(bool b)
{
return b ? ATmake("Bool(True)") : ATmake("Bool(False)");
}

75
src/libexpr/nixexpr.hh Normal file
View file

@ -0,0 +1,75 @@
#ifndef __NIXEXPR_H
#define __NIXEXPR_H
#include <map>
#include <aterm2.h>
#include "util.hh"
/* Nix expressions are represented as ATerms. The maximal sharing
property of the ATerm library allows us to implement caching of
normals forms efficiently. */
typedef ATerm Expr;
/* Mappings from ATerms to ATerms. This is just a wrapper around
ATerm tables. */
class ATermMap
{
private:
unsigned int maxLoadPct;
ATermTable table;
public:
ATermMap(unsigned int initialSize = 16, unsigned int maxLoadPct = 75);
ATermMap(const ATermMap & map);
~ATermMap();
void set(ATerm key, ATerm value);
void set(const string & key, ATerm value);
ATerm get(ATerm key) const;
ATerm get(const string & key) const;
void remove(ATerm key);
void remove(const string & key);
ATermList keys() const;
};
/* Convert a string to an ATerm (i.e., a quoted nullary function
applicaton). */
ATerm string2ATerm(const string & s);
string aterm2String(ATerm t);
/* Generic bottomup traversal over ATerms. The traversal first
recursively descends into subterms, and then applies the given term
function to the resulting term. */
struct TermFun
{
virtual ATerm operator () (ATerm e) = 0;
};
ATerm bottomupRewrite(TermFun & f, ATerm e);
/* Query all attributes in an attribute set expression. The
expression must be in normal form. */
void queryAllAttrs(Expr e, ATermMap & attrs);
/* Query a specific attribute from an attribute set expression. The
expression must be in normal form. */
Expr queryAttr(Expr e, const string & name);
/* Create an attribute set expression from an Attrs value. */
Expr makeAttrs(const ATermMap & attrs);
/* Perform a set of substitutions on an expression. */
Expr substitute(const ATermMap & subs, Expr e);
/* Create an expression representing a boolean. */
Expr makeBool(bool b);
#endif /* !__NIXEXPR_H */

164
src/libexpr/parser.cc Normal file
View file

@ -0,0 +1,164 @@
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
extern "C" {
#include <sglr.h>
#include <asfix2.h>
}
#include "aterm.hh"
#include "parser.hh"
#include "shared.hh"
#include "parse-table.h"
/* Cleanup cleans up an imploded parse tree into an actual abstract
syntax tree that we can evaluate. It removes quotes around
strings, converts integer literals into actual integers, and
absolutises paths relative to the directory containing the input
file. */
struct Cleanup : TermFun
{
string basePath;
virtual ATerm operator () (ATerm e)
{
ATMatcher m;
string s;
if (atMatch(m, e) >> "Str" >> s) {
return ATmake("Str(<str>)",
string(s, 1, s.size() - 2).c_str());
}
if (atMatch(m, e) >> "Path" >> s) {
if (s[0] != '/')
s = basePath + "/" + s;
return ATmake("Path(<str>)", canonPath(s).c_str());
}
if (atMatch(m, e) >> "Int" >> s) {
istringstream s2(s);
int n;
s2 >> n;
return ATmake("Int(<int>)", n);
}
if (atMatch(m, e) >> "Bool" >> "true")
return ATmake("Bool(True)");
if (atMatch(m, e) >> "Bool" >> "false")
return ATmake("Bool(False)");
if (atMatch(m, e) >> "ExprNil")
return (ATerm) ATempty;
ATerm e1;
ATermList e2;
if (atMatch(m, e) >> "ExprCons" >> e1 >> e2)
return (ATerm) ATinsert(e2, e1);
return e;
}
};
Expr parseExprFromFile(Path path)
{
#if 0
/* Perhaps this is already an imploded parse tree? */
Expr e = ATreadFromNamedFile(path.c_str());
if (e) return e;
#endif
/* If `path' refers to a directory, append `/default.nix'. */
struct stat st;
if (stat(path.c_str(), &st))
throw SysError(format("getting status of `%1%'") % path);
if (S_ISDIR(st.st_mode))
path = canonPath(path + "/default.nix");
/* Initialise the SDF libraries. */
static bool initialised = false;
static ATerm parseTable = 0;
static language lang = 0;
if (!initialised) {
PT_initMEPTApi();
PT_initAsFix2Api();
SGinitParser(ATfalse);
ATprotect(&parseTable);
parseTable = ATreadFromBinaryString(
(char *) nixParseTable, sizeof nixParseTable);
if (!parseTable)
throw Error(format("cannot construct parse table term"));
ATprotect(&lang);
lang = ATmake("Nix");
if (!SGopenLanguageFromTerm(
(char *) programId.c_str(), lang, parseTable))
throw Error(format("cannot open language"));
SG_STARTSYMBOL_ON();
SG_OUTPUT_ON();
SG_ASFIX2ME_ON();
SG_AMBIGUITY_ERROR_ON();
SG_FILTER_OFF();
initialised = true;
}
/* Read the input file. We can't use SGparseFile() because it's
broken, so we read the input ourselves and call
SGparseString(). */
AutoCloseFD fd = open(path.c_str(), O_RDONLY);
if (fd == -1) throw SysError(format("opening `%1%'") % path);
if (fstat(fd, &st) == -1)
throw SysError(format("statting `%1%'") % path);
char text[st.st_size + 1];
readFull(fd, (unsigned char *) text, st.st_size);
text[st.st_size] = 0;
/* Parse it. */
ATerm result = SGparseString(lang, "Expr", text);
if (!result)
throw SysError(format("parse failed in `%1%'") % path);
if (SGisParseError(result))
throw Error(format("parse error in `%1%': %2%")
% path % result);
/* Implode it. */
PT_ParseTree tree = PT_makeParseTreeFromTerm(result);
if (!tree)
throw Error(format("cannot create parse tree"));
ATerm imploded = PT_implodeParseTree(tree,
ATtrue,
ATtrue,
ATtrue,
ATtrue,
ATtrue,
ATtrue,
ATfalse,
ATtrue,
ATtrue,
ATtrue,
ATfalse);
if (!imploded)
throw Error(format("cannot implode parse tree"));
printMsg(lvlVomit, format("imploded parse tree of `%1%': %2%")
% path % imploded);
/* Finally, clean it up. */
Cleanup cleanup;
cleanup.basePath = dirOf(path);
return bottomupRewrite(cleanup, imploded);
}

10
src/libexpr/parser.hh Normal file
View file

@ -0,0 +1,10 @@
#ifndef __PARSER_H
#define __PARSER_H
#include "nixexpr.hh"
Expr parseExprFromFile(Path path);
#endif /* !__PARSER_H */

246
src/libexpr/primops.cc Normal file
View file

@ -0,0 +1,246 @@
#include "primops.hh"
#include "normalise.hh"
#include "globals.hh"
Expr primImport(EvalState & state, Expr arg)
{
ATMatcher m;
string path;
if (!(atMatch(m, arg) >> "Path" >> path))
throw badTerm("path expected", arg);
return evalFile(state, path);
}
static PathSet storeExprRootsCached(EvalState & state, const Path & nePath)
{
DrvPaths::iterator i = state.drvPaths.find(nePath);
if (i != state.drvPaths.end())
return i->second;
else {
PathSet paths = storeExprRoots(nePath);
state.drvPaths[nePath] = paths;
return paths;
}
}
static Hash hashDerivation(EvalState & state, StoreExpr ne)
{
if (ne.type == StoreExpr::neDerivation) {
PathSet inputs2;
for (PathSet::iterator i = ne.derivation.inputs.begin();
i != ne.derivation.inputs.end(); i++)
{
DrvHashes::iterator j = state.drvHashes.find(*i);
if (j == state.drvHashes.end())
throw Error(format("don't know expression `%1%'") % (string) *i);
inputs2.insert(j->second);
}
ne.derivation.inputs = inputs2;
}
return hashTerm(unparseStoreExpr(ne));
}
static Path copyAtom(EvalState & state, const Path & srcPath)
{
/* !!! should be cached */
Path dstPath(addToStore(srcPath));
ClosureElem elem;
StoreExpr ne;
ne.type = StoreExpr::neClosure;
ne.closure.roots.insert(dstPath);
ne.closure.elems[dstPath] = elem;
Hash drvHash = hashDerivation(state, ne);
Path drvPath = writeTerm(unparseStoreExpr(ne), "");
state.drvHashes[drvPath] = drvHash;
printMsg(lvlChatty, format("copied `%1%' -> closure `%2%'")
% srcPath % drvPath);
return drvPath;
}
static string addInput(EvalState & state,
Path & nePath, StoreExpr & ne)
{
PathSet paths = storeExprRootsCached(state, nePath);
if (paths.size() != 1) abort();
Path path = *(paths.begin());
ne.derivation.inputs.insert(nePath);
return path;
}
static string processBinding(EvalState & state, Expr e, StoreExpr & ne)
{
e = evalExpr(state, e);
ATMatcher m;
string s;
ATermList es;
if (atMatch(m, e) >> "Str" >> s) return s;
if (atMatch(m, e) >> "Uri" >> s) return s;
if (atMatch(m, e) >> "Bool" >> "True") return "1";
if (atMatch(m, e) >> "Bool" >> "False") return "";
if (atMatch(m, e) >> "Attrs" >> es) {
Expr a = queryAttr(e, "type");
if (a && evalString(state, a) == "derivation") {
a = queryAttr(e, "drvPath");
if (a) {
Path drvPath = evalPath(state, a);
return addInput(state, drvPath, ne);
}
}
}
if (atMatch(m, e) >> "Path" >> s) {
Path drvPath = copyAtom(state, s);
return addInput(state, drvPath, ne);
}
if (atMatch(m, e) >> "List" >> es) {
string s;
bool first = true;
for (ATermIterator i(es); i; ++i) {
startNest(nest, lvlVomit, format("processing list element"));
if (!first) s = s + " "; else first = false;
s += processBinding(state, evalExpr(state, *i), ne);
}
return s;
}
if (atMatch(m, e) >> "Null") return "";
throw badTerm("invalid derivation binding", e);
}
Expr primDerivation(EvalState & state, Expr args)
{
startNest(nest, lvlVomit, "evaluating derivation");
ATermMap attrs;
args = evalExpr(state, args);
queryAllAttrs(args, attrs);
/* Build the derivation expression by processing the attributes. */
StoreExpr ne;
ne.type = StoreExpr::neDerivation;
string drvName;
Path outPath;
Hash outHash;
bool outHashGiven = false;
for (ATermIterator i(attrs.keys()); i; ++i) {
string key = aterm2String(*i);
Expr value = attrs.get(key);
startNest(nest, lvlVomit, format("processing attribute `%1%'") % key);
/* The `args' attribute is special: it supplies the
command-line arguments to the builder. */
if (key == "args") {
throw Error("args not implemented");
#if 0
ATermList args;
if (!(ATmatch(value, "[<list>]", &args))
throw badTerm("list expected", value);
while (!ATisEmpty(args)) {
Expr arg = evalExpr(state, ATgetFirst(args));
ne.derivation.args.push_back(processBinding(state, arg, ne));
args = ATgetNext(args);
}
#endif
}
/* All other attributes are passed to the builder through the
environment. */
else {
string s = processBinding(state, value, ne);
ne.derivation.env[key] = s;
if (key == "builder") ne.derivation.builder = s;
else if (key == "system") ne.derivation.platform = s;
else if (key == "name") drvName = s;
else if (key == "outPath") outPath = s;
else if (key == "id") {
outHash = parseHash(s);
outHashGiven = true;
}
}
}
/* Do we have all required attributes? */
if (ne.derivation.builder == "")
throw badTerm("required attribute `builder' missing", args);
if (ne.derivation.platform == "")
throw badTerm("required attribute `system' missing", args);
if (drvName == "")
throw badTerm("required attribute `name' missing", args);
/* Determine the output path. */
if (!outHashGiven) outHash = hashDerivation(state, ne);
if (outPath == "")
/* Hash the Nix expression with no outputs to produce a
unique but deterministic path name for this derivation. */
outPath = canonPath(nixStore + "/" +
((string) outHash).c_str() + "-" + drvName);
ne.derivation.env["out"] = outPath;
ne.derivation.outputs.insert(outPath);
/* Write the resulting term into the Nix store directory. */
Hash drvHash = outHashGiven
? hashString((string) outHash + outPath)
: hashDerivation(state, ne);
Path drvPath = writeTerm(unparseStoreExpr(ne), "-d-" + drvName);
state.drvHashes[drvPath] = drvHash;
printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'")
% drvName % drvPath);
attrs.set("outPath", ATmake("Path(<str>)", outPath.c_str()));
attrs.set("drvPath", ATmake("Path(<str>)", drvPath.c_str()));
attrs.set("type", ATmake("Str(\"derivation\")"));
return makeAttrs(attrs);
}
Expr primBaseNameOf(EvalState & state, Expr arg)
{
string s = evalString(state, arg);
return ATmake("Str(<str>)", baseNameOf(s).c_str());
}
Expr primToString(EvalState & state, Expr arg)
{
arg = evalExpr(state, arg);
ATMatcher m;
string s;
if (atMatch(m, arg) >> "Str" >> s ||
atMatch(m, arg) >> "Path" >> s ||
atMatch(m, arg) >> "Uri" >> s)
return ATmake("Str(<str>)", s.c_str());
else throw badTerm("cannot coerce to string", arg);
}
Expr primNull(EvalState & state)
{
return ATmake("Null");
}
Expr primIsNull(EvalState & state, Expr arg)
{
arg = evalExpr(state, arg);
ATMatcher m;
return makeBool(atMatch(m, arg) >> "Null");
}

34
src/libexpr/primops.hh Normal file
View file

@ -0,0 +1,34 @@
#ifndef __PRIMOPS_H
#define __PRIMOPS_H
#include "eval.hh"
/* Load and evaluate an expression from path specified by the
argument. */
Expr primImport(EvalState & state, Expr arg);
/* Construct (as a unobservable) side effect) a Nix derivation
expression that performs the derivation described by the argument
set. Returns the original set extended with the following
attributes: `outPath' containing the primary output path of the
derivation; `drvPath' containing the path of the Nix expression;
and `type' set to `derivation' to indicate that this is a
derivation. */
Expr primDerivation(EvalState & state, Expr args);
/* Return the base name of the given string, i.e., everything
following the last slash. */
Expr primBaseNameOf(EvalState & state, Expr arg);
/* Convert the argument (which can be a path or a uri) to a string. */
Expr primToString(EvalState & state, Expr arg);
/* Return the null value. */
Expr primNull(EvalState & state);
/* Determine whether the argument is the null value. */
Expr primIsNull(EvalState & state, Expr arg);
#endif /* !__PRIMOPS_H */