1 : %glr-parser
2 : %pure-parser
3 : %locations
4 : %error-verbose
5 : %defines
6 : /* %no-lines */
7 : %parse-param { yyscan_t scanner }
8 : %parse-param { ParseData * data }
9 : %lex-param { yyscan_t scanner }
10 :
11 :
12 : %{
13 : /* Newer versions of Bison copy the declarations below to
14 : parser-tab.hh, which sucks bigtime since lexer.l doesn't want that
15 : stuff. So allow it to be excluded. */
16 : #ifndef BISON_HEADER_HACK
17 : #define BISON_HEADER_HACK
18 :
19 : #include <stdio.h>
20 : #include <stdlib.h>
21 : #include <string.h>
22 :
23 : #include "aterm.hh"
24 : #include "util.hh"
25 :
26 : #include "parser-tab.hh"
27 : #include "lexer-tab.hh"
28 :
29 : #include "nixexpr.hh"
30 : #include "nixexpr-ast.hh"
31 :
32 :
33 : using namespace nix;
34 :
35 :
36 : namespace nix {
37 :
38 :
39 : struct ParseData
40 370 : {
41 : Expr result;
42 : Path basePath;
43 : Path path;
44 : string error;
45 : };
46 :
47 :
48 507 : static Expr fixAttrs(int recursive, ATermList as)
49 : {
50 507 : ATermList bs = ATempty, cs = ATempty;
51 507 : ATermList * is = recursive ? &cs : &bs;
52 2433 : for (ATermIterator i(as); i; ++i) {
53 : ATermList names;
54 : Expr src;
55 : ATerm pos;
56 1926 : if (matchInherit(*i, src, names, pos)) {
57 135 : bool fromScope = matchScope(src);
58 337 : for (ATermIterator j(names); j; ++j) {
59 202 : Expr rhs = fromScope ? makeVar(*j) : makeSelect(src, *j);
60 202 : *is = ATinsert(*is, makeBind(*j, rhs, pos));
61 : }
62 1791 : } else bs = ATinsert(bs, *i);
63 : }
64 507 : if (recursive)
65 140 : return makeRec(bs, cs);
66 : else
67 367 : return makeAttrs(bs);
68 : }
69 :
70 :
71 15 : static Expr stripIndentation(ATermList es)
72 : {
73 15 : if (es == ATempty) return makeStr("");
74 :
75 : /* Figure out the minimum indentation. Note that by design
76 : whitespace-only final lines are not taken into account. (So
77 : the " " in "\n ''" is ignored, but the " " in "\n foo''" is.) */
78 14 : bool atStartOfLine = true; /* = seen only whitespace in the current line */
79 14 : unsigned int minIndent = 1000000;
80 14 : unsigned int curIndent = 0;
81 : ATerm e;
82 50 : for (ATermIterator i(es); i; ++i) {
83 53 : if (!matchIndStr(*i, e)) {
84 : /* Anti-quotations end the current start-of-line whitespace. */
85 17 : if (atStartOfLine) {
86 4 : atStartOfLine = false;
87 4 : if (curIndent < minIndent) minIndent = curIndent;
88 : }
89 17 : continue;
90 : }
91 36 : string s = aterm2String(e);
92 2456 : for (unsigned int j = 0; j < s.size(); ++j) {
93 2420 : if (atStartOfLine) {
94 345 : if (s[j] == ' ')
95 283 : curIndent++;
96 62 : else if (s[j] == '\n') {
97 : /* Empty line, doesn't influence minimum
98 : indentation. */
99 8 : curIndent = 0;
100 : } else {
101 54 : atStartOfLine = false;
102 54 : if (curIndent < minIndent) minIndent = curIndent;
103 : }
104 2075 : } else if (s[j] == '\n') {
105 57 : atStartOfLine = true;
106 57 : curIndent = 0;
107 : }
108 : }
109 : }
110 :
111 : /* Strip spaces from each line. */
112 14 : ATermList es2 = ATempty;
113 14 : atStartOfLine = true;
114 14 : unsigned int curDropped = 0;
115 14 : unsigned int n = ATgetLength(es);
116 50 : for (ATermIterator i(es); i; ++i, --n) {
117 53 : if (!matchIndStr(*i, e)) {
118 17 : atStartOfLine = false;
119 17 : curDropped = 0;
120 17 : es2 = ATinsert(es2, *i);
121 17 : continue;
122 : }
123 :
124 36 : string s = aterm2String(e);
125 36 : string s2;
126 2456 : for (unsigned int j = 0; j < s.size(); ++j) {
127 2420 : if (atStartOfLine) {
128 345 : if (s[j] == ' ') {
129 283 : if (curDropped++ >= minIndent)
130 35 : s2 += s[j];
131 : }
132 62 : else if (s[j] == '\n') {
133 8 : curDropped = 0;
134 8 : s2 += s[j];
135 : } else {
136 54 : atStartOfLine = false;
137 54 : curDropped = 0;
138 54 : s2 += s[j];
139 : }
140 : } else {
141 2075 : s2 += s[j];
142 2075 : if (s[j] == '\n') atStartOfLine = true;
143 : }
144 : }
145 :
146 : /* Remove the last line if it is empty and consists only of
147 : spaces. */
148 36 : if (n == 1) {
149 14 : unsigned int p = s2.find_last_of('\n');
150 14 : if (p != string::npos && s2.find_first_not_of(' ', p + 1) == string::npos)
151 11 : s2 = string(s2, 0, p + 1);
152 : }
153 :
154 36 : es2 = ATinsert(es2, makeStr(s2));
155 : }
156 :
157 14 : return makeConcatStrings(ATreverse(es2));
158 : }
159 :
160 :
161 : void backToString(yyscan_t scanner);
162 : void backToIndString(yyscan_t scanner);
163 :
164 :
165 2488 : static Pos makeCurPos(YYLTYPE * loc, ParseData * data)
166 : {
167 : return makePos(toATerm(data->path),
168 2488 : loc->first_line, loc->first_column);
169 : }
170 :
171 : #define CUR_POS makeCurPos(yylocp, data)
172 :
173 :
174 : }
175 :
176 :
177 1 : void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error)
178 : {
179 : data->error = (format("%1%, at `%2%':%3%:%4%")
180 1 : % error % data->path % loc->first_line % loc->first_column).str();
181 1 : }
182 :
183 :
184 : /* Make sure that the parse stack is scanned by the ATerm garbage
185 : collector. */
186 556 : static void * mallocAndProtect(size_t size)
187 : {
188 556 : void * p = malloc(size);
189 556 : if (p) ATprotectMemory(p, size);
190 556 : return p;
191 : }
192 :
193 556 : static void freeAndUnprotect(void * p)
194 : {
195 556 : ATunprotectMemory(p);
196 556 : free(p);
197 556 : }
198 :
199 : #define YYMALLOC mallocAndProtect
200 : #define YYFREE freeAndUnprotect
201 :
202 :
203 : #endif
204 :
205 :
206 : %}
207 :
208 : %union {
209 : ATerm t;
210 : ATermList ts;
211 : struct {
212 : ATermList formals;
213 : bool ellipsis;
214 : } formals;
215 : }
216 :
217 : %type <t> start expr expr_function expr_if expr_op
218 : %type <t> expr_app expr_select expr_simple bind inheritsrc formal
219 : %type <t> pattern pattern2
220 : %type <ts> binds ids expr_list string_parts ind_string_parts
221 : %type <formals> formals
222 : %token <t> ID INT STR IND_STR PATH URI
223 : %token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL
224 : %token DOLLAR_CURLY /* == ${ */
225 : %token IND_STRING_OPEN IND_STRING_CLOSE
226 : %token ELLIPSIS
227 :
228 : %nonassoc IMPL
229 : %left OR
230 : %left AND
231 : %nonassoc EQ NEQ
232 : %right UPDATE
233 : %left NEG
234 : %left '+'
235 : %right CONCAT
236 : %nonassoc '?'
237 : %nonassoc '~'
238 :
239 : %%
240 :
241 184 : start: expr { data->result = $1; };
242 184 :
243 : expr: expr_function;
244 :
245 : expr_function
246 : : pattern ':' expr_function
247 506 : { $$ = makeFunction($1, $3, CUR_POS); }
248 506 : | ASSERT expr ';' expr_function
249 40 : { $$ = makeAssert($2, $4, CUR_POS); }
250 40 : | WITH expr ';' expr_function
251 16 : { $$ = makeWith($2, $4, CUR_POS); }
252 16 : | LET binds IN expr_function
253 40 : { $$ = makeSelect(fixAttrs(1, ATinsert($2, makeBind(toATerm("<let-body>"), $4, CUR_POS))), toATerm("<let-body>")); }
254 40 : | expr_if
255 : ;
256 :
257 : expr_if
258 : : IF expr THEN expr ELSE expr
259 190 : { $$ = makeIf($2, $4, $6); }
260 190 : | expr_op
261 : ;
262 :
263 : expr_op
264 14 : : '!' expr_op %prec NEG { $$ = makeOpNot($2); }
265 14 : | expr_op EQ expr_op { $$ = makeOpEq($1, $3); }
266 96 : | expr_op NEQ expr_op { $$ = makeOpNEq($1, $3); }
267 10 : | expr_op AND expr_op { $$ = makeOpAnd($1, $3); }
268 122 : | expr_op OR expr_op { $$ = makeOpOr($1, $3); }
269 2 : | expr_op IMPL expr_op { $$ = makeOpImpl($1, $3); }
270 8 : | expr_op UPDATE expr_op { $$ = makeOpUpdate($1, $3); }
271 41 : | expr_op '~' expr_op { $$ = makeSubPath($1, $3); }
272 17 : | expr_op '?' ID { $$ = makeOpHasAttr($1, $3); }
273 190 : | expr_op '+' expr_op { $$ = makeOpPlus($1, $3); }
274 123 : | expr_op CONCAT expr_op { $$ = makeOpConcat($1, $3); }
275 39 : | expr_app
276 : ;
277 :
278 : expr_app
279 : : expr_app expr_select
280 2019 : { $$ = makeCall($1, $2); }
281 2019 : | expr_select { $$ = $1; }
282 3667 : ;
283 :
284 : expr_select
285 : : expr_select '.' ID
286 485 : { $$ = makeSelect($1, $3); }
287 485 : | expr_simple { $$ = $1; }
288 6561 : ;
289 :
290 : expr_simple
291 2533 : : ID { $$ = makeVar($1); }
292 2533 : | INT { $$ = makeInt(ATgetInt((ATermInt) $1)); }
293 195 : | '"' string_parts '"' {
294 : /* For efficiency, and to simplify parse trees a bit. */
295 1955 : if ($2 == ATempty) $$ = makeStr(toATerm(""), ATempty);
296 1932 : else if (ATgetNext($2) == ATempty) $$ = ATgetFirst($2);
297 24 : else $$ = makeConcatStrings(ATreverse($2));
298 : }
299 1955 : | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
300 15 : $$ = stripIndentation(ATreverse($2));
301 : }
302 15 : | PATH { $$ = makePath(toATerm(absPath(aterm2String($1), data->basePath))); }
303 253 : | URI { $$ = makeStr($1, ATempty); }
304 8 : | '(' expr ')' { $$ = $2; }
305 718 : /* Let expressions `let {..., body = ...}' are just desugared
306 : into `(rec {..., body = ...}).body'. */
307 : | LET '{' binds '}'
308 69 : { $$ = makeSelect(fixAttrs(1, $3), toATerm("body")); }
309 69 : | REC '{' binds '}'
310 31 : { $$ = fixAttrs(1, $3); }
311 31 : | '{' binds '}'
312 367 : { $$ = fixAttrs(0, $2); }
313 367 : | '[' expr_list ']' { $$ = makeList($2); }
314 425 : ;
315 :
316 : string_parts
317 1953 : : string_parts STR { $$ = ATinsert($1, $2); }
318 1953 : | string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = ATinsert($1, $3); }
319 25 : | { $$ = ATempty; }
320 1955 : ;
321 :
322 : ind_string_parts
323 39 : : ind_string_parts IND_STR { $$ = ATinsert($1, $2); }
324 39 : | ind_string_parts DOLLAR_CURLY expr '}' { backToIndString(scanner); $$ = ATinsert($1, $3); }
325 14 : | { $$ = ATempty; }
326 15 : ;
327 :
328 : pattern
329 6 : : pattern2 '@' pattern { $$ = makeAtPat($1, $3); }
330 6 : | pattern2
331 : ;
332 :
333 : pattern2
334 413 : : ID { $$ = makeVarPat($1); }
335 413 : | '{' formals '}' { $$ = makeAttrsPat($2.formals, $2.ellipsis ? eTrue : eFalse); }
336 99 : ;
337 :
338 : binds
339 1886 : : binds bind { $$ = ATinsert($1, $2); }
340 1886 : | { $$ = ATempty; }
341 508 : ;
342 :
343 : bind
344 : : ID '=' expr ';'
345 1751 : { $$ = makeBind($1, $3, CUR_POS); }
346 1751 : | INHERIT inheritsrc ids ';'
347 135 : { $$ = makeInherit($2, $3, CUR_POS); }
348 135 : ;
349 :
350 : inheritsrc
351 3 : : '(' expr ')' { $$ = $2; }
352 3 : | { $$ = makeScope(); }
353 132 : ;
354 :
355 337 : ids: ids ID { $$ = ATinsert($1, $2); } | { $$ = ATempty; };
356 337 :
357 : expr_list
358 875 : : expr_select expr_list { $$ = ATinsert($2, $1); }
359 875 : /* yes, this is right-recursive, but it doesn't matter since
360 : otherwise we would need ATreverse which requires unbounded
361 : stack space */
362 425 : | { $$ = ATempty; }
363 425 : ;
364 :
365 : formals
366 : : formal ',' formals /* idem - right recursive */
367 171 : { $$.formals = ATinsert($3.formals, $1); $$.ellipsis = $3.ellipsis; }
368 171 : | formal
369 96 : { $$.formals = ATinsert(ATempty, $1); $$.ellipsis = false; }
370 96 : |
371 2 : { $$.formals = ATempty; $$.ellipsis = false; }
372 2 : | ELLIPSIS
373 2 : { $$.formals = ATempty; $$.ellipsis = true; }
374 2 : ;
375 :
376 : formal
377 189 : : ID { $$ = makeFormal($1, makeNoDefaultValue()); }
378 189 : | ID '?' expr { $$ = makeFormal($1, makeDefaultValue($3)); }
379 : ;
380 :
381 : %%
382 :
383 :
384 : #include "eval.hh"
385 :
386 : #include <sys/types.h>
387 : #include <sys/stat.h>
388 : #include <fcntl.h>
389 : #include <unistd.h>
390 :
391 :
392 : namespace nix {
393 :
394 :
395 643 : static void checkAttrs(ATermMap & names, ATermList bnds)
396 : {
397 2629 : for (ATermIterator i(bnds); i; ++i) {
398 : ATerm name;
399 : Expr e;
400 : ATerm pos;
401 1989 : if (!matchBind(*i, name, e, pos)) abort(); /* can't happen */
402 1989 : if (names.get(name))
403 : throw EvalError(format("duplicate attribute `%1%' at %2%")
404 3 : % aterm2String(name) % showPos(pos));
405 1986 : names.set(name, name);
406 : }
407 640 : }
408 :
409 :
410 516 : static void checkPatternVars(ATerm pos, ATermMap & map, Pattern pat)
411 : {
412 : ATerm name;
413 : ATermList formals;
414 : Pattern pat1, pat2;
415 : ATermBool ellipsis;
416 516 : if (matchVarPat(pat, name)) {
417 412 : if (map.get(name))
418 : throw EvalError(format("duplicate formal function argument `%1%' at %2%")
419 0 : % aterm2String(name) % showPos(pos));
420 412 : map.set(name, name);
421 : }
422 104 : else if (matchAttrsPat(pat, formals, ellipsis)) {
423 357 : for (ATermIterator i(formals); i; ++i) {
424 : ATerm d1;
425 261 : if (!matchFormal(*i, name, d1)) abort();
426 261 : if (map.get(name))
427 : throw EvalError(format("duplicate formal function argument `%1%' at %2%")
428 2 : % aterm2String(name) % showPos(pos));
429 259 : map.set(name, name);
430 : }
431 : }
432 6 : else if (matchAtPat(pat, pat1, pat2)) {
433 6 : checkPatternVars(pos, map, pat1);
434 6 : checkPatternVars(pos, map, pat2);
435 : }
436 0 : else abort();
437 513 : }
438 :
439 :
440 35362 : static void checkAttrSets(ATerm e)
441 : {
442 : ATerm pat, body, pos;
443 35362 : if (matchFunction(e, pat, body, pos)) {
444 504 : ATermMap map(16);
445 504 : checkPatternVars(pos, map, pat);
446 : }
447 :
448 : ATermList bnds;
449 35360 : if (matchAttrs(e, bnds)) {
450 365 : ATermMap names(ATgetLength(bnds));
451 365 : checkAttrs(names, bnds);
452 : }
453 :
454 : ATermList rbnds, nrbnds;
455 35358 : if (matchRec(e, rbnds, nrbnds)) {
456 139 : ATermMap names(ATgetLength(rbnds) + ATgetLength(nrbnds));
457 139 : checkAttrs(names, rbnds);
458 139 : checkAttrs(names, nrbnds);
459 : }
460 :
461 35357 : if (ATgetType(e) == AT_APPL) {
462 26871 : int arity = ATgetArity(ATgetAFun(e));
463 58808 : for (int i = 0; i < arity; ++i)
464 31943 : checkAttrSets(ATgetArgument(e, i));
465 : }
466 :
467 8486 : else if (ATgetType(e) == AT_LIST)
468 6455 : for (ATermIterator i((ATermList) e); i; ++i)
469 3237 : checkAttrSets(*i);
470 35349 : }
471 :
472 :
473 : static Expr parse(EvalState & state,
474 : const char * text, const Path & path,
475 185 : const Path & basePath)
476 : {
477 : yyscan_t scanner;
478 185 : ParseData data;
479 185 : data.basePath = basePath;
480 185 : data.path = path;
481 :
482 185 : yylex_init(&scanner);
483 185 : yy_scan_string(text, scanner);
484 185 : int res = yyparse(scanner, &data);
485 185 : yylex_destroy(scanner);
486 :
487 185 : if (res) throw EvalError(data.error);
488 :
489 : try {
490 184 : checkVarDefs(state.primOps, data.result);
491 4 : } catch (Error & e) {
492 2 : throw EvalError(format("%1%, in `%2%'") % e.msg() % path);
493 : }
494 :
495 182 : checkAttrSets(data.result);
496 :
497 177 : return data.result;
498 : }
499 :
500 :
501 171 : Expr parseExprFromFile(EvalState & state, Path path)
502 : {
503 171 : assert(path[0] == '/');
504 :
505 : #if 0
506 : /* Perhaps this is already an imploded parse tree? */
507 : Expr e = ATreadFromNamedFile(path.c_str());
508 : if (e) return e;
509 : #endif
510 :
511 : /* If `path' is a symlink, follow it. This is so that relative
512 : path references work. */
513 : struct stat st;
514 0 : while (true) {
515 171 : if (lstat(path.c_str(), &st))
516 0 : throw SysError(format("getting status of `%1%'") % path);
517 171 : if (!S_ISLNK(st.st_mode)) break;
518 0 : path = absPath(readLink(path), dirOf(path));
519 : }
520 :
521 : /* If `path' refers to a directory, append `/default.nix'. */
522 171 : if (stat(path.c_str(), &st))
523 0 : throw SysError(format("getting status of `%1%'") % path);
524 171 : if (S_ISDIR(st.st_mode))
525 46 : path = canonPath(path + "/default.nix");
526 :
527 : /* Read and parse the input file. */
528 171 : return parse(state, readFile(path).c_str(), path, dirOf(path));
529 : }
530 :
531 :
532 : Expr parseExprFromString(EvalState & state,
533 14 : const string & s, const Path & basePath)
534 : {
535 14 : return parse(state, s.c_str(), "(string)", basePath);
536 : }
537 :
538 0 :
539 : }
|