Monorepo for Aesthetic.Computer
aesthetic.computer
1# KidLisp Optimization Implementation Guide
2
3**Generated:** August 17, 2025
4**Analyst:** GitHub Copilot
5**Target:** Optimizing KidLisp based on TinyLisp architecture insights
6**Implementation:** JavaScript/ES6 with backward compatibility
7**Performance Goal:** 3-10x improvement while maintaining feature set
8
9---
10
11## Immediate Wins (Phase 1)
12
13### 1. Bytecode Compilation System
14
15Replace the current AST evaluation with a bytecode compilation approach:
16
17```javascript
18// New bytecode operations
19const OPCODES = {
20 LOAD_NUM: 0,
21 LOAD_VAR: 1,
22 LOAD_FUNC: 2,
23 CALL: 3,
24 RETURN: 4,
25 JUMP_IF_FALSE: 5,
26 JUMP: 6,
27 POP: 7,
28 DUP: 8
29};
30
31class KidLispBytecode {
32 constructor() {
33 this.instructions = [];
34 this.constants = [];
35 this.stack = [];
36 this.pc = 0; // program counter
37 }
38
39 compile(ast) {
40 this.instructions = [];
41 this.constants = [];
42 this.compileExpression(ast);
43 return this;
44 }
45
46 compileExpression(expr) {
47 if (typeof expr === 'number') {
48 const constIndex = this.addConstant(expr);
49 this.emit(OPCODES.LOAD_NUM, constIndex);
50 } else if (typeof expr === 'string') {
51 this.emit(OPCODES.LOAD_VAR, this.addConstant(expr));
52 } else if (Array.isArray(expr)) {
53 const [head, ...args] = expr;
54
55 // Compile arguments first (reverse order for stack)
56 for (let i = args.length - 1; i >= 0; i--) {
57 this.compileExpression(args[i]);
58 }
59
60 // Load function
61 this.emit(OPCODES.LOAD_FUNC, this.addConstant(head));
62 this.emit(OPCODES.CALL, args.length);
63 }
64 }
65
66 addConstant(value) {
67 const index = this.constants.indexOf(value);
68 if (index !== -1) return index;
69 this.constants.push(value);
70 return this.constants.length - 1;
71 }
72
73 emit(opcode, operand = 0) {
74 this.instructions.push({ opcode, operand });
75 }
76
77 execute(api, globalEnv) {
78 this.stack = [];
79 this.pc = 0;
80
81 while (this.pc < this.instructions.length) {
82 const { opcode, operand } = this.instructions[this.pc];
83
84 switch (opcode) {
85 case OPCODES.LOAD_NUM:
86 this.stack.push(this.constants[operand]);
87 break;
88
89 case OPCODES.LOAD_VAR:
90 const varName = this.constants[operand];
91 const value = globalEnv[varName] || this.localEnv[varName];
92 this.stack.push(value);
93 break;
94
95 case OPCODES.LOAD_FUNC:
96 const funcName = this.constants[operand];
97 this.stack.push(globalEnv[funcName]);
98 break;
99
100 case OPCODES.CALL:
101 const argCount = operand;
102 const func = this.stack.pop();
103 const args = [];
104 for (let i = 0; i < argCount; i++) {
105 args.unshift(this.stack.pop());
106 }
107 const result = func(api, args);
108 this.stack.push(result);
109 break;
110 }
111 this.pc++;
112 }
113
114 return this.stack.pop();
115 }
116}
117
118// Integration with existing KidLisp class
119class OptimizedKidLisp extends KidLisp {
120 constructor() {
121 super();
122 this.compiledBytecode = null;
123 this.bytecodeCache = new Map();
124 }
125
126 parse(input) {
127 const ast = super.parse(input);
128
129 // Check cache first
130 const cacheKey = JSON.stringify(ast);
131 if (this.bytecodeCache.has(cacheKey)) {
132 this.compiledBytecode = this.bytecodeCache.get(cacheKey);
133 } else {
134 // Compile to bytecode
135 this.compiledBytecode = new KidLispBytecode().compile(ast);
136 this.bytecodeCache.set(cacheKey, this.compiledBytecode);
137 }
138
139 return ast;
140 }
141
142 evaluate(expr, api, env) {
143 if (this.compiledBytecode) {
144 return this.compiledBytecode.execute(api, this.getGlobalEnv());
145 }
146 return super.evaluate(expr, api, env);
147 }
148}
149```
150
151### 2. Function Memoization for Pure Functions
152
153```javascript
154class MemoizedFunction {
155 constructor(func, isPure = true, maxCacheSize = 1000) {
156 this.func = func;
157 this.isPure = isPure;
158 this.cache = new Map();
159 this.maxCacheSize = maxCacheSize;
160 this.hitCount = 0;
161 this.missCount = 0;
162 }
163
164 call(api, args) {
165 if (!this.isPure) {
166 // Non-pure functions always execute
167 return this.func(api, args);
168 }
169
170 const key = this.hashArgs(args);
171
172 if (this.cache.has(key)) {
173 this.hitCount++;
174 return this.cache.get(key);
175 }
176
177 this.missCount++;
178 const result = this.func(api, args);
179
180 // LRU eviction
181 if (this.cache.size >= this.maxCacheSize) {
182 const firstKey = this.cache.keys().next().value;
183 this.cache.delete(firstKey);
184 }
185
186 this.cache.set(key, result);
187 return result;
188 }
189
190 hashArgs(args) {
191 // Simple hash for memoization key
192 return JSON.stringify(args);
193 }
194
195 getStats() {
196 const total = this.hitCount + this.missCount;
197 return {
198 hitCount: this.hitCount,
199 missCount: this.missCount,
200 hitRate: total > 0 ? (this.hitCount / total * 100).toFixed(1) + '%' : '0%'
201 };
202 }
203}
204
205// Mark functions as pure for memoization
206const PURE_FUNCTIONS = new Set([
207 '+', '-', '*', '/', '%', 'mod',
208 '=', '>', '<', '>=', '<=',
209 'abs', 'sqrt', 'sin', 'cos', 'tan',
210 'floor', 'ceil', 'round',
211 'min', 'max'
212]);
213
214// Enhanced global environment with memoization
215class OptimizedKidLisp extends KidLisp {
216 getGlobalEnv() {
217 if (this.globalEnvCache) {
218 return this.globalEnvCache;
219 }
220
221 const baseEnv = super.getGlobalEnv();
222 const optimizedEnv = {};
223
224 // Wrap pure functions with memoization
225 for (const [name, func] of Object.entries(baseEnv)) {
226 if (typeof func === 'function' && PURE_FUNCTIONS.has(name)) {
227 optimizedEnv[name] = new MemoizedFunction(func, true).call.bind(
228 new MemoizedFunction(func, true)
229 );
230 } else {
231 optimizedEnv[name] = func;
232 }
233 }
234
235 this.globalEnvCache = optimizedEnv;
236 return optimizedEnv;
237 }
238}
239```
240
241### 3. Fast Environment Lookup
242
243```javascript
244class FastEnvironment {
245 constructor() {
246 this.scopes = [new Map()]; // Stack of scope maps
247 this.localVarStack = []; // For faster local variable access
248 }
249
250 pushScope() {
251 this.scopes.push(new Map());
252 this.localVarStack.push({});
253 }
254
255 popScope() {
256 this.scopes.pop();
257 this.localVarStack.pop();
258 }
259
260 define(name, value) {
261 const currentScope = this.scopes[this.scopes.length - 1];
262 currentScope.set(name, value);
263
264 // Also add to local var stack for O(1) access
265 const currentLocals = this.localVarStack[this.localVarStack.length - 1];
266 currentLocals[name] = value;
267 }
268
269 lookup(name) {
270 // Check local var stack first (fastest)
271 for (let i = this.localVarStack.length - 1; i >= 0; i--) {
272 if (this.localVarStack[i].hasOwnProperty(name)) {
273 return this.localVarStack[i][name];
274 }
275 }
276
277 // Fallback to scope chain
278 for (let i = this.scopes.length - 1; i >= 0; i--) {
279 if (this.scopes[i].has(name)) {
280 return this.scopes[i].get(name);
281 }
282 }
283
284 return undefined;
285 }
286
287 update(name, value) {
288 // Update in the scope where it was first defined
289 for (let i = this.scopes.length - 1; i >= 0; i--) {
290 if (this.scopes[i].has(name)) {
291 this.scopes[i].set(name, value);
292 // Also update local var stack
293 for (let j = this.localVarStack.length - 1; j >= 0; j--) {
294 if (this.localVarStack[j].hasOwnProperty(name)) {
295 this.localVarStack[j][name] = value;
296 break;
297 }
298 }
299 return true;
300 }
301 }
302 return false;
303 }
304}
305
306// Integration with KidLisp
307class OptimizedKidLisp extends KidLisp {
308 constructor() {
309 super();
310 this.fastEnv = new FastEnvironment();
311 }
312
313 evaluate(expr, api, env) {
314 // Use fast environment for variable lookups
315 if (typeof expr === 'string') {
316 const value = this.fastEnv.lookup(expr);
317 if (value !== undefined) {
318 return value;
319 }
320 // Fallback to global environment
321 const globalEnv = this.getGlobalEnv();
322 return globalEnv[expr];
323 }
324
325 return super.evaluate(expr, api, env);
326 }
327}
328```
329
330## Medium-Term Optimizations (Phase 2)
331
332### 4. Optimized Timing Expression Engine
333
334```javascript
335class TimingEngine {
336 constructor() {
337 this.timers = new Map();
338 this.sequenceStates = new Map();
339 this.compiledTimingExpressions = new Map();
340 }
341
342 compileTimingExpression(timingToken, expressions) {
343 const cacheKey = timingToken + JSON.stringify(expressions);
344
345 if (this.compiledTimingExpressions.has(cacheKey)) {
346 return this.compiledTimingExpressions.get(cacheKey);
347 }
348
349 let interval, isRepeating, isInstant;
350
351 if (/^\d*\.?\d+s\.\.\.?$/.test(timingToken)) {
352 // Repeating timer like "1s..."
353 interval = parseFloat(timingToken.slice(0, -4)) * 1000; // Convert to ms
354 isRepeating = true;
355 isInstant = false;
356 } else if (/^\d*\.?\d+s!?$/.test(timingToken)) {
357 // Delay timer like "1s" or instant "1s!"
358 interval = parseFloat(timingToken.replace(/[s!]/g, '')) * 1000;
359 isRepeating = false;
360 isInstant = timingToken.endsWith('!');
361 }
362
363 const compiled = {
364 interval,
365 isRepeating,
366 isInstant,
367 expressions: expressions.slice(), // Copy array
368 lastExecutionTime: 0,
369 currentIndex: 0,
370 hasExecuted: false
371 };
372
373 this.compiledTimingExpressions.set(cacheKey, compiled);
374 return compiled;
375 }
376
377 executeTiming(timingToken, expressions, api, currentTime) {
378 const compiled = this.compileTimingExpression(timingToken, expressions);
379 const timeSinceLastExecution = currentTime - compiled.lastExecutionTime;
380
381 // Handle instant triggers
382 if (compiled.isInstant && !compiled.hasExecuted) {
383 compiled.hasExecuted = true;
384 compiled.lastExecutionTime = currentTime;
385 return this.executeExpressions(compiled.expressions, api);
386 }
387
388 // Handle interval-based execution
389 if (timeSinceLastExecution >= compiled.interval) {
390 compiled.lastExecutionTime = currentTime;
391
392 if (compiled.isRepeating) {
393 // Cycle through expressions
394 const result = this.executeExpressions([compiled.expressions[compiled.currentIndex]], api);
395 compiled.currentIndex = (compiled.currentIndex + 1) % compiled.expressions.length;
396 return result;
397 } else {
398 // Execute all expressions once
399 return this.executeExpressions(compiled.expressions, api);
400 }
401 }
402
403 // Return current state for repeating expressions
404 if (compiled.isRepeating && compiled.expressions.length > 0) {
405 return compiled.expressions[compiled.currentIndex];
406 }
407
408 return null;
409 }
410
411 executeExpressions(expressions, api) {
412 let result = null;
413 for (const expr of expressions) {
414 result = this.evaluateExpression(expr, api);
415 }
416 return result;
417 }
418
419 evaluateExpression(expr, api) {
420 // This would delegate back to the main evaluator
421 // but with optimized context
422 return expr; // Simplified for example
423 }
424}
425
426// Integration with main evaluator
427class OptimizedKidLisp extends KidLisp {
428 constructor() {
429 super();
430 this.timingEngine = new TimingEngine();
431 }
432
433 evaluate(expr, api, env) {
434 if (Array.isArray(expr)) {
435 const [head, ...args] = expr;
436
437 // Fast path for timing expressions
438 if (typeof head === 'string' && /^\d*\.?\d+s/.test(head)) {
439 const currentTime = Date.now();
440 return this.timingEngine.executeTiming(head, args, api, currentTime);
441 }
442 }
443
444 return super.evaluate(expr, api, env);
445 }
446}
447```
448
449### 5. Symbol Interning for Reduced String Allocation
450
451```javascript
452class SymbolTable {
453 constructor() {
454 this.symbols = new Map(); // string -> symbol id
455 this.symbolNames = []; // symbol id -> string
456 this.nextId = 0;
457 }
458
459 intern(name) {
460 if (this.symbols.has(name)) {
461 return this.symbols.get(name);
462 }
463
464 const id = this.nextId++;
465 this.symbols.set(name, id);
466 this.symbolNames[id] = name;
467 return id;
468 }
469
470 getName(id) {
471 return this.symbolNames[id];
472 }
473
474 getSymbol(name) {
475 return this.symbols.get(name);
476 }
477
478 size() {
479 return this.nextId;
480 }
481}
482
483// Use interned symbols for function names and variables
484class OptimizedKidLisp extends KidLisp {
485 constructor() {
486 super();
487 this.symbolTable = new SymbolTable();
488 this.internedFunctions = new Map();
489 }
490
491 parse(input) {
492 const ast = super.parse(input);
493 return this.internSymbols(ast);
494 }
495
496 internSymbols(expr) {
497 if (typeof expr === 'string' && /^[a-zA-Z_]/.test(expr)) {
498 // Intern function names and variables
499 return this.symbolTable.intern(expr);
500 } else if (Array.isArray(expr)) {
501 return expr.map(item => this.internSymbols(item));
502 }
503 return expr;
504 }
505
506 evaluate(expr, api, env) {
507 // Handle interned symbols
508 if (typeof expr === 'number' && expr < this.symbolTable.size()) {
509 const symbolName = this.symbolTable.getName(expr);
510 if (symbolName) {
511 const globalEnv = this.getGlobalEnv();
512 return globalEnv[symbolName];
513 }
514 }
515
516 return super.evaluate(expr, api, env);
517 }
518}
519```
520
521## Advanced Optimizations (Phase 3)
522
523### 6. Tagged Union Value System (JavaScript NaN Boxing Alternative)
524
525```javascript
526// Value type constants
527const VALUE_TYPES = {
528 NUMBER: 0,
529 STRING: 1,
530 FUNCTION: 2,
531 LIST: 3,
532 BOOLEAN: 4,
533 NIL: 5
534};
535
536class KidLispValue {
537 constructor(type, data) {
538 this.type = type;
539 this.data = data;
540 }
541
542 static number(n) {
543 return new KidLispValue(VALUE_TYPES.NUMBER, n);
544 }
545
546 static string(s) {
547 return new KidLispValue(VALUE_TYPES.STRING, s);
548 }
549
550 static func(f) {
551 return new KidLispValue(VALUE_TYPES.FUNCTION, f);
552 }
553
554 static list(items) {
555 return new KidLispValue(VALUE_TYPES.LIST, items);
556 }
557
558 static bool(b) {
559 return new KidLispValue(VALUE_TYPES.BOOLEAN, b);
560 }
561
562 static nil() {
563 return new KidLispValue(VALUE_TYPES.NIL, null);
564 }
565
566 isNumber() { return this.type === VALUE_TYPES.NUMBER; }
567 isString() { return this.type === VALUE_TYPES.STRING; }
568 isFunction() { return this.type === VALUE_TYPES.FUNCTION; }
569 isList() { return this.type === VALUE_TYPES.LIST; }
570 isBoolean() { return this.type === VALUE_TYPES.BOOLEAN; }
571 isNil() { return this.type === VALUE_TYPES.NIL; }
572
573 getValue() { return this.data; }
574
575 toString() {
576 switch (this.type) {
577 case VALUE_TYPES.NUMBER: return this.data.toString();
578 case VALUE_TYPES.STRING: return this.data;
579 case VALUE_TYPES.FUNCTION: return '[Function]';
580 case VALUE_TYPES.LIST: return `(${this.data.map(v => v.toString()).join(' ')})`;
581 case VALUE_TYPES.BOOLEAN: return this.data ? '#t' : '#f';
582 case VALUE_TYPES.NIL: return '()';
583 default: return '[Unknown]';
584 }
585 }
586}
587
588// Memory pool for value objects to reduce GC pressure
589class ValuePool {
590 constructor(initialSize = 1000) {
591 this.pool = [];
592 this.index = 0;
593
594 // Pre-allocate value objects
595 for (let i = 0; i < initialSize; i++) {
596 this.pool.push(new KidLispValue(VALUE_TYPES.NIL, null));
597 }
598 }
599
600 getValue(type, data) {
601 if (this.index >= this.pool.length) {
602 // Pool exhausted, create new value
603 return new KidLispValue(type, data);
604 }
605
606 const value = this.pool[this.index++];
607 value.type = type;
608 value.data = data;
609 return value;
610 }
611
612 reset() {
613 this.index = 0;
614 }
615}
616```
617
618### 7. JIT Compilation for Hot Paths
619
620```javascript
621class JITCompiler {
622 constructor() {
623 this.hotPaths = new Map();
624 this.executionCounts = new Map();
625 this.compiledFunctions = new Map();
626 this.JIT_THRESHOLD = 100; // Compile after 100 executions
627 }
628
629 trackExecution(expression) {
630 const key = JSON.stringify(expression);
631 const count = this.executionCounts.get(key) || 0;
632 this.executionCounts.set(key, count + 1);
633
634 if (count + 1 >= this.JIT_THRESHOLD && !this.compiledFunctions.has(key)) {
635 this.compileHotPath(key, expression);
636 }
637 }
638
639 compileHotPath(key, expression) {
640 try {
641 // Generate optimized JavaScript code for hot mathematical expressions
642 if (this.isMathExpression(expression)) {
643 const compiledFunction = this.compileMathExpression(expression);
644 this.compiledFunctions.set(key, compiledFunction);
645 console.log(`📊 JIT compiled hot path: ${key}`);
646 }
647 } catch (error) {
648 console.warn(`⚠️ JIT compilation failed for ${key}:`, error);
649 }
650 }
651
652 isMathExpression(expr) {
653 if (!Array.isArray(expr)) return false;
654 const [head] = expr;
655 return ['+', '-', '*', '/', '%', 'sin', 'cos', 'tan', 'sqrt'].includes(head);
656 }
657
658 compileMathExpression(expr) {
659 const jsCode = this.generateJavaScript(expr);
660 return new Function('args', `return ${jsCode}`);
661 }
662
663 generateJavaScript(expr) {
664 if (typeof expr === 'number') {
665 return expr.toString();
666 }
667
668 if (typeof expr === 'string') {
669 // Variable reference
670 return `args['${expr}']`;
671 }
672
673 if (Array.isArray(expr)) {
674 const [head, ...args] = expr;
675
676 switch (head) {
677 case '+':
678 return args.map(arg => this.generateJavaScript(arg)).join(' + ');
679 case '-':
680 if (args.length === 1) {
681 return `-${this.generateJavaScript(args[0])}`;
682 }
683 return args.map(arg => this.generateJavaScript(arg)).join(' - ');
684 case '*':
685 return args.map(arg => this.generateJavaScript(arg)).join(' * ');
686 case '/':
687 return args.map(arg => this.generateJavaScript(arg)).join(' / ');
688 case '%':
689 return args.map(arg => this.generateJavaScript(arg)).join(' % ');
690 case 'sin':
691 return `Math.sin(${this.generateJavaScript(args[0])})`;
692 case 'cos':
693 return `Math.cos(${this.generateJavaScript(args[0])})`;
694 case 'tan':
695 return `Math.tan(${this.generateJavaScript(args[0])})`;
696 case 'sqrt':
697 return `Math.sqrt(${this.generateJavaScript(args[0])})`;
698 default:
699 throw new Error(`Unsupported operation: ${head}`);
700 }
701 }
702
703 throw new Error(`Cannot compile expression: ${expr}`);
704 }
705
706 executeOrCompile(expression, context) {
707 const key = JSON.stringify(expression);
708 this.trackExecution(expression);
709
710 if (this.compiledFunctions.has(key)) {
711 // Use compiled version
712 return this.compiledFunctions.get(key)(context);
713 }
714
715 // Fall back to interpreter
716 return null; // Indicates should use interpreter
717 }
718}
719
720// Integration with main evaluator
721class OptimizedKidLisp extends KidLisp {
722 constructor() {
723 super();
724 this.jitCompiler = new JITCompiler();
725 }
726
727 evaluate(expr, api, env) {
728 // Try JIT compilation for hot mathematical expressions
729 if (Array.isArray(expr)) {
730 const jitResult = this.jitCompiler.executeOrCompile(expr, { ...this.globalDef, ...this.localEnv });
731 if (jitResult !== null) {
732 return jitResult;
733 }
734 }
735
736 return super.evaluate(expr, api, env);
737 }
738}
739```
740
741## Benchmarking Implementation
742
743```javascript
744class KidLispBenchmark {
745 constructor() {
746 this.tests = {
747 'arithmetic': ['(+ 1 2 3 4 5)', '(* (+ 2 3) (- 4 1))', '(/ (* 7 8) (+ 2 3))'],
748 'function-calls': ['(line 0 0 100 100)', '(ink "red")', '(box 10 10 50 50)'],
749 'timing': ['(1s (ink "red"))', '(0.5s... "red" "blue" "green")'],
750 'variables': ['(def x 10) (+ x x x)', '(def y (+ 1 2 3)) (* y y)'],
751 'complex': ['(repeat 10 (+ (* x 2) (/ y 3)))', '(if (> x 5) (+ x 10) (- x 5))']
752 };
753 }
754
755 run(implementations, iterations = 1000) {
756 const results = {};
757
758 for (const [name, implementation] of Object.entries(implementations)) {
759 console.log(`\n🏃 Benchmarking ${name}...`);
760 results[name] = {};
761
762 for (const [testName, testCases] of Object.entries(this.tests)) {
763 const totalTime = this.benchmarkTestCase(implementation, testCases, iterations);
764 results[name][testName] = {
765 totalTime: totalTime.toFixed(2) + 'ms',
766 avgTime: (totalTime / iterations).toFixed(4) + 'ms',
767 opsPerSec: Math.round(iterations / (totalTime / 1000))
768 };
769 }
770 }
771
772 this.printResults(results);
773 return results;
774 }
775
776 benchmarkTestCase(implementation, testCases, iterations) {
777 const start = performance.now();
778
779 for (let i = 0; i < iterations; i++) {
780 for (const testCase of testCases) {
781 try {
782 const ast = implementation.parse(testCase);
783 implementation.evaluate(ast, {}, {});
784 } catch (e) {
785 // Ignore errors for benchmarking
786 }
787 }
788 }
789
790 return performance.now() - start;
791 }
792
793 printResults(results) {
794 console.log('\n📊 Benchmark Results:');
795 console.log('='.repeat(80));
796
797 const implementations = Object.keys(results);
798 const testNames = Object.keys(results[implementations[0]]);
799
800 // Print header
801 console.log('Test'.padEnd(15) + implementations.map(impl => impl.padEnd(20)).join(''));
802 console.log('-'.repeat(80));
803
804 // Print results
805 for (const testName of testNames) {
806 let row = testName.padEnd(15);
807 for (const impl of implementations) {
808 const result = results[impl][testName];
809 row += `${result.avgTime} (${result.opsPerSec}/s)`.padEnd(20);
810 }
811 console.log(row);
812 }
813 }
814}
815
816// Usage example
817const benchmark = new KidLispBenchmark();
818const implementations = {
819 'Original': new KidLisp(),
820 'Optimized': new OptimizedKidLisp()
821};
822
823// Run benchmarks
824benchmark.run(implementations);
825```
826
827This implementation guide provides concrete code examples for each optimization phase, allowing for incremental improvement of KidLisp's performance while maintaining compatibility and extensibility.