forked from KolibriOS/kolibrios
1068 lines
24 KiB
C
1068 lines
24 KiB
C
|
#include "jsi.h"
|
||
|
#include "jslex.h"
|
||
|
#include "jsparse.h"
|
||
|
|
||
|
#define LIST(h) jsP_newnode(J, AST_LIST, 0, h, 0, 0, 0)
|
||
|
|
||
|
#define EXP0(x) jsP_newnode(J, EXP_ ## x, line, 0, 0, 0, 0)
|
||
|
#define EXP1(x,a) jsP_newnode(J, EXP_ ## x, line, a, 0, 0, 0)
|
||
|
#define EXP2(x,a,b) jsP_newnode(J, EXP_ ## x, line, a, b, 0, 0)
|
||
|
#define EXP3(x,a,b,c) jsP_newnode(J, EXP_ ## x, line, a, b, c, 0)
|
||
|
|
||
|
#define STM0(x) jsP_newnode(J, STM_ ## x, line, 0, 0, 0, 0)
|
||
|
#define STM1(x,a) jsP_newnode(J, STM_ ## x, line, a, 0, 0, 0)
|
||
|
#define STM2(x,a,b) jsP_newnode(J, STM_ ## x, line, a, b, 0, 0)
|
||
|
#define STM3(x,a,b,c) jsP_newnode(J, STM_ ## x, line, a, b, c, 0)
|
||
|
#define STM4(x,a,b,c,d) jsP_newnode(J, STM_ ## x, line, a, b, c, d)
|
||
|
|
||
|
static js_Ast *expression(js_State *J, int notin);
|
||
|
static js_Ast *assignment(js_State *J, int notin);
|
||
|
static js_Ast *memberexp(js_State *J);
|
||
|
static js_Ast *statement(js_State *J);
|
||
|
static js_Ast *funbody(js_State *J);
|
||
|
|
||
|
JS_NORETURN static void jsP_error(js_State *J, const char *fmt, ...) JS_PRINTFLIKE(2,3);
|
||
|
|
||
|
#define INCREC() if (++J->astdepth > JS_ASTLIMIT) jsP_error(J, "too much recursion")
|
||
|
#define DECREC() --J->astdepth
|
||
|
#define SAVEREC() int SAVE=J->astdepth
|
||
|
#define POPREC() J->astdepth=SAVE
|
||
|
|
||
|
static void jsP_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 void jsP_warning(js_State *J, const char *fmt, ...)
|
||
|
{
|
||
|
va_list ap;
|
||
|
char buf[512];
|
||
|
char msg[256];
|
||
|
|
||
|
va_start(ap, fmt);
|
||
|
vsnprintf(msg, sizeof msg, fmt, ap);
|
||
|
va_end(ap);
|
||
|
|
||
|
snprintf(buf, sizeof buf, "%s:%d: warning: %s", J->filename, J->lexline, msg);
|
||
|
js_report(J, buf);
|
||
|
}
|
||
|
|
||
|
static js_Ast *jsP_newnode(js_State *J, enum js_AstType type, int line, js_Ast *a, js_Ast *b, js_Ast *c, js_Ast *d)
|
||
|
{
|
||
|
js_Ast *node = js_malloc(J, sizeof *node);
|
||
|
|
||
|
node->type = type;
|
||
|
node->line = line;
|
||
|
node->a = a;
|
||
|
node->b = b;
|
||
|
node->c = c;
|
||
|
node->d = d;
|
||
|
node->number = 0;
|
||
|
node->string = NULL;
|
||
|
node->jumps = NULL;
|
||
|
node->casejump = 0;
|
||
|
|
||
|
node->parent = NULL;
|
||
|
if (a) a->parent = node;
|
||
|
if (b) b->parent = node;
|
||
|
if (c) c->parent = node;
|
||
|
if (d) d->parent = node;
|
||
|
|
||
|
node->gcnext = J->gcast;
|
||
|
J->gcast = node;
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
static js_Ast *jsP_list(js_Ast *head)
|
||
|
{
|
||
|
/* set parent pointers in list nodes */
|
||
|
js_Ast *prev = head, *node = head->b;
|
||
|
while (node) {
|
||
|
node->parent = prev;
|
||
|
prev = node;
|
||
|
node = node->b;
|
||
|
}
|
||
|
return head;
|
||
|
}
|
||
|
|
||
|
static js_Ast *jsP_newstrnode(js_State *J, enum js_AstType type, const char *s)
|
||
|
{
|
||
|
js_Ast *node = jsP_newnode(J, type, J->lexline, 0, 0, 0, 0);
|
||
|
node->string = s;
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
static js_Ast *jsP_newnumnode(js_State *J, enum js_AstType type, double n)
|
||
|
{
|
||
|
js_Ast *node = jsP_newnode(J, type, J->lexline, 0, 0, 0, 0);
|
||
|
node->number = n;
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
static void jsP_freejumps(js_State *J, js_JumpList *node)
|
||
|
{
|
||
|
while (node) {
|
||
|
js_JumpList *next = node->next;
|
||
|
js_free(J, node);
|
||
|
node = next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void jsP_freeparse(js_State *J)
|
||
|
{
|
||
|
js_Ast *node = J->gcast;
|
||
|
while (node) {
|
||
|
js_Ast *next = node->gcnext;
|
||
|
jsP_freejumps(J, node->jumps);
|
||
|
js_free(J, node);
|
||
|
node = next;
|
||
|
}
|
||
|
J->gcast = NULL;
|
||
|
}
|
||
|
|
||
|
/* Lookahead */
|
||
|
|
||
|
static void jsP_next(js_State *J)
|
||
|
{
|
||
|
J->lookahead = jsY_lex(J);
|
||
|
}
|
||
|
|
||
|
#define jsP_accept(J,x) (J->lookahead == x ? (jsP_next(J), 1) : 0)
|
||
|
|
||
|
#define jsP_expect(J,x) if (!jsP_accept(J, x)) jsP_error(J, "unexpected token: %s (expected %s)", jsY_tokenstring(J->lookahead), jsY_tokenstring(x))
|
||
|
|
||
|
static void semicolon(js_State *J)
|
||
|
{
|
||
|
if (J->lookahead == ';') {
|
||
|
jsP_next(J);
|
||
|
return;
|
||
|
}
|
||
|
if (J->newline || J->lookahead == '}' || J->lookahead == 0)
|
||
|
return;
|
||
|
jsP_error(J, "unexpected token: %s (expected ';')", jsY_tokenstring(J->lookahead));
|
||
|
}
|
||
|
|
||
|
/* Literals */
|
||
|
|
||
|
static js_Ast *identifier(js_State *J)
|
||
|
{
|
||
|
js_Ast *a;
|
||
|
if (J->lookahead == TK_IDENTIFIER) {
|
||
|
a = jsP_newstrnode(J, AST_IDENTIFIER, J->text);
|
||
|
jsP_next(J);
|
||
|
return a;
|
||
|
}
|
||
|
jsP_error(J, "unexpected token: %s (expected identifier)", jsY_tokenstring(J->lookahead));
|
||
|
}
|
||
|
|
||
|
static js_Ast *identifieropt(js_State *J)
|
||
|
{
|
||
|
if (J->lookahead == TK_IDENTIFIER)
|
||
|
return identifier(J);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static js_Ast *identifiername(js_State *J)
|
||
|
{
|
||
|
if (J->lookahead == TK_IDENTIFIER || J->lookahead >= TK_BREAK) {
|
||
|
js_Ast *a = jsP_newstrnode(J, AST_IDENTIFIER, J->text);
|
||
|
jsP_next(J);
|
||
|
return a;
|
||
|
}
|
||
|
jsP_error(J, "unexpected token: %s (expected identifier or keyword)", jsY_tokenstring(J->lookahead));
|
||
|
}
|
||
|
|
||
|
static js_Ast *arrayelement(js_State *J)
|
||
|
{
|
||
|
int line = J->lexline;
|
||
|
if (J->lookahead == ',')
|
||
|
return EXP0(UNDEF);
|
||
|
return assignment(J, 0);
|
||
|
}
|
||
|
|
||
|
static js_Ast *arrayliteral(js_State *J)
|
||
|
{
|
||
|
js_Ast *head, *tail;
|
||
|
if (J->lookahead == ']')
|
||
|
return NULL;
|
||
|
head = tail = LIST(arrayelement(J));
|
||
|
while (jsP_accept(J, ',')) {
|
||
|
if (J->lookahead != ']')
|
||
|
tail = tail->b = LIST(arrayelement(J));
|
||
|
}
|
||
|
return jsP_list(head);
|
||
|
}
|
||
|
|
||
|
static js_Ast *propname(js_State *J)
|
||
|
{
|
||
|
js_Ast *name;
|
||
|
if (J->lookahead == TK_NUMBER) {
|
||
|
name = jsP_newnumnode(J, EXP_NUMBER, J->number);
|
||
|
jsP_next(J);
|
||
|
} else if (J->lookahead == TK_STRING) {
|
||
|
name = jsP_newstrnode(J, EXP_STRING, J->text);
|
||
|
jsP_next(J);
|
||
|
} else {
|
||
|
name = identifiername(J);
|
||
|
}
|
||
|
return name;
|
||
|
}
|
||
|
|
||
|
static js_Ast *propassign(js_State *J)
|
||
|
{
|
||
|
js_Ast *name, *value, *arg, *body;
|
||
|
int line = J->lexline;
|
||
|
|
||
|
name = propname(J);
|
||
|
|
||
|
if (J->lookahead != ':' && name->type == AST_IDENTIFIER) {
|
||
|
if (!strcmp(name->string, "get")) {
|
||
|
name = propname(J);
|
||
|
jsP_expect(J, '(');
|
||
|
jsP_expect(J, ')');
|
||
|
body = funbody(J);
|
||
|
return EXP3(PROP_GET, name, NULL, body);
|
||
|
}
|
||
|
if (!strcmp(name->string, "set")) {
|
||
|
name = propname(J);
|
||
|
jsP_expect(J, '(');
|
||
|
arg = identifier(J);
|
||
|
jsP_expect(J, ')');
|
||
|
body = funbody(J);
|
||
|
return EXP3(PROP_SET, name, LIST(arg), body);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
jsP_expect(J, ':');
|
||
|
value = assignment(J, 0);
|
||
|
return EXP2(PROP_VAL, name, value);
|
||
|
}
|
||
|
|
||
|
static js_Ast *objectliteral(js_State *J)
|
||
|
{
|
||
|
js_Ast *head, *tail;
|
||
|
if (J->lookahead == '}')
|
||
|
return NULL;
|
||
|
head = tail = LIST(propassign(J));
|
||
|
while (jsP_accept(J, ',')) {
|
||
|
if (J->lookahead == '}')
|
||
|
break;
|
||
|
tail = tail->b = LIST(propassign(J));
|
||
|
}
|
||
|
return jsP_list(head);
|
||
|
}
|
||
|
|
||
|
/* Functions */
|
||
|
|
||
|
static js_Ast *parameters(js_State *J)
|
||
|
{
|
||
|
js_Ast *head, *tail;
|
||
|
if (J->lookahead == ')')
|
||
|
return NULL;
|
||
|
head = tail = LIST(identifier(J));
|
||
|
while (jsP_accept(J, ',')) {
|
||
|
tail = tail->b = LIST(identifier(J));
|
||
|
}
|
||
|
return jsP_list(head);
|
||
|
}
|
||
|
|
||
|
static js_Ast *fundec(js_State *J, int line)
|
||
|
{
|
||
|
js_Ast *a, *b, *c;
|
||
|
a = identifier(J);
|
||
|
jsP_expect(J, '(');
|
||
|
b = parameters(J);
|
||
|
jsP_expect(J, ')');
|
||
|
c = funbody(J);
|
||
|
return jsP_newnode(J, AST_FUNDEC, line, a, b, c, 0);
|
||
|
}
|
||
|
|
||
|
static js_Ast *funstm(js_State *J, int line)
|
||
|
{
|
||
|
js_Ast *a, *b, *c;
|
||
|
a = identifier(J);
|
||
|
jsP_expect(J, '(');
|
||
|
b = parameters(J);
|
||
|
jsP_expect(J, ')');
|
||
|
c = funbody(J);
|
||
|
/* rewrite function statement as "var X = function X() {}" */
|
||
|
return STM1(VAR, LIST(EXP2(VAR, a, EXP3(FUN, a, b, c))));
|
||
|
}
|
||
|
|
||
|
static js_Ast *funexp(js_State *J, int line)
|
||
|
{
|
||
|
js_Ast *a, *b, *c;
|
||
|
a = identifieropt(J);
|
||
|
jsP_expect(J, '(');
|
||
|
b = parameters(J);
|
||
|
jsP_expect(J, ')');
|
||
|
c = funbody(J);
|
||
|
return EXP3(FUN, a, b, c);
|
||
|
}
|
||
|
|
||
|
/* Expressions */
|
||
|
|
||
|
static js_Ast *primary(js_State *J)
|
||
|
{
|
||
|
js_Ast *a;
|
||
|
int line = J->lexline;
|
||
|
|
||
|
if (J->lookahead == TK_IDENTIFIER) {
|
||
|
a = jsP_newstrnode(J, EXP_IDENTIFIER, J->text);
|
||
|
jsP_next(J);
|
||
|
return a;
|
||
|
}
|
||
|
if (J->lookahead == TK_STRING) {
|
||
|
a = jsP_newstrnode(J, EXP_STRING, J->text);
|
||
|
jsP_next(J);
|
||
|
return a;
|
||
|
}
|
||
|
if (J->lookahead == TK_REGEXP) {
|
||
|
a = jsP_newstrnode(J, EXP_REGEXP, J->text);
|
||
|
a->number = J->number;
|
||
|
jsP_next(J);
|
||
|
return a;
|
||
|
}
|
||
|
if (J->lookahead == TK_NUMBER) {
|
||
|
a = jsP_newnumnode(J, EXP_NUMBER, J->number);
|
||
|
jsP_next(J);
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
if (jsP_accept(J, TK_THIS)) return EXP0(THIS);
|
||
|
if (jsP_accept(J, TK_NULL)) return EXP0(NULL);
|
||
|
if (jsP_accept(J, TK_TRUE)) return EXP0(TRUE);
|
||
|
if (jsP_accept(J, TK_FALSE)) return EXP0(FALSE);
|
||
|
if (jsP_accept(J, '{')) {
|
||
|
a = EXP1(OBJECT, objectliteral(J));
|
||
|
jsP_expect(J, '}');
|
||
|
return a;
|
||
|
}
|
||
|
if (jsP_accept(J, '[')) {
|
||
|
a = EXP1(ARRAY, arrayliteral(J));
|
||
|
jsP_expect(J, ']');
|
||
|
return a;
|
||
|
}
|
||
|
if (jsP_accept(J, '(')) {
|
||
|
a = expression(J, 0);
|
||
|
jsP_expect(J, ')');
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
jsP_error(J, "unexpected token in expression: %s", jsY_tokenstring(J->lookahead));
|
||
|
}
|
||
|
|
||
|
static js_Ast *arguments(js_State *J)
|
||
|
{
|
||
|
js_Ast *head, *tail;
|
||
|
if (J->lookahead == ')')
|
||
|
return NULL;
|
||
|
head = tail = LIST(assignment(J, 0));
|
||
|
while (jsP_accept(J, ',')) {
|
||
|
tail = tail->b = LIST(assignment(J, 0));
|
||
|
}
|
||
|
return jsP_list(head);
|
||
|
}
|
||
|
|
||
|
static js_Ast *newexp(js_State *J)
|
||
|
{
|
||
|
js_Ast *a, *b;
|
||
|
int line = J->lexline;
|
||
|
|
||
|
if (jsP_accept(J, TK_NEW)) {
|
||
|
a = memberexp(J);
|
||
|
if (jsP_accept(J, '(')) {
|
||
|
b = arguments(J);
|
||
|
jsP_expect(J, ')');
|
||
|
return EXP2(NEW, a, b);
|
||
|
}
|
||
|
return EXP1(NEW, a);
|
||
|
}
|
||
|
|
||
|
if (jsP_accept(J, TK_FUNCTION))
|
||
|
return funexp(J, line);
|
||
|
|
||
|
return primary(J);
|
||
|
}
|
||
|
|
||
|
static js_Ast *memberexp(js_State *J)
|
||
|
{
|
||
|
js_Ast *a = newexp(J);
|
||
|
int line;
|
||
|
SAVEREC();
|
||
|
loop:
|
||
|
INCREC();
|
||
|
line = J->lexline;
|
||
|
if (jsP_accept(J, '.')) { a = EXP2(MEMBER, a, identifiername(J)); goto loop; }
|
||
|
if (jsP_accept(J, '[')) { a = EXP2(INDEX, a, expression(J, 0)); jsP_expect(J, ']'); goto loop; }
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *callexp(js_State *J)
|
||
|
{
|
||
|
js_Ast *a = newexp(J);
|
||
|
int line;
|
||
|
SAVEREC();
|
||
|
loop:
|
||
|
INCREC();
|
||
|
line = J->lexline;
|
||
|
if (jsP_accept(J, '.')) { a = EXP2(MEMBER, a, identifiername(J)); goto loop; }
|
||
|
if (jsP_accept(J, '[')) { a = EXP2(INDEX, a, expression(J, 0)); jsP_expect(J, ']'); goto loop; }
|
||
|
if (jsP_accept(J, '(')) { a = EXP2(CALL, a, arguments(J)); jsP_expect(J, ')'); goto loop; }
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *postfix(js_State *J)
|
||
|
{
|
||
|
js_Ast *a = callexp(J);
|
||
|
int line = J->lexline;
|
||
|
if (!J->newline && jsP_accept(J, TK_INC)) return EXP1(POSTINC, a);
|
||
|
if (!J->newline && jsP_accept(J, TK_DEC)) return EXP1(POSTDEC, a);
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *unary(js_State *J)
|
||
|
{
|
||
|
js_Ast *a;
|
||
|
int line = J->lexline;
|
||
|
INCREC();
|
||
|
if (jsP_accept(J, TK_DELETE)) a = EXP1(DELETE, unary(J));
|
||
|
else if (jsP_accept(J, TK_VOID)) a = EXP1(VOID, unary(J));
|
||
|
else if (jsP_accept(J, TK_TYPEOF)) a = EXP1(TYPEOF, unary(J));
|
||
|
else if (jsP_accept(J, TK_INC)) a = EXP1(PREINC, unary(J));
|
||
|
else if (jsP_accept(J, TK_DEC)) a = EXP1(PREDEC, unary(J));
|
||
|
else if (jsP_accept(J, '+')) a = EXP1(POS, unary(J));
|
||
|
else if (jsP_accept(J, '-')) a = EXP1(NEG, unary(J));
|
||
|
else if (jsP_accept(J, '~')) a = EXP1(BITNOT, unary(J));
|
||
|
else if (jsP_accept(J, '!')) a = EXP1(LOGNOT, unary(J));
|
||
|
else a = postfix(J);
|
||
|
DECREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *multiplicative(js_State *J)
|
||
|
{
|
||
|
js_Ast *a = unary(J);
|
||
|
int line;
|
||
|
SAVEREC();
|
||
|
loop:
|
||
|
INCREC();
|
||
|
line = J->lexline;
|
||
|
if (jsP_accept(J, '*')) { a = EXP2(MUL, a, unary(J)); goto loop; }
|
||
|
if (jsP_accept(J, '/')) { a = EXP2(DIV, a, unary(J)); goto loop; }
|
||
|
if (jsP_accept(J, '%')) { a = EXP2(MOD, a, unary(J)); goto loop; }
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *additive(js_State *J)
|
||
|
{
|
||
|
js_Ast *a = multiplicative(J);
|
||
|
int line;
|
||
|
SAVEREC();
|
||
|
loop:
|
||
|
INCREC();
|
||
|
line = J->lexline;
|
||
|
if (jsP_accept(J, '+')) { a = EXP2(ADD, a, multiplicative(J)); goto loop; }
|
||
|
if (jsP_accept(J, '-')) { a = EXP2(SUB, a, multiplicative(J)); goto loop; }
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *shift(js_State *J)
|
||
|
{
|
||
|
js_Ast *a = additive(J);
|
||
|
int line;
|
||
|
SAVEREC();
|
||
|
loop:
|
||
|
INCREC();
|
||
|
line = J->lexline;
|
||
|
if (jsP_accept(J, TK_SHL)) { a = EXP2(SHL, a, additive(J)); goto loop; }
|
||
|
if (jsP_accept(J, TK_SHR)) { a = EXP2(SHR, a, additive(J)); goto loop; }
|
||
|
if (jsP_accept(J, TK_USHR)) { a = EXP2(USHR, a, additive(J)); goto loop; }
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *relational(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = shift(J);
|
||
|
int line;
|
||
|
SAVEREC();
|
||
|
loop:
|
||
|
INCREC();
|
||
|
line = J->lexline;
|
||
|
if (jsP_accept(J, '<')) { a = EXP2(LT, a, shift(J)); goto loop; }
|
||
|
if (jsP_accept(J, '>')) { a = EXP2(GT, a, shift(J)); goto loop; }
|
||
|
if (jsP_accept(J, TK_LE)) { a = EXP2(LE, a, shift(J)); goto loop; }
|
||
|
if (jsP_accept(J, TK_GE)) { a = EXP2(GE, a, shift(J)); goto loop; }
|
||
|
if (jsP_accept(J, TK_INSTANCEOF)) { a = EXP2(INSTANCEOF, a, shift(J)); goto loop; }
|
||
|
if (!notin && jsP_accept(J, TK_IN)) { a = EXP2(IN, a, shift(J)); goto loop; }
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *equality(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = relational(J, notin);
|
||
|
int line;
|
||
|
SAVEREC();
|
||
|
loop:
|
||
|
INCREC();
|
||
|
line = J->lexline;
|
||
|
if (jsP_accept(J, TK_EQ)) { a = EXP2(EQ, a, relational(J, notin)); goto loop; }
|
||
|
if (jsP_accept(J, TK_NE)) { a = EXP2(NE, a, relational(J, notin)); goto loop; }
|
||
|
if (jsP_accept(J, TK_STRICTEQ)) { a = EXP2(STRICTEQ, a, relational(J, notin)); goto loop; }
|
||
|
if (jsP_accept(J, TK_STRICTNE)) { a = EXP2(STRICTNE, a, relational(J, notin)); goto loop; }
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *bitand(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = equality(J, notin);
|
||
|
SAVEREC();
|
||
|
int line = J->lexline;
|
||
|
while (jsP_accept(J, '&')) {
|
||
|
INCREC();
|
||
|
a = EXP2(BITAND, a, equality(J, notin));
|
||
|
line = J->lexline;
|
||
|
}
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *bitxor(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = bitand(J, notin);
|
||
|
SAVEREC();
|
||
|
int line = J->lexline;
|
||
|
while (jsP_accept(J, '^')) {
|
||
|
INCREC();
|
||
|
a = EXP2(BITXOR, a, bitand(J, notin));
|
||
|
line = J->lexline;
|
||
|
}
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *bitor(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = bitxor(J, notin);
|
||
|
SAVEREC();
|
||
|
int line = J->lexline;
|
||
|
while (jsP_accept(J, '|')) {
|
||
|
INCREC();
|
||
|
a = EXP2(BITOR, a, bitxor(J, notin));
|
||
|
line = J->lexline;
|
||
|
}
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *logand(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = bitor(J, notin);
|
||
|
int line = J->lexline;
|
||
|
if (jsP_accept(J, TK_AND)) {
|
||
|
INCREC();
|
||
|
a = EXP2(LOGAND, a, logand(J, notin));
|
||
|
DECREC();
|
||
|
}
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *logor(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = logand(J, notin);
|
||
|
int line = J->lexline;
|
||
|
if (jsP_accept(J, TK_OR)) {
|
||
|
INCREC();
|
||
|
a = EXP2(LOGOR, a, logor(J, notin));
|
||
|
DECREC();
|
||
|
}
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *conditional(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = logor(J, notin);
|
||
|
int line = J->lexline;
|
||
|
if (jsP_accept(J, '?')) {
|
||
|
js_Ast *b, *c;
|
||
|
INCREC();
|
||
|
b = assignment(J, 0);
|
||
|
jsP_expect(J, ':');
|
||
|
c = assignment(J, notin);
|
||
|
DECREC();
|
||
|
return EXP3(COND, a, b, c);
|
||
|
}
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *assignment(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = conditional(J, notin);
|
||
|
int line = J->lexline;
|
||
|
INCREC();
|
||
|
if (jsP_accept(J, '=')) a = EXP2(ASS, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_MUL_ASS)) a = EXP2(ASS_MUL, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_DIV_ASS)) a = EXP2(ASS_DIV, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_MOD_ASS)) a = EXP2(ASS_MOD, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_ADD_ASS)) a = EXP2(ASS_ADD, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_SUB_ASS)) a = EXP2(ASS_SUB, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_SHL_ASS)) a = EXP2(ASS_SHL, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_SHR_ASS)) a = EXP2(ASS_SHR, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_USHR_ASS)) a = EXP2(ASS_USHR, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_AND_ASS)) a = EXP2(ASS_BITAND, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_XOR_ASS)) a = EXP2(ASS_BITXOR, a, assignment(J, notin));
|
||
|
else if (jsP_accept(J, TK_OR_ASS)) a = EXP2(ASS_BITOR, a, assignment(J, notin));
|
||
|
DECREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *expression(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = assignment(J, notin);
|
||
|
SAVEREC();
|
||
|
int line = J->lexline;
|
||
|
while (jsP_accept(J, ',')) {
|
||
|
INCREC();
|
||
|
a = EXP2(COMMA, a, assignment(J, notin));
|
||
|
line = J->lexline;
|
||
|
}
|
||
|
POPREC();
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
/* Statements */
|
||
|
|
||
|
static js_Ast *vardec(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *a = identifier(J);
|
||
|
int line = J->lexline;
|
||
|
if (jsP_accept(J, '='))
|
||
|
return EXP2(VAR, a, assignment(J, notin));
|
||
|
return EXP1(VAR, a);
|
||
|
}
|
||
|
|
||
|
static js_Ast *vardeclist(js_State *J, int notin)
|
||
|
{
|
||
|
js_Ast *head, *tail;
|
||
|
head = tail = LIST(vardec(J, notin));
|
||
|
while (jsP_accept(J, ','))
|
||
|
tail = tail->b = LIST(vardec(J, notin));
|
||
|
return jsP_list(head);
|
||
|
}
|
||
|
|
||
|
static js_Ast *statementlist(js_State *J)
|
||
|
{
|
||
|
js_Ast *head, *tail;
|
||
|
if (J->lookahead == '}' || J->lookahead == TK_CASE || J->lookahead == TK_DEFAULT)
|
||
|
return NULL;
|
||
|
head = tail = LIST(statement(J));
|
||
|
while (J->lookahead != '}' && J->lookahead != TK_CASE && J->lookahead != TK_DEFAULT)
|
||
|
tail = tail->b = LIST(statement(J));
|
||
|
return jsP_list(head);
|
||
|
}
|
||
|
|
||
|
static js_Ast *caseclause(js_State *J)
|
||
|
{
|
||
|
js_Ast *a, *b;
|
||
|
int line = J->lexline;
|
||
|
|
||
|
if (jsP_accept(J, TK_CASE)) {
|
||
|
a = expression(J, 0);
|
||
|
jsP_expect(J, ':');
|
||
|
b = statementlist(J);
|
||
|
return STM2(CASE, a, b);
|
||
|
}
|
||
|
|
||
|
if (jsP_accept(J, TK_DEFAULT)) {
|
||
|
jsP_expect(J, ':');
|
||
|
a = statementlist(J);
|
||
|
return STM1(DEFAULT, a);
|
||
|
}
|
||
|
|
||
|
jsP_error(J, "unexpected token in switch: %s (expected 'case' or 'default')", jsY_tokenstring(J->lookahead));
|
||
|
}
|
||
|
|
||
|
static js_Ast *caselist(js_State *J)
|
||
|
{
|
||
|
js_Ast *head, *tail;
|
||
|
if (J->lookahead == '}')
|
||
|
return NULL;
|
||
|
head = tail = LIST(caseclause(J));
|
||
|
while (J->lookahead != '}')
|
||
|
tail = tail->b = LIST(caseclause(J));
|
||
|
return jsP_list(head);
|
||
|
}
|
||
|
|
||
|
static js_Ast *block(js_State *J)
|
||
|
{
|
||
|
js_Ast *a;
|
||
|
int line = J->lexline;
|
||
|
jsP_expect(J, '{');
|
||
|
a = statementlist(J);
|
||
|
jsP_expect(J, '}');
|
||
|
return STM1(BLOCK, a);
|
||
|
}
|
||
|
|
||
|
static js_Ast *forexpression(js_State *J, int end)
|
||
|
{
|
||
|
js_Ast *a = NULL;
|
||
|
if (J->lookahead != end)
|
||
|
a = expression(J, 0);
|
||
|
jsP_expect(J, end);
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
static js_Ast *forstatement(js_State *J, int line)
|
||
|
{
|
||
|
js_Ast *a, *b, *c, *d;
|
||
|
jsP_expect(J, '(');
|
||
|
if (jsP_accept(J, TK_VAR)) {
|
||
|
a = vardeclist(J, 1);
|
||
|
if (jsP_accept(J, ';')) {
|
||
|
b = forexpression(J, ';');
|
||
|
c = forexpression(J, ')');
|
||
|
d = statement(J);
|
||
|
return STM4(FOR_VAR, a, b, c, d);
|
||
|
}
|
||
|
if (jsP_accept(J, TK_IN)) {
|
||
|
b = expression(J, 0);
|
||
|
jsP_expect(J, ')');
|
||
|
c = statement(J);
|
||
|
return STM3(FOR_IN_VAR, a, b, c);
|
||
|
}
|
||
|
jsP_error(J, "unexpected token in for-var-statement: %s", jsY_tokenstring(J->lookahead));
|
||
|
}
|
||
|
|
||
|
if (J->lookahead != ';')
|
||
|
a = expression(J, 1);
|
||
|
else
|
||
|
a = NULL;
|
||
|
if (jsP_accept(J, ';')) {
|
||
|
b = forexpression(J, ';');
|
||
|
c = forexpression(J, ')');
|
||
|
d = statement(J);
|
||
|
return STM4(FOR, a, b, c, d);
|
||
|
}
|
||
|
if (jsP_accept(J, TK_IN)) {
|
||
|
b = expression(J, 0);
|
||
|
jsP_expect(J, ')');
|
||
|
c = statement(J);
|
||
|
return STM3(FOR_IN, a, b, c);
|
||
|
}
|
||
|
jsP_error(J, "unexpected token in for-statement: %s", jsY_tokenstring(J->lookahead));
|
||
|
}
|
||
|
|
||
|
static js_Ast *statement(js_State *J)
|
||
|
{
|
||
|
js_Ast *a, *b, *c, *d;
|
||
|
js_Ast *stm;
|
||
|
int line = J->lexline;
|
||
|
|
||
|
INCREC();
|
||
|
|
||
|
if (J->lookahead == '{') {
|
||
|
stm = block(J);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_VAR)) {
|
||
|
a = vardeclist(J, 0);
|
||
|
semicolon(J);
|
||
|
stm = STM1(VAR, a);
|
||
|
}
|
||
|
|
||
|
/* empty statement */
|
||
|
else if (jsP_accept(J, ';')) {
|
||
|
stm = STM0(EMPTY);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_IF)) {
|
||
|
jsP_expect(J, '(');
|
||
|
a = expression(J, 0);
|
||
|
jsP_expect(J, ')');
|
||
|
b = statement(J);
|
||
|
if (jsP_accept(J, TK_ELSE))
|
||
|
c = statement(J);
|
||
|
else
|
||
|
c = NULL;
|
||
|
stm = STM3(IF, a, b, c);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_DO)) {
|
||
|
a = statement(J);
|
||
|
jsP_expect(J, TK_WHILE);
|
||
|
jsP_expect(J, '(');
|
||
|
b = expression(J, 0);
|
||
|
jsP_expect(J, ')');
|
||
|
semicolon(J);
|
||
|
stm = STM2(DO, a, b);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_WHILE)) {
|
||
|
jsP_expect(J, '(');
|
||
|
a = expression(J, 0);
|
||
|
jsP_expect(J, ')');
|
||
|
b = statement(J);
|
||
|
stm = STM2(WHILE, a, b);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_FOR)) {
|
||
|
stm = forstatement(J, line);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_CONTINUE)) {
|
||
|
a = identifieropt(J);
|
||
|
semicolon(J);
|
||
|
stm = STM1(CONTINUE, a);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_BREAK)) {
|
||
|
a = identifieropt(J);
|
||
|
semicolon(J);
|
||
|
stm = STM1(BREAK, a);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_RETURN)) {
|
||
|
if (J->lookahead != ';' && J->lookahead != '}' && J->lookahead != 0)
|
||
|
a = expression(J, 0);
|
||
|
else
|
||
|
a = NULL;
|
||
|
semicolon(J);
|
||
|
stm = STM1(RETURN, a);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_WITH)) {
|
||
|
jsP_expect(J, '(');
|
||
|
a = expression(J, 0);
|
||
|
jsP_expect(J, ')');
|
||
|
b = statement(J);
|
||
|
stm = STM2(WITH, a, b);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_SWITCH)) {
|
||
|
jsP_expect(J, '(');
|
||
|
a = expression(J, 0);
|
||
|
jsP_expect(J, ')');
|
||
|
jsP_expect(J, '{');
|
||
|
b = caselist(J);
|
||
|
jsP_expect(J, '}');
|
||
|
stm = STM2(SWITCH, a, b);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_THROW)) {
|
||
|
a = expression(J, 0);
|
||
|
semicolon(J);
|
||
|
stm = STM1(THROW, a);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_TRY)) {
|
||
|
a = block(J);
|
||
|
b = c = d = NULL;
|
||
|
if (jsP_accept(J, TK_CATCH)) {
|
||
|
jsP_expect(J, '(');
|
||
|
b = identifier(J);
|
||
|
jsP_expect(J, ')');
|
||
|
c = block(J);
|
||
|
}
|
||
|
if (jsP_accept(J, TK_FINALLY)) {
|
||
|
d = block(J);
|
||
|
}
|
||
|
if (!b && !d)
|
||
|
jsP_error(J, "unexpected token in try: %s (expected 'catch' or 'finally')", jsY_tokenstring(J->lookahead));
|
||
|
stm = STM4(TRY, a, b, c, d);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_DEBUGGER)) {
|
||
|
semicolon(J);
|
||
|
stm = STM0(DEBUGGER);
|
||
|
}
|
||
|
|
||
|
else if (jsP_accept(J, TK_FUNCTION)) {
|
||
|
jsP_warning(J, "function statements are not standard");
|
||
|
stm = funstm(J, line);
|
||
|
}
|
||
|
|
||
|
/* labelled statement or expression statement */
|
||
|
else if (J->lookahead == TK_IDENTIFIER) {
|
||
|
a = expression(J, 0);
|
||
|
if (a->type == EXP_IDENTIFIER && jsP_accept(J, ':')) {
|
||
|
a->type = AST_IDENTIFIER;
|
||
|
b = statement(J);
|
||
|
stm = STM2(LABEL, a, b);
|
||
|
} else {
|
||
|
semicolon(J);
|
||
|
stm = a;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* expression statement */
|
||
|
else {
|
||
|
stm = expression(J, 0);
|
||
|
semicolon(J);
|
||
|
}
|
||
|
|
||
|
DECREC();
|
||
|
return stm;
|
||
|
}
|
||
|
|
||
|
/* Program */
|
||
|
|
||
|
static js_Ast *scriptelement(js_State *J)
|
||
|
{
|
||
|
int line = J->lexline;
|
||
|
if (jsP_accept(J, TK_FUNCTION))
|
||
|
return fundec(J, line);
|
||
|
return statement(J);
|
||
|
}
|
||
|
|
||
|
static js_Ast *script(js_State *J, int terminator)
|
||
|
{
|
||
|
js_Ast *head, *tail;
|
||
|
if (J->lookahead == terminator)
|
||
|
return NULL;
|
||
|
head = tail = LIST(scriptelement(J));
|
||
|
while (J->lookahead != terminator)
|
||
|
tail = tail->b = LIST(scriptelement(J));
|
||
|
return jsP_list(head);
|
||
|
}
|
||
|
|
||
|
static js_Ast *funbody(js_State *J)
|
||
|
{
|
||
|
js_Ast *a;
|
||
|
jsP_expect(J, '{');
|
||
|
a = script(J, '}');
|
||
|
jsP_expect(J, '}');
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
/* Constant folding */
|
||
|
|
||
|
static int toint32(double d)
|
||
|
{
|
||
|
double two32 = 4294967296.0;
|
||
|
double two31 = 2147483648.0;
|
||
|
|
||
|
if (!isfinite(d) || d == 0)
|
||
|
return 0;
|
||
|
|
||
|
d = fmod(d, two32);
|
||
|
d = d >= 0 ? floor(d) : ceil(d) + two32;
|
||
|
if (d >= two31)
|
||
|
return d - two32;
|
||
|
else
|
||
|
return d;
|
||
|
}
|
||
|
|
||
|
static unsigned int touint32(double d)
|
||
|
{
|
||
|
return (unsigned int)toint32(d);
|
||
|
}
|
||
|
|
||
|
static int jsP_setnumnode(js_Ast *node, double x)
|
||
|
{
|
||
|
node->type = EXP_NUMBER;
|
||
|
node->number = x;
|
||
|
node->a = node->b = node->c = node->d = NULL;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static int jsP_foldconst(js_Ast *node)
|
||
|
{
|
||
|
double x, y;
|
||
|
int a, b;
|
||
|
|
||
|
if (node->type == AST_LIST) {
|
||
|
while (node) {
|
||
|
jsP_foldconst(node->a);
|
||
|
node = node->b;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (node->type == EXP_NUMBER)
|
||
|
return 1;
|
||
|
|
||
|
a = node->a ? jsP_foldconst(node->a) : 0;
|
||
|
b = node->b ? jsP_foldconst(node->b) : 0;
|
||
|
if (node->c) jsP_foldconst(node->c);
|
||
|
if (node->d) jsP_foldconst(node->d);
|
||
|
|
||
|
if (a) {
|
||
|
x = node->a->number;
|
||
|
switch (node->type) {
|
||
|
default: break;
|
||
|
case EXP_NEG: return jsP_setnumnode(node, -x);
|
||
|
case EXP_POS: return jsP_setnumnode(node, x);
|
||
|
case EXP_BITNOT: return jsP_setnumnode(node, ~toint32(x));
|
||
|
}
|
||
|
|
||
|
if (b) {
|
||
|
y = node->b->number;
|
||
|
switch (node->type) {
|
||
|
default: break;
|
||
|
case EXP_MUL: return jsP_setnumnode(node, x * y);
|
||
|
case EXP_DIV: return jsP_setnumnode(node, x / y);
|
||
|
case EXP_MOD: return jsP_setnumnode(node, fmod(x, y));
|
||
|
case EXP_ADD: return jsP_setnumnode(node, x + y);
|
||
|
case EXP_SUB: return jsP_setnumnode(node, x - y);
|
||
|
case EXP_SHL: return jsP_setnumnode(node, toint32(x) << (touint32(y) & 0x1F));
|
||
|
case EXP_SHR: return jsP_setnumnode(node, toint32(x) >> (touint32(y) & 0x1F));
|
||
|
case EXP_USHR: return jsP_setnumnode(node, touint32(x) >> (touint32(y) & 0x1F));
|
||
|
case EXP_BITAND: return jsP_setnumnode(node, toint32(x) & toint32(y));
|
||
|
case EXP_BITXOR: return jsP_setnumnode(node, toint32(x) ^ toint32(y));
|
||
|
case EXP_BITOR: return jsP_setnumnode(node, toint32(x) | toint32(y));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Main entry point */
|
||
|
|
||
|
js_Ast *jsP_parse(js_State *J, const char *filename, const char *source)
|
||
|
{
|
||
|
js_Ast *p;
|
||
|
|
||
|
jsY_initlex(J, filename, source);
|
||
|
jsP_next(J);
|
||
|
J->astdepth = 0;
|
||
|
p = script(J, 0);
|
||
|
if (p)
|
||
|
jsP_foldconst(p);
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
js_Ast *jsP_parsefunction(js_State *J, const char *filename, const char *params, const char *body)
|
||
|
{
|
||
|
js_Ast *p = NULL;
|
||
|
int line = 0;
|
||
|
if (params) {
|
||
|
jsY_initlex(J, filename, params);
|
||
|
jsP_next(J);
|
||
|
J->astdepth = 0;
|
||
|
p = parameters(J);
|
||
|
}
|
||
|
return EXP3(FUN, NULL, p, jsP_parse(J, filename, body));
|
||
|
}
|