diff --git a/source/quickjs.c b/source/quickjs.c index 4e495a9f..d5c10b2c 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -8616,7 +8616,6 @@ enum { TOK_POW_ASSIGN, TOK_LAND_ASSIGN, TOK_LOR_ASSIGN, - TOK_DOUBLE_QUESTION_MARK_ASSIGN, TOK_DEC, TOK_INC, TOK_SHL, @@ -8634,8 +8633,6 @@ enum { TOK_LOR, TOK_POW, TOK_ARROW, - TOK_DOUBLE_QUESTION_MARK, - TOK_QUESTION_MARK_DOT, TOK_ERROR, TOK_PRIVATE_NAME, TOK_EOF, @@ -8727,7 +8724,6 @@ static const char *ast_token_kind_str(int token_val) { 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 "<<"; @@ -8745,8 +8741,6 @@ static const char *ast_token_kind_str(int token_val) { 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"; @@ -10060,7 +10054,7 @@ static JSValue parse_ident (JSParseState *s, const uint8_t **pp, BOOL *pident_ha } else if (c >= 128) { c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p1); } - if (!lre_js_is_ident_next (c)) break; + if (!lre_js_is_ident_next (c) && c != '?' && c != '!') break; p = p1; if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) { @@ -10451,21 +10445,7 @@ redo: } 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] >= '0' && p[2] <= '9')) { - p += 2; - s->token.val = TOK_QUESTION_MARK_DOT; - } else { - goto def_token; - } - break; + goto def_token; default: if (c >= 128) { /* unicode value */ @@ -12558,22 +12538,7 @@ static __exception int js_parse_postfix_expr (JSParseState *s, JSFunctionDef *fd = s->cur_func; BOOL has_optional_chain = FALSE; - if (s->token.val == TOK_QUESTION_MARK_DOT) { - if ((parse_flags & PF_POSTFIX_CALL) == 0) - return js_parse_error ( - s, "new keyword cannot be used with an optional chain"); - op_token_ptr = s->token.ptr; - /* optional chaining */ - if (next_token (s)) return -1; - has_optional_chain = TRUE; - if (s->token.val == '(' && accept_lparen) { - goto parse_func_call; - } else if (s->token.val == '[') { - goto parse_array_access; - } else { - goto parse_property; - } - } else if (s->token.val == '(' && accept_lparen) { + if (s->token.val == '(' && accept_lparen) { int opcode, arg_count, drop_count; /* function call */ @@ -13040,8 +13005,6 @@ static __exception int js_parse_logical_and_or (JSParseState *s, int op, int par if (js_parse_logical_and_or (s, TOK_LAND, parse_flags)) return -1; } if (s->token.val != op) { - if (s->token.val == TOK_DOUBLE_QUESTION_MARK) - return js_parse_error (s, "cannot mix ?? with && or ||"); break; } } @@ -13053,25 +13016,7 @@ static __exception int js_parse_logical_and_or (JSParseState *s, int op, int par static __exception int js_parse_coalesce_expr (JSParseState *s, int parse_flags) { - int label1; - - if (js_parse_logical_and_or (s, TOK_LOR, parse_flags)) return -1; - if (s->token.val == TOK_DOUBLE_QUESTION_MARK) { - label1 = new_label (s); - for (;;) { - if (next_token (s)) return -1; - - emit_op (s, OP_dup); - emit_op (s, OP_is_null); - emit_goto (s, OP_if_false, label1); - emit_op (s, OP_drop); - - if (js_parse_expr_binary (s, 8, parse_flags)) return -1; - if (s->token.val != TOK_DOUBLE_QUESTION_MARK) break; - } - emit_label (s, label1); - } - return 0; + return js_parse_logical_and_or (s, TOK_LOR, parse_flags); } /* allowed parse_flags: PF_IN_ACCEPTED */ @@ -13159,7 +13104,7 @@ static __exception int js_parse_assign_expr2 (JSParseState *s, emit_op (s, op); } put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); - } else if (op >= TOK_LAND_ASSIGN && op <= TOK_DOUBLE_QUESTION_MARK_ASSIGN) { + } else if (op >= TOK_LAND_ASSIGN && op <= TOK_LOR_ASSIGN) { int label, label1, depth_lvalue, label2; if (next_token (s)) return -1; @@ -13168,7 +13113,6 @@ static __exception int js_parse_assign_expr2 (JSParseState *s, return -1; emit_op (s, OP_dup); - if (op == TOK_DOUBLE_QUESTION_MARK_ASSIGN) emit_op (s, OP_is_null); label1 = emit_goto (s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false, -1); emit_op (s, OP_drop); @@ -28660,7 +28604,8 @@ redo: while (p < s->buf_end) { c = *p; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || c == '_' || c == '$') { + (c >= '0' && c <= '9') || c == '_' || c == '$' || + c == '?' || c == '!') { p++; } else if (c >= 0x80) { /* unicode identifier */ @@ -28827,12 +28772,7 @@ redo: 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; + goto def_token; default: def_token: p++; @@ -28987,7 +28927,8 @@ static int tokenize_next (ASTParseState *s) { while (p < s->buf_end) { c = *p; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || c == '_' || c == '$') { + (c >= '0' && c <= '9') || c == '_' || c == '$' || + c == '?' || c == '!') { p++; } else if (c >= 0x80) { p++; @@ -29152,12 +29093,7 @@ static int tokenize_next (ASTParseState *s) { 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; + goto def_token; default: def_token: p++; @@ -29590,44 +29526,6 @@ 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); - else ast_error (s, s->token_ptr, "expected ']'"); - 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); - else ast_error (s, s->token_ptr, "unterminated argument list, expected ')'"); - 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); - cjson_add_strn (new_node, "right", s->token_u.ident.str, s->token_u.ident.len); - ast_next_token (s); - ast_node_end (s, new_node, s->buf_ptr); - node = new_node; - } } else { break; } @@ -29733,7 +29631,6 @@ static const ASTBinOp ast_binops[] = { { '|', "|", 6 }, { TOK_LAND, "&&", 5 }, { TOK_LOR, "||", 4 }, - { TOK_DOUBLE_QUESTION_MARK, "??", 3 }, { 0, NULL, 0 } }; @@ -29813,7 +29710,6 @@ static cJSON *ast_parse_assign (ASTParseState *s) { case TOK_POW_ASSIGN: kind = "**="; break; case TOK_LAND_ASSIGN: kind = "&&="; break; case TOK_LOR_ASSIGN: kind = "||="; break; - case TOK_DOUBLE_QUESTION_MARK_ASSIGN: kind = "??="; break; default: return left; } diff --git a/syntax_suite.ce b/syntax_suite.ce index 409779d8..2eefab84 100644 --- a/syntax_suite.ce +++ b/syntax_suite.ce @@ -604,6 +604,48 @@ run("self-referencing object", function() { assert_eq(o.self.self.name, "root", "cycle access") }) +// === IDENTIFIER ? AND ! === + +run("question mark in identifier", function() { + var nil? = (x) => x == null + assert_eq(nil?(null), true, "nil? null") + assert_eq(nil?(42), false, "nil? 42") +}) + +run("bang in identifier", function() { + var set! = (x) => x + 1 + assert_eq(set!(5), 6, "set! call") +}) + +run("question mark mid identifier", function() { + var is?valid = (x) => x > 0 + assert_eq(is?valid(3), true, "is?valid true") + assert_eq(is?valid(-1), false, "is?valid false") +}) + +run("bang mid identifier", function() { + var do!stuff = () => 42 + assert_eq(do!stuff(), 42, "do!stuff call") +}) + +run("ternary after question ident", function() { + var nil? = (x) => x == null + var a = nil?(null) ? "yes" : "no" + assert_eq(a, "yes", "ternary true branch") + var b = nil?(42) ? "yes" : "no" + assert_eq(b, "no", "ternary false branch") +}) + +run("bang not confused with logical not", function() { + assert_eq(!true, false, "logical not true") + assert_eq(!false, true, "logical not false") +}) + +run("inequality not confused with bang ident", function() { + assert_eq(1 != 2, true, "inequality true") + assert_eq(1 != 1, false, "inequality false") +}) + // === SUMMARY === print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))