* More missing constructs.
This commit is contained in:
		
							parent
							
								
									4d6ad5be17
								
							
						
					
					
						commit
						a60317f20f
					
				
					 6 changed files with 161 additions and 138 deletions
				
			
		|  | @ -45,6 +45,7 @@ void run(Strings args) | |||
|     doTest(state, "({x, y, ...}@args: args.z) { x = 1; y = 2; z = 3; }"); | ||||
|     //doTest(state, "({x ? y, y ? x}: y) { }");
 | ||||
|     doTest(state, "let x = 1; in x"); | ||||
|     doTest(state, "let { x = 1; body = x; }"); | ||||
|     doTest(state, "with { x = 1; }; x"); | ||||
|     doTest(state, "let x = 2; in with { x = 1; }; x"); // => 2
 | ||||
|     doTest(state, "with { x = 1; }; with { x = 2; }; x"); // => 1
 | ||||
|  | @ -65,18 +66,19 @@ void run(Strings args) | |||
|     doTest(state, "__head [ 1 2 3 ]"); | ||||
|     doTest(state, "__add 1 2"); | ||||
|     doTest(state, "null"); | ||||
|     //doTest(state, "\"foo\"");
 | ||||
|     //doTest(state, "let s = \"bar\"; in \"foo${s}\"");
 | ||||
|     doTest(state, "\"foo\""); | ||||
|     doTest(state, "let s = \"bar\"; in \"foo${s}\""); | ||||
|     doTest(state, "if true then 1 else 2"); | ||||
|     doTest(state, "if false then 1 else 2"); | ||||
|     doTest(state, "if false || true then 1 else 2"); | ||||
|     doTest(state, "!(true || false)"); | ||||
|     doTest(state, "let x = x; in if true || x then 1 else 2"); | ||||
|     doTest(state, "http://nixos.org/"); | ||||
|     doTest(state, "/etc/passwd"); | ||||
|     //doTest(state, "import ./foo.nix");
 | ||||
|     doTest(state, "map (x: __add 1 x) [ 1 2 3 ]"); | ||||
|     doTest(state, "map (builtins.add 1) [ 1 2 3 ]"); | ||||
|     //doTest(state, "builtins.hasAttr \"x\" { x = 1; }");
 | ||||
|     doTest(state, "builtins.hasAttr \"x\" { x = 1; }"); | ||||
|     doTest(state, "let x = 1; as = rec { inherit x; y = as.x; }; in as.y"); | ||||
|     doTest(state, "let as = { x = 1; }; bs = rec { inherit (as) x; y = x; }; in bs.y"); | ||||
|     doTest(state, "let as = rec { inherit (y) x; y = { x = 1; }; }; in as.x"); | ||||
|  |  | |||
|  | @ -160,9 +160,9 @@ LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s2)) | |||
|     throw TypeError(format(s) % s2); | ||||
| } | ||||
| 
 | ||||
| LocalNoInlineNoReturn(void throwAssertionError(const char * s, const string & s2)) | ||||
| LocalNoInlineNoReturn(void throwAssertionError(const char * s, const Pos & pos)) | ||||
| { | ||||
|     throw AssertionError(format(s) % s2); | ||||
|     throw AssertionError(format(s) % pos); | ||||
| } | ||||
| 
 | ||||
| LocalNoInline(void addErrorPrefix(Error & e, const char * s)) | ||||
|  | @ -341,73 +341,13 @@ void EvalState::eval(Env & env, Expr * e, Value & v) | |||
|     char x; | ||||
|     if (&x < deepestStack) deepestStack = &x; | ||||
|      | ||||
|     //debug(format("eval: %1%") % e);
 | ||||
|     //debug(format("eval: %1%") % *e);
 | ||||
| 
 | ||||
|     checkInterrupt(); | ||||
| 
 | ||||
|     nrEvaluated++; | ||||
| 
 | ||||
|     e->eval(*this, env, v); | ||||
| 
 | ||||
