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.

tagged literals

+507 -3
+233 -2
src/ant.c
··· 1138 1138 jsval_t res = js_eval(js, &fn[fnpos], n); 1139 1139 if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); 1140 1140 js->scope = saved_scope; 1141 + 1142 + return res; 1143 + } 1144 + 1145 + static jsval_t call_js_with_args(struct js *js, jsval_t func, jsval_t *args, int nargs) { 1146 + if (vtype(func) == T_CFUNC) { 1147 + jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_t *, int)) vdata(func); 1148 + return fn(js, args, nargs); 1149 + } 1150 + 1151 + if (vtype(func) != T_FUNC) return js_mkerr(js, "not a function"); 1152 + jsval_t func_obj = mkval(T_OBJ, vdata(func)); 1153 + 1154 + jsoff_t native_off = lkp(js, func_obj, "__native_func", 13); 1155 + if (native_off != 0) { 1156 + jsval_t native_val = resolveprop(js, mkval(T_PROP, native_off)); 1157 + if (vtype(native_val) == T_CFUNC) { 1158 + jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_t *, int)) vdata(native_val); 1159 + return fn(js, args, nargs); 1160 + } 1161 + } 1162 + 1163 + jsoff_t code_off = lkp(js, func_obj, "__code", 6); 1164 + if (code_off == 0) return js_mkerr(js, "function has no code"); 1165 + jsval_t code_val = resolveprop(js, mkval(T_PROP, code_off)); 1166 + if (vtype(code_val) != T_STR) return js_mkerr(js, "function code not string"); 1167 + 1168 + jsoff_t fnlen, fnoff = vstr(js, code_val, &fnlen); 1169 + const char *fn = (const char *) (&js->mem[fnoff]); 1170 + 1171 + jsval_t closure_scope = js_mkundef(); 1172 + jsoff_t scope_off = lkp(js, func_obj, "__scope", 7); 1173 + if (scope_off != 0) { 1174 + closure_scope = resolveprop(js, mkval(T_PROP, scope_off)); 1175 + } 1176 + 1177 + jsoff_t parent_scope_offset; 1178 + if (vtype(closure_scope) == T_OBJ) { 1179 + parent_scope_offset = (jsoff_t) vdata(closure_scope); 1180 + } else { 1181 + parent_scope_offset = (jsoff_t) vdata(js->scope); 1182 + } 1183 + 1184 + jsval_t saved_scope = js->scope; 1185 + jsval_t function_scope = mkobj(js, parent_scope_offset); 1186 + js->scope = function_scope; 1187 + 1188 + jsoff_t fnpos = 1; 1189 + int arg_idx = 0; 1190 + bool has_rest = false; 1191 + jsoff_t rest_param_start = 0, rest_param_len = 0; 1192 + 1193 + while (fnpos < fnlen) { 1194 + fnpos = skiptonext(fn, fnlen, fnpos); 1195 + if (fnpos < fnlen && fn[fnpos] == ')') break; 1196 + 1197 + bool is_rest = false; 1198 + if (fnpos + 3 < fnlen && fn[fnpos] == '.' && fn[fnpos + 1] == '.' && fn[fnpos + 2] == '.') { 1199 + is_rest = true; 1200 + has_rest = true; 1201 + fnpos += 3; 1202 + fnpos = skiptonext(fn, fnlen, fnpos); 1203 + } 1204 + 1205 + jsoff_t identlen = 0; 1206 + uint8_t tok = parseident(&fn[fnpos], fnlen - fnpos, &identlen); 1207 + if (tok != TOK_IDENTIFIER) break; 1208 + 1209 + if (is_rest) { 1210 + rest_param_start = fnpos; 1211 + rest_param_len = identlen; 1212 + fnpos = skiptonext(fn, fnlen, fnpos + identlen); 1213 + break; 1214 + } 1215 + 1216 + jsval_t v = arg_idx < nargs ? args[arg_idx] : js_mkundef(); 1217 + setprop(js, function_scope, js_mkstr(js, &fn[fnpos], identlen), v); 1218 + arg_idx++; 1219 + fnpos = skiptonext(fn, fnlen, fnpos + identlen); 1220 + if (fnpos < fnlen && fn[fnpos] == ',') fnpos++; 1221 + } 1222 + 1223 + if (has_rest && rest_param_len > 0) { 1224 + jsval_t rest_array = mkarr(js); 1225 + if (!is_err(rest_array)) { 1226 + jsoff_t idx = 0; 1227 + while (arg_idx < nargs) { 1228 + char idxstr[16]; 1229 + snprintf(idxstr, sizeof(idxstr), "%u", (unsigned) idx); 1230 + jsval_t key = js_mkstr(js, idxstr, strlen(idxstr)); 1231 + setprop(js, rest_array, key, args[arg_idx]); 1232 + idx++; 1233 + arg_idx++; 1234 + } 1235 + jsval_t len_key = js_mkstr(js, "length", 6); 1236 + setprop(js, rest_array, len_key, tov((double) idx)); 1237 + rest_array = mkval(T_ARR, vdata(rest_array)); 1238 + setprop(js, function_scope, js_mkstr(js, &fn[rest_param_start], rest_param_len), rest_array); 1239 + } 1240 + } 1241 + 1242 + if (fnpos < fnlen && fn[fnpos] == ')') fnpos++; 1243 + fnpos = skiptonext(fn, fnlen, fnpos); 1244 + if (fnpos < fnlen && fn[fnpos] == '{') fnpos++; 1245 + size_t body_len = fnlen - fnpos - 1; 1246 + 1247 + jsval_t saved_this = js->this_val; 1248 + js->this_val = js_glob(js); 1249 + js->flags = F_CALL; 1250 + 1251 + jsval_t res = js_eval(js, &fn[fnpos], body_len); 1252 + if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); 1253 + 1254 + js->this_val = saved_this; 1255 + js->scope = saved_scope; 1256 + 1141 1257 return res; 1142 1258 } 1143 1259 ··· 1403 1519 pos += part_len; 1404 1520 } 1405 1521 } 1522 + 1523 + return result; 1524 + } 1525 + 1526 + static jsval_t js_tagged_template(struct js *js, jsval_t tag_func) { 1527 + if (js->flags & F_NOEXEC) return js_mkundef(); 1528 + 1529 + const char *saved_code = js->code; 1530 + jsoff_t saved_clen = js->clen, saved_pos = js->pos; 1531 + uint8_t saved_tok = js->tok; 1532 + 1533 + uint8_t *in = (uint8_t *) &js->code[js->toff]; 1534 + size_t template_len = js->tlen; 1535 + jsval_t strings[64], values[64]; 1536 + int string_count = 0, value_count = 0; 1537 + size_t n = 1; 1538 + 1539 + while (n < template_len - 1) { 1540 + size_t part_start = n; 1541 + 1542 + while (n < template_len - 1 && !(in[n] == '$' && n + 1 < template_len - 1 && in[n + 1] == '{')) { 1543 + if (in[n] == '\\' && n + 1 < template_len - 1) n += 2; 1544 + else n++; 1545 + } 1546 + 1547 + uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; 1548 + size_t out_len = 0; 1549 + if (js->brk + sizeof(jsoff_t) + (n - part_start) > js->size) return js_mkerr(js, "oom"); 1550 + 1551 + for (size_t i = part_start; i < n; i++) { 1552 + if (in[i] == '\\' && i + 1 < n) { 1553 + i++; 1554 + if (in[i] == 'n') out[out_len++] = '\n'; 1555 + else if (in[i] == 't') out[out_len++] = '\t'; 1556 + else if (in[i] == 'r') out[out_len++] = '\r'; 1557 + else if (in[i] == '\\') out[out_len++] = '\\'; 1558 + else if (in[i] == '`') out[out_len++] = '`'; 1559 + else out[out_len++] = in[i]; 1560 + } else { 1561 + out[out_len++] = in[i]; 1562 + } 1563 + } 1564 + strings[string_count++] = js_mkstr(js, NULL, out_len); 1565 + 1566 + if (n >= template_len - 1 || in[n] != '$') break; 1567 + 1568 + n += 2; 1569 + int brace_count = 1; 1570 + size_t expr_start = n; 1571 + while (n < template_len - 1 && brace_count > 0) { 1572 + if (in[n] == '{') brace_count++; 1573 + else if (in[n] == '}') brace_count--; 1574 + if (brace_count > 0) n++; 1575 + } 1576 + if (brace_count != 0) return js_mkerr(js, "unclosed ${"); 1577 + 1578 + const char *saved_code = js->code; 1579 + jsoff_t saved_clen = js->clen, saved_pos = js->pos; 1580 + uint8_t saved_tok = js->tok, saved_consumed = js->consumed; 1581 + 1582 + js->code = (const char *)&in[expr_start]; 1583 + js->clen = n - expr_start; 1584 + js->pos = 0; 1585 + js->consumed = 1; 1586 + 1587 + jsval_t expr_result = resolveprop(js, js_expr(js)); 1588 + 1589 + js->code = saved_code; 1590 + js->clen = saved_clen; 1591 + js->pos = saved_pos; 1592 + js->tok = saved_tok; 1593 + js->consumed = saved_consumed; 1594 + 1595 + if (is_err(expr_result)) return expr_result; 1596 + values[value_count++] = expr_result; 1597 + n++; 1598 + } 1599 + 1600 + jsval_t strings_arr = mkarr(js); 1601 + for (int i = 0; i < string_count; i++) { 1602 + char idx[16]; 1603 + snprintf(idx, sizeof(idx), "%d", i); 1604 + setprop(js, strings_arr, js_mkstr(js, idx, strlen(idx)), strings[i]); 1605 + } 1606 + setprop(js, strings_arr, js_mkstr(js, "length", 6), tov((double)string_count)); 1607 + strings_arr = mkval(T_ARR, vdata(strings_arr)); 1608 + 1609 + jsval_t args[65]; 1610 + args[0] = strings_arr; 1611 + for (int i = 0; i < value_count; i++) { 1612 + args[i + 1] = values[i]; 1613 + } 1614 + 1615 + uint8_t saved_flags = js->flags; 1616 + jsval_t result = call_js_with_args(js, tag_func, args, 1 + value_count); 1617 + 1618 + js->code = saved_code; 1619 + js->clen = saved_clen; 1620 + js->pos = saved_pos; 1621 + js->tok = saved_tok; 1622 + js->flags = saved_flags; 1623 + js->consumed = 1; 1406 1624 1407 1625 return result; 1408 1626 } ··· 1936 2154 if (is_err(res)) return res; 1937 2155 if (vtype(res) == T_CODEREF) { 1938 2156 if (lookahead(js) == TOK_ARROW) return res; 2157 + if (lookahead(js) == TOK_TEMPLATE) { 2158 + jsval_t tag_func = lookup(js, &js->code[coderefoff(res)], codereflen(res)); 2159 + if (!(js->flags & F_NOEXEC) && !is_err(tag_func)) tag_func = resolveprop(js, tag_func); 2160 + js->consumed = 1; 2161 + next(js); 2162 + js->consumed = 1; 2163 + jsval_t result = js_tagged_template(js, tag_func); 2164 + return result; 2165 + } 1939 2166 res = lookup(js, &js->code[coderefoff(res)], codereflen(res)); 1940 2167 } 1941 - while (next(js) == TOK_LPAREN || next(js) == TOK_DOT || next(js) == TOK_OPTIONAL_CHAIN || next(js) == TOK_LBRACKET) { 1942 - if (js->tok == TOK_DOT || js->tok == TOK_OPTIONAL_CHAIN) { 2168 + while (next(js) == TOK_LPAREN || next(js) == TOK_DOT || next(js) == TOK_OPTIONAL_CHAIN || next(js) == TOK_LBRACKET || next(js) == TOK_TEMPLATE) { 2169 + if (js->tok == TOK_TEMPLATE) { 2170 + if (vtype(res) == T_PROP) res = resolveprop(js, res); 2171 + js->consumed = 1; 2172 + return js_tagged_template(js, res); 2173 + } else if (js->tok == TOK_DOT || js->tok == TOK_OPTIONAL_CHAIN) { 1943 2174 uint8_t op = js->tok; 1944 2175 js->consumed = 1; 1945 2176 if (vtype(res) != T_PROP) {
+13 -1
tests/server/server.cjs
··· 1 1 const Radix3 = Ant.require('./radix3.cjs'); 2 2 const router = new Radix3(); 3 3 4 + function html(strings, ...values) { 5 + let result = ''; 6 + for (let i = 0; i < strings.length; i++) { 7 + result = result + strings[i]; 8 + if (i < values.length) { 9 + let escaped = values[i]; 10 + result = result + escaped; 11 + } 12 + } 13 + return result; 14 + } 15 + 4 16 router.insert('/', c => { 5 17 return c.res.body(`Welcome to Ant HTTP Server with Radix3 Router! 6 18 ··· 40 52 }); 41 53 42 54 router.insert('/files/*path', async c => { 43 - return c.res.html(c.params.path); 55 + return c.res.html(html`<div>${c.params.path}</div>`); 44 56 }); 45 57 46 58 router.printTree();
+261
tests/tagged_templates.cjs
··· 1 + // Comprehensive Tagged Template Literal Tests 2 + Ant.println("=== Tagged Template Literal Tests ===\n"); 3 + 4 + // Test 1: Basic tagged template with one substitution 5 + Ant.println("Test 1: Basic tagged template"); 6 + function tag1(strings, ...values) { 7 + Ant.println(" โœ“ strings.length:", strings.length); 8 + Ant.println(" โœ“ strings[0]:", strings[0]); 9 + Ant.println(" โœ“ strings[1]:", strings[1]); 10 + Ant.println(" โœ“ values.length:", values.length); 11 + Ant.println(" โœ“ values[0]:", values[0]); 12 + return "result1"; 13 + } 14 + 15 + let name = "World"; 16 + let result1 = tag1`Hello ${name}!`; 17 + Ant.println(" โœ“ Result:", result1); 18 + Ant.println(""); 19 + 20 + // Test 2: Building a string from parts 21 + Ant.println("Test 2: Building a string from tagged template"); 22 + function build(strings, ...values) { 23 + let result = ""; 24 + for (let i = 0; i < strings.length; i++) { 25 + result = result + strings[i]; 26 + if (i < values.length) { 27 + result = result + values[i]; 28 + } 29 + } 30 + return result; 31 + } 32 + 33 + let greeting = build`Hello ${name}!`; 34 + Ant.println(" โœ“ Result:", greeting); 35 + Ant.println(""); 36 + 37 + // Test 3: Multiple substitutions 38 + Ant.println("Test 3: Multiple substitutions"); 39 + function multi(strings, ...values) { 40 + Ant.println(" โœ“ values.length:", values.length); 41 + Ant.println(" โœ“ First value:", values[0]); 42 + Ant.println(" โœ“ Second value:", values[1]); 43 + return values[0] + values[1]; 44 + } 45 + 46 + let a = 10; 47 + let b = 20; 48 + let sum = multi`Adding ${a} and ${b}`; 49 + Ant.println(" โœ“ Sum:", sum); 50 + Ant.println(""); 51 + 52 + // Test 4: No substitutions 53 + Ant.println("Test 4: No substitutions"); 54 + function noSub(strings) { 55 + Ant.println(" โœ“ strings.length:", strings.length); 56 + Ant.println(" โœ“ strings[0]:", strings[0]); 57 + return "no-substitutions"; 58 + } 59 + 60 + let result4 = noSub`Just a plain string`; 61 + Ant.println(" โœ“ Result:", result4); 62 + Ant.println(""); 63 + 64 + // Test 5: Expression in substitution 65 + Ant.println("Test 5: Expression in substitution"); 66 + function expr(strings, ...values) { 67 + return values[0] * 2; 68 + } 69 + 70 + let x = 5; 71 + let doubled = expr`Double ${x + 3}`; 72 + Ant.println(" โœ“ Result:", doubled); 73 + Ant.println(""); 74 + 75 + // Test 6: Conditional in substitution 76 + Ant.println("Test 6: Conditional in substitution"); 77 + function cond(strings, ...values) { 78 + return values[0]; 79 + } 80 + 81 + let score = 85; 82 + let grade = cond`Grade: ${score > 80 ? "A" : "B"}`; 83 + Ant.println(" โœ“ Result:", grade); 84 + Ant.println(""); 85 + 86 + // Test 7: Object property access 87 + Ant.println("Test 7: Object property access"); 88 + function objAccess(strings, ...values) { 89 + return values[0]; 90 + } 91 + 92 + let person = { name: "Alice", age: 25 }; 93 + let info = objAccess`Name: ${person.name}`; 94 + Ant.println(" โœ“ Result:", info); 95 + Ant.println(""); 96 + 97 + // Test 8: Array indexing 98 + Ant.println("Test 8: Array indexing"); 99 + function arrAccess(strings, ...values) { 100 + return values[0]; 101 + } 102 + 103 + let colors = ["red", "green", "blue"]; 104 + let color = arrAccess`First color: ${colors[0]}`; 105 + Ant.println(" โœ“ Result:", color); 106 + Ant.println(""); 107 + 108 + // Test 9: Returning different types 109 + Ant.println("Test 9: Returning different types"); 110 + function retNum(strings) { 111 + return 42; 112 + } 113 + 114 + function retBool(strings) { 115 + return true; 116 + } 117 + 118 + let num = retNum`test`; 119 + let bool = retBool`test`; 120 + Ant.println(" โœ“ Number:", num); 121 + Ant.println(" โœ“ Boolean:", bool); 122 + Ant.println(""); 123 + 124 + // Test 10: Using result with string methods 125 + Ant.println("Test 10: Using result with string methods"); 126 + function makeString(strings, ...values) { 127 + return strings[0] + values[0] + strings[1]; 128 + } 129 + 130 + let text = makeString`Hello ${name}!`; 131 + Ant.println(" โœ“ Text:", text); 132 + Ant.println(" โœ“ Includes 'World':", text.includes("World")); 133 + Ant.println(" โœ“ Starts with 'Hello':", text.startsWith("Hello")); 134 + Ant.println(""); 135 + 136 + // Test 11: Rest params with many values 137 + Ant.println("Test 11: Rest params with many values"); 138 + function manyValues(strings, ...values) { 139 + Ant.println(" โœ“ strings.length:", strings.length); 140 + Ant.println(" โœ“ values.length:", values.length); 141 + let result = ""; 142 + for (let i = 0; i < strings.length; i++) { 143 + result = result + strings[i]; 144 + if (i < values.length) { 145 + result = result + values[i]; 146 + } 147 + } 148 + return result; 149 + } 150 + 151 + let v1 = "A"; 152 + let v2 = "B"; 153 + let v3 = "C"; 154 + let v4 = "D"; 155 + let assembled = manyValues`${v1}-${v2}-${v3}-${v4}`; 156 + Ant.println(" โœ“ Result:", assembled); 157 + Ant.println(""); 158 + 159 + // Test 12: Nested function calls 160 + Ant.println("Test 12: Nested expressions"); 161 + function nested(strings, ...values) { 162 + return values[0] + values[1]; 163 + } 164 + 165 + function double(n) { 166 + return n * 2; 167 + } 168 + 169 + let nested_result = nested`${double(5)} + ${double(10)}`; 170 + Ant.println(" โœ“ Result:", nested_result); 171 + Ant.println(""); 172 + 173 + // Test 13: Empty strings between substitutions 174 + Ant.println("Test 13: Adjacent substitutions"); 175 + function adjacent(strings, ...values) { 176 + Ant.println(" โœ“ strings:", strings); 177 + Ant.println(" โœ“ values:", values); 178 + return values[0] + values[1]; 179 + } 180 + 181 + let adj = adjacent`${10}${20}`; 182 + Ant.println(" โœ“ Result:", adj); 183 + Ant.println(""); 184 + 185 + // Test 14: SQL-style template (common use case) 186 + Ant.println("Test 14: SQL-style builder"); 187 + function sql(strings, ...values) { 188 + let query = ""; 189 + for (let i = 0; i < strings.length; i++) { 190 + query = query + strings[i]; 191 + if (i < values.length) { 192 + query = query + "$" + (i + 1); 193 + } 194 + } 195 + return { query: query, values: values }; 196 + } 197 + 198 + let userId = 42; 199 + let userName = "Alice"; 200 + let sqlResult = sql`SELECT * FROM users WHERE id = ${userId} AND name = ${userName}`; 201 + Ant.println(" โœ“ Query:", sqlResult.query); 202 + Ant.println(" โœ“ Values:", sqlResult.values); 203 + Ant.println(""); 204 + 205 + // Test 15: HTML escaping (another common use case) 206 + Ant.println("Test 15: HTML builder"); 207 + function html(strings, ...values) { 208 + let result = ""; 209 + for (let i = 0; i < strings.length; i++) { 210 + result = result + strings[i]; 211 + if (i < values.length) { 212 + let escaped = values[i]; 213 + result = result + escaped; 214 + } 215 + } 216 + return result; 217 + } 218 + 219 + let title = "My Page"; 220 + let content = "Hello World"; 221 + let htmlResult = html`<html><head><title>${title}</title></head><body>${content}</body></html>`; 222 + Ant.println(" โœ“ HTML:", htmlResult); 223 + Ant.println(""); 224 + 225 + // Test 16: Using closure instead of this 226 + Ant.println("Test 16: Tag function with closure"); 227 + function makeTag(prefix) { 228 + return function(strings, ...values) { 229 + return prefix + values[0]; 230 + }; 231 + } 232 + 233 + let prefixTag = makeTag(">>>"); 234 + let prefixed = prefixTag`Value: ${123}`; 235 + Ant.println(" โœ“ Result:", prefixed); 236 + Ant.println(""); 237 + 238 + // Test 17: Empty template 239 + Ant.println("Test 17: Empty template"); 240 + function empty(strings) { 241 + return strings[0]; 242 + } 243 + 244 + let emptyResult = empty``; 245 + Ant.println(" โœ“ Result:", emptyResult); 246 + Ant.println(""); 247 + 248 + // Test 18: Only substitutions, no strings 249 + Ant.println("Test 18: Template starting and ending with substitution"); 250 + function allSubs(strings, ...values) { 251 + Ant.println(" โœ“ strings.length:", strings.length); 252 + Ant.println(" โœ“ First string is empty:", strings[0] == ""); 253 + Ant.println(" โœ“ Last string is empty:", strings[strings.length - 1] == ""); 254 + return values.length; 255 + } 256 + 257 + let count = allSubs`${1}${2}${3}`; 258 + Ant.println(" โœ“ Count:", count); 259 + Ant.println(""); 260 + 261 + Ant.println("=== All 18 tests completed successfully! ===");