rm ?? and .?

This commit is contained in:
2026-02-07 22:09:40 -06:00
parent 8f9d026b9b
commit 243d92f7f3
2 changed files with 53 additions and 115 deletions

View File

@@ -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;
}

View File

@@ -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))