| #if 0 | ||||
|     Sym name; | ||||
|     int n; | ||||
|     ATerm s; ATermList context, es; | ||||
|     ATermList rbnds, nrbnds; | ||||
|     Expr e1, e2, e3, fun, arg, attrs; | ||||
|     Pattern pat; Expr body; Pos pos; | ||||
|      | ||||
|     else if (matchConcatStrings(e, es)) { | ||||
|         PathSet context; | ||||
|         std::ostringstream s; | ||||
|          | ||||
|         bool first = true, isPath = false; | ||||
|         Value vStr; | ||||
|          | ||||
|         for (ATermIterator i(es); i; ++i) { | ||||
|             eval(env, *i, vStr); | ||||
| 
 | ||||
|             /* If the first element is a path, then the result will
 | ||||
|                also be a path, we don't copy anything (yet - that's | ||||
|                done later, since paths are copied when they are used | ||||
|                in a derivation), and none of the strings are allowed | ||||
|                to have contexts. */ | ||||
|             if (first) { | ||||
|                 isPath = vStr.type == tPath; | ||||
|                 first = false; | ||||
|             } | ||||
|              | ||||
|             s << coerceToString(vStr, context, false, !isPath); | ||||
|         } | ||||
|          | ||||
|         if (isPath && !context.empty()) | ||||
|             throwEvalError("a string that refers to a store path cannot be appended to a path, in `%1%'", s.str()); | ||||
| 
 | ||||
|         if (isPath) | ||||
|             mkPath(v, s.str().c_str()); | ||||
|         else | ||||
|             mkString(v, s.str(), context); | ||||
|     } | ||||
| 
 | ||||
|     /* Assertions. */ | ||||
|     else if (matchAssert(e, e1, e2, pos)) { | ||||
|         if (!evalBool(env, e1)) | ||||
|             throwAssertionError("assertion failed at %1%", showPos(pos)); | ||||
|         eval(env, e2, v); | ||||
|     } | ||||
| 
 | ||||
|     /* Negation. */ | ||||
|     else if (matchOpNot(e, e1)) | ||||
|         mkBool(v, !evalBool(env, e1)); | ||||
| 
 | ||||
