diff --git a/source/cell.c b/source/cell.c index fb889c4a..c8776d4d 100644 --- a/source/cell.c +++ b/source/cell.c @@ -397,6 +397,63 @@ int cell_init(int argc, char **argv) return json ? 0 : 1; } + /* Check for --tokenize flag to output token array JSON */ + if (argc >= 3 && strcmp(argv[1], "--tokenize") == 0) { + const char *script_or_file = argv[2]; + char *script = NULL; + char *allocated_script = NULL; + const char *filename = ""; + + struct stat st; + if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) { + FILE *f = fopen(script_or_file, "r"); + if (!f) { + printf("Failed to open file: %s\n", script_or_file); + return 1; + } + allocated_script = malloc(st.st_size + 1); + if (!allocated_script) { + fclose(f); + printf("Failed to allocate memory for script\n"); + return 1; + } + size_t read_size = fread(allocated_script, 1, st.st_size, f); + fclose(f); + allocated_script[read_size] = '\0'; + script = allocated_script; + filename = script_or_file; + } else { + script = (char *)script_or_file; + } + + JSRuntime *rt = JS_NewRuntime(); + if (!rt) { + printf("Failed to create JS runtime\n"); + free(allocated_script); + return 1; + } + JSContext *ctx = JS_NewContext(rt); + if (!ctx) { + printf("Failed to create JS context\n"); + JS_FreeRuntime(rt); + free(allocated_script); + return 1; + } + + char *json = JS_Tokenize(ctx, script, strlen(script), filename); + if (json) { + printf("%s\n", json); + free(json); + } else { + printf("Failed to tokenize\n"); + } + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + free(allocated_script); + return json ? 0 : 1; + } + /* Check for --mach flag to output machine code JSON */ if (argc >= 3 && strcmp(argv[1], "--mach") == 0) { const char *script_or_file = argv[2]; diff --git a/source/quickjs.c b/source/quickjs.c index a70c1e47..3bdc70ab 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -8513,6 +8513,10 @@ enum { TOK_ERROR, TOK_PRIVATE_NAME, TOK_EOF, + /* whitespace/comment tokens for tokenizer */ + TOK_COMMENT, + TOK_NEWLINE, + TOK_SPACE, /* keywords: WARNING: same order as atoms */ TOK_NULL, /* must be first */ TOK_FALSE, @@ -8574,6 +8578,113 @@ enum { #define CP_LS 0x2028 #define CP_PS 0x2029 +/* Map token values to kind strings for tokenizer output */ +static const char *ast_token_kind_str(int token_val) { + static char single_char[2] = {0, 0}; + switch (token_val) { + case TOK_NUMBER: return "number"; + case TOK_STRING: return "text"; + case TOK_TEMPLATE: return "text"; + case TOK_IDENT: return "name"; + case TOK_COMMENT: return "comment"; + case TOK_NEWLINE: return "newline"; + case TOK_SPACE: return "space"; + case TOK_REGEXP: return "regexp"; + case TOK_PRIVATE_NAME: return "private_name"; + case TOK_EOF: return "eof"; + case TOK_ERROR: return "error"; + /* compound operators */ + case TOK_MUL_ASSIGN: return "*="; + case TOK_DIV_ASSIGN: return "/="; + case TOK_MOD_ASSIGN: return "%="; + case TOK_PLUS_ASSIGN: return "+="; + case TOK_MINUS_ASSIGN: return "-="; + case TOK_SHL_ASSIGN: return "<<="; + case TOK_SAR_ASSIGN: return ">>="; + case TOK_SHR_ASSIGN: return ">>>="; + case TOK_AND_ASSIGN: return "&="; + case TOK_XOR_ASSIGN: return "^="; + case TOK_OR_ASSIGN: return "|="; + case TOK_POW_ASSIGN: return "**="; + case TOK_LAND_ASSIGN: return "&&="; + case TOK_LOR_ASSIGN: return "||="; + case TOK_DOUBLE_QUESTION_MARK_ASSIGN: return "?\?="; + case TOK_DEC: return "--"; + case TOK_INC: return "++"; + case TOK_SHL: return "<<"; + case TOK_SAR: return ">>"; + case TOK_SHR: return ">>>"; + case TOK_LT: return "<"; + case TOK_LTE: return "<="; + case TOK_GT: return ">"; + case TOK_GTE: return ">="; + case TOK_EQ: return "=="; + case TOK_STRICT_EQ: return "==="; + case TOK_NEQ: return "!="; + case TOK_STRICT_NEQ: return "!=="; + case TOK_LAND: return "&&"; + case TOK_LOR: return "||"; + case TOK_POW: return "**"; + case TOK_ARROW: return "=>"; + case TOK_DOUBLE_QUESTION_MARK: return "??"; + case TOK_QUESTION_MARK_DOT: return "?."; + /* keywords */ + case TOK_NULL: return "null"; + case TOK_FALSE: return "false"; + case TOK_TRUE: return "true"; + case TOK_IF: return "if"; + case TOK_ELSE: return "else"; + case TOK_RETURN: return "return"; + case TOK_GO: return "go"; + case TOK_VAR: return "var"; + 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"; + case TOK_FINALLY: return "finally"; + case TOK_FUNCTION: return "function"; + case TOK_DEBUGGER: return "debugger"; + case TOK_WITH: return "with"; + case TOK_CLASS: return "class"; + case TOK_CONST: return "const"; + case TOK_ENUM: return "enum"; + case TOK_EXPORT: return "export"; + case TOK_EXTENDS: return "extends"; + case TOK_IMPORT: return "import"; + case TOK_SUPER: return "super"; + case TOK_IMPLEMENTS: return "implements"; + case TOK_INTERFACE: return "interface"; + case TOK_LET: return "let"; + case TOK_PRIVATE: return "private"; + case TOK_PROTECTED: return "protected"; + case TOK_PUBLIC: return "public"; + case TOK_STATIC: return "static"; + case TOK_YIELD: return "yield"; + case TOK_AWAIT: return "await"; + case TOK_OF: return "of"; + default: + /* Single character tokens */ + if (token_val >= 0 && token_val < 128) { + single_char[0] = (char)token_val; + return single_char; + } + return "unknown"; + } +} + typedef struct BlockEnv { struct BlockEnv *prev; JSValue label_name; /* JS_NULL if none */ @@ -27862,8 +27973,35 @@ typedef struct ASTParseState { } ASTParseState; static cJSON *ast_parse_expr (ASTParseState *s); +static cJSON *ast_parse_assign_expr (ASTParseState *s); static cJSON *ast_parse_statement (ASTParseState *s); static cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr); +static cJSON *ast_parse_arrow_function (ASTParseState *s); + +/* Check if we're looking at an arrow function starting with '(' */ +static BOOL ast_is_arrow_function (ASTParseState *s) { + if (s->token_val != '(') return FALSE; + const uint8_t *p = s->buf_ptr; + int depth = 1; + while (p < s->buf_end && depth > 0) { + uint8_t c = *p++; + if (c == '(') depth++; + else if (c == ')') depth--; + else if (c == '"' || c == '\'' || c == '`') { + /* Skip string */ + uint8_t quote = c; + while (p < s->buf_end && *p != quote) { + if (*p == '\\' && p + 1 < s->buf_end) p++; + p++; + } + if (p < s->buf_end) p++; + } + } + /* Skip whitespace */ + while (p < s->buf_end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++; + /* Check for => */ + return (p + 1 < s->buf_end && p[0] == '=' && p[1] == '>'); +} static void ast_free_token (ASTParseState *s) { switch (s->token_val) { @@ -28222,6 +28360,287 @@ redo: return 0; } +/* Tokenizer function that does NOT skip whitespace/comments - emits them as tokens */ +static int tokenize_next (ASTParseState *s) { + const uint8_t *p; + int c; + BOOL ident_has_escape; + + ast_free_token (s); + p = s->buf_ptr; + s->got_lf = FALSE; + + s->token_ptr = p; + c = *p; + switch (c) { + case 0: + if (p >= s->buf_end) { + s->token_val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': { + const uint8_t *start = p; + p++; + while (p < s->buf_end && *p != '`') { + if (*p == '\\' && p + 1 < s->buf_end) p++; + if (*p == '$' && p + 1 < s->buf_end && p[1] == '{') { + /* template with expressions - not fully supported in AST yet */ + } + p++; + } + if (p < s->buf_end) p++; + s->token_val = TOK_TEMPLATE; + s->token_u.str.str = JS_NewStringLen (s->ctx, (const char *)(start + 1), p - start - 2); + } break; + case '\'': + case '\"': { + const uint8_t *start = p; + int quote = c; + p++; + while (p < s->buf_end && *p != quote) { + if (*p == '\\' && p + 1 < s->buf_end) p++; + p++; + } + if (p < s->buf_end) p++; + s->token_val = TOK_STRING; + s->token_u.str.str = JS_NewStringLen (s->ctx, (const char *)(start + 1), p - start - 2); + } break; + case '\r': + if (p[1] == '\n') p++; + /* fall through */ + case '\n': + p++; + s->got_lf = TRUE; + s->token_val = TOK_NEWLINE; + break; + case '\f': + case '\v': + case ' ': + case '\t': { + /* Collect consecutive whitespace (excluding newlines) */ + while (p < s->buf_end && (*p == ' ' || *p == '\t' || *p == '\f' || *p == '\v')) p++; + s->token_val = TOK_SPACE; + } break; + case '/': + if (p[1] == '*') { + /* Multi-line comment */ + p += 2; + while (p < s->buf_end) { + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p == '\n' || *p == '\r') s->got_lf = TRUE; + p++; + } + s->token_val = TOK_COMMENT; + } else if (p[1] == '/') { + /* Single-line comment */ + p += 2; + while (p < s->buf_end && *p != '\n' && *p != '\r') p++; + s->token_val = TOK_COMMENT; + } else if (p[1] == '=') { + p += 2; + s->token_val = TOK_DIV_ASSIGN; + } else { + p++; + s->token_val = c; + } + break; + case '\\': + goto def_token; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': case '$': { + const uint8_t *start = p; + ident_has_escape = FALSE; + p++; + while (p < s->buf_end) { + c = *p; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '$') { + p++; + } else if (c >= 0x80) { + p++; + while (p < s->buf_end && (*p & 0xc0) == 0x80) p++; + } else { + break; + } + } + size_t len = p - start; + s->token_u.ident.str = JS_NewStringLen (s->ctx, (const char *)start, len); + s->token_u.ident.has_escape = ident_has_escape; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + + /* Check for keywords */ + if (len == 2 && !memcmp (start, "if", 2)) s->token_val = TOK_IF; + else if (len == 2 && !memcmp (start, "in", 2)) s->token_val = TOK_IN; + else if (len == 2 && !memcmp (start, "do", 2)) s->token_val = TOK_DO; + else if (len == 2 && !memcmp (start, "go", 2)) s->token_val = TOK_GO; + 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; + else if (len == 5 && !memcmp (start, "throw", 5)) s->token_val = TOK_THROW; + 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; + } break; + case '.': + if (p[1] >= '0' && p[1] <= '9') { + goto tokenize_number; + } else { + goto def_token; + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + tokenize_number: { + const uint8_t *start = p; + BOOL is_float = FALSE; + if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + p += 2; + while (p < s->buf_end && ((c = *p, (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '_'))) p++; + } else if (p[0] == '0' && (p[1] == 'b' || p[1] == 'B')) { + p += 2; + while (p < s->buf_end && (*p == '0' || *p == '1' || *p == '_')) p++; + } else if (p[0] == '0' && (p[1] == 'o' || p[1] == 'O')) { + p += 2; + while (p < s->buf_end && (*p >= '0' && *p <= '7')) p++; + } else { + while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; + if (p < s->buf_end && *p == '.') { + is_float = TRUE; + p++; + while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; + } + if (p < s->buf_end && (*p == 'e' || *p == 'E')) { + is_float = TRUE; + p++; + if (p < s->buf_end && (*p == '+' || *p == '-')) p++; + while (p < s->buf_end && (*p >= '0' && *p <= '9')) p++; + } + } + (void)is_float; + s->token_val = TOK_NUMBER; + char *numstr = sys_malloc (p - start + 1); + memcpy (numstr, start, p - start); + numstr[p - start] = '\0'; + double val = strtod (numstr, NULL); + sys_free (numstr); + s->token_u.num.val = JS_NewFloat64 (s->ctx, val); + } break; + case '*': + if (p[1] == '=') { p += 2; s->token_val = TOK_MUL_ASSIGN; } + else if (p[1] == '*') { + if (p[2] == '=') { p += 3; s->token_val = TOK_POW_ASSIGN; } + else { p += 2; s->token_val = TOK_POW; } + } else { goto def_token; } + break; + case '%': + if (p[1] == '=') { p += 2; s->token_val = TOK_MOD_ASSIGN; } + else { goto def_token; } + break; + case '+': + if (p[1] == '=') { p += 2; s->token_val = TOK_PLUS_ASSIGN; } + else if (p[1] == '+') { p += 2; s->token_val = TOK_INC; } + else { goto def_token; } + break; + case '-': + if (p[1] == '=') { p += 2; s->token_val = TOK_MINUS_ASSIGN; } + else if (p[1] == '-') { p += 2; s->token_val = TOK_DEC; } + else { goto def_token; } + break; + case '<': + if (p[1] == '=') { p += 2; s->token_val = TOK_LTE; } + else if (p[1] == '<') { + if (p[2] == '=') { p += 3; s->token_val = TOK_SHL_ASSIGN; } + else { p += 2; s->token_val = TOK_SHL; } + } else { goto def_token; } + break; + case '>': + if (p[1] == '=') { p += 2; s->token_val = TOK_GTE; } + else if (p[1] == '>') { + if (p[2] == '>') { + if (p[3] == '=') { p += 4; s->token_val = TOK_SHR_ASSIGN; } + else { p += 3; s->token_val = TOK_SHR; } + } else if (p[2] == '=') { p += 3; s->token_val = TOK_SAR_ASSIGN; } + else { p += 2; s->token_val = TOK_SAR; } + } else { goto def_token; } + break; + case '=': + if (p[1] == '=') { + if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_EQ; } + else { p += 2; s->token_val = TOK_EQ; } + } else if (p[1] == '>') { p += 2; s->token_val = TOK_ARROW; } + else { goto def_token; } + break; + case '!': + if (p[1] == '=') { + if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_NEQ; } + else { p += 2; s->token_val = TOK_NEQ; } + } else { goto def_token; } + break; + case '&': + if (p[1] == '&') { + if (p[2] == '=') { p += 3; s->token_val = TOK_LAND_ASSIGN; } + else { p += 2; s->token_val = TOK_LAND; } + } else if (p[1] == '=') { p += 2; s->token_val = TOK_AND_ASSIGN; } + else { goto def_token; } + break; + case '|': + if (p[1] == '|') { + if (p[2] == '=') { p += 3; s->token_val = TOK_LOR_ASSIGN; } + else { p += 2; s->token_val = TOK_LOR; } + } else if (p[1] == '=') { p += 2; s->token_val = TOK_OR_ASSIGN; } + else { goto def_token; } + break; + case '^': + if (p[1] == '=') { p += 2; s->token_val = TOK_XOR_ASSIGN; } + else { goto def_token; } + break; + case '?': + if (p[1] == '?') { + if (p[2] == '=') { p += 3; s->token_val = TOK_DOUBLE_QUESTION_MARK_ASSIGN; } + else { p += 2; s->token_val = TOK_DOUBLE_QUESTION_MARK; } + } else if (p[1] == '.') { p += 2; s->token_val = TOK_QUESTION_MARK_DOT; } + else { goto def_token; } + break; + default: + def_token: + p++; + s->token_val = c; + break; + } + s->buf_ptr = p; + return 0; +} + static cJSON *ast_parse_primary (ASTParseState *s) { const uint8_t *start = s->token_ptr; cJSON *node = NULL; @@ -28264,12 +28683,19 @@ static cJSON *ast_parse_primary (ASTParseState *s) { } break; case TOK_IDENT: { - node = ast_node (s, "name", start); - const char *str = JS_ToCString (s->ctx, s->token_u.ident.str); - cJSON_AddStringToObject (node, "name", str ? str : ""); - JS_FreeCString (s->ctx, str); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); + /* Check for single-param arrow function: x => ... */ + const uint8_t *p = s->buf_ptr; + while (p < s->buf_end && (*p == ' ' || *p == '\t')) p++; + if (p + 1 < s->buf_end && p[0] == '=' && p[1] == '>') { + node = ast_parse_arrow_function (s); + } else { + node = ast_node (s, "name", start); + const char *str = JS_ToCString (s->ctx, s->token_u.ident.str); + cJSON_AddStringToObject (node, "name", str ? str : ""); + JS_FreeCString (s->ctx, str); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + } } break; case TOK_NULL: @@ -28301,7 +28727,7 @@ static cJSON *ast_parse_primary (ASTParseState *s) { cJSON *list = cJSON_AddArrayToObject (node, "list"); ast_next_token (s); while (s->token_val != ']' && s->token_val != TOK_EOF) { - cJSON *elem = ast_parse_expr (s); + cJSON *elem = ast_parse_assign_expr (s); if (elem) cJSON_AddItemToArray (list, elem); if (s->token_val == ',') ast_next_token (s); else break; @@ -28321,13 +28747,13 @@ static cJSON *ast_parse_primary (ASTParseState *s) { while (s->token_val != '}' && s->token_val != TOK_EOF) { cJSON *pair = cJSON_CreateObject (); /* property name */ - if (s->token_val == TOK_IDENT || s->token_val == TOK_STRING) { + if (s->token_val == TOK_IDENT || s->token_val == TOK_STRING || s->token_val == TOK_NUMBER) { cJSON *left = ast_parse_primary (s); cJSON_AddItemToObject (pair, "left", left); } else if (s->token_val == '[') { /* computed property */ ast_next_token (s); - cJSON *left = ast_parse_expr (s); + cJSON *left = ast_parse_assign_expr (s); cJSON_AddItemToObject (pair, "left", left); if (s->token_val == ']') { ast_next_token (s); @@ -28342,7 +28768,7 @@ static cJSON *ast_parse_primary (ASTParseState *s) { /* colon and value */ if (s->token_val == ':') { ast_next_token (s); - cJSON *right = ast_parse_expr (s); + cJSON *right = ast_parse_assign_expr (s); cJSON_AddItemToObject (pair, "right", right); } else { ast_error (s, s->token_ptr, "expected ':' after property name"); @@ -28360,14 +28786,19 @@ static cJSON *ast_parse_primary (ASTParseState *s) { } break; case '(': { - ast_next_token (s); - node = ast_parse_expr (s); - if (s->token_val == ')') { - ast_next_token (s); - } else if (s->token_val == TOK_EOF) { - ast_error (s, s->token_ptr, "unterminated parenthesized expression, expected ')'"); + /* Check for arrow function: () => ..., (a, b) => ... */ + if (ast_is_arrow_function (s)) { + node = ast_parse_arrow_function (s); } else { - ast_error (s, s->token_ptr, "expected ')' after expression"); + ast_next_token (s); + node = ast_parse_expr (s); + if (s->token_val == ')') { + ast_next_token (s); + } else if (s->token_val == TOK_EOF) { + ast_error (s, s->token_ptr, "unterminated parenthesized expression, expected ')'"); + } else { + ast_error (s, s->token_ptr, "expected ')' after expression"); + } } } break; @@ -28375,6 +28806,52 @@ static cJSON *ast_parse_primary (ASTParseState *s) { node = ast_parse_function_inner (s, TRUE); } break; + case '/': { + /* Regex literal - when / appears in primary position, it's a regex */ + node = ast_node (s, "regexp", start); + const uint8_t *p = s->token_ptr + 1; /* skip opening / */ + const uint8_t *pattern_start = p; + + /* Parse pattern - find closing / (not escaped) */ + while (p < s->buf_end && *p != '/') { + if (*p == '\\' && p + 1 < s->buf_end) { + p += 2; /* skip escape sequence */ + } else if (*p == '\n' || *p == '\r') { + ast_error (s, p, "unterminated regex literal"); + break; + } else { + p++; + } + } + size_t pattern_len = p - pattern_start; + if (p < s->buf_end) p++; /* skip closing / */ + + /* Parse flags */ + const uint8_t *flags_start = p; + while (p < s->buf_end && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))) { + p++; + } + size_t flags_len = p - flags_start; + + char *pattern = sys_malloc (pattern_len + 1); + memcpy (pattern, pattern_start, pattern_len); + pattern[pattern_len] = '\0'; + cJSON_AddStringToObject (node, "pattern", pattern); + sys_free (pattern); + + if (flags_len > 0) { + char *flags = sys_malloc (flags_len + 1); + memcpy (flags, flags_start, flags_len); + flags[flags_len] = '\0'; + cJSON_AddStringToObject (node, "flags", flags); + sys_free (flags); + } + + s->buf_ptr = p; + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + } break; + default: /* Report syntax error with token info */ if (s->token_val >= 32 && s->token_val < 127) { @@ -28413,7 +28890,7 @@ static cJSON *ast_parse_postfix (ASTParseState *s) { ast_next_token (s); cJSON *new_node = ast_node (s, "[", start); cJSON_AddItemToObject (new_node, "left", node); - cJSON *index = ast_parse_expr (s); + cJSON *index = ast_parse_assign_expr (s); cJSON_AddItemToObject (new_node, "right", index); if (s->token_val == ']') ast_next_token (s); ast_node_end (s, new_node, s->buf_ptr); @@ -28424,7 +28901,7 @@ static cJSON *ast_parse_postfix (ASTParseState *s) { cJSON_AddItemToObject (new_node, "expression", node); cJSON *list = cJSON_AddArrayToObject (new_node, "list"); while (s->token_val != ')' && s->token_val != TOK_EOF) { - cJSON *arg = ast_parse_expr (s); + cJSON *arg = ast_parse_assign_expr (s); if (arg) cJSON_AddItemToArray (list, arg); if (s->token_val == ',') ast_next_token (s); else break; @@ -28446,6 +28923,44 @@ static cJSON *ast_parse_postfix (ASTParseState *s) { ast_next_token (s); ast_node_end (s, new_node, s->buf_ptr); node = new_node; + } else if (s->token_val == TOK_QUESTION_MARK_DOT) { + ast_next_token (s); + if (s->token_val == '[') { + /* Optional bracket access: o?.["a"] */ + ast_next_token (s); + cJSON *new_node = ast_node (s, "?.[", start); + cJSON_AddItemToObject (new_node, "left", node); + cJSON *index = ast_parse_assign_expr (s); + cJSON_AddItemToObject (new_node, "right", index); + if (s->token_val == ']') ast_next_token (s); + ast_node_end (s, new_node, s->buf_ptr); + node = new_node; + } else if (s->token_val == '(') { + /* Optional call: o.f?.() */ + ast_next_token (s); + cJSON *new_node = ast_node (s, "?.(", start); + cJSON_AddItemToObject (new_node, "expression", node); + cJSON *list = cJSON_AddArrayToObject (new_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, new_node, s->buf_ptr); + node = new_node; + } else if (s->token_val == TOK_IDENT) { + /* Optional property access: o?.a */ + cJSON *new_node = ast_node (s, "?.", start); + cJSON_AddItemToObject (new_node, "left", node); + const char *str = JS_ToCString (s->ctx, s->token_u.ident.str); + cJSON_AddStringToObject (new_node, "right", str ? str : ""); + JS_FreeCString (s->ctx, str); + ast_next_token (s); + ast_node_end (s, new_node, s->buf_ptr); + node = new_node; + } } else { break; } @@ -28533,7 +29048,7 @@ static cJSON *ast_parse_unary (ASTParseState *s) { ast_next_token (s); cJSON *list = cJSON_AddArrayToObject (node, "list"); while (s->token_val != ')' && s->token_val != TOK_EOF) { - cJSON *arg = ast_parse_expr (s); + cJSON *arg = ast_parse_assign_expr (s); if (arg) cJSON_AddItemToArray (list, arg); if (s->token_val == ',') ast_next_token (s); else break; @@ -28681,10 +29196,31 @@ static cJSON *ast_parse_assign (ASTParseState *s) { return node; } -static cJSON *ast_parse_expr (ASTParseState *s) { +/* Parse assignment expression (excludes comma operator) */ +static cJSON *ast_parse_assign_expr (ASTParseState *s) { return ast_parse_assign (s); } +/* Parse full expression including comma operator */ +static cJSON *ast_parse_expr (ASTParseState *s) { + cJSON *left = ast_parse_assign (s); + if (!left) return NULL; + + /* Handle comma operator: (1, 2, 3) => 3 */ + while (s->token_val == ',') { + const uint8_t *start = s->token_ptr; + ast_next_token (s); + cJSON *right = ast_parse_assign (s); + + cJSON *node = ast_node (s, ",", start); + cJSON_AddItemToObject (node, "left", left); + cJSON_AddItemToObject (node, "right", right); + ast_node_end (s, node, s->buf_ptr); + left = node; + } + return left; +} + static cJSON *ast_parse_block_statements (ASTParseState *s) { cJSON *stmts = cJSON_CreateArray (); while (s->token_val != '}' && s->token_val != TOK_EOF) { @@ -28756,6 +29292,82 @@ static cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr) { return node; } +/* Parse arrow function: x => expr, (a, b) => expr, (x = 10) => expr, () => expr */ +static cJSON *ast_parse_arrow_function (ASTParseState *s) { + const uint8_t *start = s->token_ptr; + cJSON *node = ast_node (s, "function", start); + cJSON_AddBoolToObject (node, "arrow", 1); + + /* Parameters */ + cJSON *params = cJSON_AddArrayToObject (node, "list"); + + if (s->token_val == TOK_IDENT) { + /* Single parameter without parens: x => ... */ + cJSON *param = ast_node (s, "name", s->token_ptr); + const char *str = JS_ToCString (s->ctx, s->token_u.ident.str); + cJSON_AddStringToObject (param, "name", str ? str : ""); + JS_FreeCString (s->ctx, str); + ast_node_end (s, param, s->buf_ptr); + cJSON_AddItemToArray (params, param); + ast_next_token (s); + } else if (s->token_val == '(') { + /* Parenthesized parameters: () => ..., (a, b) => ..., (x = 10) => ... */ + ast_next_token (s); + while (s->token_val != ')' && s->token_val != TOK_EOF) { + if (s->token_val == TOK_IDENT) { + cJSON *param = ast_node (s, "name", s->token_ptr); + const char *str = JS_ToCString (s->ctx, s->token_u.ident.str); + cJSON_AddStringToObject (param, "name", str ? str : ""); + JS_FreeCString (s->ctx, str); + ast_node_end (s, param, s->buf_ptr); + ast_next_token (s); + + /* Check for default value */ + if (s->token_val == '=') { + ast_next_token (s); + cJSON *default_val = ast_parse_expr (s); + cJSON_AddItemToObject (param, "default", default_val); + } + cJSON_AddItemToArray (params, param); + } else { + ast_error (s, s->token_ptr, "expected parameter name"); + break; + } + if (s->token_val == ',') ast_next_token (s); + else break; + } + if (s->token_val == ')') ast_next_token (s); + } + + /* Arrow token */ + if (s->token_val != TOK_ARROW) { + ast_error (s, s->token_ptr, "expected '=>' in arrow function"); + } else { + ast_next_token (s); + } + + /* Body: either block or expression */ + if (s->token_val == '{') { + ast_next_token (s); + cJSON *stmts = ast_parse_block_statements (s); + cJSON_AddItemToObject (node, "statements", stmts); + if (s->token_val == '}') ast_next_token (s); + } else { + /* Expression body - wrap in implicit return */ + cJSON *stmts = cJSON_CreateArray (); + cJSON *ret = ast_node (s, "return", s->token_ptr); + cJSON *expr = ast_parse_expr (s); + cJSON_AddItemToObject (ret, "expression", expr); + ast_node_end (s, ret, s->buf_ptr); + cJSON_AddItemToArray (stmts, ret); + cJSON_AddItemToObject (node, "statements", stmts); + } + + cJSON_AddNumberToObject (node, "function_nr", s->function_nr++); + ast_node_end (s, node, s->buf_ptr); + return node; +} + static cJSON *ast_parse_statement (ASTParseState *s) { const uint8_t *start = s->token_ptr; cJSON *node = NULL; @@ -29074,6 +29686,33 @@ static cJSON *ast_parse_statement (ASTParseState *s) { ast_next_token (s); return NULL; + case TOK_IDENT: { + /* Check if this is a labeled statement: identifier: statement */ + const uint8_t *p = s->buf_ptr; + while (p < s->buf_end && (*p == ' ' || *p == '\t')) p++; + if (p < s->buf_end && *p == ':') { + /* Labeled statement */ + node = ast_node (s, "label", start); + const char *str = JS_ToCString (s->ctx, s->token_u.ident.str); + cJSON_AddStringToObject (node, "name", str ? str : ""); + JS_FreeCString (s->ctx, str); + ast_next_token (s); /* skip identifier */ + ast_next_token (s); /* skip colon */ + cJSON *stmt = ast_parse_statement (s); + cJSON_AddItemToObject (node, "statement", stmt); + ast_node_end (s, node, s->buf_ptr); + } else { + /* Expression statement */ + cJSON *expr = ast_parse_expr (s); + if (expr) { + node = ast_node (s, "call", start); + cJSON_AddItemToObject (node, "expression", expr); + ast_node_end (s, node, s->buf_ptr); + } + if (s->token_val == ';') ast_next_token (s); + } + } break; + default: { /* Expression statement */ cJSON *expr = ast_parse_expr (s); @@ -29147,6 +29786,102 @@ char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filena return json; } +/* Build a token object for the tokenizer output */ +static cJSON *build_token_object (ASTParseState *s) { + cJSON *tok = cJSON_CreateObject (); + const char *kind = ast_token_kind_str (s->token_val); + cJSON_AddStringToObject (tok, "kind", kind); + + /* Position info */ + int at = (int)(s->token_ptr - s->buf_start); + int from_row, from_col; + ast_get_line_col (s, s->token_ptr, &from_row, &from_col); + int to_row, to_col; + ast_get_line_col (s, s->buf_ptr, &to_row, &to_col); + + cJSON_AddNumberToObject (tok, "at", at); + cJSON_AddNumberToObject (tok, "from_row", from_row); + cJSON_AddNumberToObject (tok, "from_column", from_col); + cJSON_AddNumberToObject (tok, "to_row", to_row); + cJSON_AddNumberToObject (tok, "to_column", to_col); + + /* Value field based on token type */ + switch (s->token_val) { + case TOK_NUMBER: { + /* Store original source text as value */ + size_t len = s->buf_ptr - s->token_ptr; + char *text = sys_malloc (len + 1); + memcpy (text, s->token_ptr, len); + text[len] = '\0'; + cJSON_AddStringToObject (tok, "value", text); + sys_free (text); + /* Store parsed number */ + double d = JS_VALUE_GET_FLOAT64 (s->token_u.num.val); + if (JS_VALUE_GET_TAG (s->token_u.num.val) == JS_TAG_INT) { + d = JS_VALUE_GET_INT (s->token_u.num.val); + } + cJSON_AddNumberToObject (tok, "number", d); + } break; + case TOK_STRING: + case TOK_TEMPLATE: { + const char *str = JS_ToCString (s->ctx, s->token_u.str.str); + cJSON_AddStringToObject (tok, "value", str ? str : ""); + JS_FreeCString (s->ctx, str); + } break; + case TOK_IDENT: { + const char *str = JS_ToCString (s->ctx, s->token_u.ident.str); + cJSON_AddStringToObject (tok, "value", str ? str : ""); + JS_FreeCString (s->ctx, str); + } break; + case TOK_COMMENT: + case TOK_SPACE: + case TOK_NEWLINE: { + /* Store the raw source text */ + size_t len = s->buf_ptr - s->token_ptr; + char *text = sys_malloc (len + 1); + memcpy (text, s->token_ptr, len); + text[len] = '\0'; + cJSON_AddStringToObject (tok, "value", text); + sys_free (text); + } break; + default: + /* No value field for operators/punctuators/keywords */ + break; + } + + return tok; +} + +char *JS_Tokenize (JSContext *ctx, const char *source, size_t len, const char *filename) { + ASTParseState s; + memset (&s, 0, sizeof (s)); + + s.ctx = ctx; + s.filename = filename; + 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.errors = NULL; + s.has_error = 0; + + cJSON *root = cJSON_CreateObject (); + cJSON_AddStringToObject (root, "filename", filename); + cJSON *tokens = cJSON_AddArrayToObject (root, "tokens"); + + /* Tokenize all tokens including whitespace */ + while (1) { + tokenize_next (&s); + cJSON *tok = build_token_object (&s); + cJSON_AddItemToArray (tokens, tok); + if (s.token_val == TOK_EOF) break; + } + + char *json = cJSON_Print (root); + cJSON_Delete (root); + return json; +} + /* ============================================================ Register-Based Machine Code Generator ============================================================ */ diff --git a/source/quickjs.h b/source/quickjs.h index 80b621e0..d304e578 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -1222,6 +1222,10 @@ CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_le Returns malloc'd JSON string (caller must free), or NULL on error. */ char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filename); +/* Tokenize source code and return token array as JSON string. + Returns malloc'd JSON string (caller must free), or NULL on error. */ +char *JS_Tokenize (JSContext *ctx, const char *source, size_t len, const char *filename); + /* Generate register-based machine code from AST JSON. Returns malloc'd JSON string (caller must free), or NULL on error. */ char *JS_Mach (JSContext *ctx, const char *ast_json); diff --git a/vm_test/arrow_default.txt b/vm_test/arrow_default.txt new file mode 100644 index 00000000..9a90bc50 --- /dev/null +++ b/vm_test/arrow_default.txt @@ -0,0 +1 @@ +var f = (x = 10) => x; f() diff --git a/vm_test/arrow_no_param.txt b/vm_test/arrow_no_param.txt new file mode 100644 index 00000000..96309939 --- /dev/null +++ b/vm_test/arrow_no_param.txt @@ -0,0 +1 @@ +var f = () => 42; f() diff --git a/vm_test/assign_add.txt b/vm_test/assign_add.txt new file mode 100644 index 00000000..efcb4ba7 --- /dev/null +++ b/vm_test/assign_add.txt @@ -0,0 +1 @@ +var x = 5; x += 3; x diff --git a/vm_test/assign_and.txt b/vm_test/assign_and.txt new file mode 100644 index 00000000..836a9e39 --- /dev/null +++ b/vm_test/assign_and.txt @@ -0,0 +1 @@ +var x = 7; x &= 3; x diff --git a/vm_test/assign_div.txt b/vm_test/assign_div.txt new file mode 100644 index 00000000..e89b6a07 --- /dev/null +++ b/vm_test/assign_div.txt @@ -0,0 +1 @@ +var x = 6; x /= 2; x diff --git a/vm_test/assign_land.txt b/vm_test/assign_land.txt new file mode 100644 index 00000000..424711cc --- /dev/null +++ b/vm_test/assign_land.txt @@ -0,0 +1 @@ +var x = 5; x &&= 10; x diff --git a/vm_test/assign_lor.txt b/vm_test/assign_lor.txt new file mode 100644 index 00000000..165208bc --- /dev/null +++ b/vm_test/assign_lor.txt @@ -0,0 +1 @@ +var x = 0; x ||= 10; x diff --git a/vm_test/assign_mod.txt b/vm_test/assign_mod.txt new file mode 100644 index 00000000..60725e7f --- /dev/null +++ b/vm_test/assign_mod.txt @@ -0,0 +1 @@ +var x = 7; x %= 3; x diff --git a/vm_test/assign_mul.txt b/vm_test/assign_mul.txt new file mode 100644 index 00000000..9df1a1bf --- /dev/null +++ b/vm_test/assign_mul.txt @@ -0,0 +1 @@ +var x = 5; x *= 3; x diff --git a/vm_test/assign_nullish.txt b/vm_test/assign_nullish.txt new file mode 100644 index 00000000..db07bfb9 --- /dev/null +++ b/vm_test/assign_nullish.txt @@ -0,0 +1 @@ +var x = null; x ??= 10; x diff --git a/vm_test/assign_or.txt b/vm_test/assign_or.txt new file mode 100644 index 00000000..d9d6dbd5 --- /dev/null +++ b/vm_test/assign_or.txt @@ -0,0 +1 @@ +var x = 5; x |= 2; x diff --git a/vm_test/assign_power.txt b/vm_test/assign_power.txt new file mode 100644 index 00000000..e2345c16 --- /dev/null +++ b/vm_test/assign_power.txt @@ -0,0 +1 @@ +var x = 2; x **= 3; x diff --git a/vm_test/assign_shl.txt b/vm_test/assign_shl.txt new file mode 100644 index 00000000..dd4b03d4 --- /dev/null +++ b/vm_test/assign_shl.txt @@ -0,0 +1 @@ +var x = 2; x <<= 3; x diff --git a/vm_test/assign_shr.txt b/vm_test/assign_shr.txt new file mode 100644 index 00000000..bdf25edb --- /dev/null +++ b/vm_test/assign_shr.txt @@ -0,0 +1 @@ +var x = 8; x >>= 2; x diff --git a/vm_test/assign_shru.txt b/vm_test/assign_shru.txt new file mode 100644 index 00000000..bff9c9e7 --- /dev/null +++ b/vm_test/assign_shru.txt @@ -0,0 +1 @@ +var x = -8; x >>>= 2; x diff --git a/vm_test/assign_sub.txt b/vm_test/assign_sub.txt new file mode 100644 index 00000000..dd9b8dd5 --- /dev/null +++ b/vm_test/assign_sub.txt @@ -0,0 +1 @@ +var x = 5; x -= 3; x diff --git a/vm_test/assign_xor.txt b/vm_test/assign_xor.txt new file mode 100644 index 00000000..f1fb2261 --- /dev/null +++ b/vm_test/assign_xor.txt @@ -0,0 +1 @@ +var x = 5; x ^= 3; x diff --git a/vm_test/chained_assign.txt b/vm_test/chained_assign.txt new file mode 100644 index 00000000..9c06c45b --- /dev/null +++ b/vm_test/chained_assign.txt @@ -0,0 +1 @@ +var x, y; x = y = 5; x + y diff --git a/vm_test/comment_block.txt b/vm_test/comment_block.txt new file mode 100644 index 00000000..effe80e0 --- /dev/null +++ b/vm_test/comment_block.txt @@ -0,0 +1 @@ +/* comment */ 5 diff --git a/vm_test/comment_multi.txt b/vm_test/comment_multi.txt new file mode 100644 index 00000000..31f637fe --- /dev/null +++ b/vm_test/comment_multi.txt @@ -0,0 +1 @@ +1 /* a */ + /* b */ 2 diff --git a/vm_test/empty_statement.txt b/vm_test/empty_statement.txt new file mode 100644 index 00000000..5ab484f5 --- /dev/null +++ b/vm_test/empty_statement.txt @@ -0,0 +1 @@ +;;; 5 diff --git a/vm_test/func_expr.txt b/vm_test/func_expr.txt new file mode 100644 index 00000000..65485b1d --- /dev/null +++ b/vm_test/func_expr.txt @@ -0,0 +1 @@ +var f = function(x) { return x * 2 }; f(3) diff --git a/vm_test/func_iife.txt b/vm_test/func_iife.txt new file mode 100644 index 00000000..e03daadf --- /dev/null +++ b/vm_test/func_iife.txt @@ -0,0 +1 @@ +(function(x) { return x * 2 })(5) diff --git a/vm_test/func_recursive.txt b/vm_test/func_recursive.txt new file mode 100644 index 00000000..d79c9d26 --- /dev/null +++ b/vm_test/func_recursive.txt @@ -0,0 +1 @@ +function fac(n) { if (n <= 1) return 1; return n * fac(n - 1) }; fac(5) diff --git a/vm_test/label_break.txt b/vm_test/label_break.txt new file mode 100644 index 00000000..cddbae9b --- /dev/null +++ b/vm_test/label_break.txt @@ -0,0 +1 @@ +var x = 0; outer: { x = 1; break outer; x = 2 }; x diff --git a/vm_test/label_continue.txt b/vm_test/label_continue.txt new file mode 100644 index 00000000..e1173b80 --- /dev/null +++ b/vm_test/label_continue.txt @@ -0,0 +1 @@ +var s = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (j == 1) continue outer; s = s + 1 } }; s diff --git a/vm_test/multi_var.txt b/vm_test/multi_var.txt new file mode 100644 index 00000000..2a577920 --- /dev/null +++ b/vm_test/multi_var.txt @@ -0,0 +1 @@ +var x = 1, y = 2; x + y diff --git a/vm_test/nested_block.txt b/vm_test/nested_block.txt new file mode 100644 index 00000000..fef7625f --- /dev/null +++ b/vm_test/nested_block.txt @@ -0,0 +1 @@ +var x = 1; { var y = 2; { var z = 3; x = x + y + z } }; x diff --git a/vm_test/num_binary.txt b/vm_test/num_binary.txt new file mode 100644 index 00000000..22573f61 --- /dev/null +++ b/vm_test/num_binary.txt @@ -0,0 +1 @@ +0b1010 diff --git a/vm_test/num_exp.txt b/vm_test/num_exp.txt new file mode 100644 index 00000000..f1c9880c --- /dev/null +++ b/vm_test/num_exp.txt @@ -0,0 +1 @@ +1e3 diff --git a/vm_test/num_float.txt b/vm_test/num_float.txt new file mode 100644 index 00000000..6324d401 --- /dev/null +++ b/vm_test/num_float.txt @@ -0,0 +1 @@ +3.14 diff --git a/vm_test/num_hex.txt b/vm_test/num_hex.txt new file mode 100644 index 00000000..7a35b297 --- /dev/null +++ b/vm_test/num_hex.txt @@ -0,0 +1 @@ +0xff diff --git a/vm_test/num_octal.txt b/vm_test/num_octal.txt new file mode 100644 index 00000000..b99d581b --- /dev/null +++ b/vm_test/num_octal.txt @@ -0,0 +1 @@ +0o17 diff --git a/vm_test/num_underscore.txt b/vm_test/num_underscore.txt new file mode 100644 index 00000000..20d36fa5 --- /dev/null +++ b/vm_test/num_underscore.txt @@ -0,0 +1 @@ +1_000_000 diff --git a/vm_test/op_bitwise_not.txt b/vm_test/op_bitwise_not.txt new file mode 100644 index 00000000..072132fe --- /dev/null +++ b/vm_test/op_bitwise_not.txt @@ -0,0 +1 @@ +~5 diff --git a/vm_test/op_bitwise_or.txt b/vm_test/op_bitwise_or.txt new file mode 100644 index 00000000..c59fb1f2 --- /dev/null +++ b/vm_test/op_bitwise_or.txt @@ -0,0 +1 @@ +5 | 2 diff --git a/vm_test/op_bitwise_xor.txt b/vm_test/op_bitwise_xor.txt new file mode 100644 index 00000000..c04a0a77 --- /dev/null +++ b/vm_test/op_bitwise_xor.txt @@ -0,0 +1 @@ +5 ^ 3 diff --git a/vm_test/op_comma.txt b/vm_test/op_comma.txt new file mode 100644 index 00000000..caa68212 --- /dev/null +++ b/vm_test/op_comma.txt @@ -0,0 +1 @@ +(1, 2, 3) diff --git a/vm_test/op_compare_eq.txt b/vm_test/op_compare_eq.txt new file mode 100644 index 00000000..fdb417f1 --- /dev/null +++ b/vm_test/op_compare_eq.txt @@ -0,0 +1 @@ +3 == 3 diff --git a/vm_test/op_compare_gte.txt b/vm_test/op_compare_gte.txt new file mode 100644 index 00000000..3ee8c9c3 --- /dev/null +++ b/vm_test/op_compare_gte.txt @@ -0,0 +1 @@ +5 >= 5 diff --git a/vm_test/op_compare_lt.txt b/vm_test/op_compare_lt.txt new file mode 100644 index 00000000..0a724e35 --- /dev/null +++ b/vm_test/op_compare_lt.txt @@ -0,0 +1 @@ +3 < 5 diff --git a/vm_test/op_compare_lte.txt b/vm_test/op_compare_lte.txt new file mode 100644 index 00000000..de31adce --- /dev/null +++ b/vm_test/op_compare_lte.txt @@ -0,0 +1 @@ +3 <= 3 diff --git a/vm_test/op_compare_neq.txt b/vm_test/op_compare_neq.txt new file mode 100644 index 00000000..14025437 --- /dev/null +++ b/vm_test/op_compare_neq.txt @@ -0,0 +1 @@ +3 != 4 diff --git a/vm_test/op_decrement_post.txt b/vm_test/op_decrement_post.txt new file mode 100644 index 00000000..d7a4a711 --- /dev/null +++ b/vm_test/op_decrement_post.txt @@ -0,0 +1 @@ +var x = 5; x--; x diff --git a/vm_test/op_decrement_pre.txt b/vm_test/op_decrement_pre.txt new file mode 100644 index 00000000..96a884dc --- /dev/null +++ b/vm_test/op_decrement_pre.txt @@ -0,0 +1 @@ +var x = 5; --x diff --git a/vm_test/op_delete.txt b/vm_test/op_delete.txt new file mode 100644 index 00000000..578d1bd5 --- /dev/null +++ b/vm_test/op_delete.txt @@ -0,0 +1 @@ +var o = {x: 1}; delete o.x; o.x diff --git a/vm_test/op_in.txt b/vm_test/op_in.txt new file mode 100644 index 00000000..ab6ccdc5 --- /dev/null +++ b/vm_test/op_in.txt @@ -0,0 +1 @@ +var o = {x: 1}; "x" in o diff --git a/vm_test/op_increment_post.txt b/vm_test/op_increment_post.txt new file mode 100644 index 00000000..5e7db6a1 --- /dev/null +++ b/vm_test/op_increment_post.txt @@ -0,0 +1 @@ +var x = 5; x++; x diff --git a/vm_test/op_increment_pre.txt b/vm_test/op_increment_pre.txt new file mode 100644 index 00000000..8b484611 --- /dev/null +++ b/vm_test/op_increment_pre.txt @@ -0,0 +1 @@ +var x = 5; ++x diff --git a/vm_test/op_logical_not.txt b/vm_test/op_logical_not.txt new file mode 100644 index 00000000..8c9104a7 --- /dev/null +++ b/vm_test/op_logical_not.txt @@ -0,0 +1 @@ +!false diff --git a/vm_test/op_logical_or.txt b/vm_test/op_logical_or.txt new file mode 100644 index 00000000..84ffdd6e --- /dev/null +++ b/vm_test/op_logical_or.txt @@ -0,0 +1 @@ +false || true diff --git a/vm_test/op_nullish.txt b/vm_test/op_nullish.txt new file mode 100644 index 00000000..89287bf3 --- /dev/null +++ b/vm_test/op_nullish.txt @@ -0,0 +1 @@ +null ?? 5 diff --git a/vm_test/op_power.txt b/vm_test/op_power.txt new file mode 100644 index 00000000..faa4f383 --- /dev/null +++ b/vm_test/op_power.txt @@ -0,0 +1 @@ +2 ** 3 diff --git a/vm_test/op_shift_left.txt b/vm_test/op_shift_left.txt new file mode 100644 index 00000000..b96791ec --- /dev/null +++ b/vm_test/op_shift_left.txt @@ -0,0 +1 @@ +2 << 3 diff --git a/vm_test/op_shift_right.txt b/vm_test/op_shift_right.txt new file mode 100644 index 00000000..c5e98a1f --- /dev/null +++ b/vm_test/op_shift_right.txt @@ -0,0 +1 @@ +8 >> 2 diff --git a/vm_test/op_shift_right_unsigned.txt b/vm_test/op_shift_right_unsigned.txt new file mode 100644 index 00000000..3032ae48 --- /dev/null +++ b/vm_test/op_shift_right_unsigned.txt @@ -0,0 +1 @@ +-8 >>> 2 diff --git a/vm_test/op_typeof.txt b/vm_test/op_typeof.txt new file mode 100644 index 00000000..6eea91be --- /dev/null +++ b/vm_test/op_typeof.txt @@ -0,0 +1 @@ +typeof 5 diff --git a/vm_test/op_unary_minus.txt b/vm_test/op_unary_minus.txt new file mode 100644 index 00000000..d83d2bc1 --- /dev/null +++ b/vm_test/op_unary_minus.txt @@ -0,0 +1 @@ +-5 diff --git a/vm_test/op_unary_plus.txt b/vm_test/op_unary_plus.txt new file mode 100644 index 00000000..19977310 --- /dev/null +++ b/vm_test/op_unary_plus.txt @@ -0,0 +1 @@ ++"5" diff --git a/vm_test/op_void.txt b/vm_test/op_void.txt new file mode 100644 index 00000000..2701e32e --- /dev/null +++ b/vm_test/op_void.txt @@ -0,0 +1 @@ +void 0 diff --git a/vm_test/optional_bracket.txt b/vm_test/optional_bracket.txt new file mode 100644 index 00000000..e946f644 --- /dev/null +++ b/vm_test/optional_bracket.txt @@ -0,0 +1 @@ +var o = {a: 1}; o?.["a"] diff --git a/vm_test/optional_call.txt b/vm_test/optional_call.txt new file mode 100644 index 00000000..47dcf31b --- /dev/null +++ b/vm_test/optional_call.txt @@ -0,0 +1 @@ +var o = {f: () => 1}; o.f?.() diff --git a/vm_test/optional_null.txt b/vm_test/optional_null.txt new file mode 100644 index 00000000..bf3b5c38 --- /dev/null +++ b/vm_test/optional_null.txt @@ -0,0 +1 @@ +var o = null; o?.a diff --git a/vm_test/optional_prop.txt b/vm_test/optional_prop.txt new file mode 100644 index 00000000..2b00babb --- /dev/null +++ b/vm_test/optional_prop.txt @@ -0,0 +1 @@ +var o = {a: 1}; o?.a diff --git a/vm_test/paren_precedence.txt b/vm_test/paren_precedence.txt new file mode 100644 index 00000000..5dfe5729 --- /dev/null +++ b/vm_test/paren_precedence.txt @@ -0,0 +1 @@ +(1 + 2) * 3 diff --git a/vm_test/record_chain.txt b/vm_test/record_chain.txt new file mode 100644 index 00000000..81b5f9fd --- /dev/null +++ b/vm_test/record_chain.txt @@ -0,0 +1 @@ +var o = {a: {b: {c: 1}}}; o.a.b.c diff --git a/vm_test/record_nested.txt b/vm_test/record_nested.txt new file mode 100644 index 00000000..7369e5f4 --- /dev/null +++ b/vm_test/record_nested.txt @@ -0,0 +1 @@ +var o = {a: {b: 1}}; o.a.b diff --git a/vm_test/record_numeric_key.txt b/vm_test/record_numeric_key.txt new file mode 100644 index 00000000..742006ff --- /dev/null +++ b/vm_test/record_numeric_key.txt @@ -0,0 +1 @@ +var o = {1: "one"}; o[1] diff --git a/vm_test/string_escape.txt b/vm_test/string_escape.txt new file mode 100644 index 00000000..9839e480 --- /dev/null +++ b/vm_test/string_escape.txt @@ -0,0 +1 @@ +"hello\nworld" diff --git a/vm_test/string_unicode.txt b/vm_test/string_unicode.txt new file mode 100644 index 00000000..0d6d3bf7 --- /dev/null +++ b/vm_test/string_unicode.txt @@ -0,0 +1 @@ +"\u0041" diff --git a/vm_test/try_catch_finally.txt b/vm_test/try_catch_finally.txt new file mode 100644 index 00000000..87f944c7 --- /dev/null +++ b/vm_test/try_catch_finally.txt @@ -0,0 +1 @@ +var x = 1; try { throw 0 } catch(e) { x = 2 } finally { x = x + 1 }; x diff --git a/vm_test/try_finally.txt b/vm_test/try_finally.txt new file mode 100644 index 00000000..18e1abab --- /dev/null +++ b/vm_test/try_finally.txt @@ -0,0 +1 @@ +var x = 1; try { x = 2 } finally { x = 3 }; x