summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Dolan <mu@netsoc.tcd.ie>2012-09-17 20:41:57 +0100
committerStephen Dolan <mu@netsoc.tcd.ie>2012-09-17 20:41:57 +0100
commit3db27b01a1ee58180e1c97592ceac24d0e434f0a (patch)
tree13537ba98eb26c031feefd220d73a1be326e410f
parent50ebb036c4bfff28e6288e69751efbd9e7298f4f (diff)
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.
-rw-r--r--c/lexer.l77
-rw-r--r--c/parser.y23
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;
+}
+
+<IN_QQSTRING>{
+ "%(" {
+ 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 <literal> QQSTRING_TEXT
+%token QQSTRING_INTERP_START
+%token QQSTRING_INTERP_END
+%token QQSTRING_END
+
/* revolting hack */
%left ';'
%left '|'
@@ -74,7 +80,7 @@
%left '*' '/'
-%type <blk> Exp Term MkDict MkDictPair ExpD ElseBody
+%type <blk> 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 {