|
| 1 | +/** |
| 2 | + * Example: Creating a Custom Compilation Target |
| 3 | + * |
| 4 | + * This example demonstrates how to create a custom compilation target |
| 5 | + * using the exported CompileTarget interface and BaseCompiler. |
| 6 | + * |
| 7 | + * We'll create a simple "Calculator DSL" that compiles to function calls |
| 8 | + * with uppercase names, useful for interfacing with legacy systems or |
| 9 | + * domain-specific calculators. |
| 10 | + */ |
| 11 | + |
| 12 | +import { |
| 13 | + ComputeEngine, |
| 14 | + BaseCompiler, |
| 15 | + JavaScriptTarget, |
| 16 | +} from '../dist/compute-engine.esm.js'; |
| 17 | + |
| 18 | +const ce = new ComputeEngine(); |
| 19 | + |
| 20 | +// ============================================================================ |
| 21 | +// Example 1: Using JavaScriptTarget as a Base |
| 22 | +// ============================================================================ |
| 23 | +console.log('Example 1: Extending JavaScriptTarget'); |
| 24 | +console.log('='.repeat(70)); |
| 25 | + |
| 26 | +// Create a custom target that extends the JavaScript target |
| 27 | +// with custom formatting for constants |
| 28 | +const jsTarget = new JavaScriptTarget(); |
| 29 | +const customTarget = jsTarget.createTarget({ |
| 30 | + // Override variable formatting |
| 31 | + var: (id) => { |
| 32 | + // Map common constants to uppercase |
| 33 | + const constants = { |
| 34 | + Pi: 'PI', |
| 35 | + ExponentialE: 'E', |
| 36 | + ImaginaryUnit: 'I', |
| 37 | + }; |
| 38 | + if (id in constants) return constants[id]; |
| 39 | + return `VAR_${id.toUpperCase()}`; |
| 40 | + }, |
| 41 | + // Add custom preamble |
| 42 | + preamble: '// Generated by Custom Calculator DSL\n', |
| 43 | +}); |
| 44 | + |
| 45 | +const expr1 = ce.parse('2\\pi + e'); |
| 46 | +const code1 = BaseCompiler.compile(expr1, customTarget); |
| 47 | +console.log('Expression:', expr1.toString()); |
| 48 | +console.log('Compiled:', code1); |
| 49 | +console.log(); |
| 50 | + |
| 51 | +// ============================================================================ |
| 52 | +// Example 2: Custom Target with Function Calls |
| 53 | +// ============================================================================ |
| 54 | +console.log('Example 2: Custom Target with All Function Calls'); |
| 55 | +console.log('='.repeat(70)); |
| 56 | + |
| 57 | +// Create a target where ALL operations are function calls |
| 58 | +// (useful for DSLs that don't support infix operators) |
| 59 | +const functionTarget = { |
| 60 | + language: 'calculator-dsl', |
| 61 | + operators: (op) => { |
| 62 | + // Map all operators to uppercase function names |
| 63 | + const opMap = { |
| 64 | + Add: ['ADD', 11], |
| 65 | + Subtract: ['SUB', 11], |
| 66 | + Multiply: ['MUL', 12], |
| 67 | + Divide: ['DIV', 13], |
| 68 | + Power: ['POW', 14], |
| 69 | + Negate: ['NEG', 14], |
| 70 | + Equal: ['EQ', 8], |
| 71 | + Less: ['LT', 9], |
| 72 | + Greater: ['GT', 9], |
| 73 | + }; |
| 74 | + return opMap[op]; |
| 75 | + }, |
| 76 | + functions: (id) => { |
| 77 | + // Map common functions to uppercase |
| 78 | + const fnMap = { |
| 79 | + Sin: 'SIN', |
| 80 | + Cos: 'COS', |
| 81 | + Tan: 'TAN', |
| 82 | + Sqrt: 'SQRT', |
| 83 | + Abs: 'ABS', |
| 84 | + Exp: 'EXP', |
| 85 | + Log: 'LOG', |
| 86 | + Ln: 'LN', |
| 87 | + }; |
| 88 | + return fnMap[id]; |
| 89 | + }, |
| 90 | + var: (id) => `VAR("${id}")`, |
| 91 | + string: (s) => `"${s}"`, |
| 92 | + number: (n) => n.toString(), |
| 93 | + ws: () => ' ', |
| 94 | + preamble: '', |
| 95 | + indent: 0, |
| 96 | +}; |
| 97 | + |
| 98 | +const expr2 = ce.parse('\\sin(x) + \\cos(y)^2'); |
| 99 | +const code2 = BaseCompiler.compile(expr2, functionTarget); |
| 100 | +console.log('Expression:', expr2.toString()); |
| 101 | +console.log('Compiled:', code2); |
| 102 | +console.log(); |
| 103 | + |
| 104 | +// ============================================================================ |
| 105 | +// Example 3: RPN (Reverse Polish Notation) Target |
| 106 | +// ============================================================================ |
| 107 | +console.log('Example 3: Custom RPN (Reverse Polish Notation) Target'); |
| 108 | +console.log('='.repeat(70)); |
| 109 | + |
| 110 | +// Create a target that outputs RPN (like HP calculators) |
| 111 | +// We'll use a custom compilation approach |
| 112 | +class RPNTarget { |
| 113 | + constructor() { |
| 114 | + this.jsTarget = new JavaScriptTarget(); |
| 115 | + } |
| 116 | + |
| 117 | + compile(expr) { |
| 118 | + // For RPN, we need to traverse the expression tree differently |
| 119 | + // This is a simplified example |
| 120 | + return this.compileToRPN(expr); |
| 121 | + } |
| 122 | + |
| 123 | + compileToRPN(expr) { |
| 124 | + if (expr.symbol) return expr.symbol; |
| 125 | + if (expr.isNumberLiteral) return expr.re.toString(); |
| 126 | + |
| 127 | + const op = expr.operator; |
| 128 | + const args = expr.ops || []; |
| 129 | + |
| 130 | + // Compile arguments first (postorder traversal) |
| 131 | + const compiledArgs = args.map((arg) => this.compileToRPN(arg)); |
| 132 | + |
| 133 | + // Map operators to RPN commands |
| 134 | + const opMap = { |
| 135 | + Add: '+', |
| 136 | + Subtract: '-', |
| 137 | + Multiply: '*', |
| 138 | + Divide: '/', |
| 139 | + Power: '^', |
| 140 | + Sin: 'SIN', |
| 141 | + Cos: 'COS', |
| 142 | + Sqrt: 'SQRT', |
| 143 | + }; |
| 144 | + |
| 145 | + const rpnOp = opMap[op] || op; |
| 146 | + |
| 147 | + // In RPN: operands first, then operator |
| 148 | + return `${compiledArgs.join(' ')} ${rpnOp}`; |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +const rpnTarget = new RPNTarget(); |
| 153 | +const expr3 = ce.parse('(3 + 4) * 5'); |
| 154 | +const rpn = rpnTarget.compile(expr3); |
| 155 | +console.log('Expression:', expr3.toString()); |
| 156 | +console.log('RPN:', rpn); |
| 157 | +console.log('(Stack-based evaluation: push 3, push 4, add, push 5, multiply)'); |
| 158 | +console.log(); |
| 159 | + |
| 160 | +// ============================================================================ |
| 161 | +// Example 4: SQL-like Target |
| 162 | +// ============================================================================ |
| 163 | +console.log('Example 4: SQL-like Expression Target'); |
| 164 | +console.log('='.repeat(70)); |
| 165 | + |
| 166 | +// Create a target for SQL-like expressions |
| 167 | +const sqlTarget = { |
| 168 | + language: 'sql', |
| 169 | + operators: (op) => { |
| 170 | + const opMap = { |
| 171 | + Add: ['+', 11], |
| 172 | + Subtract: ['-', 11], |
| 173 | + Multiply: ['*', 12], |
| 174 | + Divide: ['/', 13], |
| 175 | + Equal: ['=', 8], |
| 176 | + NotEqual: ['<>', 8], |
| 177 | + Less: ['<', 9], |
| 178 | + Greater: ['>', 9], |
| 179 | + LessEqual: ['<=', 9], |
| 180 | + GreaterEqual: ['>=', 9], |
| 181 | + And: ['AND', 4], |
| 182 | + Or: ['OR', 3], |
| 183 | + Not: ['NOT', 14], |
| 184 | + }; |
| 185 | + return opMap[op]; |
| 186 | + }, |
| 187 | + functions: (id) => { |
| 188 | + const fnMap = { |
| 189 | + Abs: 'ABS', |
| 190 | + Sqrt: 'SQRT', |
| 191 | + Power: 'POWER', |
| 192 | + Ceiling: 'CEILING', |
| 193 | + Floor: 'FLOOR', |
| 194 | + Round: 'ROUND', |
| 195 | + }; |
| 196 | + return fnMap[id]; |
| 197 | + }, |
| 198 | + var: (id) => `"${id}"`, // Quote column names |
| 199 | + string: (s) => `'${s.replace(/'/g, "''")}'`, // SQL string escaping |
| 200 | + number: (n) => n.toString(), |
| 201 | + ws: () => ' ', |
| 202 | + preamble: '', |
| 203 | + indent: 0, |
| 204 | +}; |
| 205 | + |
| 206 | +const expr4 = ce.parse('x > 10 \\land y \\leq 20'); |
| 207 | +const sql = BaseCompiler.compile(expr4, sqlTarget); |
| 208 | +console.log('Expression:', expr4.toString()); |
| 209 | +console.log('SQL WHERE clause:', `WHERE ${sql}`); |
| 210 | +console.log(); |
| 211 | + |
| 212 | +// ============================================================================ |
| 213 | +// Example 5: Pretty-Print Target with Indentation |
| 214 | +// ============================================================================ |
| 215 | +console.log('Example 5: Pretty-Print Target with Indentation'); |
| 216 | +console.log('='.repeat(70)); |
| 217 | + |
| 218 | +// Create a target that pretty-prints with indentation |
| 219 | +const prettyTarget = { |
| 220 | + language: 'pretty', |
| 221 | + operators: (op) => { |
| 222 | + const opMap = { |
| 223 | + Add: ['add', 11], |
| 224 | + Multiply: ['multiply', 12], |
| 225 | + Power: ['power', 14], |
| 226 | + }; |
| 227 | + return opMap[op]; |
| 228 | + }, |
| 229 | + functions: (id) => id.toLowerCase(), |
| 230 | + var: (id) => id, |
| 231 | + string: (s) => `"${s}"`, |
| 232 | + number: (n) => n.toString(), |
| 233 | + ws: (s) => { |
| 234 | + if (s === '\n') return '\n '; // Add indentation |
| 235 | + return s || ''; |
| 236 | + }, |
| 237 | + preamble: '', |
| 238 | + indent: 0, |
| 239 | +}; |
| 240 | + |
| 241 | +const expr5 = ce.parse('(a + b) * (c + d)'); |
| 242 | +const pretty = BaseCompiler.compile(expr5, prettyTarget); |
| 243 | +console.log('Expression:', expr5.toString()); |
| 244 | +console.log('Pretty-printed:'); |
| 245 | +console.log(pretty); |
| 246 | +console.log(); |
| 247 | + |
| 248 | +// ============================================================================ |
| 249 | +// Summary |
| 250 | +// ============================================================================ |
| 251 | +console.log('Summary'); |
| 252 | +console.log('='.repeat(70)); |
| 253 | +console.log('✓ Extended JavaScriptTarget with custom variable mapping'); |
| 254 | +console.log('✓ Created all-function-call target for legacy systems'); |
| 255 | +console.log('✓ Implemented custom RPN compilation'); |
| 256 | +console.log('✓ Created SQL-like expression target'); |
| 257 | +console.log('✓ Built pretty-print target with formatting'); |
| 258 | +console.log(); |
| 259 | +console.log('The CompileTarget interface enables:'); |
| 260 | +console.log(' • Custom operator and function mappings'); |
| 261 | +console.log(' • Domain-specific language generation'); |
| 262 | +console.log(' • Code generation for external systems'); |
| 263 | +console.log(' • Alternative evaluation strategies'); |
0 commit comments