Skip to content

Commit

Permalink
Add an 'include' keyword
Browse files Browse the repository at this point in the history
Inclusion is like importing, except that all variables that in scope
at the 'include' site are in scope of the included file.

Thus, if ./foo.nix contains 'x', then

  let x = 1; in include ./foo.nix

evaluates to 1.

(Note that 'include' has to be a keyword, not a builtin function,
because inclusion takes place within a specific scope. I.e. 'include
./foo.nix' would not be equivalent to 'let f = include; in ... f
./foo.nix ...'.)
  • Loading branch information
edolstra committed Mar 2, 2018
1 parent 939cf4c commit 64ea7cc
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 4 deletions.
17 changes: 17 additions & 0 deletions src/libexpr/eval.cc
Expand Up @@ -1386,6 +1386,23 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
}


void ExprInclude::eval(EvalState & state, Env & env, Value & v)
{
Value vPath;
path->eval(state, env, vPath);

PathSet context;
auto realPath = state.checkSourcePath(
state.toRealPath(state.coerceToPath(pos, vPath, context), context));

printError("including file '%1%'", realPath);

auto e = state.parseExprFromFile(resolveExprPath(realPath), *staticEnv);

e->eval(state, env, v);
}


void EvalState::forceValueDeep(Value & v)
{
std::set<const Value *> seen;
Expand Down
1 change: 1 addition & 0 deletions src/libexpr/lexer.l
Expand Up @@ -109,6 +109,7 @@ in { return IN; }
rec { return REC; }
inherit { return INHERIT; }
or { return OR_KW; }
include { return INCLUDE; }
\.\.\. { return ELLIPSIS; }

\=\= { return EQ; }
Expand Down
23 changes: 23 additions & 0 deletions src/libexpr/nixexpr.cc
Expand Up @@ -191,6 +191,11 @@ void ExprPos::show(std::ostream & str)
str << "__curPos";
}

void ExprInclude::show(std::ostream & str)
{
str << "(include " << *path << ")";
}


std::ostream & operator << (std::ostream & str, const Pos & pos)
{
Expand Down Expand Up @@ -402,6 +407,24 @@ void ExprPos::bindVars(const StaticEnv & env)
{
}

void ExprInclude::bindVars(const StaticEnv & env)
{
path->bindVars(env);

/* Copy the static environment so that ExprInclude::eval() can
pass it to parseExprFromFile(). FIXME: if there are many uses
of 'include' in the same scope, we should cache this. */

const StaticEnv * srcEnv = &env;
StaticEnv * * dstEnv = &staticEnv;

while (srcEnv) {
*dstEnv = new StaticEnv(*srcEnv);
srcEnv = srcEnv->up;
dstEnv = (StaticEnv * *) &(*dstEnv)->up;
}
}


/* Storing function names. */

Expand Down
15 changes: 12 additions & 3 deletions src/libexpr/nixexpr.hh
Expand Up @@ -86,9 +86,9 @@ struct Expr
std::ostream & operator << (std::ostream & str, Expr & e);

#define COMMON_METHODS \
void show(std::ostream & str); \
void eval(EvalState & state, Env & env, Value & v); \
void bindVars(const StaticEnv & env);
void show(std::ostream & str) override; \
void eval(EvalState & state, Env & env, Value & v) override; \
void bindVars(const StaticEnv & env) override;

struct ExprInt : Expr
{
Expand Down Expand Up @@ -326,6 +326,15 @@ struct ExprPos : Expr
COMMON_METHODS
};

struct ExprInclude : Expr
{
Pos pos;
Expr * path;
StaticEnv * staticEnv;
ExprInclude(const Pos & pos, Expr * path) : pos(pos), path(path) { };
COMMON_METHODS
};


/* Static environments are used to map variable names onto (level,
displacement) pairs used to obtain the value of the variable at
Expand Down
3 changes: 2 additions & 1 deletion src/libexpr/parser.y
Expand Up @@ -270,7 +270,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
%token <nf> FLOAT
%token <path> PATH HPATH SPATH
%token <uri> URI
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW INCLUDE
%token DOLLAR_CURLY /* == ${ */
%token IND_STRING_OPEN IND_STRING_CLOSE
%token ELLIPSIS
Expand Down Expand Up @@ -348,6 +348,7 @@ expr_app
: expr_app expr_select
{ $$ = new ExprApp(CUR_POS, $1, $2); }
| expr_select { $$ = $1; }
| INCLUDE expr_select { $$ = new ExprInclude(CUR_POS, $2); }
;

expr_select
Expand Down
1 change: 1 addition & 0 deletions tests/lang/eval-okay-include.exp
@@ -0,0 +1 @@
true
16 changes: 16 additions & 0 deletions tests/lang/eval-okay-include.nix
@@ -0,0 +1,16 @@
assert include ./include-1.nix == 1;
assert let x = 100; in include ./include-1.nix == 1;
assert with { x = 123; }; include ./include-1.nix == 1;

assert let x = 2; in include ./include-2.nix == 3;
assert let x = 3; in include ./include-2.nix == 4;
assert let x = 3; in let x = 4; in include ./include-2.nix == 5;
assert let x = 3; in let x = 4; in include ./include-2.nix == 5;
assert let x = 6; in with { x = 7; }; include ./include-2.nix == 7;
assert with { x = 0; }; let x = 6; in with { x = 7; }; include ./include-2.nix == 7;
assert with { x = 0; }; let x = 6; in with { x = 7; }; let x = 7; in include ./include-2.nix == 8;
assert with { x = 8; }; include ./include-2.nix == 9;

assert (let x = 10; in include ./include-3.nix) == 3628800;

true
1 change: 1 addition & 0 deletions tests/lang/include-1.nix
@@ -0,0 +1 @@
let x = 1; in x
1 change: 1 addition & 0 deletions tests/lang/include-2.nix
@@ -0,0 +1 @@
1 + x
1 change: 1 addition & 0 deletions tests/lang/include-3.nix
@@ -0,0 +1 @@
if x > 0 then x * (let x_ = x; in let x = x_ - 1; in include ./include-3.nix) else 1

0 comments on commit 64ea7cc

Please sign in to comment.