UNPKG

subscript

Version:

Modular expression parser & evaluator

186 lines (152 loc) 7.66 kB
/** * AST → JS Source String (codegen) * * Simple recursive stringifier using pattern matching on AST structure. */ // Operator-specific overrides export const generators = {}; // Register custom generator export const generator = (op, fn) => generators[op] = fn; // Stringify AST to JS source export const codegen = node => { // Handle undefined/null if (node === undefined) return 'undefined'; if (node === null) return 'null'; if (node === '') return ''; // Identifier if (!Array.isArray(node)) return String(node); const [op, ...args] = node; // Literal: [, value] if (op === undefined) return typeof args[0] === 'string' ? JSON.stringify(args[0]) : String(args[0]); // Custom generator (if returns result, use it; undefined falls through) if (generators[op]) { const result = generators[op](...args); if (result !== undefined) return result; } // Brackets: [], {}, () if (op === '[]' || op === '{}' || op === '()') { const prefix = args.length > 1 ? codegen(args.shift()) : ''; // Empty brackets if (args[0] === undefined || args[0] === null) return prefix + op; // Comma sequence: [',', a, b, c] → a, b, c (null → empty for sparse arrays) const inner = args[0]?.[0] === ',' ? args[0].slice(1).map(a => a === null ? '' : codegen(a)).join(', ') : codegen(args[0]); return prefix + op[0] + inner + op[1]; } // Unary: [op, a] if (args.length === 1) return op + codegen(args[0]); // N-ary/sequence: comma, semicolon if (op === ',' || op === ';') return args.filter(Boolean).map(codegen).join(op === ';' ? '; ' : ', '); // Binary: [op, a, b] if (args.length === 2) { const [a, b] = args; // Postfix: [op, a, null] if (b === null) return codegen(a) + op; // Property access: no spaces if (op === '.') return codegen(a) + '.' + b; return codegen(a) + ' ' + op + ' ' + codegen(b); } // Ternary: a ? b : c if (op === '?' && args.length === 3) { return codegen(args[0]) + ' ? ' + codegen(args[1]) + ' : ' + codegen(args[2]); } // Fallback n-ary return args.filter(Boolean).map(codegen).join(op === ';' ? '; ' : ', '); }; // --- Statement generators (need structure) --- // Variables: ['let', decl] or ['let', decl1, decl2, ...] const varGen = kw => (...args) => kw + ' ' + args.map(codegen).join(', '); generator('let', varGen('let')); generator('const', varGen('const')); generator('var', varGen('var')); // Control flow const wrap = s => '{ ' + (s ? codegen(s) : '') + ' }'; generator('if', (cond, then, els) => 'if (' + codegen(cond) + ') ' + wrap(then) + (els ? ' else ' + wrap(els) : '')); generator('while', (cond, body) => 'while (' + codegen(cond) + ') ' + wrap(body)); generator('do', (body, cond) => 'do ' + wrap(body) + ' while (' + codegen(cond) + ')'); generator('for', (head, body) => { if (head?.[0] === ';') { const [, init, cond, step] = head; return 'for (' + (init ? codegen(init) : '') + '; ' + (cond ? codegen(cond) : '') + '; ' + (step ? codegen(step) : '') + ') ' + wrap(body); } return 'for (' + codegen(head) + ') ' + wrap(body); }); generator('return', a => a === undefined ? 'return' : 'return ' + codegen(a)); generator('break', () => 'break'); generator('continue', () => 'continue'); generator('throw', a => 'throw ' + codegen(a)); // Try/Catch - semi-faithful: ['try', body, ['catch', param, handler]?, ['finally', cleanup]?] generator('try', (body, ...clauses) => { let result = 'try { ' + codegen(body) + ' }'; for (const clause of clauses) { if (clause?.[0] === 'catch') { result += ' catch (' + codegen(clause[1]) + ') { ' + codegen(clause[2]) + ' }'; } else if (clause?.[0] === 'finally') { result += ' finally { ' + codegen(clause[1]) + ' }'; } } return result; }); generator('catch', (param, body) => 'catch (' + codegen(param) + ') { ' + codegen(body) + ' }'); generator('finally', body => 'finally { ' + codegen(body) + ' }'); // Functions generator('function', (name, params, body) => { const args = !params ? '' : params[0] === ',' ? params.slice(1).map(codegen).join(', ') : codegen(params); return 'function' + (name ? ' ' + name : '') + '(' + args + ') ' + wrap(body); }); generator('=>', (params, body) => { if (params?.[0] === '()') params = params[1]; const args = !params ? '()' : typeof params === 'string' ? params : params[0] === ',' ? '(' + params.slice(1).map(codegen).join(', ') + ')' : '(' + codegen(params) + ')'; return args + ' => ' + codegen(body); }); // Class generator('class', (name, base, body) => 'class' + (name ? ' ' + name : '') + (base ? ' extends ' + codegen(base) : '') + ' { ' + (body ? codegen(body) : '') + ' }'); // Async/Await/Yield generator('async', fn => 'async ' + codegen(fn)); generator('await', a => 'await ' + codegen(a)); generator('yield', a => a !== undefined ? 'yield ' + codegen(a) : 'yield'); generator('yield*', a => 'yield* ' + codegen(a)); // Switch generator('switch', (expr, body) => 'switch (' + codegen(expr) + ') ' + codegen(body)); generator('case', (test, body) => 'case ' + codegen(test) + ':' + (body ? ' ' + codegen(body) : '')); generator('default:', body => 'default:' + (body ? ' ' + codegen(body) : '')); // Keywords generator('typeof', a => '(typeof ' + codegen(a) + ')'); generator('void', a => '(void ' + codegen(a) + ')'); generator('delete', a => '(delete ' + codegen(a) + ')'); generator('new', a => 'new ' + codegen(a)); generator('instanceof', (a, b) => '(' + codegen(a) + ' instanceof ' + codegen(b) + ')'); // Optional chaining generator('?.', (a, b) => codegen(a) + '?.' + b); generator('?.[]', (a, b) => codegen(a) + '?.[' + codegen(b) + ']'); generator('?.()', (a, b) => codegen(a) + '?.(' + (!b ? '' : b[0] === ',' ? b.slice(1).map(codegen).join(', ') : codegen(b)) + ')'); // Object literal generator(':', (k, v) => (typeof k === 'string' ? k : '[' + codegen(k) + ']') + ': ' + codegen(v)); // Template literals generator('`', (...parts) => '`' + parts.map(p => p?.[0] === undefined ? String(p[1]).replace(/`/g, '\\`').replace(/\$/g, '\\$') : '${' + codegen(p) + '}').join('') + '`'); generator('``', (tag, ...parts) => codegen(tag) + '`' + parts.map(p => p?.[0] === undefined ? String(p[1]).replace(/`/g, '\\`').replace(/\$/g, '\\$') : '${' + codegen(p) + '}').join('') + '`'); // Regex constructor: ['//', pattern, flags?] generator('//', (a, b) => '/' + a + '/' + (b || '')); // Getter/Setter generator('get', (name, body) => 'get ' + name + '() { ' + (body ? codegen(body) : '') + ' }'); generator('set', (name, param, body) => 'set ' + name + '(' + param + ') { ' + (body ? codegen(body) : '') + ' }'); generator('static', a => 'static ' + codegen(a)); // Non-JS operators (emit as helpers) generator('..', (a, b) => 'range(' + codegen(a) + ', ' + codegen(b) + ')'); generator('..<', (a, b) => 'range(' + codegen(a) + ', ' + codegen(b) + ', true)'); generator('as', (a, b) => b ? codegen(a) + ' as ' + b : codegen(a)); // import alias or type assertion generator('is', (a, b) => '(' + codegen(a) + ' instanceof ' + codegen(b) + ')'); generator('defer', a => '/* defer */ ' + codegen(a)); // For-in/of helper generator('in', (a, b) => codegen(a) + ' in ' + codegen(b)); generator('of', (a, b) => codegen(a) + ' of ' + codegen(b)); generator('for await', (head, body) => 'for await (' + codegen(head) + ') ' + wrap(body)); // Module syntax generator('export', spec => 'export ' + codegen(spec)); generator('import', spec => 'import ' + codegen(spec)); generator('from', (what, path) => codegen(what) + ' from ' + codegen(path)); generator('default', a => 'default ' + codegen(a)); export default codegen;