Merge branch 'newsyn' into mach
This commit is contained in:
449
source/quickjs.c
449
source/quickjs.c
@@ -8514,17 +8514,12 @@ enum {
|
||||
TOK_DEF,
|
||||
TOK_THIS,
|
||||
TOK_DELETE,
|
||||
TOK_VOID,
|
||||
TOK_NEW,
|
||||
TOK_IN,
|
||||
TOK_DO,
|
||||
TOK_WHILE,
|
||||
TOK_FOR,
|
||||
TOK_BREAK,
|
||||
TOK_CONTINUE,
|
||||
TOK_SWITCH,
|
||||
TOK_CASE,
|
||||
TOK_DEFAULT,
|
||||
TOK_THROW,
|
||||
TOK_TRY,
|
||||
TOK_CATCH,
|
||||
@@ -8625,17 +8620,12 @@ static const char *ast_token_kind_str(int token_val) {
|
||||
case TOK_DEF: return "def";
|
||||
case TOK_THIS: return "this";
|
||||
case TOK_DELETE: return "delete";
|
||||
case TOK_VOID: return "void";
|
||||
case TOK_NEW: return "new";
|
||||
case TOK_IN: return "in";
|
||||
case TOK_DO: return "do";
|
||||
case TOK_WHILE: return "while";
|
||||
case TOK_FOR: return "for";
|
||||
case TOK_BREAK: return "break";
|
||||
case TOK_CONTINUE: return "continue";
|
||||
case TOK_SWITCH: return "switch";
|
||||
case TOK_CASE: return "case";
|
||||
case TOK_DEFAULT: return "default";
|
||||
case TOK_THROW: return "throw";
|
||||
case TOK_TRY: return "try";
|
||||
case TOK_CATCH: return "catch";
|
||||
@@ -9784,17 +9774,12 @@ static const JSKeywordEntry js_keywords[] = {
|
||||
{ "def", TOK_DEF, FALSE },
|
||||
{ "this", TOK_THIS, FALSE },
|
||||
{ "delete", TOK_DELETE, FALSE },
|
||||
{ "void", TOK_VOID, FALSE },
|
||||
{ "new", TOK_NEW, FALSE },
|
||||
{ "in", TOK_IN, FALSE },
|
||||
{ "do", TOK_DO, FALSE },
|
||||
{ "while", TOK_WHILE, FALSE },
|
||||
{ "for", TOK_FOR, FALSE },
|
||||
{ "break", TOK_BREAK, FALSE },
|
||||
{ "continue", TOK_CONTINUE, FALSE },
|
||||
{ "switch", TOK_SWITCH, FALSE },
|
||||
{ "case", TOK_CASE, FALSE },
|
||||
{ "default", TOK_DEFAULT, FALSE },
|
||||
{ "throw", TOK_THROW, FALSE },
|
||||
{ "try", TOK_TRY, FALSE },
|
||||
{ "catch", TOK_CATCH, FALSE },
|
||||
@@ -12428,8 +12413,6 @@ static __exception int js_parse_postfix_expr (JSParseState *s,
|
||||
if (js_parse_array_literal (s)) return -1;
|
||||
}
|
||||
break;
|
||||
case TOK_NEW:
|
||||
return js_parse_error (s, "'new' keyword is not supported");
|
||||
default:
|
||||
return js_parse_error (s, "unexpected token in expression: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr);
|
||||
}
|
||||
@@ -13593,89 +13576,6 @@ static __exception int js_parse_statement_or_decl (JSParseState *s,
|
||||
}
|
||||
if (js_parse_expect_semi (s)) goto fail;
|
||||
} break;
|
||||
case TOK_SWITCH: {
|
||||
int label_case, label_break, label1;
|
||||
int default_label_pos;
|
||||
BlockEnv break_entry;
|
||||
|
||||
if (next_token (s)) goto fail;
|
||||
|
||||
if (js_parse_expr_paren (s)) goto fail;
|
||||
|
||||
push_scope (s);
|
||||
label_break = new_label (s);
|
||||
push_break_entry (s->cur_func, &break_entry, label_name, label_break, -1, 1);
|
||||
|
||||
if (js_parse_expect (s, '{')) goto fail;
|
||||
|
||||
default_label_pos = -1;
|
||||
label_case = -1;
|
||||
while (s->token.val != '}') {
|
||||
if (s->token.val == TOK_CASE) {
|
||||
label1 = -1;
|
||||
if (label_case >= 0) {
|
||||
/* skip the case if needed */
|
||||
label1 = emit_goto (s, OP_goto, -1);
|
||||
}
|
||||
emit_label (s, label_case);
|
||||
label_case = -1;
|
||||
for (;;) {
|
||||
/* parse a sequence of case clauses */
|
||||
if (next_token (s)) goto fail;
|
||||
emit_op (s, OP_dup);
|
||||
if (js_parse_expr (s)) goto fail;
|
||||
if (js_parse_expect (s, ':')) goto fail;
|
||||
emit_op (s, OP_strict_eq);
|
||||
if (s->token.val == TOK_CASE) {
|
||||
label1 = emit_goto (s, OP_if_true, label1);
|
||||
} else {
|
||||
label_case = emit_goto (s, OP_if_false, -1);
|
||||
emit_label (s, label1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (s->token.val == TOK_DEFAULT) {
|
||||
if (next_token (s)) goto fail;
|
||||
if (js_parse_expect (s, ':')) goto fail;
|
||||
if (default_label_pos >= 0) {
|
||||
js_parse_error (s, "duplicate default");
|
||||
goto fail;
|
||||
}
|
||||
if (label_case < 0) {
|
||||
/* falling thru direct from switch expression */
|
||||
label_case = emit_goto (s, OP_goto, -1);
|
||||
}
|
||||
/* Emit a dummy label opcode. Label will be patched after
|
||||
the end of the switch body. Do not use emit_label(s, 0)
|
||||
because it would clobber label 0 address, preventing
|
||||
proper optimizer operation.
|
||||
*/
|
||||
emit_op (s, OP_label);
|
||||
emit_u32 (s, 0);
|
||||
default_label_pos = s->cur_func->byte_code.size - 4;
|
||||
} else {
|
||||
if (label_case < 0) {
|
||||
/* falling thru direct from switch expression */
|
||||
js_parse_error (s, "invalid switch statement");
|
||||
goto fail;
|
||||
}
|
||||
if (js_parse_statement_or_decl (s, DECL_MASK_ALL)) goto fail;
|
||||
}
|
||||
}
|
||||
if (js_parse_expect (s, '}')) goto fail;
|
||||
if (default_label_pos >= 0) {
|
||||
/* Ugly patch for the the `default` label, shameful and risky */
|
||||
put_u32 (s->cur_func->byte_code.buf + default_label_pos, label_case);
|
||||
s->cur_func->label_slots[label_case].pos = default_label_pos + 4;
|
||||
} else {
|
||||
emit_label (s, label_case);
|
||||
}
|
||||
emit_label (s, label_break);
|
||||
emit_op (s, OP_drop); /* drop the switch expression */
|
||||
|
||||
pop_break_entry (s->cur_func);
|
||||
pop_scope (s);
|
||||
} break;
|
||||
case TOK_TRY: {
|
||||
int label_catch, label_catch2, label_finally, label_end;
|
||||
JSValue name;
|
||||
@@ -16648,11 +16548,9 @@ static __exception int js_parse_directives (JSParseState *s) {
|
||||
case TOK_VAR:
|
||||
case TOK_THIS:
|
||||
case TOK_DELETE:
|
||||
case TOK_NEW:
|
||||
case TOK_DO:
|
||||
case TOK_WHILE:
|
||||
case TOK_FOR:
|
||||
case TOK_SWITCH:
|
||||
case TOK_THROW:
|
||||
case TOK_TRY:
|
||||
case TOK_FUNCTION:
|
||||
@@ -28224,14 +28122,11 @@ redo:
|
||||
else if (len == 3 && !memcmp (start, "var", 3)) s->token_val = TOK_VAR;
|
||||
else if (len == 3 && !memcmp (start, "def", 3)) s->token_val = TOK_DEF;
|
||||
else if (len == 3 && !memcmp (start, "for", 3)) s->token_val = TOK_FOR;
|
||||
else if (len == 3 && !memcmp (start, "new", 3)) s->token_val = TOK_NEW;
|
||||
else if (len == 3 && !memcmp (start, "try", 3)) s->token_val = TOK_TRY;
|
||||
else if (len == 4 && !memcmp (start, "else", 4)) s->token_val = TOK_ELSE;
|
||||
else if (len == 4 && !memcmp (start, "this", 4)) s->token_val = TOK_THIS;
|
||||
else if (len == 4 && !memcmp (start, "null", 4)) s->token_val = TOK_NULL;
|
||||
else if (len == 4 && !memcmp (start, "true", 4)) s->token_val = TOK_TRUE;
|
||||
else if (len == 4 && !memcmp (start, "void", 4)) s->token_val = TOK_VOID;
|
||||
else if (len == 4 && !memcmp (start, "case", 4)) s->token_val = TOK_CASE;
|
||||
else if (len == 5 && !memcmp (start, "false", 5)) s->token_val = TOK_FALSE;
|
||||
else if (len == 5 && !memcmp (start, "while", 5)) s->token_val = TOK_WHILE;
|
||||
else if (len == 5 && !memcmp (start, "break", 5)) s->token_val = TOK_BREAK;
|
||||
@@ -28239,8 +28134,6 @@ redo:
|
||||
else if (len == 5 && !memcmp (start, "catch", 5)) s->token_val = TOK_CATCH;
|
||||
else if (len == 6 && !memcmp (start, "return", 6)) s->token_val = TOK_RETURN;
|
||||
else if (len == 6 && !memcmp (start, "delete", 6)) s->token_val = TOK_DELETE;
|
||||
else if (len == 6 && !memcmp (start, "switch", 6)) s->token_val = TOK_SWITCH;
|
||||
else if (len == 7 && !memcmp (start, "default", 7)) s->token_val = TOK_DEFAULT;
|
||||
else if (len == 7 && !memcmp (start, "finally", 7)) s->token_val = TOK_FINALLY;
|
||||
else if (len == 8 && !memcmp (start, "function", 8)) s->token_val = TOK_FUNCTION;
|
||||
else if (len == 8 && !memcmp (start, "continue", 8)) s->token_val = TOK_CONTINUE;
|
||||
@@ -28552,14 +28445,11 @@ static int tokenize_next (ASTParseState *s) {
|
||||
else if (len == 3 && !memcmp (start, "var", 3)) s->token_val = TOK_VAR;
|
||||
else if (len == 3 && !memcmp (start, "def", 3)) s->token_val = TOK_DEF;
|
||||
else if (len == 3 && !memcmp (start, "for", 3)) s->token_val = TOK_FOR;
|
||||
else if (len == 3 && !memcmp (start, "new", 3)) s->token_val = TOK_NEW;
|
||||
else if (len == 3 && !memcmp (start, "try", 3)) s->token_val = TOK_TRY;
|
||||
else if (len == 4 && !memcmp (start, "else", 4)) s->token_val = TOK_ELSE;
|
||||
else if (len == 4 && !memcmp (start, "this", 4)) s->token_val = TOK_THIS;
|
||||
else if (len == 4 && !memcmp (start, "null", 4)) s->token_val = TOK_NULL;
|
||||
else if (len == 4 && !memcmp (start, "true", 4)) s->token_val = TOK_TRUE;
|
||||
else if (len == 4 && !memcmp (start, "void", 4)) s->token_val = TOK_VOID;
|
||||
else if (len == 4 && !memcmp (start, "case", 4)) s->token_val = TOK_CASE;
|
||||
else if (len == 5 && !memcmp (start, "false", 5)) s->token_val = TOK_FALSE;
|
||||
else if (len == 5 && !memcmp (start, "while", 5)) s->token_val = TOK_WHILE;
|
||||
else if (len == 5 && !memcmp (start, "break", 5)) s->token_val = TOK_BREAK;
|
||||
@@ -28567,8 +28457,6 @@ static int tokenize_next (ASTParseState *s) {
|
||||
else if (len == 5 && !memcmp (start, "catch", 5)) s->token_val = TOK_CATCH;
|
||||
else if (len == 6 && !memcmp (start, "return", 6)) s->token_val = TOK_RETURN;
|
||||
else if (len == 6 && !memcmp (start, "delete", 6)) s->token_val = TOK_DELETE;
|
||||
else if (len == 6 && !memcmp (start, "switch", 6)) s->token_val = TOK_SWITCH;
|
||||
else if (len == 7 && !memcmp (start, "default", 7)) s->token_val = TOK_DEFAULT;
|
||||
else if (len == 7 && !memcmp (start, "finally", 7)) s->token_val = TOK_FINALLY;
|
||||
else if (len == 8 && !memcmp (start, "function", 8)) s->token_val = TOK_FUNCTION;
|
||||
else if (len == 8 && !memcmp (start, "continue", 8)) s->token_val = TOK_CONTINUE;
|
||||
@@ -29239,34 +29127,6 @@ static cJSON *ast_parse_unary (ASTParseState *s) {
|
||||
ast_node_end (s, node, s->buf_ptr);
|
||||
return node;
|
||||
}
|
||||
case TOK_VOID: {
|
||||
ast_next_token (s);
|
||||
cJSON *node = ast_node (s, "void", start);
|
||||
cJSON *expr = ast_parse_unary (s);
|
||||
cJSON_AddItemToObject (node, "expression", expr);
|
||||
ast_node_end (s, node, s->buf_ptr);
|
||||
return node;
|
||||
}
|
||||
case TOK_NEW: {
|
||||
ast_next_token (s);
|
||||
cJSON *node = ast_node (s, "new", start);
|
||||
cJSON *expr = ast_parse_postfix (s);
|
||||
cJSON_AddItemToObject (node, "expression", expr);
|
||||
/* If followed by (, parse arguments */
|
||||
if (s->token_val == '(') {
|
||||
ast_next_token (s);
|
||||
cJSON *list = cJSON_AddArrayToObject (node, "list");
|
||||
while (s->token_val != ')' && s->token_val != TOK_EOF) {
|
||||
cJSON *arg = ast_parse_assign_expr (s);
|
||||
if (arg) cJSON_AddItemToArray (list, arg);
|
||||
if (s->token_val == ',') ast_next_token (s);
|
||||
else break;
|
||||
}
|
||||
if (s->token_val == ')') ast_next_token (s);
|
||||
}
|
||||
ast_node_end (s, node, s->buf_ptr);
|
||||
return node;
|
||||
}
|
||||
default:
|
||||
return ast_parse_postfix (s);
|
||||
}
|
||||
@@ -29505,7 +29365,7 @@ static cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr) {
|
||||
ast_next_token (s);
|
||||
if (s->token_val == '=' || s->token_val == '|') {
|
||||
ast_next_token (s);
|
||||
cJSON *default_val = ast_parse_expr (s);
|
||||
cJSON *default_val = ast_parse_assign_expr (s);
|
||||
cJSON_AddItemToObject (param, "expression", default_val);
|
||||
}
|
||||
cJSON_AddItemToArray (params, param);
|
||||
@@ -29591,7 +29451,7 @@ static cJSON *ast_parse_arrow_function (ASTParseState *s) {
|
||||
/* Check for default value */
|
||||
if (s->token_val == '=' || s->token_val == '|') {
|
||||
ast_next_token (s);
|
||||
cJSON *default_val = ast_parse_expr (s);
|
||||
cJSON *default_val = ast_parse_assign_expr (s);
|
||||
cJSON_AddItemToObject (param, "expression", default_val);
|
||||
}
|
||||
cJSON_AddItemToArray (params, param);
|
||||
@@ -29655,7 +29515,7 @@ static void ast_sync_to_statement (ASTParseState *s) {
|
||||
return; /* don't consume - let caller handle */
|
||||
case TOK_VAR: case TOK_DEF: case TOK_IF: case TOK_WHILE:
|
||||
case TOK_FOR: case TOK_RETURN: case TOK_THROW: case TOK_TRY:
|
||||
case TOK_SWITCH: case TOK_FUNCTION: case TOK_BREAK:
|
||||
case TOK_FUNCTION: case TOK_BREAK:
|
||||
case TOK_CONTINUE: case TOK_DO:
|
||||
return; /* statement-starting keyword found */
|
||||
default:
|
||||
@@ -29894,72 +29754,6 @@ static cJSON *ast_parse_statement (ASTParseState *s) {
|
||||
ast_node_end (s, node, s->buf_ptr);
|
||||
} break;
|
||||
|
||||
case TOK_SWITCH: {
|
||||
node = ast_node (s, "switch", start);
|
||||
ast_next_token (s);
|
||||
if (s->token_val == '(') ast_next_token (s);
|
||||
else ast_error (s, s->token_ptr, "expected '(' before switch expression");
|
||||
cJSON *expr = ast_parse_expr (s);
|
||||
cJSON_AddItemToObject (node, "expression", expr);
|
||||
if (s->token_val == ')') ast_next_token (s);
|
||||
else ast_error (s, s->token_ptr, "expected ')' after switch expression");
|
||||
|
||||
cJSON *cases = cJSON_AddArrayToObject (node, "cases");
|
||||
if (s->token_val == '{') {
|
||||
int has_default = 0;
|
||||
ast_next_token (s);
|
||||
while (s->token_val != '}' && s->token_val != TOK_EOF) {
|
||||
if (s->token_val == TOK_CASE) {
|
||||
cJSON *case_node = ast_node (s, "case", s->token_ptr);
|
||||
ast_next_token (s);
|
||||
cJSON *case_expr = ast_parse_expr (s);
|
||||
cJSON_AddItemToObject (case_node, "expression", case_expr);
|
||||
if (s->token_val == ':') ast_next_token (s);
|
||||
else ast_error (s, s->token_ptr, "expected ':' after case expression");
|
||||
cJSON *case_stmts = cJSON_AddArrayToObject (case_node, "statements");
|
||||
while (s->token_val != TOK_CASE && s->token_val != TOK_DEFAULT &&
|
||||
s->token_val != '}' && s->token_val != TOK_EOF) {
|
||||
const uint8_t *before = s->token_ptr;
|
||||
cJSON *stmt = ast_parse_statement (s);
|
||||
if (stmt) {
|
||||
cJSON_AddItemToArray (case_stmts, stmt);
|
||||
} else if (s->token_ptr == before) {
|
||||
ast_sync_to_statement (s);
|
||||
}
|
||||
}
|
||||
ast_node_end (s, case_node, s->buf_ptr);
|
||||
cJSON_AddItemToArray (cases, case_node);
|
||||
} else if (s->token_val == TOK_DEFAULT) {
|
||||
if (has_default)
|
||||
ast_error (s, s->token_ptr, "more than one default clause in switch");
|
||||
has_default = 1;
|
||||
cJSON *default_node = ast_node (s, "default", s->token_ptr);
|
||||
ast_next_token (s);
|
||||
if (s->token_val == ':') ast_next_token (s);
|
||||
else ast_error (s, s->token_ptr, "expected ':' after default");
|
||||
cJSON *default_stmts = cJSON_AddArrayToObject (default_node, "statements");
|
||||
while (s->token_val != TOK_CASE && s->token_val != '}' && s->token_val != TOK_EOF) {
|
||||
const uint8_t *before = s->token_ptr;
|
||||
cJSON *stmt = ast_parse_statement (s);
|
||||
if (stmt) {
|
||||
cJSON_AddItemToArray (default_stmts, stmt);
|
||||
} else if (s->token_ptr == before) {
|
||||
ast_sync_to_statement (s);
|
||||
}
|
||||
}
|
||||
ast_node_end (s, default_node, s->buf_ptr);
|
||||
cJSON_AddItemToArray (cases, default_node);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s->token_val == '}') ast_next_token (s);
|
||||
} else {
|
||||
ast_error (s, s->token_ptr, "expected '{' after switch");
|
||||
}
|
||||
ast_node_end (s, node, s->buf_ptr);
|
||||
} break;
|
||||
|
||||
case TOK_TRY: {
|
||||
node = ast_node (s, "try", start);
|
||||
ast_next_token (s);
|
||||
@@ -30103,20 +29897,28 @@ static cJSON *ast_parse_program (ASTParseState *s) {
|
||||
|
||||
typedef struct ASTSemVar {
|
||||
const char *name;
|
||||
int is_const; /* 1 for def, function args; 0 for var */
|
||||
int is_const;
|
||||
const char *make; /* "def", "var", "function", "input" */
|
||||
int function_nr; /* which function this var belongs to */
|
||||
int nr_uses; /* reference count */
|
||||
int closure; /* 1 if used by inner function */
|
||||
} ASTSemVar;
|
||||
|
||||
typedef struct ASTSemScope {
|
||||
struct ASTSemScope *parent;
|
||||
ASTSemVar vars[AST_SEM_MAX_VARS];
|
||||
int var_count;
|
||||
int in_loop; /* inside a loop (while, for, do) */
|
||||
int in_switch; /* inside a switch */
|
||||
int in_loop;
|
||||
int function_nr; /* function_nr of enclosing function */
|
||||
int is_function_scope; /* 1 if this is a function's top-level scope */
|
||||
} ASTSemScope;
|
||||
|
||||
typedef struct ASTSemState {
|
||||
cJSON *errors;
|
||||
int has_error;
|
||||
cJSON *scopes_array;
|
||||
const char *intrinsics[256];
|
||||
int intrinsic_count;
|
||||
} ASTSemState;
|
||||
|
||||
static void ast_sem_error (ASTSemState *st, cJSON *node, const char *fmt, ...) {
|
||||
@@ -30140,22 +29942,79 @@ static void ast_sem_error (ASTSemState *st, cJSON *node, const char *fmt, ...) {
|
||||
st->has_error = 1;
|
||||
}
|
||||
|
||||
static void ast_sem_add_var (ASTSemScope *scope, const char *name, int is_const) {
|
||||
static void ast_sem_add_var (ASTSemScope *scope, const char *name, int is_const,
|
||||
const char *make, int function_nr) {
|
||||
if (scope->var_count < AST_SEM_MAX_VARS) {
|
||||
scope->vars[scope->var_count].name = name;
|
||||
scope->vars[scope->var_count].is_const = is_const;
|
||||
ASTSemVar *v = &scope->vars[scope->var_count];
|
||||
v->name = name;
|
||||
v->is_const = is_const;
|
||||
v->make = make;
|
||||
v->function_nr = function_nr;
|
||||
v->nr_uses = 0;
|
||||
v->closure = 0;
|
||||
scope->var_count++;
|
||||
}
|
||||
}
|
||||
|
||||
static ASTSemVar *ast_sem_find_var (ASTSemScope *scope, const char *name) {
|
||||
typedef struct {
|
||||
ASTSemVar *var;
|
||||
int level;
|
||||
int def_function_nr;
|
||||
} ASTSemLookup;
|
||||
|
||||
static ASTSemLookup ast_sem_lookup_var (ASTSemScope *scope, const char *name) {
|
||||
ASTSemLookup result = {NULL, 0, -1};
|
||||
int cur_fn = scope->function_nr;
|
||||
for (ASTSemScope *s = scope; s; s = s->parent) {
|
||||
for (int i = 0; i < s->var_count; i++) {
|
||||
if (strcmp (s->vars[i].name, name) == 0)
|
||||
return &s->vars[i];
|
||||
if (strcmp (s->vars[i].name, name) == 0) {
|
||||
result.var = &s->vars[i];
|
||||
result.def_function_nr = s->vars[i].function_nr;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
/* When crossing into a parent with a different function_nr, increment level */
|
||||
if (s->parent && s->parent->function_nr != cur_fn) {
|
||||
result.level++;
|
||||
cur_fn = s->parent->function_nr;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
static ASTSemVar *ast_sem_find_var (ASTSemScope *scope, const char *name) {
|
||||
ASTSemLookup r = ast_sem_lookup_var (scope, name);
|
||||
return r.var;
|
||||
}
|
||||
|
||||
static void ast_sem_add_intrinsic (ASTSemState *st, const char *name) {
|
||||
for (int i = 0; i < st->intrinsic_count; i++) {
|
||||
if (strcmp (st->intrinsics[i], name) == 0) return;
|
||||
}
|
||||
if (st->intrinsic_count < 256) {
|
||||
st->intrinsics[st->intrinsic_count++] = name;
|
||||
}
|
||||
}
|
||||
|
||||
static cJSON *ast_sem_build_scope_record (ASTSemScope *scope, int *nr_slots, int *nr_close) {
|
||||
cJSON *rec = cJSON_CreateObject ();
|
||||
cJSON_AddNumberToObject (rec, "function_nr", scope->function_nr);
|
||||
int slots = 0, close_slots = 0;
|
||||
for (int i = 0; i < scope->var_count; i++) {
|
||||
ASTSemVar *v = &scope->vars[i];
|
||||
cJSON *entry = cJSON_CreateObject ();
|
||||
cJSON_AddStringToObject (entry, "make", v->make);
|
||||
cJSON_AddNumberToObject (entry, "function_nr", v->function_nr);
|
||||
cJSON_AddNumberToObject (entry, "nr_uses", v->nr_uses);
|
||||
cJSON_AddBoolToObject (entry, "closure", v->closure);
|
||||
cJSON_AddNumberToObject (entry, "level", 0);
|
||||
cJSON_AddItemToObject (rec, v->name, entry);
|
||||
slots++;
|
||||
if (v->closure) close_slots++;
|
||||
}
|
||||
*nr_slots = slots;
|
||||
*nr_close = close_slots;
|
||||
return rec;
|
||||
}
|
||||
|
||||
static int ast_sem_in_loop (ASTSemScope *scope) {
|
||||
@@ -30165,13 +30024,6 @@ static int ast_sem_in_loop (ASTSemScope *scope) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ast_sem_in_loop_or_switch (ASTSemScope *scope) {
|
||||
for (ASTSemScope *s = scope; s; s = s->parent) {
|
||||
if (s->in_loop || s->in_switch) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr);
|
||||
static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt);
|
||||
|
||||
@@ -30269,23 +30121,13 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr
|
||||
|
||||
/* Unary ops */
|
||||
if (strcmp (kind, "!") == 0 || strcmp (kind, "~") == 0 ||
|
||||
strcmp (kind, "typeof") == 0 || strcmp (kind, "void") == 0 ||
|
||||
strcmp (kind, "typeof") == 0 ||
|
||||
strcmp (kind, "delete") == 0 || strcmp (kind, "neg") == 0 ||
|
||||
strcmp (kind, "pos") == 0 || strcmp (kind, "spread") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "expression"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* new expression */
|
||||
if (strcmp (kind, "new") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "expression"));
|
||||
cJSON *arg;
|
||||
cJSON_ArrayForEach (arg, cJSON_GetObjectItem (expr, "list")) {
|
||||
ast_sem_check_expr (st, scope, arg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Array literal */
|
||||
if (strcmp (kind, "array") == 0) {
|
||||
cJSON *el;
|
||||
@@ -30306,14 +30148,24 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr
|
||||
|
||||
/* Function expression / arrow function — create new scope */
|
||||
if (strcmp (kind, "function") == 0) {
|
||||
cJSON *fn_nr_node = cJSON_GetObjectItem (expr, "function_nr");
|
||||
int fn_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : scope->function_nr;
|
||||
|
||||
ASTSemScope fn_scope = {0};
|
||||
fn_scope.parent = scope;
|
||||
fn_scope.function_nr = fn_nr;
|
||||
fn_scope.is_function_scope = 1;
|
||||
|
||||
/* Add parameters as const */
|
||||
cJSON_AddNumberToObject (expr, "outer", scope->function_nr);
|
||||
|
||||
/* Add parameters as input */
|
||||
cJSON *param;
|
||||
cJSON_ArrayForEach (param, cJSON_GetObjectItem (expr, "list")) {
|
||||
const char *pname = cJSON_GetStringValue (cJSON_GetObjectItem (param, "name"));
|
||||
if (pname) ast_sem_add_var (&fn_scope, pname, 1);
|
||||
if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr);
|
||||
/* Check default value expressions */
|
||||
cJSON *def_val = cJSON_GetObjectItem (param, "default");
|
||||
if (def_val) ast_sem_check_expr (st, &fn_scope, def_val);
|
||||
}
|
||||
|
||||
/* Check function body */
|
||||
@@ -30321,6 +30173,13 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr
|
||||
cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (expr, "statements")) {
|
||||
ast_sem_check_stmt (st, &fn_scope, stmt);
|
||||
}
|
||||
|
||||
/* Build scope record and attach to scopes array */
|
||||
int nr_slots, nr_close;
|
||||
cJSON *rec = ast_sem_build_scope_record (&fn_scope, &nr_slots, &nr_close);
|
||||
cJSON_AddItemToArray (st->scopes_array, rec);
|
||||
cJSON_AddNumberToObject (expr, "nr_slots", nr_slots);
|
||||
cJSON_AddNumberToObject (expr, "nr_close_slots", nr_close);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -30333,7 +30192,25 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr
|
||||
return;
|
||||
}
|
||||
|
||||
/* name, number, string, regexp, null, true, false, this — leaf nodes, no check needed */
|
||||
/* Name token — annotate with level and function_nr */
|
||||
if (strcmp (kind, "name") == 0) {
|
||||
const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "name"));
|
||||
if (name) {
|
||||
ASTSemLookup r = ast_sem_lookup_var (scope, name);
|
||||
if (r.var) {
|
||||
cJSON_AddNumberToObject (expr, "level", r.level);
|
||||
cJSON_AddNumberToObject (expr, "function_nr", r.def_function_nr);
|
||||
r.var->nr_uses++;
|
||||
if (r.level > 0) r.var->closure = 1;
|
||||
} else {
|
||||
cJSON_AddNumberToObject (expr, "level", -1);
|
||||
ast_sem_add_intrinsic (st, name);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* number, string, regexp, null, true, false, this — leaf nodes, no check needed */
|
||||
}
|
||||
|
||||
/* Check a statement for semantic errors */
|
||||
@@ -30351,7 +30228,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
if (existing && existing->is_const) {
|
||||
ast_sem_error (st, left, "cannot redeclare constant '%s'", name);
|
||||
}
|
||||
ast_sem_add_var (scope, name, 0);
|
||||
ast_sem_add_var (scope, name, 0, "var", scope->function_nr);
|
||||
}
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "right"));
|
||||
return;
|
||||
@@ -30368,7 +30245,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
} else if (existing) {
|
||||
ast_sem_error (st, left, "cannot redeclare '%s' as constant", name);
|
||||
}
|
||||
ast_sem_add_var (scope, name, 1);
|
||||
ast_sem_add_var (scope, name, 1, "def", scope->function_nr);
|
||||
}
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "right"));
|
||||
return;
|
||||
@@ -30399,6 +30276,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
ASTSemScope loop_scope = {0};
|
||||
loop_scope.parent = scope;
|
||||
loop_scope.in_loop = 1;
|
||||
loop_scope.function_nr = scope->function_nr;
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &loop_scope, s2);
|
||||
@@ -30410,6 +30288,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
ASTSemScope loop_scope = {0};
|
||||
loop_scope.parent = scope;
|
||||
loop_scope.in_loop = 1;
|
||||
loop_scope.function_nr = scope->function_nr;
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &loop_scope, s2);
|
||||
@@ -30422,6 +30301,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
ASTSemScope loop_scope = {0};
|
||||
loop_scope.parent = scope;
|
||||
loop_scope.in_loop = 1;
|
||||
loop_scope.function_nr = scope->function_nr;
|
||||
/* init may be a var/def statement or expression */
|
||||
cJSON *init = cJSON_GetObjectItem (stmt, "init");
|
||||
if (init) {
|
||||
@@ -30441,22 +30321,6 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "switch") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression"));
|
||||
ASTSemScope sw_scope = {0};
|
||||
sw_scope.parent = scope;
|
||||
sw_scope.in_switch = 1;
|
||||
cJSON *c;
|
||||
cJSON_ArrayForEach (c, cJSON_GetObjectItem (stmt, "cases")) {
|
||||
ast_sem_check_expr (st, &sw_scope, cJSON_GetObjectItem (c, "expression"));
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (c, "statements")) {
|
||||
ast_sem_check_stmt (st, &sw_scope, s2);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "return") == 0 || strcmp (kind, "throw") == 0 ||
|
||||
strcmp (kind, "go") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression"));
|
||||
@@ -30464,8 +30328,8 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
}
|
||||
|
||||
if (strcmp (kind, "break") == 0) {
|
||||
if (!ast_sem_in_loop_or_switch (scope)) {
|
||||
ast_sem_error (st, stmt, "'break' used outside of loop or switch");
|
||||
if (!ast_sem_in_loop (scope)) {
|
||||
ast_sem_error (st, stmt, "'break' used outside of loop");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -30486,8 +30350,9 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
if (catch_node) {
|
||||
ASTSemScope catch_scope = {0};
|
||||
catch_scope.parent = scope;
|
||||
catch_scope.function_nr = scope->function_nr;
|
||||
const char *catch_name = cJSON_GetStringValue (cJSON_GetObjectItem (catch_node, "name"));
|
||||
if (catch_name) ast_sem_add_var (&catch_scope, catch_name, 0);
|
||||
if (catch_name) ast_sem_add_var (&catch_scope, catch_name, 0, "var", scope->function_nr);
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (catch_node, "statements")) {
|
||||
ast_sem_check_stmt (st, &catch_scope, s2);
|
||||
}
|
||||
@@ -30504,6 +30369,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
if (strcmp (kind, "block") == 0) {
|
||||
ASTSemScope block_scope = {0};
|
||||
block_scope.parent = scope;
|
||||
block_scope.function_nr = scope->function_nr;
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &block_scope, s2);
|
||||
@@ -30519,35 +30385,57 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt
|
||||
if (strcmp (kind, "function") == 0) {
|
||||
/* Function declaration — register name, then check body in new scope */
|
||||
const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "name"));
|
||||
if (name) ast_sem_add_var (scope, name, 0);
|
||||
if (name) ast_sem_add_var (scope, name, 0, "function", scope->function_nr);
|
||||
|
||||
cJSON *fn_nr_node = cJSON_GetObjectItem (stmt, "function_nr");
|
||||
int fn_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : scope->function_nr;
|
||||
|
||||
ASTSemScope fn_scope = {0};
|
||||
fn_scope.parent = scope;
|
||||
fn_scope.function_nr = fn_nr;
|
||||
fn_scope.is_function_scope = 1;
|
||||
|
||||
cJSON_AddNumberToObject (stmt, "outer", scope->function_nr);
|
||||
|
||||
cJSON *param;
|
||||
cJSON_ArrayForEach (param, cJSON_GetObjectItem (stmt, "list")) {
|
||||
const char *pname = cJSON_GetStringValue (cJSON_GetObjectItem (param, "name"));
|
||||
if (pname) ast_sem_add_var (&fn_scope, pname, 1);
|
||||
if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr);
|
||||
/* Check default value expressions */
|
||||
cJSON *def_val = cJSON_GetObjectItem (param, "default");
|
||||
if (def_val) ast_sem_check_expr (st, &fn_scope, def_val);
|
||||
}
|
||||
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &fn_scope, s2);
|
||||
}
|
||||
|
||||
/* Build scope record and attach to scopes array */
|
||||
int nr_slots, nr_close;
|
||||
cJSON *rec = ast_sem_build_scope_record (&fn_scope, &nr_slots, &nr_close);
|
||||
cJSON_AddItemToArray (st->scopes_array, rec);
|
||||
cJSON_AddNumberToObject (stmt, "nr_slots", nr_slots);
|
||||
cJSON_AddNumberToObject (stmt, "nr_close_slots", nr_close);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Run the semantic pass on a parsed AST, adding errors to the AST */
|
||||
static void ast_semantic_check (cJSON *ast, cJSON **errors_out) {
|
||||
static void ast_semantic_check (cJSON *ast, cJSON **errors_out,
|
||||
cJSON **scopes_out, cJSON **intrinsics_out) {
|
||||
ASTSemState st = {0};
|
||||
st.scopes_array = cJSON_CreateArray ();
|
||||
|
||||
ASTSemScope global_scope = {0};
|
||||
global_scope.function_nr = 0;
|
||||
global_scope.is_function_scope = 1;
|
||||
|
||||
/* Process top-level function declarations first (they are hoisted) */
|
||||
cJSON *stmt;
|
||||
cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (ast, "functions")) {
|
||||
const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "name"));
|
||||
if (name) ast_sem_add_var (&global_scope, name, 0);
|
||||
if (name) ast_sem_add_var (&global_scope, name, 0, "function", 0);
|
||||
}
|
||||
|
||||
/* Check all statements (var/def are registered as they are encountered) */
|
||||
@@ -30560,7 +30448,28 @@ static void ast_semantic_check (cJSON *ast, cJSON **errors_out) {
|
||||
ast_sem_check_stmt (&st, &global_scope, stmt);
|
||||
}
|
||||
|
||||
/* Build program scope record (function_nr 0) and prepend to scopes array */
|
||||
int nr_slots, nr_close;
|
||||
cJSON *prog_rec = ast_sem_build_scope_record (&global_scope, &nr_slots, &nr_close);
|
||||
/* Prepend: detach all children, add prog_rec, re-add children */
|
||||
cJSON *existing = st.scopes_array->child;
|
||||
st.scopes_array->child = NULL;
|
||||
cJSON_AddItemToArray (st.scopes_array, prog_rec);
|
||||
if (existing) {
|
||||
cJSON *last = prog_rec;
|
||||
last->next = existing;
|
||||
existing->prev = last;
|
||||
}
|
||||
|
||||
/* Build intrinsics array */
|
||||
cJSON *intr_arr = cJSON_CreateArray ();
|
||||
for (int i = 0; i < st.intrinsic_count; i++) {
|
||||
cJSON_AddItemToArray (intr_arr, cJSON_CreateString (st.intrinsics[i]));
|
||||
}
|
||||
|
||||
*errors_out = st.errors;
|
||||
*scopes_out = st.scopes_array;
|
||||
*intrinsics_out = intr_arr;
|
||||
}
|
||||
|
||||
char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filename) {
|
||||
@@ -30572,7 +30481,7 @@ char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filena
|
||||
s.buf_start = (const uint8_t *)source;
|
||||
s.buf_ptr = (const uint8_t *)source;
|
||||
s.buf_end = (const uint8_t *)source + len;
|
||||
s.function_nr = 0;
|
||||
s.function_nr = 1;
|
||||
s.errors = NULL;
|
||||
s.has_error = 0;
|
||||
|
||||
@@ -30588,7 +30497,13 @@ char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filena
|
||||
|
||||
/* Run semantic pass */
|
||||
cJSON *sem_errors = NULL;
|
||||
ast_semantic_check (ast, &sem_errors);
|
||||
cJSON *scopes = NULL;
|
||||
cJSON *intrinsics = NULL;
|
||||
ast_semantic_check (ast, &sem_errors, &scopes, &intrinsics);
|
||||
|
||||
/* Attach scopes and intrinsics to AST */
|
||||
if (scopes) cJSON_AddItemToObject (ast, "scopes", scopes);
|
||||
if (intrinsics) cJSON_AddItemToObject (ast, "intrinsics", intrinsics);
|
||||
|
||||
/* Merge parse errors and semantic errors */
|
||||
if (s.errors && sem_errors) {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
void 0
|
||||
344
vm_test/syntax.txt
Normal file
344
vm_test/syntax.txt
Normal file
@@ -0,0 +1,344 @@
|
||||
/* comprehensive syntax test - exercises all AST paths */
|
||||
|
||||
// --- variables and constants ---
|
||||
var a
|
||||
var b = 1
|
||||
var c = 2, d = 3
|
||||
def PI = 3.14159
|
||||
def MSG = "hello"
|
||||
|
||||
// --- number literals ---
|
||||
var n_int = 42
|
||||
var n_float = 3.14
|
||||
var n_hex = 0xFF
|
||||
var n_octal = 0o77
|
||||
var n_binary = 0b1010
|
||||
var n_underscore = 1_000_000
|
||||
var n_exp = 1e10
|
||||
var n_exp_neg = 2.5e-3
|
||||
|
||||
// --- string literals ---
|
||||
var s_single = 'single'
|
||||
var s_double = "double"
|
||||
var s_escape = "tab\there\nnewline\\slash\"quote"
|
||||
var s_unicode = "\u0041\u0042"
|
||||
|
||||
// --- template literals ---
|
||||
var t_basic = `hello ${b} world`
|
||||
var t_nested = `a ${b + c} b ${d}`
|
||||
var t_expr = `result: ${b > 0 ? "yes" : "no"}`
|
||||
|
||||
// --- array literals ---
|
||||
var arr_empty = []
|
||||
var arr_simple = [1, 2, 3]
|
||||
var arr_mixed = [1, "two", true, null]
|
||||
var arr_nested = [[1, 2], [3, [4, 5]]]
|
||||
|
||||
// --- record/object literals ---
|
||||
var rec_empty = {}
|
||||
var rec_simple = {x: 1, y: 2}
|
||||
var rec_nested = {a: {b: {c: 3}}}
|
||||
var shorthand_var = 10
|
||||
var rec_shorthand = {shorthand_var}
|
||||
var rec_computed = {["key" + "1"]: 100}
|
||||
var rec_method = {
|
||||
greet() { return "hi" }
|
||||
}
|
||||
var rec_mixed = {
|
||||
name: "test",
|
||||
value: 42,
|
||||
nested: {inner: true},
|
||||
items: [1, 2, 3]
|
||||
}
|
||||
|
||||
// --- all binary arithmetic operators ---
|
||||
var arith_add = 1 + 2
|
||||
var arith_sub = 5 - 3
|
||||
var arith_mul = 4 * 5
|
||||
var arith_div = 10 / 3
|
||||
var arith_mod = 10 % 3
|
||||
var arith_pow = 2 ** 8
|
||||
|
||||
// --- comparison operators ---
|
||||
var cmp_lt = 1 < 2
|
||||
var cmp_gt = 2 > 1
|
||||
var cmp_lte = 1 <= 1
|
||||
var cmp_gte = 2 >= 2
|
||||
var cmp_eq = 1 == 1
|
||||
var cmp_neq = 1 != 2
|
||||
|
||||
// --- logical operators ---
|
||||
var log_and = true && false
|
||||
var log_or = false || true
|
||||
var log_not = !false
|
||||
|
||||
// --- bitwise operators ---
|
||||
var bit_and = 0xFF & 0x0F
|
||||
var bit_or = 0xF0 | 0x0F
|
||||
var bit_xor = 0xFF ^ 0x0F
|
||||
var bit_not = ~0
|
||||
var bit_shl = 1 << 8
|
||||
var bit_shr = 256 >> 4
|
||||
var bit_shru = -1 >>> 0
|
||||
|
||||
// --- nullish coalescing ---
|
||||
var nullish = null ?? "default"
|
||||
|
||||
// --- ternary operator ---
|
||||
var tern = b > 0 ? "pos" : "neg"
|
||||
var tern_nested = b > 0 ? (b > 10 ? "big" : "small") : "neg"
|
||||
|
||||
// --- comma operator ---
|
||||
var comma_result = (1, 2, 3)
|
||||
|
||||
// --- unary operators ---
|
||||
var unary_pos = +b
|
||||
var unary_neg = -b
|
||||
|
||||
// --- increment/decrement ---
|
||||
var inc_val = 0
|
||||
inc_val++
|
||||
inc_val--
|
||||
;++inc_val
|
||||
;--inc_val
|
||||
|
||||
// --- all compound assignments ---
|
||||
var ca = 10
|
||||
ca += 5
|
||||
ca -= 3
|
||||
ca *= 2
|
||||
ca /= 4
|
||||
ca %= 3
|
||||
ca **= 2
|
||||
ca <<= 1
|
||||
ca >>= 1
|
||||
ca >>>= 0
|
||||
ca &= 0xFF
|
||||
ca |= 0x01
|
||||
ca ^= 0x10
|
||||
ca &&= true
|
||||
ca ||= false
|
||||
ca ??= 1
|
||||
|
||||
// --- chained assignment ---
|
||||
var ch1, ch2
|
||||
ch1 = ch2 = 42
|
||||
|
||||
// --- property access ---
|
||||
var obj = {a: {b: {c: 1}}, items: [10, 20, 30]}
|
||||
var dot_access = obj.a
|
||||
var dot_chain = obj.a.b.c
|
||||
var bracket_access = obj["a"]
|
||||
var bracket_dynamic = obj["it" + "ems"]
|
||||
var bracket_index = obj.items[1]
|
||||
|
||||
// --- optional chaining ---
|
||||
var opt_obj = {x: {y: 1}}
|
||||
var opt_prop = opt_obj?.x
|
||||
var opt_deep = opt_obj?.x?.y
|
||||
var opt_null = null?.foo
|
||||
var opt_bracket = opt_obj?.["x"]
|
||||
var opt_call_obj = {f: function() { return 1 }}
|
||||
var opt_call = opt_call_obj?.f?.()
|
||||
|
||||
// --- function declarations ---
|
||||
function add(a, b) {
|
||||
return a + b
|
||||
}
|
||||
|
||||
function no_params() {
|
||||
return 42
|
||||
}
|
||||
|
||||
function with_default(x = 10, y = 20) {
|
||||
return x + y
|
||||
}
|
||||
|
||||
// --- function expressions ---
|
||||
var func_expr = function(x) { return x * 2 }
|
||||
var func_named = function multiply(x, y) { return x * y }
|
||||
|
||||
// --- arrow functions (all forms) ---
|
||||
var arrow_no_param = () => 0
|
||||
var arrow_one = x => x + 1
|
||||
var arrow_multi = (a, b) => a + b
|
||||
var arrow_block = (a, b) => { return a + b }
|
||||
var arrow_default = (x = 1, y = 2) => x + y
|
||||
var arrow_default_expr = (x = 1 + 2, y = arr_simple[0]) => x + y
|
||||
|
||||
// --- closures ---
|
||||
function make_counter() {
|
||||
var count = 0
|
||||
return function() {
|
||||
count += 1
|
||||
return count
|
||||
}
|
||||
}
|
||||
|
||||
function outer_fn() {
|
||||
var x = 10
|
||||
function middle() {
|
||||
var y = 20
|
||||
function inner() {
|
||||
return x + y
|
||||
}
|
||||
return inner()
|
||||
}
|
||||
return middle()
|
||||
}
|
||||
|
||||
// --- this in methods ---
|
||||
var counter = {
|
||||
val: 0,
|
||||
inc() { this.val += 1 },
|
||||
get() { return this.val }
|
||||
}
|
||||
|
||||
// --- if/else/else-if ---
|
||||
var if_result
|
||||
if (b > 0) {
|
||||
if_result = "positive"
|
||||
} else if (b == 0) {
|
||||
if_result = "zero"
|
||||
} else {
|
||||
if_result = "negative"
|
||||
}
|
||||
|
||||
// --- while loop ---
|
||||
var w = 0
|
||||
while (w < 5) {
|
||||
w += 1
|
||||
}
|
||||
|
||||
// --- do-while loop ---
|
||||
var dw = 0
|
||||
do {
|
||||
dw += 1
|
||||
} while (dw < 3)
|
||||
|
||||
// --- for loop ---
|
||||
var f_sum = 0
|
||||
for (var i = 0; i < 10; i++) {
|
||||
f_sum += i
|
||||
}
|
||||
|
||||
// --- for loop with break ---
|
||||
var fb = 0
|
||||
for (var j = 0; j < 100; j++) {
|
||||
if (j == 5) break
|
||||
fb = j
|
||||
}
|
||||
|
||||
// --- for loop with continue ---
|
||||
var fc = 0
|
||||
for (var k = 0; k < 10; k++) {
|
||||
if (k % 2 == 0) continue
|
||||
fc += k
|
||||
}
|
||||
|
||||
// --- labeled break ---
|
||||
outer: for (var x = 0; x < 3; x++) {
|
||||
for (var y = 0; y < 3; y++) {
|
||||
if (y == 1) break outer
|
||||
}
|
||||
}
|
||||
|
||||
// --- labeled continue ---
|
||||
var lc = 0
|
||||
loop: for (var m = 0; m < 3; m++) {
|
||||
for (var n = 0; n < 3; n++) {
|
||||
if (n == 1) continue loop
|
||||
lc += 1
|
||||
}
|
||||
}
|
||||
|
||||
// --- try/catch/finally ---
|
||||
var tc
|
||||
try {
|
||||
throw "error"
|
||||
} catch (e) {
|
||||
tc = e
|
||||
}
|
||||
|
||||
var tcf = 0
|
||||
try {
|
||||
throw "err"
|
||||
} catch (e) {
|
||||
tcf = 1
|
||||
} finally {
|
||||
tcf += 10
|
||||
}
|
||||
|
||||
// --- try/finally (no catch) ---
|
||||
var tf = 0
|
||||
try {
|
||||
tf = 1
|
||||
} finally {
|
||||
tf += 1
|
||||
}
|
||||
|
||||
// --- delete operator ---
|
||||
var del_obj = {a: 1, b: 2}
|
||||
delete del_obj.a
|
||||
|
||||
// --- in operator ---
|
||||
var in_result = "b" in del_obj
|
||||
|
||||
// --- go statement ---
|
||||
function async_task() { return 1 }
|
||||
function caller() { go async_task() }
|
||||
|
||||
// --- IIFE ---
|
||||
var iife = (function() { return 99 })()
|
||||
|
||||
// --- recursive function ---
|
||||
function factorial(n) {
|
||||
if (n <= 1) return 1
|
||||
return n * factorial(n - 1)
|
||||
}
|
||||
|
||||
// --- mutually recursive functions ---
|
||||
function is_even(n) {
|
||||
if (n == 0) return true
|
||||
return is_odd(n - 1)
|
||||
}
|
||||
function is_odd(n) {
|
||||
if (n == 0) return false
|
||||
return is_even(n - 1)
|
||||
}
|
||||
|
||||
// --- block scoping ---
|
||||
var block_val = 1
|
||||
{
|
||||
var block_val = 2
|
||||
}
|
||||
|
||||
// --- nested blocks ---
|
||||
{
|
||||
var nb = 1
|
||||
{
|
||||
var nb2 = nb + 1
|
||||
{
|
||||
var nb3 = nb2 + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- empty statement ---
|
||||
;
|
||||
|
||||
// --- parenthesized expression for precedence ---
|
||||
var prec = (1 + 2) * (3 + 4)
|
||||
|
||||
// --- complex expressions ---
|
||||
var complex = arr_simple[0] + rec_simple.x * (b > 0 ? 2 : 1)
|
||||
|
||||
// --- operator precedence chain ---
|
||||
var prec_chain = 1 + 2 * 3 ** 2 - 4 / 2 % 3
|
||||
|
||||
// --- regex literals ---
|
||||
var re = /hello/
|
||||
var re_flags = /world/gi
|
||||
|
||||
// --- line comment at end ---
|
||||
var end = 1 // done
|
||||
Reference in New Issue
Block a user