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.

implement unified escape sequence decoder

+345 -114
+22
examples/demo/event_loop.js
··· 1 + const DURATION = 5_000; 2 + const origin = performance.now(); 3 + let count = 0; 4 + 5 + function iter() { 6 + count++; 7 + if (performance.now() - origin < DURATION) setImmediate(iter); 8 + } 9 + 10 + iter(); 11 + 12 + process.once('beforeExit', () => { 13 + const elapsed = performance.now() - origin; 14 + const rate = count / (elapsed / 1000); 15 + 16 + let formatted; 17 + if (rate >= 1_000_000) formatted = (rate / 1_000_000).toFixed(2) + 'M'; 18 + else if (rate >= 1_000) formatted = (rate / 1_000).toFixed(1) + 'K'; 19 + else formatted = String(Math.round(rate)); 20 + 21 + console.log(`\x1b[1;36m${formatted} event loop iterations/sec\x1b[0m \x1b[2m(${count.toLocaleString()} in ${(elapsed / 1000).toFixed(1)}s)\x1b[0m`); 22 + });
+85 -114
src/ant.c
··· 8226 8226 return js_mkerr(js, "unknown op %d", (int)op); 8227 8227 } 8228 8228 8229 + static size_t decode_escape(const uint8_t *in, size_t pos, size_t end, uint8_t *out, size_t *out_pos, uint8_t quote) { 8230 + size_t n2 = pos; 8231 + uint8_t c = in[n2 + 1]; 8232 + size_t n1 = *out_pos; 8233 + 8234 + if (c == quote) { 8235 + out[n1++] = quote; 8236 + } else if (c == 'n') { 8237 + out[n1++] = '\n'; 8238 + } else if (c == 't') { 8239 + out[n1++] = '\t'; 8240 + } else if (c == 'r') { 8241 + out[n1++] = '\r'; 8242 + } else if (c == '0' && !(in[n2 + 2] >= '0' && in[n2 + 2] <= '7')) { 8243 + out[n1++] = '\0'; 8244 + } else if (c >= '1' && c <= '7') { 8245 + int val = c - '0'; 8246 + int extra = 0; 8247 + if (in[n2 + 2] >= '0' && in[n2 + 2] <= '7') { 8248 + val = val * 8 + (in[n2 + 2] - '0'); 8249 + extra++; 8250 + if (in[n2 + 3] >= '0' && in[n2 + 3] <= '7' && val * 8 + (in[n2 + 3] - '0') <= 255) { 8251 + val = val * 8 + (in[n2 + 3] - '0'); 8252 + extra++; 8253 + } 8254 + } 8255 + n2 += extra; 8256 + out[n1++] = (uint8_t)val; 8257 + } else if (c == 'v') { 8258 + out[n1++] = '\v'; 8259 + } else if (c == 'f') { 8260 + out[n1++] = '\f'; 8261 + } else if (c == 'b') { 8262 + out[n1++] = '\b'; 8263 + } else if (c == 'x' && n2 + 3 < end && is_xdigit(in[n2 + 2]) && is_xdigit(in[n2 + 3])) { 8264 + out[n1++] = (uint8_t)((unhex(in[n2 + 2]) << 4U) | unhex(in[n2 + 3])); 8265 + n2 += 2; 8266 + } else if (c == 'u' && n2 + 2 < end && in[n2 + 2] == '{') { 8267 + uint32_t cp = 0; 8268 + size_t i = n2 + 3; 8269 + while (i < end && is_xdigit(in[i])) { cp = (cp << 4) | unhex(in[i]); i++; } 8270 + if (i < end && in[i] == '}') { 8271 + if (cp < 0x80) { out[n1++] = (uint8_t)cp; } 8272 + else if (cp < 0x800) { out[n1++] = (uint8_t)(0xC0 | (cp >> 6)); out[n1++] = (uint8_t)(0x80 | (cp & 0x3F)); } 8273 + else if (cp < 0x10000) { out[n1++] = (uint8_t)(0xE0 | (cp >> 12)); out[n1++] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F)); out[n1++] = (uint8_t)(0x80 | (cp & 0x3F)); } 8274 + else { out[n1++] = (uint8_t)(0xF0 | (cp >> 18)); out[n1++] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F)); out[n1++] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F)); out[n1++] = (uint8_t)(0x80 | (cp & 0x3F)); } 8275 + n2 = i; 8276 + } else { 8277 + out[n1++] = c; 8278 + } 8279 + } else if (c == 'u' && n2 + 5 < end && is_xdigit(in[n2 + 2]) && is_xdigit(in[n2 + 3]) && is_xdigit(in[n2 + 4]) && is_xdigit(in[n2 + 5])) { 8280 + uint32_t cp = (unhex(in[n2 + 2]) << 12U) | (unhex(in[n2 + 3]) << 8U) | (unhex(in[n2 + 4]) << 4U) | unhex(in[n2 + 5]); 8281 + if (cp < 0x80) { out[n1++] = (uint8_t)cp; } 8282 + else if (cp < 0x800) { out[n1++] = (uint8_t)(0xC0 | (cp >> 6)); out[n1++] = (uint8_t)(0x80 | (cp & 0x3F)); } 8283 + else { out[n1++] = (uint8_t)(0xE0 | (cp >> 12)); out[n1++] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F)); out[n1++] = (uint8_t)(0x80 | (cp & 0x3F)); } 8284 + n2 += 4; 8285 + } else if (c == '\\') { 8286 + out[n1++] = '\\'; 8287 + } else { 8288 + out[n1++] = c; 8289 + } 8290 + 8291 + *out_pos = n1; 8292 + return n2 - pos; 8293 + } 8294 + 8229 8295 static jsval_t js_template_literal(struct js *js) { 8230 8296 uint8_t *in = (uint8_t *) &js->code[js->toff]; 8231 8297 size_t template_len = js->tlen; ··· 8257 8323 8258 8324 for (size_t i = part_start; i < n; i++) { 8259 8325 if (in[i] == '\\' && i + 1 < n) { 8260 - i++; 8261 - if (in[i] == 'n') out[out_len++] = '\n'; 8262 - else if (in[i] == 't') out[out_len++] = '\t'; 8263 - else if (in[i] == 'r') out[out_len++] = '\r'; 8264 - else if (in[i] == '\\') out[out_len++] = '\\'; 8265 - else if (in[i] == '`') out[out_len++] = '`'; 8266 - else out[out_len++] = in[i]; 8267 - } else { 8268 - out[out_len++] = in[i]; 8269 - } 8326 + if (in[i + 1] >= '1' && in[i + 1] <= '7') return js_mkerr_typed( 8327 + js, JS_ERR_SYNTAX, "octal escape sequences are not allowed in template literals" 8328 + ); 8329 + i += 1 + decode_escape(in, i, n, out, &out_len, '`'); 8330 + } else out[out_len++] = in[i]; 8270 8331 } 8271 8332 8272 8333 parts[part_count++] = js_mkstr(js, NULL, out_len); ··· 8353 8414 size_t out_len = 0; 8354 8415 size_t needed = sizeof(jsoff_t) + (n - part_start); 8355 8416 if (js->brk + needed > js->size) { 8356 - if (!js_try_grow_memory(js, needed)) { 8357 - return js_mkerr(js, "oom"); 8358 - } 8359 - } 8360 - uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; 8417 + if (!js_try_grow_memory(js, needed)) return js_mkerr(js, "oom"); 8418 + } uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; 8361 8419 8362 8420 for (size_t i = part_start; i < n; i++) { 8363 8421 if (in[i] == '\\' && i + 1 < n) { 8364 - i++; 8365 - if (in[i] == 'n') out[out_len++] = '\n'; 8366 - else if (in[i] == 't') out[out_len++] = '\t'; 8367 - else if (in[i] == 'r') out[out_len++] = '\r'; 8368 - else if (in[i] == '\\') out[out_len++] = '\\'; 8369 - else if (in[i] == '`') out[out_len++] = '`'; 8370 - else out[out_len++] = in[i]; 8371 - } else { 8372 - out[out_len++] = in[i]; 8373 - } 8422 + if (in[i + 1] >= '1' && in[i + 1] <= '7') return js_mkerr_typed( 8423 + js, JS_ERR_SYNTAX, "octal escape sequences are not allowed in template literals" 8424 + ); 8425 + i += 1 + decode_escape(in, i, n, out, &out_len, '`'); 8426 + } else out[out_len++] = in[i]; 8374 8427 } 8375 - strings[string_count++] = js_mkstr(js, NULL, out_len); 8376 8428 8429 + strings[string_count++] = js_mkstr(js, NULL, out_len); 8377 8430 if (n >= template_len - 1 || in[n] != '$') break; 8378 8431 8379 8432 n += 2; ··· 8424 8477 uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; 8425 8478 while (n2++ + 2 < js->tlen) { 8426 8479 if (in[n2] == '\\') { 8427 - if (in[n2 + 1] == in[0]) { 8428 - out[n1++] = in[0]; 8429 - } else if (in[n2 + 1] == 'n') { 8430 - out[n1++] = '\n'; 8431 - } else if (in[n2 + 1] == 't') { 8432 - out[n1++] = '\t'; 8433 - } else if (in[n2 + 1] == 'r') { 8434 - out[n1++] = '\r'; 8435 - } else if (in[n2 + 1] == '0' && !(in[n2 + 2] >= '0' && in[n2 + 2] <= '7')) { 8436 - out[n1++] = '\0'; 8437 - } else if (in[n2 + 1] >= '0' && in[n2 + 1] <= '7') { 8438 - if (js->flags & F_STRICT) { 8439 - return js_mkerr_typed(js, JS_ERR_SYNTAX, "octal escape sequences are not allowed in strict mode"); 8440 - } 8441 - int val = in[n2 + 1] - '0'; 8442 - int extra = 0; 8443 - if (in[n2 + 2] >= '0' && in[n2 + 2] <= '7') { 8444 - val = val * 8 + (in[n2 + 2] - '0'); 8445 - extra++; 8446 - if (in[n2 + 3] >= '0' && in[n2 + 3] <= '7' && val * 8 + (in[n2 + 3] - '0') <= 255) { 8447 - val = val * 8 + (in[n2 + 3] - '0'); 8448 - extra++; 8449 - } 8450 - } 8451 - n2 += extra; 8452 - out[n1++] = (uint8_t)val; 8453 - } else if (in[n2 + 1] == 'v') { 8454 - out[n1++] = '\v'; 8455 - } else if (in[n2 + 1] == 'f') { 8456 - out[n1++] = '\f'; 8457 - } else if (in[n2 + 1] == 'b') { 8458 - out[n1++] = '\b'; 8459 - } else if (in[n2 + 1] == 'x' && is_xdigit(in[n2 + 2]) && 8460 - is_xdigit(in[n2 + 3])) { 8461 - out[n1++] = (uint8_t) ((unhex(in[n2 + 2]) << 4U) | unhex(in[n2 + 3])); 8462 - n2 += 2; 8463 - } else if (in[n2 + 1] == 'u' && in[n2 + 2] == '{') { 8464 - uint32_t cp = 0; 8465 - size_t i = n2 + 3; 8466 - while (i < js->tlen && is_xdigit(in[i])) { 8467 - cp = (cp << 4) | unhex(in[i]); 8468 - i++; 8469 - } 8470 - if (in[i] == '}') { 8471 - if (cp < 0x80) { 8472 - out[n1++] = (uint8_t) cp; 8473 - } else if (cp < 0x800) { 8474 - out[n1++] = (uint8_t) (0xC0 | (cp >> 6)); 8475 - out[n1++] = (uint8_t) (0x80 | (cp & 0x3F)); 8476 - } else if (cp < 0x10000) { 8477 - out[n1++] = (uint8_t) (0xE0 | (cp >> 12)); 8478 - out[n1++] = (uint8_t) (0x80 | ((cp >> 6) & 0x3F)); 8479 - out[n1++] = (uint8_t) (0x80 | (cp & 0x3F)); 8480 - } else { 8481 - out[n1++] = (uint8_t) (0xF0 | (cp >> 18)); 8482 - out[n1++] = (uint8_t) (0x80 | ((cp >> 12) & 0x3F)); 8483 - out[n1++] = (uint8_t) (0x80 | ((cp >> 6) & 0x3F)); 8484 - out[n1++] = (uint8_t) (0x80 | (cp & 0x3F)); 8485 - } 8486 - n2 = i; 8487 - } else { 8488 - out[n1++] = in[n2 + 1]; 8489 - } 8490 - } else if (in[n2 + 1] == 'u' && is_xdigit(in[n2 + 2]) && 8491 - is_xdigit(in[n2 + 3]) && is_xdigit(in[n2 + 4]) && 8492 - is_xdigit(in[n2 + 5])) { 8493 - uint32_t cp = (unhex(in[n2 + 2]) << 12U) | (unhex(in[n2 + 3]) << 8U) | 8494 - (unhex(in[n2 + 4]) << 4U) | unhex(in[n2 + 5]); 8495 - if (cp < 0x80) { 8496 - out[n1++] = (uint8_t) cp; 8497 - } else if (cp < 0x800) { 8498 - out[n1++] = (uint8_t) (0xC0 | (cp >> 6)); 8499 - out[n1++] = (uint8_t) (0x80 | (cp & 0x3F)); 8500 - } else { 8501 - out[n1++] = (uint8_t) (0xE0 | (cp >> 12)); 8502 - out[n1++] = (uint8_t) (0x80 | ((cp >> 6) & 0x3F)); 8503 - out[n1++] = (uint8_t) (0x80 | (cp & 0x3F)); 8504 - } 8505 - n2 += 4; 8506 - } else if (in[n2 + 1] == '\\') { 8507 - out[n1++] = '\\'; 8508 - } else { 8509 - out[n1++] = in[n2 + 1]; 8510 - } 8511 - n2++; 8512 - } else { 8513 - out[n1++] = ((uint8_t *) js->code)[js->toff + n2]; 8514 - } 8480 + if (in[n2 + 1] >= '1' && in[n2 + 1] <= '7' && (js->flags & F_STRICT)) return js_mkerr_typed( 8481 + js, JS_ERR_SYNTAX, "octal escape sequences are not allowed in strict mode" 8482 + ); 8483 + size_t extra = decode_escape(in, n2, js->tlen, out, &n1, in[0]); 8484 + n2 += extra + 1; 8485 + } else out[n1++] = ((uint8_t *) js->code)[js->toff + n2]; 8515 8486 } 8516 8487 return js_mkstr(js, NULL, n1); 8517 8488 }
+238
tests/stack.js
··· 1 + const STACK_SIZE = 256; 2 + const PROGRAM_SIZE = 1024; 3 + 4 + const Opcode = { 5 + OP_PUSH: 0, 6 + OP_POP: 1, 7 + OP_ADD: 2, 8 + OP_SUB: 3, 9 + OP_MUL: 4, 10 + OP_DIV: 5, 11 + OP_PRINT: 6, 12 + OP_JMP: 7, 13 + OP_JZ: 8, 14 + OP_LT: 9, 15 + OP_EQ: 10, 16 + OP_NEG: 11, 17 + OP_HALT: 12 18 + }; 19 + 20 + const opcodeNames = { 21 + [Opcode.OP_PUSH]: 'OP_PUSH', 22 + [Opcode.OP_POP]: 'OP_POP', 23 + [Opcode.OP_ADD]: 'OP_ADD', 24 + [Opcode.OP_SUB]: 'OP_SUB', 25 + [Opcode.OP_MUL]: 'OP_MUL', 26 + [Opcode.OP_DIV]: 'OP_DIV', 27 + [Opcode.OP_PRINT]: 'OP_PRINT', 28 + [Opcode.OP_JMP]: 'OP_JMP', 29 + [Opcode.OP_JZ]: 'OP_JZ', 30 + [Opcode.OP_LT]: 'OP_LT', 31 + [Opcode.OP_EQ]: 'OP_EQ', 32 + [Opcode.OP_NEG]: 'OP_NEG', 33 + [Opcode.OP_HALT]: 'OP_HALT' 34 + }; 35 + 36 + // Debug settings 37 + const DEBUG_ENABLED = true; 38 + 39 + class VM { 40 + constructor() { 41 + this.sp = -1; 42 + this.pc = 0; 43 + this.stack = new Array(STACK_SIZE).fill(0); 44 + this.program = new Uint8Array(PROGRAM_SIZE); 45 + } 46 + 47 + free() { 48 + this.sp = -1; 49 + this.pc = 0; 50 + } 51 + 52 + checkStackHasValues(count) { 53 + if (this.sp < count - 1) { 54 + throw new Error(`Stack Underflow! Need ${count} value(s), but stack has ${this.sp + 1}`); 55 + } 56 + } 57 + 58 + push(value) { 59 + if (this.sp >= STACK_SIZE - 1) { 60 + throw new Error('Stack Overflow!'); 61 + } 62 + this.sp++; 63 + this.stack[this.sp] = value; 64 + this.tracePush(value); 65 + } 66 + 67 + pop() { 68 + this.checkStackHasValues(1); 69 + const result = this.stack[this.sp]; 70 + this.sp--; 71 + this.tracePop(result); 72 + return result; 73 + } 74 + 75 + performBinary(op) { 76 + this.checkStackHasValues(2); 77 + const b = this.pop(); 78 + const a = this.pop(); 79 + let result; 80 + switch (op) { 81 + case '+': 82 + result = a + b; 83 + break; 84 + case '-': 85 + result = a - b; 86 + break; 87 + case '*': 88 + result = a * b; 89 + break; 90 + case '/': 91 + result = Math.trunc(a / b); 92 + break; 93 + } 94 + this.push(result); 95 + } 96 + 97 + performUnary(op) { 98 + this.checkStackHasValues(1); 99 + const a = this.pop(); 100 + let result; 101 + switch (op) { 102 + case '-': 103 + result = -a; 104 + break; 105 + } 106 + this.push(result); 107 + } 108 + 109 + execute() { 110 + this.traceHeader(); 111 + 112 + while (true) { 113 + const op = this.program[this.pc]; 114 + this.traceInstruction(this.pc, op); 115 + this.pc++; 116 + 117 + switch (op) { 118 + case Opcode.OP_PUSH: { 119 + const val = this.readInt64(); 120 + this.push(val); 121 + break; 122 + } 123 + case Opcode.OP_POP: 124 + this.pop(); 125 + break; 126 + case Opcode.OP_HALT: 127 + this.traceHalt(); 128 + return; 129 + case Opcode.OP_PRINT: 130 + this.checkStackHasValues(1); 131 + console.log(this.stack[this.sp]); 132 + break; 133 + case Opcode.OP_ADD: 134 + this.performBinary('+'); 135 + break; 136 + case Opcode.OP_SUB: 137 + this.performBinary('-'); 138 + break; 139 + case Opcode.OP_MUL: 140 + this.performBinary('*'); 141 + break; 142 + case Opcode.OP_DIV: 143 + this.performBinary('/'); 144 + break; 145 + case Opcode.OP_NEG: 146 + this.performUnary('-'); 147 + break; 148 + default: 149 + throw new Error('Unknown Opcode!'); 150 + } 151 + 152 + this.printStack(); 153 + this.traceSeparator(); 154 + } 155 + } 156 + 157 + readInt64() { 158 + // Read 8 bytes as little-endian 64-bit integer 159 + // JS doesn't handle 64-bit ints natively, but for small values this works 160 + let val = 0; 161 + for (let i = 0; i < 8; i++) { 162 + val |= this.program[this.pc + i] << (i * 8); 163 + } 164 + this.pc += 8; 165 + return val; 166 + } 167 + 168 + // Debug/trace methods 169 + traceHeader() { 170 + if (!DEBUG_ENABLED) return; 171 + console.log('\n\x1b[96m\x1b[1m=== VM Execution Trace ===\x1b[0m\n'); 172 + } 173 + 174 + traceInstruction(pc, opcode) { 175 + if (!DEBUG_ENABLED) return; 176 + const name = opcodeNames[opcode] || 'UNKNOWN'; 177 + console.log(`\x1b[94m[PC=${pc}]\x1b[0m \x1b[32m\x1b[1m${name}\x1b[0m`); 178 + } 179 + 180 + tracePush(value) { 181 + if (!DEBUG_ENABLED) return; 182 + console.log(`\x1b[2m \x1b[35mPUSH\x1b[0m \x1b[93m${value}\x1b[0m`); 183 + } 184 + 185 + tracePop(value) { 186 + if (!DEBUG_ENABLED) return; 187 + console.log(`\x1b[2m \x1b[31mPOP \x1b[0m \x1b[93m${value}\x1b[0m`); 188 + } 189 + 190 + traceHalt() { 191 + if (!DEBUG_ENABLED) return; 192 + console.log('\n\x1b[91m\x1b[1m=== Execution Halted ===\x1b[0m'); 193 + } 194 + 195 + traceSeparator() { 196 + if (!DEBUG_ENABLED) return; 197 + console.log(); 198 + } 199 + 200 + printStack() { 201 + if (!DEBUG_ENABLED) return; 202 + let stackStr = this.stack.slice(0, this.sp + 1).join(' '); 203 + console.log(`\x1b[2m Stack \x1b[36m[sp=${this.sp}]\x1b[0m: [ \x1b[33m${stackStr}\x1b[0m ]`); 204 + } 205 + } 206 + 207 + // Helper to write int64 to program 208 + function writeInt(program, pc, val) { 209 + for (let i = 0; i < 8; i++) { 210 + program[pc.value] = (val >> (i * 8)) & 0xff; 211 + pc.value++; 212 + } 213 + } 214 + 215 + // Main 216 + function main() { 217 + const v = new VM(); 218 + const values = [10, 13, 6, 25, 42]; 219 + const pc = { value: 0 }; 220 + 221 + for (const val of values) { 222 + v.program[pc.value++] = Opcode.OP_PUSH; 223 + writeInt(v.program, pc, val); 224 + } 225 + 226 + v.program[pc.value++] = Opcode.OP_MUL; 227 + v.program[pc.value++] = Opcode.OP_ADD; 228 + v.program[pc.value++] = Opcode.OP_NEG; 229 + v.program[pc.value++] = Opcode.OP_DIV; 230 + v.program[pc.value++] = Opcode.OP_SUB; 231 + v.program[pc.value++] = Opcode.OP_PRINT; 232 + v.program[pc.value++] = Opcode.OP_HALT; 233 + 234 + v.execute(); 235 + v.free(); 236 + } 237 + 238 + main();