#include "jsi.h" #include "jslex.h" #include "utf.h" JS_NORETURN static void jsY_error(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3); static void jsY_error(js_State *J, const char *fmt, ...) { va_list ap; char buf[512]; char msgbuf[256]; va_start(ap, fmt); vsnprintf(msgbuf, 256, fmt, ap); va_end(ap); snprintf(buf, 256, "%s:%d: ", J->filename, J->lexline); strcat(buf, msgbuf); js_newsyntaxerror(J, buf); js_throw(J); } static const char *tokenstring[] = { "(end-of-file)", "'\\x01'", "'\\x02'", "'\\x03'", "'\\x04'", "'\\x05'", "'\\x06'", "'\\x07'", "'\\x08'", "'\\x09'", "'\\x0A'", "'\\x0B'", "'\\x0C'", "'\\x0D'", "'\\x0E'", "'\\x0F'", "'\\x10'", "'\\x11'", "'\\x12'", "'\\x13'", "'\\x14'", "'\\x15'", "'\\x16'", "'\\x17'", "'\\x18'", "'\\x19'", "'\\x1A'", "'\\x1B'", "'\\x1C'", "'\\x1D'", "'\\x1E'", "'\\x1F'", "' '", "'!'", "'\"'", "'#'", "'$'", "'%'", "'&'", "'\\''", "'('", "')'", "'*'", "'+'", "','", "'-'", "'.'", "'/'", "'0'", "'1'", "'2'", "'3'", "'4'", "'5'", "'6'", "'7'", "'8'", "'9'", "':'", "';'", "'<'", "'='", "'>'", "'?'", "'@'", "'A'", "'B'", "'C'", "'D'", "'E'", "'F'", "'G'", "'H'", "'I'", "'J'", "'K'", "'L'", "'M'", "'N'", "'O'", "'P'", "'Q'", "'R'", "'S'", "'T'", "'U'", "'V'", "'W'", "'X'", "'Y'", "'Z'", "'['", "'\'", "']'", "'^'", "'_'", "'`'", "'a'", "'b'", "'c'", "'d'", "'e'", "'f'", "'g'", "'h'", "'i'", "'j'", "'k'", "'l'", "'m'", "'n'", "'o'", "'p'", "'q'", "'r'", "'s'", "'t'", "'u'", "'v'", "'w'", "'x'", "'y'", "'z'", "'{'", "'|'", "'}'", "'~'", "'\\x7F'", 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, "(identifier)", "(number)", "(string)", "(regexp)", "'<='", "'>='", "'=='", "'!='", "'==='", "'!=='", "'<<'", "'>>'", "'>>>'", "'&&'", "'||'", "'+='", "'-='", "'*='", "'/='", "'%='", "'<<='", "'>>='", "'>>>='", "'&='", "'|='", "'^='", "'++'", "'--'", "'break'", "'case'", "'catch'", "'continue'", "'debugger'", "'default'", "'delete'", "'do'", "'else'", "'false'", "'finally'", "'for'", "'function'", "'if'", "'in'", "'instanceof'", "'new'", "'null'", "'return'", "'switch'", "'this'", "'throw'", "'true'", "'try'", "'typeof'", "'var'", "'void'", "'while'", "'with'", }; const char *jsY_tokenstring(int token) { if (token >= 0 && token < (int)nelem(tokenstring)) if (tokenstring[token]) return tokenstring[token]; return ""; } static const char *keywords[] = { "break", "case", "catch", "continue", "debugger", "default", "delete", "do", "else", "false", "finally", "for", "function", "if", "in", "instanceof", "new", "null", "return", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", }; int jsY_findword(const char *s, const char **list, int num) { int l = 0; int r = num - 1; while (l <= r) { int m = (l + r) >> 1; int c = strcmp(s, list[m]); if (c < 0) r = m - 1; else if (c > 0) l = m + 1; else return m; } return -1; } static int jsY_findkeyword(js_State *J, const char *s) { int i = jsY_findword(s, keywords, nelem(keywords)); if (i >= 0) { J->text = keywords[i]; return TK_BREAK + i; /* first keyword + i */ } J->text = js_intern(J, s); return TK_IDENTIFIER; } int jsY_iswhite(int c) { return c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0 || c == 0xFEFF; } int jsY_isnewline(int c) { return c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029; } #ifndef isalpha #define isalpha(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) #endif #ifndef isdigit #define isdigit(c) (c >= '0' && c <= '9') #endif #ifndef ishex #define ishex(c) ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) #endif static int jsY_isidentifierstart(int c) { return isalpha(c) || c == '$' || c == '_' || isalpharune(c); } static int jsY_isidentifierpart(int c) { return isdigit(c) || isalpha(c) || c == '$' || c == '_' || isalpharune(c); } static int jsY_isdec(int c) { return isdigit(c); } int jsY_ishex(int c) { return isdigit(c) || ishex(c); } int jsY_tohex(int c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 0xA; if (c >= 'A' && c <= 'F') return c - 'A' + 0xA; return 0; } static void jsY_next(js_State *J) { Rune c; if (*J->source == 0) { J->lexchar = EOF; return; } J->source += chartorune(&c, J->source); /* consume CR LF as one unit */ if (c == '\r' && *J->source == '\n') ++J->source; if (jsY_isnewline(c)) { J->line++; c = '\n'; } J->lexchar = c; } #define jsY_accept(J, x) (J->lexchar == x ? (jsY_next(J), 1) : 0) #define jsY_expect(J, x) if (!jsY_accept(J, x)) jsY_error(J, "expected '%c'", x) static void jsY_unescape(js_State *J) { if (jsY_accept(J, '\\')) { if (jsY_accept(J, 'u')) { int x = 0; if (!jsY_ishex(J->lexchar)) { goto error; } x |= jsY_tohex(J->lexchar) << 12; jsY_next(J); if (!jsY_ishex(J->lexchar)) { goto error; } x |= jsY_tohex(J->lexchar) << 8; jsY_next(J); if (!jsY_ishex(J->lexchar)) { goto error; } x |= jsY_tohex(J->lexchar) << 4; jsY_next(J); if (!jsY_ishex(J->lexchar)) { goto error; } x |= jsY_tohex(J->lexchar); J->lexchar = x; return; } error: jsY_error(J, "unexpected escape sequence"); } } static void textinit(js_State *J) { if (!J->lexbuf.text) { J->lexbuf.cap = 4096; J->lexbuf.text = js_malloc(J, J->lexbuf.cap); } J->lexbuf.len = 0; } static void textpush(js_State *J, Rune c) { int n; if (c == EOF) n = 1; else n = runelen(c); if (J->lexbuf.len + n > J->lexbuf.cap) { J->lexbuf.cap = J->lexbuf.cap * 2; J->lexbuf.text = js_realloc(J, J->lexbuf.text, J->lexbuf.cap); } if (c == EOF) J->lexbuf.text[J->lexbuf.len++] = 0; else J->lexbuf.len += runetochar(J->lexbuf.text + J->lexbuf.len, &c); } static char *textend(js_State *J) { textpush(J, EOF); return J->lexbuf.text; } static void lexlinecomment(js_State *J) { while (J->lexchar != EOF && J->lexchar != '\n') jsY_next(J); } static int lexcomment(js_State *J) { /* already consumed initial '/' '*' sequence */ while (J->lexchar != EOF) { if (jsY_accept(J, '*')) { while (J->lexchar == '*') jsY_next(J); if (jsY_accept(J, '/')) return 0; } else jsY_next(J); } return -1; } static double lexhex(js_State *J) { double n = 0; if (!jsY_ishex(J->lexchar)) jsY_error(J, "malformed hexadecimal number"); while (jsY_ishex(J->lexchar)) { n = n * 16 + jsY_tohex(J->lexchar); jsY_next(J); } return n; } #if 0 static double lexinteger(js_State *J) { double n = 0; if (!jsY_isdec(J->lexchar)) jsY_error(J, "malformed number"); while (jsY_isdec(J->lexchar)) { n = n * 10 + (J->lexchar - '0'); jsY_next(J); } return n; } static double lexfraction(js_State *J) { double n = 0; double d = 1; while (jsY_isdec(J->lexchar)) { n = n * 10 + (J->lexchar - '0'); d = d * 10; jsY_next(J); } return n / d; } static double lexexponent(js_State *J) { double sign; if (jsY_accept(J, 'e') || jsY_accept(J, 'E')) { if (jsY_accept(J, '-')) sign = -1; else if (jsY_accept(J, '+')) sign = 1; else sign = 1; return sign * lexinteger(J); } return 0; } static int lexnumber(js_State *J) { double n; double e; if (jsY_accept(J, '0')) { if (jsY_accept(J, 'x') || jsY_accept(J, 'X')) { J->number = lexhex(J); return TK_NUMBER; } if (jsY_isdec(J->lexchar)) jsY_error(J, "number with leading zero"); n = 0; if (jsY_accept(J, '.')) n += lexfraction(J); } else if (jsY_accept(J, '.')) { if (!jsY_isdec(J->lexchar)) return '.'; n = lexfraction(J); } else { n = lexinteger(J); if (jsY_accept(J, '.')) n += lexfraction(J); } e = lexexponent(J); if (e < 0) n /= pow(10, -e); else if (e > 0) n *= pow(10, e); if (jsY_isidentifierstart(J->lexchar)) jsY_error(J, "number with letter suffix"); J->number = n; return TK_NUMBER; } #else static int lexnumber(js_State *J) { const char *s = J->source - 1; if (jsY_accept(J, '0')) { if (jsY_accept(J, 'x') || jsY_accept(J, 'X')) { J->number = lexhex(J); return TK_NUMBER; } if (jsY_isdec(J->lexchar)) jsY_error(J, "number with leading zero"); if (jsY_accept(J, '.')) { while (jsY_isdec(J->lexchar)) jsY_next(J); } } else if (jsY_accept(J, '.')) { if (!jsY_isdec(J->lexchar)) return '.'; while (jsY_isdec(J->lexchar)) jsY_next(J); } else { while (jsY_isdec(J->lexchar)) jsY_next(J); if (jsY_accept(J, '.')) { while (jsY_isdec(J->lexchar)) jsY_next(J); } } if (jsY_accept(J, 'e') || jsY_accept(J, 'E')) { if (J->lexchar == '-' || J->lexchar == '+') jsY_next(J); if (jsY_isdec(J->lexchar)) while (jsY_isdec(J->lexchar)) jsY_next(J); else jsY_error(J, "missing exponent"); } if (jsY_isidentifierstart(J->lexchar)) jsY_error(J, "number with letter suffix"); J->number = js_strtod(s, NULL); return TK_NUMBER; } #endif static int lexescape(js_State *J) { int x = 0; /* already consumed '\' */ if (jsY_accept(J, '\n')) return 0; switch (J->lexchar) { case EOF: jsY_error(J, "unterminated escape sequence"); case 'u': jsY_next(J); if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 12; jsY_next(J); } if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 8; jsY_next(J); } if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 4; jsY_next(J); } if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar); jsY_next(J); } textpush(J, x); break; case 'x': jsY_next(J); if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 4; jsY_next(J); } if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar); jsY_next(J); } textpush(J, x); break; case '0': textpush(J, 0); jsY_next(J); break; case '\\': textpush(J, '\\'); jsY_next(J); break; case '\'': textpush(J, '\''); jsY_next(J); break; case '"': textpush(J, '"'); jsY_next(J); break; case 'b': textpush(J, '\b'); jsY_next(J); break; case 'f': textpush(J, '\f'); jsY_next(J); break; case 'n': textpush(J, '\n'); jsY_next(J); break; case 'r': textpush(J, '\r'); jsY_next(J); break; case 't': textpush(J, '\t'); jsY_next(J); break; case 'v': textpush(J, '\v'); jsY_next(J); break; default: textpush(J, J->lexchar); jsY_next(J); break; } return 0; } static int lexstring(js_State *J) { const char *s; int q = J->lexchar; jsY_next(J); textinit(J); while (J->lexchar != q) { if (J->lexchar == EOF || J->lexchar == '\n') jsY_error(J, "string not terminated"); if (jsY_accept(J, '\\')) { if (lexescape(J)) jsY_error(J, "malformed escape sequence"); } else { textpush(J, J->lexchar); jsY_next(J); } } jsY_expect(J, q); s = textend(J); J->text = js_intern(J, s); return TK_STRING; } /* the ugliest language wart ever... */ static int isregexpcontext(int last) { switch (last) { case ']': case ')': case '}': case TK_IDENTIFIER: case TK_NUMBER: case TK_STRING: case TK_FALSE: case TK_NULL: case TK_THIS: case TK_TRUE: return 0; default: return 1; } } static int lexregexp(js_State *J) { const char *s; int g, m, i; int inclass = 0; /* already consumed initial '/' */ textinit(J); /* regexp body */ while (J->lexchar != '/' || inclass) { if (J->lexchar == EOF || J->lexchar == '\n') { jsY_error(J, "regular expression not terminated"); } else if (jsY_accept(J, '\\')) { if (jsY_accept(J, '/')) { textpush(J, '/'); } else { textpush(J, '\\'); if (J->lexchar == EOF || J->lexchar == '\n') jsY_error(J, "regular expression not terminated"); textpush(J, J->lexchar); jsY_next(J); } } else { if (J->lexchar == '[' && !inclass) inclass = 1; if (J->lexchar == ']' && inclass) inclass = 0; textpush(J, J->lexchar); jsY_next(J); } } jsY_expect(J, '/'); s = textend(J); /* regexp flags */ g = i = m = 0; while (jsY_isidentifierpart(J->lexchar)) { if (jsY_accept(J, 'g')) ++g; else if (jsY_accept(J, 'i')) ++i; else if (jsY_accept(J, 'm')) ++m; else jsY_error(J, "illegal flag in regular expression: %c", J->lexchar); } if (g > 1 || i > 1 || m > 1) jsY_error(J, "duplicated flag in regular expression"); J->text = js_intern(J, s); J->number = 0; if (g) J->number += JS_REGEXP_G; if (i) J->number += JS_REGEXP_I; if (m) J->number += JS_REGEXP_M; return TK_REGEXP; } /* simple "return [no Line Terminator here] ..." contexts */ static int isnlthcontext(int last) { switch (last) { case TK_BREAK: case TK_CONTINUE: case TK_RETURN: case TK_THROW: return 1; default: return 0; } } static int jsY_lexx(js_State *J) { J->newline = 0; while (1) { J->lexline = J->line; /* save location of beginning of token */ while (jsY_iswhite(J->lexchar)) jsY_next(J); if (jsY_accept(J, '\n')) { J->newline = 1; if (isnlthcontext(J->lasttoken)) return ';'; continue; } if (jsY_accept(J, '/')) { if (jsY_accept(J, '/')) { lexlinecomment(J); continue; } else if (jsY_accept(J, '*')) { if (lexcomment(J)) jsY_error(J, "multi-line comment not terminated"); continue; } else if (isregexpcontext(J->lasttoken)) { return lexregexp(J); } else if (jsY_accept(J, '=')) { return TK_DIV_ASS; } else { return '/'; } } if (J->lexchar >= '0' && J->lexchar <= '9') { return lexnumber(J); } switch (J->lexchar) { case '(': jsY_next(J); return '('; case ')': jsY_next(J); return ')'; case ',': jsY_next(J); return ','; case ':': jsY_next(J); return ':'; case ';': jsY_next(J); return ';'; case '?': jsY_next(J); return '?'; case '[': jsY_next(J); return '['; case ']': jsY_next(J); return ']'; case '{': jsY_next(J); return '{'; case '}': jsY_next(J); return '}'; case '~': jsY_next(J); return '~'; case '\'': case '"': return lexstring(J); case '.': return lexnumber(J); case '<': jsY_next(J); if (jsY_accept(J, '<')) { if (jsY_accept(J, '=')) return TK_SHL_ASS; return TK_SHL; } if (jsY_accept(J, '=')) return TK_LE; return '<'; case '>': jsY_next(J); if (jsY_accept(J, '>')) { if (jsY_accept(J, '>')) { if (jsY_accept(J, '=')) return TK_USHR_ASS; return TK_USHR; } if (jsY_accept(J, '=')) return TK_SHR_ASS; return TK_SHR; } if (jsY_accept(J, '=')) return TK_GE; return '>'; case '=': jsY_next(J); if (jsY_accept(J, '=')) { if (jsY_accept(J, '=')) return TK_STRICTEQ; return TK_EQ; } return '='; case '!': jsY_next(J); if (jsY_accept(J, '=')) { if (jsY_accept(J, '=')) return TK_STRICTNE; return TK_NE; } return '!'; case '+': jsY_next(J); if (jsY_accept(J, '+')) return TK_INC; if (jsY_accept(J, '=')) return TK_ADD_ASS; return '+'; case '-': jsY_next(J); if (jsY_accept(J, '-')) return TK_DEC; if (jsY_accept(J, '=')) return TK_SUB_ASS; return '-'; case '*': jsY_next(J); if (jsY_accept(J, '=')) return TK_MUL_ASS; return '*'; case '%': jsY_next(J); if (jsY_accept(J, '=')) return TK_MOD_ASS; return '%'; case '&': jsY_next(J); if (jsY_accept(J, '&')) return TK_AND; if (jsY_accept(J, '=')) return TK_AND_ASS; return '&'; case '|': jsY_next(J); if (jsY_accept(J, '|')) return TK_OR; if (jsY_accept(J, '=')) return TK_OR_ASS; return '|'; case '^': jsY_next(J); if (jsY_accept(J, '=')) return TK_XOR_ASS; return '^'; case EOF: return 0; /* EOF */ } /* Handle \uXXXX escapes in identifiers */ jsY_unescape(J); if (jsY_isidentifierstart(J->lexchar)) { textinit(J); textpush(J, J->lexchar); jsY_next(J); jsY_unescape(J); while (jsY_isidentifierpart(J->lexchar)) { textpush(J, J->lexchar); jsY_next(J); jsY_unescape(J); } textend(J); return jsY_findkeyword(J, J->lexbuf.text); } if (J->lexchar >= 0x20 && J->lexchar <= 0x7E) jsY_error(J, "unexpected character: '%c'", J->lexchar); jsY_error(J, "unexpected character: \\u%04X", J->lexchar); } } void jsY_initlex(js_State *J, const char *filename, const char *source) { J->filename = filename; J->source = source; J->line = 1; J->lasttoken = 0; jsY_next(J); /* load first lookahead character */ } int jsY_lex(js_State *J) { return J->lasttoken = jsY_lexx(J); } static int lexjsonnumber(js_State *J) { const char *s = J->source - 1; if (J->lexchar == '-') jsY_next(J); if (J->lexchar == '0') jsY_next(J); else if (J->lexchar >= '1' && J->lexchar <= '9') while (isdigit(J->lexchar)) jsY_next(J); else jsY_error(J, "unexpected non-digit"); if (jsY_accept(J, '.')) { if (isdigit(J->lexchar)) while (isdigit(J->lexchar)) jsY_next(J); else jsY_error(J, "missing digits after decimal point"); } if (jsY_accept(J, 'e') || jsY_accept(J, 'E')) { if (J->lexchar == '-' || J->lexchar == '+') jsY_next(J); if (isdigit(J->lexchar)) while (isdigit(J->lexchar)) jsY_next(J); else jsY_error(J, "missing digits after exponent indicator"); } J->number = js_strtod(s, NULL); return TK_NUMBER; } static int lexjsonescape(js_State *J) { int x = 0; /* already consumed '\' */ switch (J->lexchar) { default: jsY_error(J, "invalid escape sequence"); case 'u': jsY_next(J); if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 12; jsY_next(J); } if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 8; jsY_next(J); } if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar) << 4; jsY_next(J); } if (!jsY_ishex(J->lexchar)) return 1; else { x |= jsY_tohex(J->lexchar); jsY_next(J); } textpush(J, x); break; case '"': textpush(J, '"'); jsY_next(J); break; case '\\': textpush(J, '\\'); jsY_next(J); break; case '/': textpush(J, '/'); jsY_next(J); break; case 'b': textpush(J, '\b'); jsY_next(J); break; case 'f': textpush(J, '\f'); jsY_next(J); break; case 'n': textpush(J, '\n'); jsY_next(J); break; case 'r': textpush(J, '\r'); jsY_next(J); break; case 't': textpush(J, '\t'); jsY_next(J); break; } return 0; } static int lexjsonstring(js_State *J) { const char *s; textinit(J); while (J->lexchar != '"') { if (J->lexchar == EOF) jsY_error(J, "unterminated string"); else if (J->lexchar < 32) jsY_error(J, "invalid control character in string"); else if (jsY_accept(J, '\\')) lexjsonescape(J); else { textpush(J, J->lexchar); jsY_next(J); } } jsY_expect(J, '"'); s = textend(J); J->text = js_intern(J, s); return TK_STRING; } int jsY_lexjson(js_State *J) { while (1) { J->lexline = J->line; /* save location of beginning of token */ while (jsY_iswhite(J->lexchar) || J->lexchar == '\n') jsY_next(J); if ((J->lexchar >= '0' && J->lexchar <= '9') || J->lexchar == '-') return lexjsonnumber(J); switch (J->lexchar) { case ',': jsY_next(J); return ','; case ':': jsY_next(J); return ':'; case '[': jsY_next(J); return '['; case ']': jsY_next(J); return ']'; case '{': jsY_next(J); return '{'; case '}': jsY_next(J); return '}'; case '"': jsY_next(J); return lexjsonstring(J); case 'f': jsY_next(J); jsY_expect(J, 'a'); jsY_expect(J, 'l'); jsY_expect(J, 's'); jsY_expect(J, 'e'); return TK_FALSE; case 'n': jsY_next(J); jsY_expect(J, 'u'); jsY_expect(J, 'l'); jsY_expect(J, 'l'); return TK_NULL; case 't': jsY_next(J); jsY_expect(J, 'r'); jsY_expect(J, 'u'); jsY_expect(J, 'e'); return TK_TRUE; case EOF: return 0; /* EOF */ } if (J->lexchar >= 0x20 && J->lexchar <= 0x7E) jsY_error(J, "unexpected character: '%c'", J->lexchar); jsY_error(J, "unexpected character: \\u%04X", J->lexchar); } }