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.

move examples into /demo, improve comma handler, add fizzbuzz

+489 -10
examples/advanced.js examples/demo/advanced.js
examples/atomics.js examples/demo/atomics.js
+354
examples/demo/bunny.js
··· 1 + const Opcode = { 2 + CONST: 0, 3 + LOAD: 1, 4 + ADD: 2, 5 + CALL: 3, 6 + RETURN: 4, 7 + EXTERN: 5, 8 + HALT: 6 9 + }; 10 + 11 + const TokenType = { 12 + EOF: 0, 13 + FN: 1, 14 + RETURN: 2, 15 + IDENT: 3, 16 + NUMBER: 4, 17 + LPAREN: 5, 18 + RPAREN: 6, 19 + LBRACE: 7, 20 + RBRACE: 8, 21 + COMMA: 9, 22 + SEMICOLON: 10, 23 + PLUS: 11, 24 + DOT: 12 25 + }; 26 + 27 + class Lexer { 28 + constructor(src) { 29 + this.src = src; 30 + this.pos = 0; 31 + this.current = this.nextToken(); 32 + } 33 + 34 + skipWhitespace() { 35 + while (this.pos < this.src.length && /\s/.test(this.src[this.pos])) { 36 + this.pos++; 37 + } 38 + } 39 + 40 + nextToken() { 41 + this.skipWhitespace(); 42 + 43 + if (this.pos >= this.src.length) { 44 + return { type: TokenType.EOF }; 45 + } 46 + 47 + let c = this.src[this.pos]; 48 + 49 + if (/[a-zA-Z]/.test(c)) { 50 + let start = this.pos; 51 + while (this.pos < this.src.length && /[a-zA-Z0-9_]/.test(this.src[this.pos])) { 52 + this.pos++; 53 + } 54 + let value = this.src.slice(start, this.pos); 55 + 56 + if (value === 'fn') return { type: TokenType.FN, value }; 57 + if (value === 'return') return { type: TokenType.RETURN, value }; 58 + return { type: TokenType.IDENT, value }; 59 + } 60 + 61 + if (/[0-9]/.test(c)) { 62 + let start = this.pos; 63 + while (this.pos < this.src.length && /[0-9]/.test(this.src[this.pos])) { 64 + this.pos++; 65 + } 66 + return { type: TokenType.NUMBER, numValue: parseInt(this.src.slice(start, this.pos)) }; 67 + } 68 + 69 + this.pos++; 70 + const charMap = { 71 + '(': TokenType.LPAREN, 72 + ')': TokenType.RPAREN, 73 + '{': TokenType.LBRACE, 74 + '}': TokenType.RBRACE, 75 + ',': TokenType.COMMA, 76 + ';': TokenType.SEMICOLON, 77 + '+': TokenType.PLUS, 78 + '.': TokenType.DOT 79 + }; 80 + return { type: charMap[c] || TokenType.EOF }; 81 + } 82 + 83 + advance() { 84 + this.current = this.nextToken(); 85 + } 86 + } 87 + 88 + class VM { 89 + constructor() { 90 + this.code = []; 91 + this.entryPoint = 0; 92 + this.stack = []; 93 + this.callStack = []; 94 + this.functions = []; 95 + this.externs = [ 96 + { 97 + name: 'std.io.println', 98 + fn: vm => { 99 + console.log(vm.pop()); 100 + vm.push(0); 101 + }, 102 + params: 1 103 + }, 104 + { 105 + name: 'bunny.squeak', 106 + fn: vm => { 107 + console.log('squeak'); 108 + vm.push(0); 109 + }, 110 + params: 0 111 + } 112 + ]; 113 + this.output = []; 114 + } 115 + 116 + emit(op, operand = 0) { 117 + this.code.push({ op, operand }); 118 + } 119 + 120 + push(v) { 121 + this.stack.push(v); 122 + } 123 + pop() { 124 + return this.stack.pop(); 125 + } 126 + 127 + findParam(func, name) { 128 + if (!func) return -1; 129 + let idx = func.paramNames.indexOf(name); 130 + return idx !== -1 ? func.params - 1 - idx : -1; 131 + } 132 + 133 + findFunction(name) { 134 + return this.functions.findIndex(f => f.name === name); 135 + } 136 + 137 + findExtern(name) { 138 + return this.externs.findIndex(e => e.name === name); 139 + } 140 + 141 + run() { 142 + let pc = this.entryPoint; 143 + let fp = 0; 144 + this.output = []; 145 + 146 + const origLog = console.log; 147 + console.log = (...args) => this.output.push(args.join(' ')); 148 + 149 + while (pc < this.code.length) { 150 + const instr = this.code[pc]; 151 + 152 + switch (instr.op) { 153 + case Opcode.CONST: 154 + this.push(instr.operand); 155 + pc++; 156 + break; 157 + 158 + case Opcode.LOAD: 159 + this.push(this.stack[fp + instr.operand]); 160 + pc++; 161 + break; 162 + 163 + case Opcode.ADD: { 164 + let b = this.pop(), 165 + a = this.pop(); 166 + this.push(a + b); 167 + pc++; 168 + break; 169 + } 170 + 171 + case Opcode.CALL: { 172 + let func = this.functions[instr.operand]; 173 + this.callStack.push(pc + 1, fp); 174 + fp = this.stack.length - func.params; 175 + pc = func.addr; 176 + break; 177 + } 178 + 179 + case Opcode.RETURN: { 180 + let ret = this.pop(); 181 + this.stack.length = fp; 182 + fp = this.callStack.pop(); 183 + pc = this.callStack.pop(); 184 + this.push(ret); 185 + break; 186 + } 187 + 188 + case Opcode.EXTERN: 189 + this.externs[instr.operand].fn(this); 190 + pc++; 191 + break; 192 + 193 + case Opcode.HALT: 194 + console.log = origLog; 195 + return; 196 + } 197 + } 198 + console.log = origLog; 199 + } 200 + 201 + printBytecode() { 202 + const names = ['CONST', 'LOAD', 'ADD', 'CALL', 'RETURN', 'EXTERN', 'HALT']; 203 + let out = 'bytecode:\n'; 204 + this.code.forEach((instr, i) => { 205 + out += `${String(i).padStart(3)}: ${names[instr.op].padEnd(8)} ${instr.operand}\n`; 206 + }); 207 + return out; 208 + } 209 + } 210 + 211 + function parseQualifiedName(lex) { 212 + if (lex.current.type !== TokenType.IDENT) return null; 213 + 214 + let name = lex.current.value; 215 + lex.advance(); 216 + 217 + while (lex.current.type === TokenType.DOT) { 218 + lex.advance(); 219 + if (lex.current.type !== TokenType.IDENT) break; 220 + name += '.' + lex.current.value; 221 + lex.advance(); 222 + } 223 + return name; 224 + } 225 + 226 + function parseExpr(lex, vm, currentFunc) { 227 + if (lex.current.type === TokenType.NUMBER) { 228 + vm.emit(Opcode.CONST, lex.current.numValue); 229 + lex.advance(); 230 + } else if (lex.current.type === TokenType.IDENT) { 231 + let name = parseQualifiedName(lex); 232 + 233 + if (lex.current.type === TokenType.LPAREN) { 234 + parseCall(lex, vm, currentFunc, name); 235 + } else { 236 + let offset = vm.findParam(currentFunc, name); 237 + vm.emit(Opcode.LOAD, offset); 238 + } 239 + } 240 + 241 + if (lex.current.type === TokenType.PLUS) { 242 + lex.advance(); 243 + parseExpr(lex, vm, currentFunc); 244 + vm.emit(Opcode.ADD); 245 + } 246 + } 247 + 248 + function parseCall(lex, vm, currentFunc, name) { 249 + lex.advance(); // skip ( 250 + 251 + if (lex.current.type !== TokenType.RPAREN) { 252 + parseExpr(lex, vm, currentFunc); 253 + while (lex.current.type === TokenType.COMMA) { 254 + lex.advance(); 255 + parseExpr(lex, vm, currentFunc); 256 + } 257 + } 258 + lex.advance(); // skip ) 259 + 260 + let externId = vm.findExtern(name); 261 + if (externId !== -1) { 262 + vm.emit(Opcode.EXTERN, externId); 263 + } else { 264 + vm.emit(Opcode.CALL, vm.findFunction(name)); 265 + } 266 + } 267 + 268 + function parseFunction(lex, vm) { 269 + lex.advance(); // skip 'fn' 270 + 271 + let funcName = lex.current.value; 272 + lex.advance(); 273 + lex.advance(); // skip ( 274 + 275 + let paramNames = []; 276 + if (lex.current.type === TokenType.IDENT) { 277 + paramNames.push(lex.current.value); 278 + lex.advance(); 279 + while (lex.current.type === TokenType.COMMA) { 280 + lex.advance(); 281 + paramNames.push(lex.current.value); 282 + lex.advance(); 283 + } 284 + } 285 + lex.advance(); // skip ) 286 + lex.advance(); // skip { 287 + 288 + let func = { 289 + name: funcName, 290 + addr: vm.code.length, 291 + params: paramNames.length, 292 + paramNames 293 + }; 294 + vm.functions.push(func); 295 + 296 + while (lex.current.type !== TokenType.RBRACE) { 297 + if (lex.current.type === TokenType.RETURN) { 298 + lex.advance(); 299 + parseExpr(lex, vm, func); 300 + vm.emit(Opcode.RETURN); 301 + lex.advance(); // skip ; 302 + } 303 + } 304 + lex.advance(); // skip } 305 + } 306 + 307 + function parseProgram(lex, vm) { 308 + while (lex.current.type === TokenType.FN) { 309 + parseFunction(lex, vm); 310 + } 311 + 312 + vm.entryPoint = vm.code.length; 313 + 314 + while (lex.current.type !== TokenType.EOF) { 315 + if (lex.current.type === TokenType.IDENT) { 316 + let name = parseQualifiedName(lex); 317 + 318 + if (lex.current.type === TokenType.LPAREN) { 319 + parseCall(lex, vm, null, name); 320 + if (lex.current.type === TokenType.SEMICOLON) lex.advance(); 321 + } 322 + } else { 323 + lex.advance(); 324 + } 325 + } 326 + 327 + vm.emit(Opcode.HALT); 328 + } 329 + 330 + function compile(source) { 331 + const vm = new VM(); 332 + const lex = new Lexer(source); 333 + parseProgram(lex, vm); 334 + return vm; 335 + } 336 + 337 + const source = ` 338 + fn add(a, b) { 339 + return a + b; 340 + } 341 + bunny.squeak(); 342 + std.io.println(add(5, 10)); 343 + `; 344 + 345 + console.log('Source:'); 346 + console.log(source); 347 + console.log('\n' + '='.repeat(40) + '\n'); 348 + 349 + const vm = compile(source); 350 + 351 + console.log(vm.printBytecode()); 352 + console.log('Output:'); 353 + vm.run(); 354 + vm.output.forEach(line => console.log(line));
+102
examples/demo/fizzbuzz.js
··· 1 + function fizzbuzz(n) { 2 + const result = []; 3 + for (let i = 1; i <= n; i++) { 4 + if (i % 15 === 0) result.push('FizzBuzz'); 5 + else if (i % 3 === 0) result.push('Fizz'); 6 + else if (i % 5 === 0) result.push('Buzz'); 7 + else result.push(i); 8 + } 9 + return result; 10 + } 11 + 12 + function fizzbuzz_bitwise(n) { 13 + const result = new Array(n); 14 + const F = 'Fizz', 15 + B = 'Buzz', 16 + FB = 'FizzBuzz'; 17 + 18 + const mask = new Uint8Array([0, 0, 1, 0, 2, 1, 0, 0, 1, 2, 0, 1, 0, 0, 3]); 19 + 20 + for (let i = 0; i < n; ) { 21 + const batch = Math.min(15, n - i); 22 + for (let j = 0; j < batch; j++, i++) { 23 + const m = mask[i % 15]; 24 + result[i] = m === 0 ? i + 1 : m === 1 ? F : m === 2 ? B : FB; 25 + } 26 + } 27 + return result; 28 + } 29 + 30 + function fizzbuzz_unrolled(n) { 31 + const r = new Array(n); 32 + const F = 'Fizz', 33 + B = 'Buzz', 34 + FB = 'FizzBuzz'; 35 + let i = 0, 36 + num = 1; 37 + 38 + while (i + 15 <= n) { 39 + r[i++] = num++; 40 + r[i++] = num++; 41 + r[i++] = F; 42 + num++; 43 + r[i++] = num++; 44 + r[i++] = B; 45 + num++; 46 + r[i++] = F; 47 + num++; 48 + r[i++] = num++; 49 + r[i++] = num++; 50 + r[i++] = F; 51 + num++; 52 + r[i++] = B; 53 + num++; 54 + r[i++] = num++; 55 + r[i++] = F; 56 + num++; 57 + r[i++] = num++; 58 + r[i++] = num++; 59 + r[i++] = FB; 60 + num++; 61 + } 62 + 63 + const rem = [0, 0, 1, 0, 2, 1, 0, 0, 1, 2, 0, 1, 0, 0]; 64 + const w = [0, F, B]; 65 + while (i < n) { 66 + const m = rem[i % 15]; 67 + r[i++] = m ? w[m] : num; 68 + num++; 69 + } 70 + 71 + return r; 72 + } 73 + 74 + function benchmark(name, fn, n, iterations = 100) { 75 + for (let i = 0; i < 10; i++) fn(n); 76 + 77 + const start = performance.now(); 78 + for (let i = 0; i < iterations; i++) fn(n); 79 + const end = performance.now(); 80 + 81 + const avg = (end - start) / iterations; 82 + console.log(`${name}: ${avg.toFixed(3)}ms for n=${n}`); 83 + return avg; 84 + } 85 + 86 + const N = 1_000; 87 + console.log(`\nbenchmarking FizzBuzz implementations (n=${N.toLocaleString()}):\n`); 88 + 89 + benchmark('normal', fizzbuzz, N); 90 + benchmark('bitwise', fizzbuzz_bitwise, N); 91 + benchmark('unrolled', fizzbuzz_unrolled, N); 92 + 93 + function verify(fn) { 94 + const r = fn(15); 95 + const expected = [1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz', 11, 'Fizz', 13, 14, 'FizzBuzz']; 96 + return JSON.stringify(r) === JSON.stringify(expected); 97 + } 98 + 99 + console.log('\ncorrectness check:'); 100 + console.log('normal:', verify(fizzbuzz)); 101 + console.log('bitwise:', verify(fizzbuzz_bitwise)); 102 + console.log('ultimate:', verify(fizzbuzz_unrolled));
+17
examples/demo/microbuzz.js
··· 1 + function microbuzz(n) { 2 + var next_mult_3 = 0, 3 + next_mult_5 = 0, 4 + next_n = 0, 5 + answer = 0; 6 + 7 + while (next_n <= n) { 8 + if (next_n == next_mult_3 || next_n == next_mult_5) answer = answer + 1; 9 + if (next_n == next_mult_3) next_mult_3 = next_mult_3 + 3; 10 + if (next_n == next_mult_5) next_mult_5 = next_mult_5 + 5; 11 + next_n = next_n + 1; 12 + } 13 + 14 + return answer; 15 + } 16 + 17 + console.log(microbuzz(10000));
examples/events.js examples/demo/events.js
+2 -2
examples/fibonacci.js examples/demo/fibonacci.js
··· 6 6 } 7 7 8 8 const start = performance.now(); 9 - const result = fibonacci(40); 9 + const result = fibonacci(621); 10 10 const end = performance.now(); 11 11 12 - console.log(`fibonacci(40) = ${result}`); 12 + console.log(`fibonacci(621) = ${result}`); 13 13 console.log(`Time: ${(end - start).toFixed(2)} ms`);
examples/graph.js examples/demo/graph.js
examples/lib/nanoid.js examples/demo/lib/nanoid.js
examples/lib/uuid.js examples/demo/lib/uuid.js
examples/uuid.min.js examples/demo/uuid.min.js
+1 -1
meson.build
··· 96 96 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 97 97 98 98 version_conf = configuration_data() 99 - version_conf.set('ANT_VERSION', '0.3.2.23') 99 + version_conf.set('ANT_VERSION', '0.3.2.24') 100 100 version_conf.set('ANT_GIT_HASH', git_hash) 101 101 version_conf.set('ANT_BUILD_DATE', build_date) 102 102
+3 -3
src/ant.c
··· 9891 9891 if (!expect(js, TOK_SEMICOLON, &res)) goto done; 9892 9892 pos2 = js->pos; 9893 9893 if (next(js) != TOK_RPAREN) { 9894 - v = js_expr(js); 9894 + v = js_expr_comma(js); 9895 9895 if (is_err2(&v, &res)) goto done; 9896 9896 } 9897 9897 if (!expect(js, TOK_RPAREN, &res)) goto done; ··· 9948 9948 js->flags = flags; 9949 9949 js->pos = pos2, js->consumed = 1; 9950 9950 if (next(js) != TOK_RPAREN) { 9951 - v = js_expr(js); 9951 + v = js_expr_comma(js); 9952 9952 if (is_err2(&v, &res)) goto done; 9953 9953 } 9954 9954 continue; ··· 9965 9965 } 9966 9966 js->flags = flags, js->pos = pos2, js->consumed = 1; 9967 9967 if (next(js) != TOK_RPAREN) { 9968 - v = js_expr(js); 9968 + v = js_expr_comma(js); 9969 9969 if (is_err2(&v, &res)) goto done; 9970 9970 } 9971 9971 }
+7
tests/bench_server.js
··· 1 + function meow(c) {} 2 + 3 + function server(c) { 4 + c.res.body('meow'); 5 + } 6 + 7 + Ant.serve(8000, server);
+3 -4
tests/microbench.js examples/demo/microbench.js
··· 1 - // Simple microbenchmark for function call overhead 2 - 3 1 function add(a, b) { 4 2 return a + b; 5 3 } ··· 9 7 10 8 let start = Date.now(); 11 9 let result = 0; 10 + 12 11 for (let i = 0; i < iterations; i++) { 13 12 result += add(i, i + 1); 14 13 } 15 - console.log(`Simple add: ${Date.now() - start}ms (result: ${result})`); 16 14 17 - console.log("Done!"); 15 + console.log(`simple add: ${Date.now() - start}ms (result: ${result})`); 16 + console.log('Done!');