MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

optional chaining

+293 -16
+20 -14
src/ant.c
··· 57 57 TOK_YIELD, TOK_UNDEF, TOK_NULL, TOK_TRUE, TOK_FALSE, 58 58 TOK_DOT = 100, TOK_CALL, TOK_BRACKET, TOK_POSTINC, TOK_POSTDEC, TOK_NOT, TOK_TILDA, 59 59 TOK_TYPEOF, TOK_UPLUS, TOK_UMINUS, TOK_EXP, TOK_MUL, TOK_DIV, TOK_REM, 60 + TOK_OPTIONAL_CHAIN, 60 61 TOK_PLUS, TOK_MINUS, TOK_SHL, TOK_SHR, TOK_ZSHR, TOK_LT, TOK_LE, TOK_GT, 61 62 TOK_GE, TOK_EQ, TOK_NE, TOK_AND, TOK_XOR, TOK_OR, TOK_LAND, TOK_LOR, 62 63 TOK_COLON, TOK_Q, TOK_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN, ··· 620 621 #define LOOK(OFS, CH) js->toff + OFS < js->clen && buf[OFS] == CH 621 622 622 623 switch (buf[0]) { 623 - case '?': TOK(TOK_Q, 1); 624 + case '?': if (LOOK(1, '.')) TOK(TOK_OPTIONAL_CHAIN, 2); TOK(TOK_Q, 1); 624 625 case ':': TOK(TOK_COLON, 1); 625 626 case '(': TOK(TOK_LPAREN, 1); 626 627 case ')': TOK(TOK_RPAREN, 1); ··· 958 959 return mkval(T_PROP, off); 959 960 } 960 961 962 + static jsval_t do_optional_chain_op(struct js *js, jsval_t l, jsval_t r) { 963 + if (vtype(l) == T_NULL || vtype(l) == T_UNDEF) { 964 + return js_mkundef(); 965 + } 966 + return do_dot_op(js, l, r); 967 + } 968 + 961 969 static jsval_t js_call_params(struct js *js) { 962 970 jsoff_t pos = js->pos; 963 971 uint8_t flags = js->flags; ··· 1174 1182 } 1175 1183 if (vtype(l) == T_STR && vtype(r) == T_STR) return do_string_op(js, op, l, r); 1176 1184 if (is_unary(op) && vtype(r) != T_NUM) return js_mkerr(js, "type mismatch"); 1177 - if (!is_unary(op) && op != TOK_DOT && op != TOK_INSTANCEOF && (vtype(l) != T_NUM || vtype(r) != T_NUM)) return js_mkerr(js, "type mismatch"); 1185 + if (!is_unary(op) && op != TOK_DOT && op != TOK_OPTIONAL_CHAIN && op != TOK_INSTANCEOF && (vtype(l) != T_NUM || vtype(r) != T_NUM)) return js_mkerr(js, "type mismatch"); 1178 1186 1179 1187 double a = tod(l), b = tod(r); 1180 1188 switch (op) { ··· 1192 1200 case TOK_SHL: return tov((double)((long) a << (long) b)); 1193 1201 case TOK_SHR: return tov((double)((long) a >> (long) b)); 1194 1202 case TOK_DOT: return do_dot_op(js, l, r); 1203 + case TOK_OPTIONAL_CHAIN: return do_optional_chain_op(js, l, r); 1195 1204 case TOK_LT: return mkval(T_BOOL, a < b); 1196 1205 case TOK_LE: return mkval(T_BOOL, a <= b); 1197 1206 case TOK_GT: return mkval(T_BOOL, a > b); ··· 1402 1411 } 1403 1412 js->consumed = 1; 1404 1413 1405 - // Check for shorthand property syntax 1406 1414 if (id_len > 0 && (next(js) == TOK_COMMA || next(js) == TOK_RBRACE)) { 1407 - // Shorthand: { handler, params } means { handler: handler, params: params } 1408 1415 jsval_t val = lookup(js, js->code + id_off, id_len); 1409 1416 if (exe) { 1410 1417 if (is_err(val)) return val; ··· 1413 1420 if (is_err(res)) return res; 1414 1421 } 1415 1422 } else { 1416 - // Normal syntax: { key: value } 1417 1423 EXPECT(TOK_COLON, ); 1418 1424 jsval_t val = js_expr(js); 1419 1425 if (exe) { ··· 1818 1824 if (lookahead(js) == TOK_ARROW) return res; 1819 1825 res = lookup(js, &js->code[coderefoff(res)], codereflen(res)); 1820 1826 } 1821 - while (next(js) == TOK_LPAREN || next(js) == TOK_DOT || next(js) == TOK_LBRACKET) { 1822 - if (js->tok == TOK_DOT) { 1827 + while (next(js) == TOK_LPAREN || next(js) == TOK_DOT || next(js) == TOK_OPTIONAL_CHAIN || next(js) == TOK_LBRACKET) { 1828 + if (js->tok == TOK_DOT || js->tok == TOK_OPTIONAL_CHAIN) { 1829 + uint8_t op = js->tok; 1823 1830 js->consumed = 1; 1824 1831 if (vtype(res) != T_PROP) { 1825 1832 obj = res; 1826 1833 } else { 1827 1834 obj = resolveprop(js, res); 1828 1835 } 1829 - res = do_op(js, TOK_DOT, res, js_group(js)); 1836 + if (op == TOK_OPTIONAL_CHAIN && (vtype(obj) == T_NULL || vtype(obj) == T_UNDEF)) { 1837 + js_group(js); 1838 + res = js_mkundef(); 1839 + } else { 1840 + res = do_op(js, op, res, js_group(js)); 1841 + } 1830 1842 } else if (js->tok == TOK_LBRACKET) { 1831 1843 js->consumed = 1; 1832 1844 if (vtype(res) != T_PROP) { ··· 2140 2152 uint8_t exe = !(js->flags & F_NOEXEC); 2141 2153 js->consumed = 1; 2142 2154 for (;;) { 2143 - // Check for object destructuring pattern { a, b } or { handler, params } 2144 2155 if (next(js) == TOK_LBRACE) { 2145 2156 js->consumed = 1; 2146 2157 2147 - // Parse destructuring pattern 2148 2158 typedef struct { jsoff_t name_off; jsoff_t name_len; } PropName; 2149 2159 PropName props[32]; 2150 2160 int prop_count = 0; ··· 2156 2166 prop_count++; 2157 2167 js->consumed = 1; 2158 2168 2159 - // Skip colon and alias if present (e.g., { handler: h }) 2160 2169 if (next(js) == TOK_COLON) { 2161 2170 js->consumed = 1; 2162 2171 EXPECT(TOK_IDENTIFIER, ); ··· 2169 2178 2170 2179 EXPECT(TOK_RBRACE, ); 2171 2180 2172 - // Expect assignment 2173 2181 jsval_t v = js_mkundef(); 2174 2182 if (next(js) == TOK_ASSIGN) { 2175 2183 js->consumed = 1; ··· 2179 2187 return js_mkerr(js, "destructuring requires assignment"); 2180 2188 } 2181 2189 2182 - // Extract properties from the object 2183 2190 if (exe) { 2184 2191 jsval_t obj = resolveprop(js, v); 2185 2192 if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR) { ··· 2206 2213 } 2207 2214 } 2208 2215 } else { 2209 - // Regular identifier declaration 2210 2216 EXPECT(TOK_IDENTIFIER, ); 2211 2217 js->consumed = 0; 2212 2218 jsoff_t noff = js->toff, nlen = js->tlen;
+248
tests/object_in_if.cjs
··· 1 + // Test objects in if statements 2 + // Tests property access and truthiness evaluation 3 + 4 + Ant.println("=== Object in If Statements Test ===\n"); 5 + 6 + // Test 1: Basic object property truthiness 7 + Ant.println("Test 1: Object property truthiness"); 8 + let thing = { 9 + hasThing: true, 10 + hasOther: false, 11 + count: 5, 12 + name: "test" 13 + }; 14 + 15 + if (thing.hasThing) { 16 + Ant.println(" thing.hasThing is truthy: PASS"); 17 + } 18 + 19 + if (!thing.hasOther) { 20 + Ant.println(" !thing.hasOther is falsy: PASS"); 21 + } 22 + 23 + if (thing.count) { 24 + Ant.println(" thing.count (5) is truthy: PASS"); 25 + } 26 + 27 + if (thing.name) { 28 + Ant.println(" thing.name ('test') is truthy: PASS"); 29 + } 30 + 31 + // Test 2: Undefined and null properties 32 + Ant.println("\nTest 2: Undefined and null properties"); 33 + let obj = { 34 + defined: "value", 35 + nullValue: null, 36 + undefinedValue: undefined, 37 + zeroValue: 0, 38 + emptyString: "" 39 + }; 40 + 41 + if (!obj.nonExistent) { 42 + Ant.println(" !obj.nonExistent (undefined): PASS"); 43 + } 44 + 45 + if (!obj.nullValue) { 46 + Ant.println(" !obj.nullValue (null): PASS"); 47 + } 48 + 49 + if (!obj.undefinedValue) { 50 + Ant.println(" !obj.undefinedValue (undefined): PASS"); 51 + } 52 + 53 + if (!obj.zeroValue) { 54 + Ant.println(" !obj.zeroValue (0): PASS"); 55 + } 56 + 57 + if (!obj.emptyString) { 58 + Ant.println(" !obj.emptyString (''): PASS"); 59 + } 60 + 61 + if (obj.defined) { 62 + Ant.println(" obj.defined ('value'): PASS"); 63 + } 64 + 65 + // Test 3: Nested object properties 66 + Ant.println("\nTest 3: Nested object properties"); 67 + let config = { 68 + settings: { 69 + enabled: true, 70 + disabled: false, 71 + nested: { 72 + deep: "value" 73 + } 74 + } 75 + }; 76 + 77 + if (config.settings) { 78 + Ant.println(" config.settings exists: PASS"); 79 + } 80 + 81 + if (config.settings.enabled) { 82 + Ant.println(" config.settings.enabled is true: PASS"); 83 + } 84 + 85 + if (!config.settings.disabled) { 86 + Ant.println(" !config.settings.disabled is false: PASS"); 87 + } 88 + 89 + if (config.settings.nested) { 90 + Ant.println(" config.settings.nested exists: PASS"); 91 + } 92 + 93 + if (config.settings.nested.deep) { 94 + Ant.println(" config.settings.nested.deep has value: PASS"); 95 + } 96 + 97 + // Test 4: Function properties 98 + Ant.println("\nTest 4: Function properties"); 99 + let api = { 100 + hasMethod: function() { 101 + return "called"; 102 + }, 103 + noMethod: null 104 + }; 105 + 106 + if (api.hasMethod) { 107 + Ant.println(" api.hasMethod exists:", api.hasMethod()); 108 + } 109 + 110 + if (!api.noMethod) { 111 + Ant.println(" !api.noMethod is null: PASS"); 112 + } 113 + 114 + // Test 5: Array properties 115 + Ant.println("\nTest 5: Array properties"); 116 + let data = { 117 + items: [1, 2, 3], 118 + emptyItems: [], 119 + noItems: null 120 + }; 121 + 122 + if (data.items) { 123 + Ant.println(" data.items exists, length:", data.items.length); 124 + } 125 + 126 + if (data.emptyItems) { 127 + Ant.println(" data.emptyItems exists but empty, length:", data.emptyItems.length); 128 + } 129 + 130 + if (!data.noItems) { 131 + Ant.println(" !data.noItems is null: PASS"); 132 + } 133 + 134 + // Test 6: Conditional checks with property access 135 + Ant.println("\nTest 6: Conditional property checks"); 136 + let user = { 137 + name: "John", 138 + age: 30, 139 + active: true 140 + }; 141 + 142 + if (user.name && user.age) { 143 + Ant.println(" user.name && user.age both exist: PASS"); 144 + } 145 + 146 + if (user.active && user.name) { 147 + Ant.println(" user.active && user.name both truthy: PASS"); 148 + } 149 + 150 + if (!user.deleted || user.active) { 151 + Ant.println(" !user.deleted || user.active: PASS"); 152 + } 153 + 154 + // Test 7: Return based on object property 155 + Ant.println("\nTest 7: Return based on property"); 156 + function checkUser(user) { 157 + if (!user) return { error: "no user" }; 158 + if (!user.name) return { error: "no name" }; 159 + return { success: true, name: user.name }; 160 + } 161 + 162 + Ant.println(" With null:", checkUser(null)); 163 + Ant.println(" With no name:", checkUser({ age: 25 })); 164 + Ant.println(" With name:", checkUser({ name: "Alice" })); 165 + 166 + // Test 8: Object itself as condition 167 + Ant.println("\nTest 8: Object as condition"); 168 + let obj1 = { value: 1 }; 169 + let obj2 = null; 170 + let obj3 = undefined; 171 + 172 + if (obj1) { 173 + Ant.println(" obj1 (object) is truthy: PASS"); 174 + } 175 + 176 + if (!obj2) { 177 + Ant.println(" !obj2 (null) is falsy: PASS"); 178 + } 179 + 180 + if (!obj3) { 181 + Ant.println(" !obj3 (undefined) is falsy: PASS"); 182 + } 183 + 184 + // Test 9: Boolean properties in complex conditions 185 + Ant.println("\nTest 9: Complex boolean property checks"); 186 + let feature = { 187 + enabled: true, 188 + experimental: false, 189 + beta: true, 190 + stable: false 191 + }; 192 + 193 + if (feature.enabled && feature.beta) { 194 + Ant.println(" enabled && beta: PASS"); 195 + } 196 + 197 + if (feature.enabled && !feature.stable) { 198 + Ant.println(" enabled && !stable: PASS"); 199 + } 200 + 201 + if (!feature.experimental && !feature.stable) { 202 + Ant.println(" !experimental && !stable: PASS"); 203 + } 204 + 205 + // Test 10: Property chain with guard 206 + Ant.println("\nTest 10: Safe property access"); 207 + function getValue(obj) { 208 + if (!obj) return "no object"; 209 + if (!obj.data) return "no data"; 210 + if (!obj.data.value) return "no value"; 211 + return obj.data.value; 212 + } 213 + 214 + Ant.println(" With null:", getValue(null)); 215 + Ant.println(" With no data:", getValue({})); 216 + Ant.println(" With no value:", getValue({ data: {} })); 217 + Ant.println(" With value:", getValue({ data: { value: "found" } })); 218 + 219 + // Test 11: Optional chaining with undefined/null values 220 + Ant.println("\nTest 11: Optional chaining (?.)"); 221 + const value = undefined; 222 + const nullValue = null; 223 + const obj11 = { nested: { deep: "value" } }; 224 + 225 + Ant.println(" value?.thing (undefined):", value?.thing); 226 + Ant.println(" nullValue?.thing (null):", nullValue?.thing); 227 + Ant.println(" obj11?.nested?.deep:", obj11?.nested?.deep); 228 + Ant.println(" obj11?.missing?.deep:", obj11?.missing?.deep); 229 + 230 + if (value?.thing) { 231 + Ant.println(" FAIL: value?.thing should be undefined"); 232 + } else { 233 + Ant.println(" value?.thing is falsy: PASS"); 234 + } 235 + 236 + if (!nullValue?.thing) { 237 + Ant.println(" !nullValue?.thing is falsy: PASS"); 238 + } 239 + 240 + if (obj11?.nested?.deep) { 241 + Ant.println(" obj11?.nested?.deep exists: PASS"); 242 + } 243 + 244 + if (!obj11?.missing?.deep) { 245 + Ant.println(" !obj11?.missing?.deep is falsy: PASS"); 246 + } 247 + 248 + Ant.println("\n=== All tests completed ===");
+2 -2
tests/server/server.cjs
··· 49 49 async function handleRequest(req, res) { 50 50 Ant.println('request:', req.method, req.uri); 51 51 52 - const { handler, params } = router.lookup(req.uri); 53 - if (handler) return handler({ req, res, params }); 52 + const result = router.lookup(req.uri); 53 + if (result?.handler) return result.handler(result.params); 54 54 55 55 return res.body('not found: ' + req.uri, 404); 56 56 }
+3
tests/test_dot.cjs
··· 1 + const obj = { thing: 42 }; 2 + const result = obj.thing; 3 + Ant.println(result);
+20
tests/test_optional_chain.cjs
··· 1 + // Simple test for optional chaining 2 + 3 + Ant.println("Test 1: Basic optional chaining"); 4 + const value = undefined; 5 + const result = value?.thing; 6 + Ant.println("value?.thing:", result); 7 + 8 + Ant.println("\nTest 2: With object"); 9 + const obj = { nested: { deep: "value" } }; 10 + const result2 = obj?.nested?.deep; 11 + Ant.println("obj?.nested?.deep:", result2); 12 + 13 + Ant.println("\nTest 3: In if statement"); 14 + if (value?.thing) { 15 + Ant.println("FAIL"); 16 + } else { 17 + Ant.println("PASS: value?.thing is falsy"); 18 + } 19 + 20 + Ant.println("\nAll tests done");