From ee14d95f860dafd652642bb43f086f3e795c4d59 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 10 Feb 2026 12:22:19 -0800 Subject: [PATCH] implement unified escape sequence decoder --- examples/demo/event_loop.js | 22 ++++ src/ant.c | 199 +++++++++++++----------------- tests/stack.js | 238 ++++++++++++++++++++++++++++++++++++ 3 files changed, 345 insertions(+), 114 deletions(-) create mode 100644 examples/demo/event_loop.js create mode 100644 tests/stack.js diff --git a/examples/demo/event_loop.js b/examples/demo/event_loop.js new file mode 100644 index 0000000..4da219e --- /dev/null +++ b/examples/demo/event_loop.js @@ -0,0 +1,22 @@ +const DURATION = 5_000; +const origin = performance.now(); +let count = 0; + +function iter() { + count++; + if (performance.now() - origin < DURATION) setImmediate(iter); +} + +iter(); + +process.once('beforeExit', () => { + const elapsed = performance.now() - origin; + const rate = count / (elapsed / 1000); + + let formatted; + if (rate >= 1_000_000) formatted = (rate / 1_000_000).toFixed(2) + 'M'; + else if (rate >= 1_000) formatted = (rate / 1_000).toFixed(1) + 'K'; + else formatted = String(Math.round(rate)); + + console.log(`\x1b[1;36m${formatted} event loop iterations/sec\x1b[0m \x1b[2m(${count.toLocaleString()} in ${(elapsed / 1000).toFixed(1)}s)\x1b[0m`); +}); diff --git a/src/ant.c b/src/ant.c index 94792f0..343a14a 100644 --- a/src/ant.c +++ b/src/ant.c @@ -8226,6 +8226,72 @@ static jsval_t do_op(struct js *js, uint8_t op, jsval_t lhs, jsval_t rhs) { return js_mkerr(js, "unknown op %d", (int)op); } +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) { + size_t n2 = pos; + uint8_t c = in[n2 + 1]; + size_t n1 = *out_pos; + + if (c == quote) { + out[n1++] = quote; + } else if (c == 'n') { + out[n1++] = '\n'; + } else if (c == 't') { + out[n1++] = '\t'; + } else if (c == 'r') { + out[n1++] = '\r'; + } else if (c == '0' && !(in[n2 + 2] >= '0' && in[n2 + 2] <= '7')) { + out[n1++] = '\0'; + } else if (c >= '1' && c <= '7') { + int val = c - '0'; + int extra = 0; + if (in[n2 + 2] >= '0' && in[n2 + 2] <= '7') { + val = val * 8 + (in[n2 + 2] - '0'); + extra++; + if (in[n2 + 3] >= '0' && in[n2 + 3] <= '7' && val * 8 + (in[n2 + 3] - '0') <= 255) { + val = val * 8 + (in[n2 + 3] - '0'); + extra++; + } + } + n2 += extra; + out[n1++] = (uint8_t)val; + } else if (c == 'v') { + out[n1++] = '\v'; + } else if (c == 'f') { + out[n1++] = '\f'; + } else if (c == 'b') { + out[n1++] = '\b'; + } else if (c == 'x' && n2 + 3 < end && is_xdigit(in[n2 + 2]) && is_xdigit(in[n2 + 3])) { + out[n1++] = (uint8_t)((unhex(in[n2 + 2]) << 4U) | unhex(in[n2 + 3])); + n2 += 2; + } else if (c == 'u' && n2 + 2 < end && in[n2 + 2] == '{') { + uint32_t cp = 0; + size_t i = n2 + 3; + while (i < end && is_xdigit(in[i])) { cp = (cp << 4) | unhex(in[i]); i++; } + if (i < end && in[i] == '}') { + if (cp < 0x80) { out[n1++] = (uint8_t)cp; } + else if (cp < 0x800) { out[n1++] = (uint8_t)(0xC0 | (cp >> 6)); out[n1++] = (uint8_t)(0x80 | (cp & 0x3F)); } + 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)); } + 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)); } + n2 = i; + } else { + out[n1++] = c; + } + } 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])) { + uint32_t cp = (unhex(in[n2 + 2]) << 12U) | (unhex(in[n2 + 3]) << 8U) | (unhex(in[n2 + 4]) << 4U) | unhex(in[n2 + 5]); + if (cp < 0x80) { out[n1++] = (uint8_t)cp; } + else if (cp < 0x800) { out[n1++] = (uint8_t)(0xC0 | (cp >> 6)); out[n1++] = (uint8_t)(0x80 | (cp & 0x3F)); } + else { out[n1++] = (uint8_t)(0xE0 | (cp >> 12)); out[n1++] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F)); out[n1++] = (uint8_t)(0x80 | (cp & 0x3F)); } + n2 += 4; + } else if (c == '\\') { + out[n1++] = '\\'; + } else { + out[n1++] = c; + } + + *out_pos = n1; + return n2 - pos; +} + static jsval_t js_template_literal(struct js *js) { uint8_t *in = (uint8_t *) &js->code[js->toff]; size_t template_len = js->tlen; @@ -8257,16 +8323,11 @@ static jsval_t js_template_literal(struct js *js) { for (size_t i = part_start; i < n; i++) { if (in[i] == '\\' && i + 1 < n) { - i++; - if (in[i] == 'n') out[out_len++] = '\n'; - else if (in[i] == 't') out[out_len++] = '\t'; - else if (in[i] == 'r') out[out_len++] = '\r'; - else if (in[i] == '\\') out[out_len++] = '\\'; - else if (in[i] == '`') out[out_len++] = '`'; - else out[out_len++] = in[i]; - } else { - out[out_len++] = in[i]; - } + if (in[i + 1] >= '1' && in[i + 1] <= '7') return js_mkerr_typed( + js, JS_ERR_SYNTAX, "octal escape sequences are not allowed in template literals" + ); + i += 1 + decode_escape(in, i, n, out, &out_len, '`'); + } else out[out_len++] = in[i]; } parts[part_count++] = js_mkstr(js, NULL, out_len); @@ -8353,27 +8414,19 @@ static jsval_t js_tagged_template(struct js *js, jsval_t tag_func) { size_t out_len = 0; size_t needed = sizeof(jsoff_t) + (n - part_start); if (js->brk + needed > js->size) { - if (!js_try_grow_memory(js, needed)) { - return js_mkerr(js, "oom"); - } - } - uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; + if (!js_try_grow_memory(js, needed)) return js_mkerr(js, "oom"); + } uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; for (size_t i = part_start; i < n; i++) { if (in[i] == '\\' && i + 1 < n) { - i++; - if (in[i] == 'n') out[out_len++] = '\n'; - else if (in[i] == 't') out[out_len++] = '\t'; - else if (in[i] == 'r') out[out_len++] = '\r'; - else if (in[i] == '\\') out[out_len++] = '\\'; - else if (in[i] == '`') out[out_len++] = '`'; - else out[out_len++] = in[i]; - } else { - out[out_len++] = in[i]; - } + if (in[i + 1] >= '1' && in[i + 1] <= '7') return js_mkerr_typed( + js, JS_ERR_SYNTAX, "octal escape sequences are not allowed in template literals" + ); + i += 1 + decode_escape(in, i, n, out, &out_len, '`'); + } else out[out_len++] = in[i]; } - strings[string_count++] = js_mkstr(js, NULL, out_len); + strings[string_count++] = js_mkstr(js, NULL, out_len); if (n >= template_len - 1 || in[n] != '$') break; n += 2; @@ -8424,94 +8477,12 @@ static jsval_t js_str_literal(struct js *js) { uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)]; while (n2++ + 2 < js->tlen) { if (in[n2] == '\\') { - if (in[n2 + 1] == in[0]) { - out[n1++] = in[0]; - } else if (in[n2 + 1] == 'n') { - out[n1++] = '\n'; - } else if (in[n2 + 1] == 't') { - out[n1++] = '\t'; - } else if (in[n2 + 1] == 'r') { - out[n1++] = '\r'; - } else if (in[n2 + 1] == '0' && !(in[n2 + 2] >= '0' && in[n2 + 2] <= '7')) { - out[n1++] = '\0'; - } else if (in[n2 + 1] >= '0' && in[n2 + 1] <= '7') { - if (js->flags & F_STRICT) { - return js_mkerr_typed(js, JS_ERR_SYNTAX, "octal escape sequences are not allowed in strict mode"); - } - int val = in[n2 + 1] - '0'; - int extra = 0; - if (in[n2 + 2] >= '0' && in[n2 + 2] <= '7') { - val = val * 8 + (in[n2 + 2] - '0'); - extra++; - if (in[n2 + 3] >= '0' && in[n2 + 3] <= '7' && val * 8 + (in[n2 + 3] - '0') <= 255) { - val = val * 8 + (in[n2 + 3] - '0'); - extra++; - } - } - n2 += extra; - out[n1++] = (uint8_t)val; - } else if (in[n2 + 1] == 'v') { - out[n1++] = '\v'; - } else if (in[n2 + 1] == 'f') { - out[n1++] = '\f'; - } else if (in[n2 + 1] == 'b') { - out[n1++] = '\b'; - } else if (in[n2 + 1] == 'x' && is_xdigit(in[n2 + 2]) && - is_xdigit(in[n2 + 3])) { - out[n1++] = (uint8_t) ((unhex(in[n2 + 2]) << 4U) | unhex(in[n2 + 3])); - n2 += 2; - } else if (in[n2 + 1] == 'u' && in[n2 + 2] == '{') { - uint32_t cp = 0; - size_t i = n2 + 3; - while (i < js->tlen && is_xdigit(in[i])) { - cp = (cp << 4) | unhex(in[i]); - i++; - } - if (in[i] == '}') { - if (cp < 0x80) { - out[n1++] = (uint8_t) cp; - } else if (cp < 0x800) { - out[n1++] = (uint8_t) (0xC0 | (cp >> 6)); - out[n1++] = (uint8_t) (0x80 | (cp & 0x3F)); - } 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)); - } 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)); - } - n2 = i; - } else { - out[n1++] = in[n2 + 1]; - } - } else if (in[n2 + 1] == 'u' && is_xdigit(in[n2 + 2]) && - is_xdigit(in[n2 + 3]) && is_xdigit(in[n2 + 4]) && - is_xdigit(in[n2 + 5])) { - uint32_t cp = (unhex(in[n2 + 2]) << 12U) | (unhex(in[n2 + 3]) << 8U) | - (unhex(in[n2 + 4]) << 4U) | unhex(in[n2 + 5]); - if (cp < 0x80) { - out[n1++] = (uint8_t) cp; - } else if (cp < 0x800) { - out[n1++] = (uint8_t) (0xC0 | (cp >> 6)); - out[n1++] = (uint8_t) (0x80 | (cp & 0x3F)); - } else { - out[n1++] = (uint8_t) (0xE0 | (cp >> 12)); - out[n1++] = (uint8_t) (0x80 | ((cp >> 6) & 0x3F)); - out[n1++] = (uint8_t) (0x80 | (cp & 0x3F)); - } - n2 += 4; - } else if (in[n2 + 1] == '\\') { - out[n1++] = '\\'; - } else { - out[n1++] = in[n2 + 1]; - } - n2++; - } else { - out[n1++] = ((uint8_t *) js->code)[js->toff + n2]; - } + if (in[n2 + 1] >= '1' && in[n2 + 1] <= '7' && (js->flags & F_STRICT)) return js_mkerr_typed( + js, JS_ERR_SYNTAX, "octal escape sequences are not allowed in strict mode" + ); + size_t extra = decode_escape(in, n2, js->tlen, out, &n1, in[0]); + n2 += extra + 1; + } else out[n1++] = ((uint8_t *) js->code)[js->toff + n2]; } return js_mkstr(js, NULL, n1); } diff --git a/tests/stack.js b/tests/stack.js new file mode 100644 index 0000000..28f20e0 --- /dev/null +++ b/tests/stack.js @@ -0,0 +1,238 @@ +const STACK_SIZE = 256; +const PROGRAM_SIZE = 1024; + +const Opcode = { + OP_PUSH: 0, + OP_POP: 1, + OP_ADD: 2, + OP_SUB: 3, + OP_MUL: 4, + OP_DIV: 5, + OP_PRINT: 6, + OP_JMP: 7, + OP_JZ: 8, + OP_LT: 9, + OP_EQ: 10, + OP_NEG: 11, + OP_HALT: 12 +}; + +const opcodeNames = { + [Opcode.OP_PUSH]: 'OP_PUSH', + [Opcode.OP_POP]: 'OP_POP', + [Opcode.OP_ADD]: 'OP_ADD', + [Opcode.OP_SUB]: 'OP_SUB', + [Opcode.OP_MUL]: 'OP_MUL', + [Opcode.OP_DIV]: 'OP_DIV', + [Opcode.OP_PRINT]: 'OP_PRINT', + [Opcode.OP_JMP]: 'OP_JMP', + [Opcode.OP_JZ]: 'OP_JZ', + [Opcode.OP_LT]: 'OP_LT', + [Opcode.OP_EQ]: 'OP_EQ', + [Opcode.OP_NEG]: 'OP_NEG', + [Opcode.OP_HALT]: 'OP_HALT' +}; + +// Debug settings +const DEBUG_ENABLED = true; + +class VM { + constructor() { + this.sp = -1; + this.pc = 0; + this.stack = new Array(STACK_SIZE).fill(0); + this.program = new Uint8Array(PROGRAM_SIZE); + } + + free() { + this.sp = -1; + this.pc = 0; + } + + checkStackHasValues(count) { + if (this.sp < count - 1) { + throw new Error(`Stack Underflow! Need ${count} value(s), but stack has ${this.sp + 1}`); + } + } + + push(value) { + if (this.sp >= STACK_SIZE - 1) { + throw new Error('Stack Overflow!'); + } + this.sp++; + this.stack[this.sp] = value; + this.tracePush(value); + } + + pop() { + this.checkStackHasValues(1); + const result = this.stack[this.sp]; + this.sp--; + this.tracePop(result); + return result; + } + + performBinary(op) { + this.checkStackHasValues(2); + const b = this.pop(); + const a = this.pop(); + let result; + switch (op) { + case '+': + result = a + b; + break; + case '-': + result = a - b; + break; + case '*': + result = a * b; + break; + case '/': + result = Math.trunc(a / b); + break; + } + this.push(result); + } + + performUnary(op) { + this.checkStackHasValues(1); + const a = this.pop(); + let result; + switch (op) { + case '-': + result = -a; + break; + } + this.push(result); + } + + execute() { + this.traceHeader(); + + while (true) { + const op = this.program[this.pc]; + this.traceInstruction(this.pc, op); + this.pc++; + + switch (op) { + case Opcode.OP_PUSH: { + const val = this.readInt64(); + this.push(val); + break; + } + case Opcode.OP_POP: + this.pop(); + break; + case Opcode.OP_HALT: + this.traceHalt(); + return; + case Opcode.OP_PRINT: + this.checkStackHasValues(1); + console.log(this.stack[this.sp]); + break; + case Opcode.OP_ADD: + this.performBinary('+'); + break; + case Opcode.OP_SUB: + this.performBinary('-'); + break; + case Opcode.OP_MUL: + this.performBinary('*'); + break; + case Opcode.OP_DIV: + this.performBinary('/'); + break; + case Opcode.OP_NEG: + this.performUnary('-'); + break; + default: + throw new Error('Unknown Opcode!'); + } + + this.printStack(); + this.traceSeparator(); + } + } + + readInt64() { + // Read 8 bytes as little-endian 64-bit integer + // JS doesn't handle 64-bit ints natively, but for small values this works + let val = 0; + for (let i = 0; i < 8; i++) { + val |= this.program[this.pc + i] << (i * 8); + } + this.pc += 8; + return val; + } + + // Debug/trace methods + traceHeader() { + if (!DEBUG_ENABLED) return; + console.log('\n\x1b[96m\x1b[1m=== VM Execution Trace ===\x1b[0m\n'); + } + + traceInstruction(pc, opcode) { + if (!DEBUG_ENABLED) return; + const name = opcodeNames[opcode] || 'UNKNOWN'; + console.log(`\x1b[94m[PC=${pc}]\x1b[0m \x1b[32m\x1b[1m${name}\x1b[0m`); + } + + tracePush(value) { + if (!DEBUG_ENABLED) return; + console.log(`\x1b[2m \x1b[35mPUSH\x1b[0m \x1b[93m${value}\x1b[0m`); + } + + tracePop(value) { + if (!DEBUG_ENABLED) return; + console.log(`\x1b[2m \x1b[31mPOP \x1b[0m \x1b[93m${value}\x1b[0m`); + } + + traceHalt() { + if (!DEBUG_ENABLED) return; + console.log('\n\x1b[91m\x1b[1m=== Execution Halted ===\x1b[0m'); + } + + traceSeparator() { + if (!DEBUG_ENABLED) return; + console.log(); + } + + printStack() { + if (!DEBUG_ENABLED) return; + let stackStr = this.stack.slice(0, this.sp + 1).join(' '); + console.log(`\x1b[2m Stack \x1b[36m[sp=${this.sp}]\x1b[0m: [ \x1b[33m${stackStr}\x1b[0m ]`); + } +} + +// Helper to write int64 to program +function writeInt(program, pc, val) { + for (let i = 0; i < 8; i++) { + program[pc.value] = (val >> (i * 8)) & 0xff; + pc.value++; + } +} + +// Main +function main() { + const v = new VM(); + const values = [10, 13, 6, 25, 42]; + const pc = { value: 0 }; + + for (const val of values) { + v.program[pc.value++] = Opcode.OP_PUSH; + writeInt(v.program, pc, val); + } + + v.program[pc.value++] = Opcode.OP_MUL; + v.program[pc.value++] = Opcode.OP_ADD; + v.program[pc.value++] = Opcode.OP_NEG; + v.program[pc.value++] = Opcode.OP_DIV; + v.program[pc.value++] = Opcode.OP_SUB; + v.program[pc.value++] = Opcode.OP_PRINT; + v.program[pc.value++] = Opcode.OP_HALT; + + v.execute(); + v.free(); +} + +main(); -- 2.51.2