|     /* Attribute existence test (?). */ | ||||
|     else if (matchOpHasAttr(e, e1, name)) { | ||||
|         Value vAttrs; | ||||
|         eval(env, e1, vAttrs); | ||||
|         forceAttrs(vAttrs); | ||||
|         mkBool(v, vAttrs.attrs->find(name) != vAttrs.attrs->end()); | ||||
|     } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -516,6 +456,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) | ||||
| { | ||||
|     Value vAttrs; | ||||
|     state.eval(env, e, vAttrs); | ||||
|     state.forceAttrs(vAttrs); | ||||
|     mkBool(v, vAttrs.attrs->find(name) != vAttrs.attrs->end()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void ExprLambda::eval(EvalState & state, Env & env, Value & v) | ||||
| { | ||||
|     v.type = tLambda; | ||||
|  | @ -663,6 +612,20 @@ void ExprIf::eval(EvalState & state, Env & env, Value & v) | |||
| } | ||||
| 
 | ||||
|      | ||||
| void ExprAssert::eval(EvalState & state, Env & env, Value & v) | ||||
| { | ||||
|     if (!state.evalBool(env, cond)) | ||||
|         throwAssertionError("assertion failed at %1%", pos); | ||||
|     state.eval(env, body, v); | ||||
| } | ||||
| 
 | ||||
|      | ||||
| void ExprOpNot::eval(EvalState & state, Env & env, Value & v) | ||||
| { | ||||
|     mkBool(v, !state.evalBool(env, e)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void ExprOpEq::eval(EvalState & state, Env & env, Value & v) | ||||
| { | ||||
|     Value v1; state.eval(env, e1, v1); | ||||
|  | @ -713,12 +676,6 @@ void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void ExprOpConcatStrings::eval(EvalState & state, Env & env, Value & v) | ||||
| { | ||||
|     abort(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) | ||||
| { | ||||
|     Value v1; state.eval(env, e1, v1); | ||||
|  | @ -735,6 +692,39 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) | ||||
| { | ||||
|     PathSet context; | ||||
|     std::ostringstream s; | ||||
|          | ||||
|     bool first = true, isPath = false; | ||||
|     Value vStr; | ||||
| 
 | ||||
|     foreach (vector<Expr *>::iterator, i, *es) { | ||||
|         state.eval(env, *i, vStr); | ||||
| 
 | ||||
|         /* If the first element is a path, then the result will also
 | ||||
|            be a path, we don't copy anything (yet - that's done later, | ||||
|            since paths are copied when they are used in a derivation), | ||||
|            and none of the strings are allowed to have contexts. */ | ||||
|         if (first) { | ||||
|             isPath = vStr.type == tPath; | ||||
|             first = false; | ||||
|         } | ||||
|              | ||||
|         s << state.coerceToString(vStr, context, false, !isPath); | ||||
|     } | ||||
|          | ||||
|     if (isPath && !context.empty()) | ||||
|         throwEvalError("a string that refers to a store path cannot be appended to a path, in `%1%'", s.str()); | ||||
| 
 | ||||
|     if (isPath) | ||||
|         mkPath(v, s.str().c_str()); | ||||
|     else | ||||
|         mkString(v, s.str(), context); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void EvalState::forceValue(Value & v) | ||||
| { | ||||
|     if (v.type == tThunk) { | ||||
|  |  | |||
|  | @ -45,7 +45,6 @@ static void adjustLoc(YYLTYPE * loc, const char * s, size_t len) | |||
| 
 | ||||
| static Expr * unescapeStr(const char * s) | ||||
| { | ||||
| #if 0 | ||||
|     string t; | ||||
|     char c; | ||||
|     while ((c = *s++)) { | ||||
|  | @ -64,8 +63,7 @@ static Expr * unescapeStr(const char * s) | |||
|         } | ||||
|         else t += c; | ||||
|     } | ||||
|     return makeStr(toATerm(t), ATempty); | ||||
| #endif | ||||
|     return new ExprString(t); | ||||
| } | ||||
| 
 | ||||
|   | ||||
|  |  | |||
|  | @ -40,6 +40,11 @@ void ExprSelect::show(std::ostream & str) | |||
|     str << "(" << *e << ")." << name; | ||||
| } | ||||
| 
 | ||||
| void ExprOpHasAttr::show(std::ostream & str) | ||||
| { | ||||
|     str << "(" << *e << ") ? " << name; | ||||
| } | ||||
| 
 | ||||
| void ExprAttrs::show(std::ostream & str) | ||||
| { | ||||
|     if (recursive) str << "rec "; | ||||
|  | @ -87,19 +92,37 @@ void ExprIf::show(std::ostream & str) | |||
|     str << "if " << *cond << " then " << *then << " else " << *else_; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| string showPos(ATerm pos) | ||||
| void ExprAssert::show(std::ostream & str) | ||||
| { | ||||
|     ATerm path; | ||||
|     int line, column; | ||||
|     if (matchNoPos(pos)) return "undefined position"; | ||||
|     if (!matchPos(pos, path, line, column)) | ||||
|         throw badTerm("position expected", pos); | ||||
|     return (format("`%1%:%2%:%3%'") % aterm2String(path) % line % column).str(); | ||||
|     str << "assert " << *cond << "; " << *body; | ||||
| } | ||||
| 
 | ||||
| void ExprOpNot::show(std::ostream & str) | ||||
| { | ||||
|     str << "! " << *e; | ||||
| } | ||||
| 
 | ||||
| void ExprConcatStrings::show(std::ostream & str) | ||||
| { | ||||
|     bool first = true; | ||||
|     foreach (vector<Expr *>::iterator, i, *es) { | ||||
|         if (first) first = false; else str << " + "; | ||||
|         str << **i; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| std::ostream & operator << (std::ostream & str, const Pos & pos) | ||||
| { | ||||
|     if (!pos.line) | ||||
|         str << "undefined position"; | ||||
|     else | ||||
|         str << (format("`%1%:%2%:%3%'") % pos.file % pos.line % pos.column).str(); | ||||
|     return str; | ||||
| } | ||||
|      | ||||
| 
 | ||||
| #if 0 | ||||
| ATerm bottomupRewrite(TermFun & f, ATerm e) | ||||
| { | ||||
|     checkInterrupt(); | ||||
|  |  | |||
|  | @ -24,6 +24,9 @@ struct Pos | |||
| }; | ||||
| 
 | ||||
| 
 | ||||
| std::ostream & operator << (std::ostream & str, const Pos & pos); | ||||
| 
 | ||||
| 
 | ||||
| /* Abstract syntax of Nix expressions. */ | ||||
| 
 | ||||
| struct Env; | ||||
|  | @ -81,6 +84,14 @@ struct ExprSelect : Expr | |||
|     COMMON_METHODS | ||||
| }; | ||||
| 
 | ||||
| struct ExprOpHasAttr : Expr | ||||
| { | ||||
|     Expr * e; | ||||
|     string name; | ||||
|     ExprOpHasAttr(Expr * e, const string & name) : e(e), name(name) { }; | ||||
|     COMMON_METHODS | ||||
| }; | ||||
| 
 | ||||
| struct ExprAttrs : Expr | ||||
| { | ||||
|     bool recursive; | ||||
|  | @ -139,6 +150,21 @@ struct ExprIf : Expr | |||
|     COMMON_METHODS | ||||
| }; | ||||
| 
 | ||||
| struct ExprAssert : Expr | ||||
| { | ||||
|     Pos pos; | ||||
|     Expr * cond, * body; | ||||
|     ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; | ||||
|     COMMON_METHODS | ||||
| }; | ||||
| 
 | ||||
| struct ExprOpNot : Expr | ||||
| { | ||||
|     Expr * e; | ||||
|     ExprOpNot(Expr * e) : e(e) { }; | ||||
|     COMMON_METHODS | ||||
| }; | ||||
| 
 | ||||
| #define MakeBinOp(name, s) \ | ||||
|     struct Expr##name : Expr \ | ||||
|     { \ | ||||
|  | @ -158,15 +184,17 @@ MakeBinOp(OpAnd, "&&") | |||
| MakeBinOp(OpOr, "||") | ||||
| MakeBinOp(OpImpl, "->") | ||||
| MakeBinOp(OpUpdate, "//") | ||||
| MakeBinOp(OpConcatStrings, "+") | ||||
| MakeBinOp(OpConcatLists, "++") | ||||
| 
 | ||||
| struct ExprConcatStrings : Expr | ||||
| { | ||||
|     vector<Expr *> * es; | ||||
|     ExprConcatStrings(vector<Expr *> * es) : es(es) { }; | ||||
|     COMMON_METHODS | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #if 0 | ||||
| /* Show a position. */ | ||||
| string showPos(ATerm pos); | ||||
| 
 | ||||
| 
 | ||||
| /* Generic bottomup traversal over ATerms.  The traversal first
 | ||||
|    recursively descends into subterms, and then applies the given term | ||||
|    function to the resulting term. */ | ||||
|  |  | |||
|  | @ -20,7 +20,6 @@ | |||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "aterm.hh" | ||||
| #include "util.hh" | ||||
|      | ||||
| #include "nixexpr.hh" | ||||
|  | @ -116,13 +115,13 @@ static void fixAttrs(ExprAttrs & attrs) | |||
|             for (ATermIterator j(attrPath); j; ) { | ||||
|                 name = *j; ++j; | ||||
|                 if (t->leaf) throw ParseError(format("attribute set containing `%1%' at %2% already defined at %3%") | ||||
|                     % showAttrPath(attrPath) % showPos(pos) % showPos (t->pos)); | ||||
|                     % showAttrPath(attrPath) % showPos(pos) % showPos(t->pos)); | ||||
|                 t = &(t->children[name]); | ||||
|             } | ||||
| 
 | ||||
|             if (t->leaf) | ||||
|                 throw ParseError(format("duplicate definition of attribute `%1%' at %2% and %3%") | ||||
|                     % showAttrPath(attrPath) % showPos(pos) % showPos (t->pos)); | ||||
|                     % showAttrPath(attrPath) % showPos(pos) % showPos(t->pos)); | ||||
|             if (!t->children.empty()) | ||||
|                 throw ParseError(format("duplicate definition of attribute `%1%' at %2%") | ||||
|                     % showAttrPath(attrPath) % showPos(pos)); | ||||
|  | @ -289,30 +288,11 @@ static Pos makeCurPos(YYLTYPE * loc, ParseData * data) | |||
| 
 | ||||
| void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error) | ||||
| { | ||||
|     data->error = (format("%1%, at `%2%':%3%:%4%") | ||||
|         % error % data->path % loc->first_line % loc->first_column).str(); | ||||
|     data->error = (format("%1%, at %2%") | ||||
|         % error % makeCurPos(loc, data)).str(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Make sure that the parse stack is scanned by the ATerm garbage | ||||
|    collector. */ | ||||
| static void * mallocAndProtect(size_t size) | ||||
| { | ||||
|     void * p = malloc(size); | ||||
|     if (p) ATprotectMemory(p, size); | ||||
|     return p; | ||||
| } | ||||
| 
 | ||||
| static void freeAndUnprotect(void * p) | ||||
| { | ||||
|     ATunprotectMemory(p); | ||||
|     free(p); | ||||
| } | ||||
| 
 | ||||
| #define YYMALLOC mallocAndProtect | ||||
| #define YYFREE freeAndUnprotect | ||||
| 
 | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
|  | @ -329,18 +309,20 @@ static void freeAndUnprotect(void * p) | |||
|   char * path; | ||||
|   char * uri; | ||||
|   std::list<std::string> * ids; | ||||
|   std::vector<nix::Expr *> * string_parts; | ||||
| } | ||||
| 
 | ||||
| %type <e> start expr expr_function expr_if expr_op | ||||
| %type <e> expr_app expr_select expr_simple | ||||
| %type <list> expr_list | ||||
| %type <attrs> binds | ||||
| %type <ts> attrpath string_parts ind_string_parts | ||||
| %type <ts> attrpath ind_string_parts | ||||
| %type <formals> formals | ||||
| %type <formal> formal | ||||
| %type <ids> ids | ||||
| %type <string_parts> string_parts | ||||
| %token <id> ID ATTRPATH | ||||
| %token <t> STR IND_STR | ||||
| %token <e> STR IND_STR | ||||
| %token <n> INT | ||||
| %token <path> PATH | ||||
| %token <uri> URI | ||||
|  | @ -375,9 +357,8 @@ expr_function | |||
|     { $$ = new ExprLambda(CUR_POS, $5, true, $2, $7); } | ||||
|   | ID '@' '{' formals '}' ':' expr_function | ||||
|     { $$ = new ExprLambda(CUR_POS, $1, true, $4, $7); } | ||||
|   /* | ASSERT expr ';' expr_function | ||||
|     { $$ = makeAssert($2, $4, CUR_POS); } | ||||
|     */ | ||||
|   | ASSERT expr ';' expr_function | ||||
|     { $$ = new ExprAssert(CUR_POS, $2, $4); } | ||||
|   | WITH expr ';' expr_function | ||||
|     { $$ = new ExprWith(CUR_POS, $2, $4); } | ||||
|   | LET binds IN expr_function | ||||
|  | @ -391,18 +372,20 @@ expr_if | |||
|   ; | ||||
| 
 | ||||
| expr_op | ||||
|   : /* '!' expr_op %prec NEG { $$ = makeOpNot($2); } | ||||
|        | */ | ||||
|     expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); } | ||||
|   : '!' expr_op %prec NEG { $$ = new ExprOpNot($2); } | ||||
|   | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); } | ||||
|   | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); } | ||||
|   | expr_op AND expr_op { $$ = new ExprOpAnd($1, $3); } | ||||
|   | expr_op OR expr_op { $$ = new ExprOpOr($1, $3); } | ||||
|   | expr_op IMPL expr_op { $$ = new ExprOpImpl($1, $3); } | ||||
|   | expr_op UPDATE expr_op { $$ = new ExprOpUpdate($1, $3); } | ||||
|   /* | ||||
|   | expr_op '?' ID { $$ = makeOpHasAttr($1, $3); } | ||||
|   */ | ||||
|   | expr_op '+' expr_op { $$ = new ExprOpConcatStrings($1, $3); } | ||||
|   | expr_op '?' ID { $$ = new ExprOpHasAttr($1, $3); } | ||||
|   | expr_op '+' expr_op | ||||
|     { vector<Expr *> * l = new vector<Expr *>; | ||||
|       l->push_back($1); | ||||
|       l->push_back($3); | ||||
|       $$ = new ExprConcatStrings(l); | ||||
|     } | ||||
|   | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists($1, $3); } | ||||
|   | expr_app | ||||
|   ; | ||||
|  | @ -421,26 +404,25 @@ expr_select | |||
| 
 | ||||
| expr_simple | ||||
|   : ID { $$ = new ExprVar($1); } | ||||
|   | INT { $$ = new ExprInt($1); } /* | ||||
|   | INT { $$ = new ExprInt($1); } | ||||
|   | '"' string_parts '"' { | ||||
|       /* For efficiency, and to simplify parse trees a bit. * / | ||||
|       if ($2 == ATempty) $$ = makeStr(toATerm(""), ATempty); | ||||
|       else if (ATgetNext($2) == ATempty) $$ = ATgetFirst($2); | ||||
|       else $$ = makeConcatStrings(ATreverse($2)); | ||||
|       /* For efficiency, and to simplify parse trees a bit. */ | ||||
|       if ($2->empty()) $$ = new ExprString(""); | ||||
|       else if ($2->size() == 1) $$ = $2->front(); | ||||
|       else $$ = new ExprConcatStrings($2); | ||||
|   } | ||||
|   /* | ||||
|   | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE { | ||||
|       $$ = stripIndentation(ATreverse($2)); | ||||
|   } | ||||
|                                   */ | ||||
|   */ | ||||
|   | PATH { $$ = new ExprPath(absPath($1, data->basePath)); } | ||||
|   | URI { $$ = new ExprString($1); } | ||||
|   | '(' expr ')' { $$ = $2; } | ||||
| /* | ||||
|   /* Let expressions `let {..., body = ...}' are just desugared | ||||
|      into `(rec {..., body = ...}).body'. * / | ||||
|      into `(rec {..., body = ...}).body'. */ | ||||
|   | LET '{' binds '}' | ||||
|     { $$ = makeSelect(fixAttrs(true, $3), toATerm("body")); } | ||||
|   */ | ||||
|     { fixAttrs(*$3); $3->recursive = true; $$ = new ExprSelect($3, "body"); } | ||||
|   | REC '{' binds '}' | ||||
|     { fixAttrs(*$3); $3->recursive = true; $$ = $3; } | ||||
|   | '{' binds '}' | ||||
|  | @ -449,9 +431,9 @@ expr_simple | |||
|   ; | ||||
| 
 | ||||
| string_parts | ||||
|   : string_parts STR { $$ = ATinsert($1, $2); } | ||||
|   | string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = ATinsert($1, $3); } | ||||
|   | { $$ = ATempty; } | ||||
|   : string_parts STR { $$ = $1; $1->push_back($2); } | ||||
|   | string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); } | ||||
|   | { $$ = new vector<Expr *>; } | ||||
|   ; | ||||
| 
 | ||||
| ind_string_parts | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue