@soundor/expression 0.0.0-stage → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-Db6XRMc8.mjs","names":[],"sources":["../src/ast.ts","../src/types.ts","../src/check.ts"],"sourcesContent":["/**\n * The expression AST: plain, immutable, JSON-serializable objects. Every node\n * carries its result type, so a tree means the same thing wherever it is read,\n * and {@link Expression} can tell a `float32` expression from an `int32` one.\n *\n * There are no loops, assignments, calls of user functions, or anything with\n * an effect: an expression is a pure function of its inputs. Two forms bind a\n * name, immutably: `let` names a value used more than once, and `map` the\n * value of a nullable that is not null.\n */\n\nimport type { ExpressionType, NullableType, NumericType } from './types';\n\n/** Operators of one operand. */\nexport const UNARY_OPERATORS = [\n 'neg',\n 'abs',\n 'sqrt',\n 'exp',\n 'log',\n 'floor',\n 'ceil',\n 'round',\n 'not',\n 'trim',\n 'lower',\n] as const;\n\n/** Operators of two operands. */\nexport const BINARY_OPERATORS = [\n 'add',\n 'sub',\n 'mul',\n 'div',\n 'min',\n 'max',\n 'pow',\n 'eq',\n 'ne',\n 'lt',\n 'lte',\n 'gt',\n 'gte',\n 'and',\n 'or',\n 'endsWith',\n 'removeSuffix',\n] as const;\n\nexport type UnaryOperator = (typeof UNARY_OPERATORS)[number];\nexport type BinaryOperator = (typeof BINARY_OPERATORS)[number];\n\n/**\n * A literal. Numbers of up to 32 bits and floats are JSON numbers (finite);\n * 64-bit integers are decimal strings, so a tree survives JSON. `null` only\n * for a nullable type.\n */\nexport interface LiteralNode {\n readonly kind: 'literal';\n readonly type: ExpressionType;\n readonly value: number | string | boolean | null;\n}\n\n/** A value the expression is evaluated with, bound by its context. */\nexport interface InputNode {\n readonly kind: 'input';\n readonly type: ExpressionType;\n readonly name: string;\n}\n\n/** The value a `map` binds. */\nexport interface VariableNode {\n readonly kind: 'variable';\n readonly type: ExpressionType;\n readonly name: string;\n}\n\nexport interface UnaryNode {\n readonly kind: 'unary';\n readonly type: ExpressionType;\n readonly operator: UnaryOperator;\n readonly operand: ExpressionNode;\n}\n\nexport interface BinaryNode {\n readonly kind: 'binary';\n readonly type: ExpressionType;\n readonly operator: BinaryOperator;\n readonly left: ExpressionNode;\n readonly right: ExpressionNode;\n}\n\nexport interface IfNode {\n readonly kind: 'if';\n readonly type: ExpressionType;\n readonly condition: ExpressionNode;\n readonly then: ExpressionNode;\n readonly else: ExpressionNode;\n}\n\n/** A numeric value as another numeric type. */\nexport interface ConvertNode {\n readonly kind: 'convert';\n readonly type: NumericType;\n readonly operand: ExpressionNode;\n}\n\n/** A number in decimal notation with a fixed number of decimals. */\nexport interface FixedNode {\n readonly kind: 'fixed';\n readonly type: 'string';\n readonly operand: ExpressionNode;\n readonly digits: number;\n}\n\nexport interface ConcatNode {\n readonly kind: 'concat';\n readonly type: 'string';\n readonly operands: readonly ExpressionNode[];\n}\n\n/** Text read as a number of `type`'s base type, or `null`. */\nexport interface ParseNode {\n readonly kind: 'parse';\n readonly type: NullableType;\n readonly operand: ExpressionNode;\n}\n\n/** A nullable's value, or `fallback` when it is null. */\nexport interface CoalesceNode {\n readonly kind: 'coalesce';\n readonly type: ExpressionType;\n readonly operand: ExpressionNode;\n readonly fallback: ExpressionNode;\n}\n\nexport interface IsNullNode {\n readonly kind: 'isNull';\n readonly type: 'boolean';\n readonly operand: ExpressionNode;\n}\n\n/**\n * `null` when `operand` is null; otherwise `body`, in which `variable` is\n * the operand's value.\n */\nexport interface MapNode {\n readonly kind: 'map';\n readonly type: NullableType;\n readonly operand: ExpressionNode;\n readonly variable: string;\n readonly body: ExpressionNode;\n}\n\n/** `body`, in which `variable` is the value of `value` (computed once). */\nexport interface LetNode {\n readonly kind: 'let';\n readonly type: ExpressionType;\n readonly value: ExpressionNode;\n readonly variable: string;\n readonly body: ExpressionNode;\n}\n\nexport type ExpressionNode =\n | LiteralNode\n | InputNode\n | VariableNode\n | UnaryNode\n | BinaryNode\n | IfNode\n | ConvertNode\n | FixedNode\n | ConcatNode\n | ParseNode\n | CoalesceNode\n | IsNullNode\n | MapNode\n | LetNode;\n\n/**\n * An expression whose value is of type `T`. Its `type` keeps expressions of\n * different types apart even where both are a JavaScript `number`.\n */\nexport type Expression<T extends ExpressionType = ExpressionType> =\n ExpressionNode & { readonly type: T };\n\nexport type ExpressionKind = ExpressionNode['kind'];\n\n/** Whether `value` looks like an expression node (its shape is checked by `checkExpression`). */\nexport function isExpression(value: unknown): value is Expression {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { kind?: unknown }).kind === 'string' &&\n typeof (value as { type?: unknown }).type === 'string'\n );\n}\n","/**\n * The types of the expression language. They say what a value is (a 32-bit\n * float, a 16-bit unsigned integer), not what one language calls it, so the\n * same expression means the same thing in every language it compiles to.\n */\n\n/** Fixed-width integers. Up to 32 bits a JavaScript number, 64 bits a bigint. */\nexport const INTEGER_TYPES = [\n 'int8',\n 'uint8',\n 'int16',\n 'uint16',\n 'int32',\n 'uint32',\n 'int64',\n 'uint64',\n] as const;\n\n/** IEEE 754 binary32 and binary64. */\nexport const FLOAT_TYPES = ['float32', 'float64'] as const;\n\nexport const NUMERIC_TYPES = [...INTEGER_TYPES, ...FLOAT_TYPES] as const;\n\nexport const SCALAR_TYPES = [...NUMERIC_TYPES, 'boolean', 'string'] as const;\n\nexport type IntegerType = (typeof INTEGER_TYPES)[number];\nexport type FloatType = (typeof FLOAT_TYPES)[number];\nexport type NumericType = (typeof NUMERIC_TYPES)[number];\n/** The integers JavaScript holds as a bigint. */\nexport type BigIntType = 'int64' | 'uint64';\nexport type ScalarType = (typeof SCALAR_TYPES)[number];\n/** A number or `null`: what parsing text gives. */\nexport type NullableType = `${NumericType}?`;\n/** The type of an expression's value. */\nexport type ExpressionType = ScalarType | NullableType;\n\n/** `float32` of `float32?`; a non-nullable type stays as it is. */\nexport type BaseType<T extends ExpressionType> = T extends `${infer B}?`\n ? B & NumericType\n : T;\n\n/** The JavaScript value of a type: what compiled JavaScript computes. */\nexport type JsValue<T extends ExpressionType> = T extends BigIntType\n ? bigint\n : T extends NumericType\n ? number\n : T extends 'boolean'\n ? boolean\n : T extends 'string'\n ? string\n : T extends `${infer B}?`\n ? JsValue<B & NumericType> | null\n : never;\n\n/** What a literal of a type is written as in the builder. */\nexport type LiteralValue<T extends ExpressionType> = T extends BigIntType\n ? bigint\n : T extends NumericType\n ? number\n : T extends 'boolean'\n ? boolean\n : T extends 'string'\n ? string\n : never;\n\nexport interface IntegerInfo {\n readonly bits: 8 | 16 | 32 | 64;\n readonly signed: boolean;\n readonly min: bigint;\n readonly max: bigint;\n}\n\nfunction integer(bits: 8 | 16 | 32 | 64, signed: boolean): IntegerInfo {\n const width = BigInt(bits);\n return signed\n ? {\n bits,\n signed,\n min: -(1n << (width - 1n)),\n max: (1n << (width - 1n)) - 1n,\n }\n : { bits, signed, min: 0n, max: (1n << width) - 1n };\n}\n\n/** Width, signedness and bounds of every integer type. */\nexport const INTEGERS: Readonly<Record<IntegerType, IntegerInfo>> = {\n int8: integer(8, true),\n uint8: integer(8, false),\n int16: integer(16, true),\n uint16: integer(16, false),\n int32: integer(32, true),\n uint32: integer(32, false),\n int64: integer(64, true),\n uint64: integer(64, false),\n};\n\nexport function isIntegerType(type: string): type is IntegerType {\n return (INTEGER_TYPES as readonly string[]).includes(type);\n}\n\nexport function isFloatType(type: string): type is FloatType {\n return type === 'float32' || type === 'float64';\n}\n\nexport function isNumericType(type: string): type is NumericType {\n return isIntegerType(type) || isFloatType(type);\n}\n\nexport function isBigIntType(type: string): type is BigIntType {\n return type === 'int64' || type === 'uint64';\n}\n\nexport function isNullableType(type: string): type is NullableType {\n return type.endsWith('?') && isNumericType(type.slice(0, -1));\n}\n\nexport function isExpressionType(type: unknown): type is ExpressionType {\n return (\n typeof type === 'string' &&\n ((SCALAR_TYPES as readonly string[]).includes(type) || isNullableType(type))\n );\n}\n\n/** The base type of a nullable type, or the type itself. */\nexport function baseType(type: ExpressionType): ScalarType {\n return isNullableType(type) ? (type.slice(0, -1) as NumericType) : type;\n}\n","/**\n * Build-time validation of an expression tree. TypeScript keeps the builder's\n * output well typed, but a tree can also come from JSON or be built by hand,\n * so compilers check every tree they are given: its shape, every node's type,\n * literals within their type, inputs and variables that exist.\n */\n\nimport { BINARY_OPERATORS, UNARY_OPERATORS, type ExpressionNode } from './ast';\nimport {\n INTEGERS,\n baseType,\n isBigIntType,\n isExpressionType,\n isFloatType,\n isIntegerType,\n isNullableType,\n isNumericType,\n type ExpressionType,\n} from './types';\n\n/** A problem in a tree, at a path such as `left.operand`. */\nexport interface ExpressionIssue {\n readonly path: string;\n readonly message: string;\n}\n\n/** Thrown by compilers for a tree that fails {@link checkExpression}. */\nexport class ExpressionError extends Error {\n readonly issues: readonly ExpressionIssue[];\n\n constructor(message: string, issues: readonly ExpressionIssue[]) {\n super(\n `${message}\\n${issues.map((issue) => ` ${issue.path || '(root)'}: ${issue.message}`).join('\\n')}`,\n );\n this.name = 'ExpressionError';\n this.issues = issues;\n }\n}\n\n/** The inputs an expression is evaluated with, by name. */\nexport type ExpressionInputs = Readonly<Record<string, ExpressionType>>;\n\nexport interface CheckOptions {\n /** The type the expression must have. */\n readonly expected?: ExpressionType;\n}\n\n/** The deepest tree accepted; compilers recurse. */\nexport const MAX_DEPTH = 128;\n/** The most nodes in one tree. */\nexport const MAX_NODES = 10_000;\n/** The most decimals `fixed` writes. */\nexport const MAX_FIXED_DIGITS = 15;\n\n/** Every problem in `node`, evaluated with `inputs`; empty when it is valid. */\nexport function checkExpression(\n node: unknown,\n inputs: ExpressionInputs,\n options: CheckOptions = {},\n): ExpressionIssue[] {\n const issues: ExpressionIssue[] = [];\n const state = { nodes: 0 };\n const type = visit(node, '', 0, inputs, new Map(), issues, state);\n if (\n type !== undefined &&\n options.expected !== undefined &&\n type !== options.expected\n ) {\n issues.push({\n path: '',\n message: `expected an expression of type '${options.expected}', got '${type}'`,\n });\n }\n return issues;\n}\n\n/** Throws {@link ExpressionError} when `node` is not valid. */\nexport function assertExpression(\n node: unknown,\n inputs: ExpressionInputs,\n options: CheckOptions = {},\n): asserts node is ExpressionNode {\n const issues = checkExpression(node, inputs, options);\n if (issues.length > 0)\n throw new ExpressionError('Invalid expression.', issues);\n}\n\nconst join = (path: string, key: string): string =>\n path === '' ? key : `${path}.${key}`;\n\nfunction visit(\n value: unknown,\n path: string,\n depth: number,\n inputs: ExpressionInputs,\n scope: Map<string, ExpressionType>,\n issues: ExpressionIssue[],\n state: { nodes: number },\n): ExpressionType | undefined {\n const fail = (message: string, at = path): undefined => {\n issues.push({ path: at, message });\n return undefined;\n };\n if (++state.nodes > MAX_NODES) {\n if (state.nodes === MAX_NODES + 1)\n return fail(`an expression has at most ${MAX_NODES} nodes`);\n return undefined;\n }\n if (depth > MAX_DEPTH)\n return fail(`an expression nests at most ${MAX_DEPTH} levels deep`);\n if (typeof value !== 'object' || value === null)\n return fail('expected an expression node');\n const node = value as Record<string, unknown> & { kind?: unknown };\n const type = node['type'];\n if (!isExpressionType(type))\n return fail(`unknown expression type ${JSON.stringify(type)}`);\n const child = (key: string): ExpressionType | undefined =>\n visit(node[key], join(path, key), depth + 1, inputs, scope, issues, state);\n const expect = (actual: ExpressionType, message: string) => {\n if (actual !== type)\n fail(`${message}: the node says '${type}', its value is '${actual}'`);\n };\n\n switch (node.kind) {\n case 'literal':\n return checkLiteral(type, node['value'], path, issues);\n case 'input':\n case 'variable': {\n const name = node['name'];\n if (typeof name !== 'string') return fail('expected a name');\n const declared = node.kind === 'input' ? inputs[name] : scope.get(name);\n if (declared === undefined) {\n return fail(\n node.kind === 'input'\n ? `unknown input '${name}'; this expression has ${\n Object.keys(inputs).length === 0\n ? 'no inputs'\n : Object.keys(inputs)\n .map((input) => `'${input}'`)\n .join(', ')\n }`\n : `variable '${name}' is not bound by an enclosing let or map`,\n );\n }\n if (declared !== type)\n return fail(`'${name}' is '${declared}', not '${type}'`);\n return type;\n }\n case 'unary': {\n const operator = node['operator'];\n if (!(UNARY_OPERATORS as readonly unknown[]).includes(operator))\n return fail(`unknown unary operator ${JSON.stringify(operator)}`);\n const operand = child('operand');\n if (operand === undefined) return undefined;\n let result: ExpressionType | undefined;\n switch (operator) {\n case 'neg':\n case 'abs':\n if (isNumericType(operand)) result = operand;\n break;\n case 'sqrt':\n case 'exp':\n case 'log':\n case 'floor':\n case 'ceil':\n case 'round':\n if (isFloatType(operand)) result = operand;\n break;\n case 'not':\n if (operand === 'boolean') result = operand;\n break;\n case 'trim':\n case 'lower':\n if (operand === 'string') result = operand;\n break;\n }\n if (result === undefined)\n return fail(`'${String(operator)}' does not take a '${operand}'`);\n expect(result, `'${String(operator)}'`);\n return type;\n }\n case 'binary': {\n const operator = node['operator'];\n if (!(BINARY_OPERATORS as readonly unknown[]).includes(operator))\n return fail(`unknown binary operator ${JSON.stringify(operator)}`);\n const left = child('left');\n const right = child('right');\n if (left === undefined || right === undefined) return undefined;\n if (left !== right)\n return fail(\n `'${String(operator)}' takes two operands of one type, got '${left}' and '${right}'; convert one explicitly`,\n );\n let result: ExpressionType | undefined;\n switch (operator) {\n case 'add':\n case 'sub':\n case 'mul':\n case 'div':\n case 'min':\n case 'max':\n if (isNumericType(left)) result = left;\n break;\n case 'pow':\n if (isFloatType(left)) result = left;\n break;\n case 'eq':\n case 'ne':\n if (!isNullableType(left)) result = 'boolean';\n break;\n case 'lt':\n case 'lte':\n case 'gt':\n case 'gte':\n if (isNumericType(left)) result = 'boolean';\n break;\n case 'and':\n case 'or':\n if (left === 'boolean') result = 'boolean';\n break;\n case 'endsWith':\n if (left === 'string') result = 'boolean';\n break;\n case 'removeSuffix':\n if (left === 'string') result = 'string';\n break;\n }\n if (result === undefined)\n return fail(`'${String(operator)}' does not take '${left}' operands`);\n expect(result, `'${String(operator)}'`);\n return type;\n }\n case 'if': {\n const condition = child('condition');\n const then = child('then');\n const otherwise = child('else');\n if (\n condition === undefined ||\n then === undefined ||\n otherwise === undefined\n )\n return undefined;\n if (condition !== 'boolean')\n return fail(\n `a condition is 'boolean', got '${condition}'`,\n join(path, 'condition'),\n );\n const joined = joinTypes(then, otherwise);\n if (joined === undefined)\n return fail(\n `the branches have different types, '${then}' and '${otherwise}'`,\n );\n expect(joined, 'if');\n return type;\n }\n case 'convert': {\n const operand = child('operand');\n if (operand === undefined) return undefined;\n if (!isNumericType(operand) || !isNumericType(type))\n return fail(\n `convert turns a number into another number, not '${operand}' into '${type}'`,\n );\n return type;\n }\n case 'fixed': {\n const operand = child('operand');\n if (operand === undefined) return undefined;\n const digits = node['digits'];\n if (\n typeof digits !== 'number' ||\n !Number.isInteger(digits) ||\n digits < 0 ||\n digits > MAX_FIXED_DIGITS\n )\n return fail(\n `fixed writes 0 to ${MAX_FIXED_DIGITS} decimals, got ${JSON.stringify(digits)}`,\n join(path, 'digits'),\n );\n if (!isNumericType(operand))\n return fail(`fixed formats a number, not a '${operand}'`);\n expect('string', 'fixed');\n return type;\n }\n case 'concat': {\n const operands = node['operands'];\n if (!Array.isArray(operands) || operands.length === 0)\n return fail('concat takes at least one string');\n let ok = true;\n operands.forEach((operand, index) => {\n const at = join(path, `operands[${index}]`);\n const operandType = visit(\n operand,\n at,\n depth + 1,\n inputs,\n scope,\n issues,\n state,\n );\n if (operandType === undefined) ok = false;\n else if (operandType !== 'string') {\n ok = false;\n fail(\n `concat takes strings, got '${operandType}'; format numbers with fixed`,\n at,\n );\n }\n });\n if (!ok) return undefined;\n expect('string', 'concat');\n return type;\n }\n case 'parse': {\n const operand = child('operand');\n if (operand === undefined) return undefined;\n if (operand !== 'string')\n return fail(`parse reads a string, not a '${operand}'`);\n if (!isNullableType(type))\n return fail(`parse gives a nullable number, not '${type}'`);\n return type;\n }\n case 'coalesce': {\n const operand = child('operand');\n const fallback = child('fallback');\n if (operand === undefined || fallback === undefined) return undefined;\n if (!isNullableType(operand))\n return fail(`coalesce takes a nullable value, not a '${operand}'`);\n if (fallback !== baseType(operand))\n return fail(\n `the fallback of a '${operand}' is a '${baseType(operand)}', not a '${fallback}'`,\n );\n expect(fallback, 'coalesce');\n return type;\n }\n case 'isNull': {\n const operand = child('operand');\n if (operand === undefined) return undefined;\n if (!isNullableType(operand))\n return fail(`isNull takes a nullable value, not a '${operand}'`);\n expect('boolean', 'isNull');\n return type;\n }\n case 'map': {\n const operand = child('operand');\n if (operand === undefined) return undefined;\n const variable = node['variable'];\n if (typeof variable !== 'string' || variable === '')\n return fail('a map names its variable', join(path, 'variable'));\n if (scope.has(variable))\n return fail(\n `variable '${variable}' is already bound`,\n join(path, 'variable'),\n );\n if (!isNullableType(operand))\n return fail(`map takes a nullable value, not a '${operand}'`);\n const inner = new Map(scope).set(variable, baseType(operand));\n const body = visit(\n node['body'],\n join(path, 'body'),\n depth + 1,\n inputs,\n inner,\n issues,\n state,\n );\n if (body === undefined) return undefined;\n if (!isNumericType(body) && !isNullableType(body))\n return fail(\n `a map gives a number, not a '${body}'`,\n join(path, 'body'),\n );\n expect(isNullableType(body) ? body : `${body}?`, 'map');\n return type;\n }\n case 'let': {\n const value = child('value');\n if (value === undefined) return undefined;\n const variable = node['variable'];\n if (typeof variable !== 'string' || variable === '')\n return fail('a let names its variable', join(path, 'variable'));\n if (scope.has(variable))\n return fail(\n `variable '${variable}' is already bound`,\n join(path, 'variable'),\n );\n const inner = new Map(scope).set(variable, value);\n const body = visit(\n node['body'],\n join(path, 'body'),\n depth + 1,\n inputs,\n inner,\n issues,\n state,\n );\n if (body === undefined) return undefined;\n expect(body, 'let');\n return type;\n }\n default:\n return fail(`unknown expression kind ${JSON.stringify(node.kind)}`);\n }\n}\n\n/** The type of `if (c) a else b`: equal types, or a number and its nullable. */\nexport function joinTypes(\n a: ExpressionType,\n b: ExpressionType,\n): ExpressionType | undefined {\n if (a === b) return a;\n if (baseType(a) === baseType(b) && isNumericType(baseType(a)))\n return `${baseType(a)}?` as ExpressionType;\n return undefined;\n}\n\nfunction checkLiteral(\n type: ExpressionType,\n value: unknown,\n path: string,\n issues: ExpressionIssue[],\n): ExpressionType | undefined {\n const problem = literalProblem(type, value);\n if (problem !== undefined) {\n issues.push({ path, message: problem });\n return undefined;\n }\n return type;\n}\n\n/** Why `value` is not a literal of `type`, or undefined when it is. */\nexport function literalProblem(\n type: ExpressionType,\n value: unknown,\n): string | undefined {\n if (isNullableType(type)) {\n return value === null ? undefined : literalProblem(baseType(type), value);\n }\n if (type === 'boolean')\n return typeof value === 'boolean'\n ? undefined\n : `expected a boolean, got ${describe(value)}`;\n if (type === 'string')\n return typeof value === 'string'\n ? undefined\n : `expected a string, got ${describe(value)}`;\n if (isFloatType(type)) {\n if (typeof value !== 'number' || !Number.isFinite(value))\n return `a '${type}' literal is a finite number, got ${describe(value)}`;\n return undefined;\n }\n if (isIntegerType(type)) {\n const { min, max } = INTEGERS[type];\n let integer: bigint;\n if (isBigIntType(type)) {\n if (typeof value !== 'string' || !/^-?(0|[1-9][0-9]*)$/.test(value))\n return `a '${type}' literal is a decimal string, got ${describe(value)}`;\n integer = BigInt(value);\n } else {\n if (\n typeof value !== 'number' ||\n !Number.isInteger(value) ||\n Object.is(value, -0)\n )\n return `a '${type}' literal is an integer, got ${describe(value)}`;\n integer = BigInt(value);\n }\n if (integer < min || integer > max)\n return `${String(integer)} is outside '${type}' (${min} to ${max})`;\n return undefined;\n }\n return `no literal of type '${type}'`;\n}\n\nfunction describe(value: unknown): string {\n if (typeof value === 'bigint') return `${value}n`;\n if (typeof value === 'number' && Object.is(value, -0)) return '-0';\n return typeof value === 'string' ? JSON.stringify(value) : String(value);\n}\n"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Compiles expressions to C++17: a C++ expression per tree, over helpers
3
+ * ({@link cppRuntime}) that give it the language's semantics — the same as the
4
+ * JavaScript compiler's, operation for operation. The output is plain C++ of
5
+ * the standard library: no framework, no allocation except for strings, no
6
+ * exceptions; reusable by any C++ plugin framework.
7
+ *
8
+ * Emit {@link CPP_PRAGMAS} at the top of the file and {@link CPP_INCLUDES},
9
+ * then `cppRuntime()` in the namespace the compiled code is in. The pragmas
10
+ * stop the compiler from fusing a multiplication and an addition (which
11
+ * JavaScript never does), so both languages compute the same bits. Code
12
+ * built with `-ffast-math` does not have these semantics.
13
+ */
14
+ import { P as ExpressionType, r as ExpressionInputs } from "../check-DMbokSSO.mjs";
15
+ /** The namespace {@link cppRuntime} declares, which compiled code calls. */
16
+ export declare const CPP_RUNTIME_NAMESPACE = "soundor_expression";
17
+ /** The headers compiled code and the runtime need. */
18
+ export declare const CPP_INCLUDES: readonly string[];
19
+ /**
20
+ * File-scope pragmas: no floating-point contraction in this file, and no
21
+ * warning for comparing floats exactly, which the language defines.
22
+ */
23
+ export declare const CPP_PRAGMAS = "#if defined(__clang__)\n#pragma clang fp contract(off)\n#pragma clang diagnostic ignored \"-Wfloat-equal\"\n#elif defined(__GNUC__)\n#pragma GCC optimize(\"fp-contract=off\")\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#elif defined(_MSC_VER)\n#pragma fp_contract(off)\n#endif\n";
24
+ /** The C++ type of an expression type. */
25
+ export declare function cppType(type: ExpressionType): string;
26
+ /** The helpers compiled code calls, in `namespace soundor_expression`. */
27
+ export declare function cppRuntime(): string;
28
+ export interface CompileCppOptions {
29
+ /** The C++ name of each input; by default its name. */
30
+ readonly names?: Readonly<Record<string, string>>;
31
+ }
32
+ /**
33
+ * The C++ expression that computes `expression` from its `inputs` (of the C++
34
+ * types {@link cppType} gives). Checks the tree first; throws
35
+ * `ExpressionError` for an invalid one.
36
+ */
37
+ export declare function compileCpp(expression: unknown, inputs: ExpressionInputs, options?: CompileCppOptions): string;
38
+ /** A C++ string literal of `value`'s UTF-8 bytes, as a `std::string`. */
39
+ export declare function cppStringLiteral(value: string): string;
40
+ //# sourceMappingURL=cpp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cpp.d.mts","names":[],"sources":["../../src/compiler/cpp.ts"],"mappings":";;;;;;;;;;;;;;;qBA4Ba;;qBAGA;;;;;qBAgBA;;wBAcG,QAAQ,MAAM;;wBAiBd;iBA8TC;;WAEN,QAAQ,SAAS;;;;;;;wBAQZ,WACd,qBACA,QAAQ,kBACR,UAAS;;wBAYK,iBAAiB"}
@@ -0,0 +1,498 @@
1
+ import { _ as isFloatType, a as assertExpression, m as baseType, u as INTEGERS, v as isIntegerType, y as isNullableType } from "../check-Db6XRMc8.mjs";
2
+ // src/compiler/cpp.ts
3
+ /** The namespace {@link cppRuntime} declares, which compiled code calls. */
4
+ const CPP_RUNTIME_NAMESPACE = "soundor_expression";
5
+ /** The headers compiled code and the runtime need. */
6
+ const CPP_INCLUDES = [
7
+ "<cmath>",
8
+ "<cstddef>",
9
+ "<cstdint>",
10
+ "<cstdio>",
11
+ "<limits>",
12
+ "<optional>",
13
+ "<string>",
14
+ "<string_view>",
15
+ "<type_traits>"
16
+ ];
17
+ /**
18
+ * File-scope pragmas: no floating-point contraction in this file, and no
19
+ * warning for comparing floats exactly, which the language defines.
20
+ */
21
+ const CPP_PRAGMAS = `#if defined(__clang__)
22
+ #pragma clang fp contract(off)
23
+ #pragma clang diagnostic ignored "-Wfloat-equal"
24
+ #elif defined(__GNUC__)
25
+ #pragma GCC optimize("fp-contract=off")
26
+ #pragma GCC diagnostic ignored "-Wfloat-equal"
27
+ #elif defined(_MSC_VER)
28
+ #pragma fp_contract(off)
29
+ #endif
30
+ `;
31
+ const N = CPP_RUNTIME_NAMESPACE;
32
+ /** The C++ type of an expression type. */
33
+ function cppType(type) {
34
+ if (isNullableType(type)) return `std::optional<${cppType(baseType(type))}>`;
35
+ switch (type) {
36
+ case "boolean": return "bool";
37
+ case "string": return "std::string";
38
+ case "float32": return "float";
39
+ case "float64": return "double";
40
+ default: return `std::${type}_t`;
41
+ }
42
+ }
43
+ /** The helpers compiled code calls, in `namespace soundor_expression`. */
44
+ function cppRuntime() {
45
+ return `namespace ${N}
46
+ {
47
+ constexpr double pow10[] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
48
+ 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 };
49
+
50
+ // Integers wrap around (two's complement): computed modulo 2^64.
51
+ template <class T> inline T iadd(T a, T b) noexcept { return static_cast<T>(static_cast<std::uint64_t>(a) + static_cast<std::uint64_t>(b)); }
52
+ template <class T> inline T isub(T a, T b) noexcept { return static_cast<T>(static_cast<std::uint64_t>(a) - static_cast<std::uint64_t>(b)); }
53
+ template <class T> inline T imul(T a, T b) noexcept { return static_cast<T>(static_cast<std::uint64_t>(a) * static_cast<std::uint64_t>(b)); }
54
+ template <class T> inline T ineg(T a) noexcept { return static_cast<T>(std::uint64_t { 0 } - static_cast<std::uint64_t>(a)); }
55
+ template <class T> inline T iabs(T a) noexcept
56
+ {
57
+ if constexpr (std::is_signed_v<T>)
58
+ return a < 0 ? ineg(a) : a;
59
+ else
60
+ return a;
61
+ }
62
+ // Truncates toward zero; by zero gives 0, and the minimum by -1 wraps.
63
+ template <class T> inline T idiv(T a, T b) noexcept
64
+ {
65
+ if (b == 0)
66
+ return 0;
67
+ if constexpr (std::is_signed_v<T>)
68
+ if (a == std::numeric_limits<T>::min() && b == -1)
69
+ return a;
70
+ return static_cast<T>(a / b);
71
+ }
72
+ template <class T> inline T imin(T a, T b) noexcept { return b < a ? b : a; }
73
+ template <class T> inline T imax(T a, T b) noexcept { return a < b ? b : a; }
74
+
75
+ // JavaScript's Math.min and Math.max: NaN wins, -0 is below +0.
76
+ template <class F> inline F fmin(F a, F b) noexcept
77
+ {
78
+ if (a != a || b != b)
79
+ return std::numeric_limits<F>::quiet_NaN();
80
+ if (a == b)
81
+ return std::signbit(a) ? a : b;
82
+ return a < b ? a : b;
83
+ }
84
+ template <class F> inline F fmax(F a, F b) noexcept
85
+ {
86
+ if (a != a || b != b)
87
+ return std::numeric_limits<F>::quiet_NaN();
88
+ if (a == b)
89
+ return std::signbit(a) ? b : a;
90
+ return a < b ? b : a;
91
+ }
92
+
93
+ // To the nearest binary32, overflowing to infinity (as Math.fround).
94
+ inline float f32(double x) noexcept
95
+ {
96
+ constexpr double overflow = 3.4028235677973366e38; // halfway between FLT_MAX and 2^128
97
+ if (x >= overflow)
98
+ return std::numeric_limits<float>::infinity();
99
+ if (x <= -overflow)
100
+ return -std::numeric_limits<float>::infinity();
101
+ return static_cast<float>(x);
102
+ }
103
+ template <class F> inline F narrow(double x) noexcept
104
+ {
105
+ if constexpr (std::is_same_v<F, float>)
106
+ return f32(x);
107
+ else
108
+ return x;
109
+ }
110
+ template <class F> inline F fsqrt(F x) noexcept { return narrow<F>(std::sqrt(static_cast<double>(x))); }
111
+ template <class F> inline F fexp(F x) noexcept { return narrow<F>(std::exp(static_cast<double>(x))); }
112
+ template <class F> inline F flog(F x) noexcept { return narrow<F>(std::log(static_cast<double>(x))); }
113
+ // Math.pow: NaN for a NaN exponent, and for ±1 to an infinite power.
114
+ template <class F> inline F fpow(F a, F b) noexcept
115
+ {
116
+ const double x = a;
117
+ const double y = b;
118
+ if (y != y || ((x == 1.0 || x == -1.0) && std::isinf(y)))
119
+ return std::numeric_limits<F>::quiet_NaN();
120
+ return narrow<F>(std::pow(x, y));
121
+ }
122
+
123
+ // Toward zero, saturated, NaN to 0.
124
+ template <class T, class F> inline T toInt(F value) noexcept
125
+ {
126
+ const double x = static_cast<double>(value);
127
+ if (x != x)
128
+ return 0;
129
+ constexpr double low = static_cast<double>(std::numeric_limits<T>::min());
130
+ constexpr double limit = std::is_signed_v<T> ? -low : 2.0 * (static_cast<double>(std::numeric_limits<T>::max() / 2) + 1.0);
131
+ if (x <= low)
132
+ return std::numeric_limits<T>::min();
133
+ if (x >= limit)
134
+ return std::numeric_limits<T>::max();
135
+ return static_cast<T>(std::trunc(x));
136
+ }
137
+
138
+ inline std::string fixedFloat(double x, int digits)
139
+ {
140
+ if (x != x)
141
+ return "NaN";
142
+ if (std::isinf(x))
143
+ return x < 0 ? "-Infinity" : "Infinity";
144
+ const double scaled = x * pow10[digits];
145
+ if (std::isinf(scaled))
146
+ return x < 0 ? "-Infinity" : "Infinity";
147
+ const double n = std::round(scaled);
148
+ const double magnitude = std::fabs(n);
149
+ std::string text;
150
+ if (magnitude < 18446744073709551616.0)
151
+ text = std::to_string(static_cast<unsigned long long>(magnitude));
152
+ else
153
+ {
154
+ char buffer[512];
155
+ std::snprintf(buffer, sizeof buffer, "%.0f", magnitude);
156
+ text = buffer;
157
+ }
158
+ if (digits > 0)
159
+ {
160
+ const auto width = static_cast<std::size_t>(digits) + 1;
161
+ if (text.size() < width)
162
+ text.insert(0, width - text.size(), '0');
163
+ text.insert(text.size() - static_cast<std::size_t>(digits), 1, '.');
164
+ }
165
+ if (n < 0)
166
+ text.insert(0, 1, '-');
167
+ return text;
168
+ }
169
+
170
+ template <class T> inline std::string fixedInt(T x, int digits)
171
+ {
172
+ std::string text = std::to_string(x);
173
+ if (digits > 0)
174
+ {
175
+ text += '.';
176
+ text.append(static_cast<std::size_t>(digits), '0');
177
+ }
178
+ return text;
179
+ }
180
+
181
+ inline bool isSpace(char c) noexcept { return c == ' ' || c == '\\t' || c == '\\n' || c == '\\r' || c == '\\f' || c == '\\v'; }
182
+ inline bool isDigit(char c) noexcept { return c >= '0' && c <= '9'; }
183
+
184
+ inline std::string_view trimView(std::string_view text) noexcept
185
+ {
186
+ while (! text.empty() && isSpace(text.front()))
187
+ text.remove_prefix(1);
188
+ while (! text.empty() && isSpace(text.back()))
189
+ text.remove_suffix(1);
190
+ return text;
191
+ }
192
+ inline std::string trim(std::string_view text) { return std::string(trimView(text)); }
193
+ inline std::string lower(std::string text)
194
+ {
195
+ for (char& c : text)
196
+ if (c >= 'A' && c <= 'Z')
197
+ c = static_cast<char>(c - 'A' + 'a');
198
+ return text;
199
+ }
200
+ inline bool endsWith(std::string_view text, std::string_view suffix) noexcept
201
+ {
202
+ return text.size() >= suffix.size() && text.substr(text.size() - suffix.size()) == suffix;
203
+ }
204
+ inline std::string removeSuffix(std::string_view text, std::string_view suffix)
205
+ {
206
+ return std::string(endsWith(text, suffix) ? text.substr(0, text.size() - suffix.size()) : text);
207
+ }
208
+
209
+ template <class T> inline std::optional<T> parseInt(std::string_view text) noexcept
210
+ {
211
+ text = trimView(text);
212
+ std::size_t i = 0;
213
+ bool negative = false;
214
+ if (i < text.size() && (text[i] == '+' || text[i] == '-'))
215
+ negative = text[i++] == '-';
216
+ if (i == text.size())
217
+ return std::nullopt;
218
+ std::uint64_t magnitude = 0;
219
+ bool overflow = false;
220
+ for (; i < text.size(); ++i)
221
+ {
222
+ if (! isDigit(text[i]))
223
+ return std::nullopt;
224
+ const auto digit = static_cast<std::uint64_t>(text[i] - '0');
225
+ if (magnitude > (std::numeric_limits<std::uint64_t>::max() - digit) / 10)
226
+ overflow = true;
227
+ else
228
+ magnitude = magnitude * 10 + digit;
229
+ }
230
+ if (overflow)
231
+ return std::nullopt;
232
+ if (negative)
233
+ {
234
+ if constexpr (std::is_signed_v<T>)
235
+ {
236
+ const auto limit = static_cast<std::uint64_t>(-(std::numeric_limits<T>::min() + 1)) + 1;
237
+ if (magnitude > limit)
238
+ return std::nullopt;
239
+ return static_cast<T>(std::uint64_t { 0 } - magnitude);
240
+ }
241
+ else
242
+ {
243
+ if (magnitude != 0)
244
+ return std::nullopt;
245
+ return T { 0 };
246
+ }
247
+ }
248
+ if (magnitude > static_cast<std::uint64_t>(std::numeric_limits<T>::max()))
249
+ return std::nullopt;
250
+ return static_cast<T>(magnitude);
251
+ }
252
+
253
+ // Decimal notation to a double, by the same operations as the JavaScript
254
+ // runtime (exact for up to 15 significant digits and exponents to ±22).
255
+ inline std::optional<double> parseDecimal(std::string_view text) noexcept
256
+ {
257
+ text = trimView(text);
258
+ std::size_t i = 0;
259
+ bool negative = false;
260
+ if (i < text.size() && (text[i] == '+' || text[i] == '-'))
261
+ negative = text[i++] == '-';
262
+ const std::size_t integerStart = i;
263
+ while (i < text.size() && isDigit(text[i]))
264
+ ++i;
265
+ const std::string_view integerDigits = text.substr(integerStart, i - integerStart);
266
+ std::string_view fractionDigits;
267
+ if (i < text.size() && text[i] == '.')
268
+ {
269
+ const std::size_t fractionStart = ++i;
270
+ while (i < text.size() && isDigit(text[i]))
271
+ ++i;
272
+ fractionDigits = text.substr(fractionStart, i - fractionStart);
273
+ }
274
+ if (integerDigits.empty() && fractionDigits.empty())
275
+ return std::nullopt;
276
+ long exponent = 0;
277
+ if (i < text.size() && (text[i] == 'e' || text[i] == 'E'))
278
+ {
279
+ ++i;
280
+ bool exponentNegative = false;
281
+ if (i < text.size() && (text[i] == '+' || text[i] == '-'))
282
+ exponentNegative = text[i++] == '-';
283
+ const std::size_t exponentStart = i;
284
+ while (i < text.size() && isDigit(text[i]))
285
+ ++i;
286
+ if (i == exponentStart)
287
+ return std::nullopt;
288
+ std::size_t first = exponentStart;
289
+ while (first < i && text[first] == '0')
290
+ ++first;
291
+ if (i - first > 6)
292
+ exponent = 100000;
293
+ else
294
+ for (std::size_t k = first; k < i; ++k)
295
+ exponent = exponent * 10 + (text[k] - '0');
296
+ if (exponentNegative)
297
+ exponent = -exponent;
298
+ }
299
+ if (i != text.size())
300
+ return std::nullopt;
301
+ exponent -= static_cast<long>(fractionDigits.size());
302
+
303
+ std::string digits;
304
+ digits.reserve(integerDigits.size() + fractionDigits.size());
305
+ digits.append(integerDigits);
306
+ digits.append(fractionDigits);
307
+ std::size_t start = 0;
308
+ while (start < digits.size() && digits[start] == '0')
309
+ ++start;
310
+ std::size_t end = digits.size();
311
+ double value = 0;
312
+ if (start < end)
313
+ {
314
+ while (digits[end - 1] == '0')
315
+ {
316
+ --end;
317
+ ++exponent;
318
+ }
319
+ if (end - start > 19)
320
+ {
321
+ exponent += static_cast<long>(end - start - 19);
322
+ end = start + 19;
323
+ }
324
+ std::uint64_t mantissa = 0;
325
+ for (std::size_t k = start; k < end; ++k)
326
+ mantissa = mantissa * 10 + static_cast<std::uint64_t>(digits[k] - '0');
327
+ value = static_cast<double>(mantissa);
328
+ while (exponent > 22 && ! std::isinf(value))
329
+ {
330
+ value *= 1e22;
331
+ exponent -= 22;
332
+ }
333
+ while (exponent < -22 && value != 0)
334
+ {
335
+ value /= 1e22;
336
+ exponent += 22;
337
+ }
338
+ if (exponent > 22)
339
+ value = std::numeric_limits<double>::infinity();
340
+ else if (exponent < -22)
341
+ value = 0;
342
+ else
343
+ value = exponent >= 0 ? value * pow10[exponent] : value / pow10[-exponent];
344
+ }
345
+ return negative ? -value : value;
346
+ }
347
+
348
+ template <class F> inline std::optional<F> parseFloat(std::string_view text) noexcept
349
+ {
350
+ const auto value = parseDecimal(text);
351
+ if (! value)
352
+ return std::nullopt;
353
+ const F result = narrow<F>(*value);
354
+ if (std::isinf(result))
355
+ return std::nullopt;
356
+ return result;
357
+ }
358
+ } // namespace ${N}
359
+ `;
360
+ }
361
+ /**
362
+ * The C++ expression that computes `expression` from its `inputs` (of the C++
363
+ * types {@link cppType} gives). Checks the tree first; throws
364
+ * `ExpressionError` for an invalid one.
365
+ */
366
+ function compileCpp(expression, inputs, options = {}) {
367
+ assertExpression(expression, inputs);
368
+ return emit(expression, (name) => {
369
+ const identifier = options.names?.[name] ?? name;
370
+ if (!/^[A-Za-z_][\w]*$/.test(identifier)) throw new TypeError(`'${identifier}' is not a C++ identifier`);
371
+ return identifier;
372
+ });
373
+ }
374
+ /** A C++ string literal of `value`'s UTF-8 bytes, as a `std::string`. */
375
+ function cppStringLiteral(value) {
376
+ const bytes = new TextEncoder().encode(value);
377
+ let body = "";
378
+ for (const byte of bytes) if (byte === 34) body += "\\\"";
379
+ else if (byte === 92) body += "\\\\";
380
+ else if (byte === 63) body += "\\?";
381
+ else if (byte >= 32 && byte < 127) body += String.fromCharCode(byte);
382
+ else body += `\\${byte.toString(8).padStart(3, "0")}`;
383
+ return `"${body}"`;
384
+ }
385
+ function floatLiteral(type, value) {
386
+ const number = type === "float32" ? Math.fround(value) : value;
387
+ let text = String(number);
388
+ if (!/[.e]/.test(text)) text += ".0";
389
+ if (type === "float32") text += "f";
390
+ return number < 0 ? `(${text})` : text;
391
+ }
392
+ function integerLiteral(type, value) {
393
+ const integer = BigInt(value);
394
+ if (type === "uint64") return `std::uint64_t { ${integer}ULL }`;
395
+ if (type === "int64") return integer === INTEGERS.int64.min ? "(-9223372036854775807LL - 1)" : `std::int64_t { ${integer}LL }`;
396
+ return `static_cast<${cppType(type)}>(${integer}LL)`;
397
+ }
398
+ function literal(type, value) {
399
+ if (value === null) return `${cppType(type)}()`;
400
+ const base = baseType(type);
401
+ if (base === "string") return `std::string(${cppStringLiteral(value)}, ${new TextEncoder().encode(value).length})`;
402
+ if (base === "boolean") return value ? "true" : "false";
403
+ if (isFloatType(base)) return floatLiteral(base, value);
404
+ return integerLiteral(base, value);
405
+ }
406
+ const COMPARISON = {
407
+ eq: "==",
408
+ ne: "!=",
409
+ lt: "<",
410
+ lte: "<=",
411
+ gt: ">",
412
+ gte: ">=",
413
+ and: "&&",
414
+ or: "||"
415
+ };
416
+ const FLOAT_OPERATOR = {
417
+ add: "+",
418
+ sub: "-",
419
+ mul: "*",
420
+ div: "/"
421
+ };
422
+ /** Code of type `node.type`, as `as` when that is the nullable of it. */
423
+ function as(code, from, to) {
424
+ return from === to ? code : `${cppType(to)}(${code})`;
425
+ }
426
+ function emit(node, name) {
427
+ const go = (child) => emit(child, name);
428
+ switch (node.kind) {
429
+ case "literal": return literal(node.type, node.value);
430
+ case "input": return name(node.name);
431
+ case "variable": return `m_${node.name}`;
432
+ case "unary": {
433
+ const a = go(node.operand);
434
+ const type = node.operand.type;
435
+ const t = cppType(type);
436
+ switch (node.operator) {
437
+ case "not": return `(! ${a})`;
438
+ case "trim": return `${N}::trim(${a})`;
439
+ case "lower": return `${N}::lower(${a})`;
440
+ case "neg": return isIntegerType(type) ? `${N}::ineg<${t}>(${a})` : `(-${a})`;
441
+ case "abs": return isIntegerType(type) ? `${N}::iabs<${t}>(${a})` : `std::fabs(${a})`;
442
+ case "floor":
443
+ case "ceil":
444
+ case "round": return `static_cast<${t}>(std::${node.operator}(${a}))`;
445
+ default: return `${N}::f${node.operator}<${t}>(${a})`;
446
+ }
447
+ }
448
+ case "binary": {
449
+ const a = go(node.left);
450
+ const b = go(node.right);
451
+ const type = node.left.type;
452
+ const t = cppType(type);
453
+ switch (node.operator) {
454
+ case "endsWith": return `${N}::endsWith(${a}, ${b})`;
455
+ case "removeSuffix": return `${N}::removeSuffix(${a}, ${b})`;
456
+ case "pow": return `${N}::fpow<${t}>(${a}, ${b})`;
457
+ case "min":
458
+ case "max": return `${N}::${isIntegerType(type) ? "i" : "f"}${node.operator}<${t}>(${a}, ${b})`;
459
+ case "add":
460
+ case "sub":
461
+ case "mul":
462
+ case "div":
463
+ if (isIntegerType(type)) return `${N}::i${node.operator}<${t}>(${a}, ${b})`;
464
+ return `static_cast<${t}>(${a} ${FLOAT_OPERATOR[node.operator]} ${b})`;
465
+ default: return `(${a} ${COMPARISON[node.operator]} ${b})`;
466
+ }
467
+ }
468
+ case "if": return `(${go(node.condition)} ? ${as(go(node.then), node.then.type, node.type)} : ${as(go(node.else), node.else.type, node.type)})`;
469
+ case "convert": return convert(go(node.operand), node.operand.type, node.type);
470
+ case "fixed": {
471
+ const a = go(node.operand);
472
+ return isFloatType(node.operand.type) ? `${N}::fixedFloat(static_cast<double>(${a}), ${node.digits})` : `${N}::fixedInt(${a}, ${node.digits})`;
473
+ }
474
+ case "concat": return node.operands.length === 1 ? go(node.operands[0]) : `(${node.operands.map(go).join(" + ")})`;
475
+ case "parse": {
476
+ const type = baseType(node.type);
477
+ return `${N}::parse${isFloatType(type) ? "Float" : "Int"}<${cppType(type)}>(${go(node.operand)})`;
478
+ }
479
+ case "coalesce": return `(${go(node.operand)}).value_or(${go(node.fallback)})`;
480
+ case "isNull": return `(! (${go(node.operand)}).has_value())`;
481
+ case "let": return `[&](const ${cppType(node.value.type)} m_${node.variable}) -> ${cppType(node.type)} { return ${go(node.body)}; }(${go(node.value)})`;
482
+ case "map": {
483
+ const inner = cppType(baseType(node.operand.type));
484
+ return `[&](const std::optional<${inner}>& o_${node.variable}) -> ${cppType(node.type)} { if (! o_${node.variable}) return std::nullopt; const ${inner} m_${node.variable} = *o_${node.variable}; return ${go(node.body)}; }(${go(node.operand)})`;
485
+ }
486
+ }
487
+ }
488
+ function convert(code, from, to) {
489
+ if (from === to) return code;
490
+ const target = cppType(to);
491
+ if (to === "float32") return `${N}::f32(static_cast<double>(${code}))`;
492
+ if (to === "float64") return `static_cast<double>(${code})`;
493
+ if (isFloatType(from)) return `${N}::toInt<${target}>(${code})`;
494
+ return `static_cast<${target}>(${code})`;
495
+ }
496
+ export { CPP_INCLUDES, CPP_PRAGMAS, CPP_RUNTIME_NAMESPACE, compileCpp, cppRuntime, cppStringLiteral, cppType };
497
+
498
+ //# sourceMappingURL=cpp.mjs.map