From 3db27b01a1ee58180e1c97592ceac24d0e434f0a Mon Sep 17 00:00:00 2001 From: Stephen Dolan Date: Mon, 17 Sep 2012 20:41:57 +0100 Subject: First pass at string interpolation. Requires a lexer hack, but a surprisingly un-hideous one. The lexer maintains a stack of bracket characters so that it can match parens/brackets/braces to tell where a subexp nested in a string actually ends. --- c/lexer.l | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- c/parser.y | 23 ++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/c/lexer.l b/c/lexer.l index dd93f497..94196146 100644 --- a/c/lexer.l +++ b/c/lexer.l @@ -11,11 +11,23 @@ %} +%s IN_PAREN +%s IN_BRACKET +%s IN_BRACE +%s IN_QQINTERP +%x IN_QQSTRING +%{ + static int enter(int opening, int state, yyscan_t yyscanner); + static int try_exit(int closing, int state, yyscan_t yyscanner); +%} + %option noyywrap nounput noinput nodefault %option reentrant %option extra-type="int" %option bison-bridge bison-locations %option prefix="jq_yy" +%option stack + %% @@ -37,7 +49,16 @@ "*=" { return SETMULT; } "/=" { return SETDIV; } "//=" { return SETDEFINEDOR; } -"."|"="|";"|"["|"]"|","|":"|"("|")"|"{"|"}"|"|"|"+"|"-"|"*"|"/"|"\$" { return yytext[0];} +"."|"="|";"|","|":"|"|"|"+"|"-"|"*"|"/"|"\$" { return yytext[0];} + +"["|"{"|"(" { + return enter(yytext[0], YY_START, yyscanner); +} + +"]"|"}"|")" { + return try_exit(yytext[0], YY_START, yyscanner); +} + \"(\\.|[^\\\"])*\" | -?[0-9.]+([eE][+-]?[0-9]+)? { @@ -48,6 +69,25 @@ yylval->literal = jv_invalid_with_msg(jv_string("Unterminated string")); return LITERAL; } + +"@(" { + yy_push_state(IN_QQSTRING, yyscanner); + return QQSTRING_START; +} + +{ + "%(" { + return enter(QQSTRING_INTERP_START, YY_START, yyscanner); + } + [a-z]+ { + yylval->literal = jv_string_sized(yytext, yyleng); + return QQSTRING_TEXT; + } + ")" { + yy_pop_state(yyscanner); + return QQSTRING_END; + } +} [[:alnum:]]+ { yylval->literal = jv_string(yytext); return IDENT;} @@ -63,3 +103,38 @@ "false" { return FALSE; } "null" { return NULL; } */ +static int try_exit(int c, int state, yyscan_t yyscanner) { + char match = 0; + int ret; + switch (state) { + case IN_PAREN: match = ret = ')'; break; + case IN_BRACKET: match = ret = ']'; break; + case IN_BRACE: match = ret = '}'; break; + + case IN_QQINTERP: + match = ')'; + ret = QQSTRING_INTERP_END; + break; + } + assert(match); + if (match == c) { + yy_pop_state(yyscanner); + return ret; + } else { + // FIXME: should we pop? Give a better error at least + return INVALID_CHARACTER; + } +} + +static int enter(int c, int currstate, yyscan_t yyscanner) { + int state = 0; + switch (c) { + case '(': state = IN_PAREN; break; + case '[': state = IN_BRACKET; break; + case '{': state = IN_BRACE; break; + case QQSTRING_INTERP_START: state = IN_QQINTERP; break; + } + assert(state); + yy_push_state(state, yyscanner); + return c; +} diff --git a/c/parser.y b/c/parser.y index d8780a87..2684a26e 100644 --- a/c/parser.y +++ b/c/parser.y @@ -61,6 +61,12 @@ %token SETDIV "/=" %token SETDEFINEDOR "//=" +%token QQSTRING_START +%token QQSTRING_TEXT +%token QQSTRING_INTERP_START +%token QQSTRING_INTERP_END +%token QQSTRING_END + /* revolting hack */ %left ';' %left '|' @@ -74,7 +80,7 @@ %left '*' '/' -%type Exp Term MkDict MkDictPair ExpD ElseBody +%type Exp Term MkDict MkDictPair ExpD ElseBody QQString %{ #include "lexer.yy.h" #define FAIL(loc, msg) \ @@ -264,11 +270,26 @@ Exp "==" Exp { $$ = gen_binop($1, $3, EQ); } | +QQSTRING_START QQString QQSTRING_END { + $$ = $2; +} | + Term { $$ = $1; } +QQString: +/* empty */ { + $$ = gen_op_const(LOADK, jv_string("")); +} | +QQString QQSTRING_TEXT { + $$ = gen_binop($1, gen_op_const(LOADK, $2), '+'); +} | +QQString QQSTRING_INTERP_START Exp QQSTRING_INTERP_END { + $$ = gen_binop($1, $3, '+'); +} + ElseBody: "elif" Exp "then" Exp ElseBody { -- cgit v1.2.3