"use strict";
// ---------------------------------------------------------------------------
// Expression Evaluator
//
// Reduces TypeScript expressions to LatticeValues for constant propagation.
// Upgrade of tryFoldConstant() in variableResolver.ts using the lattice.
// ---------------------------------------------------------------------------
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressionEvaluator = void 0;
const typescript_1 = __importDefault(require("typescript"));
const lattice_js_1 = require("./lattice.js");
class ExpressionEvaluator {
    checker;
    resolveSymbol;
    resolveCall;
    constructor(checker, resolveSymbol, resolveCall) {
        this.checker = checker;
        this.resolveSymbol = resolveSymbol;
        this.resolveCall = resolveCall;
    }
    /**
     * Evaluate a TS expression to a LatticeValue.
     */
    evaluate(expr) {
        // Unwrap parenthesized / as / satisfies
        if (typescript_1.default.isParenthesizedExpression(expr)) {
            return this.evaluate(expr.expression);
        }
        if (typescript_1.default.isAsExpression(expr)) {
            return this.evaluate(expr.expression);
        }
        if (typescript_1.default.isSatisfiesExpression(expr)) {
            return this.evaluate(expr.expression);
        }
        if (typescript_1.default.isNonNullExpression(expr)) {
            return this.evaluate(expr.expression);
        }
        // 1. Literals
        if (typescript_1.default.isStringLiteral(expr) || typescript_1.default.isNoSubstitutionTemplateLiteral(expr)) {
            return (0, lattice_js_1.constant)(expr.text);
        }
        if (typescript_1.default.isNumericLiteral(expr)) {
            return (0, lattice_js_1.constant)(Number(expr.text));
        }
        if (expr.kind === typescript_1.default.SyntaxKind.TrueKeyword)
            return (0, lattice_js_1.constant)(true);
        if (expr.kind === typescript_1.default.SyntaxKind.FalseKeyword)
            return (0, lattice_js_1.constant)(false);
        if (expr.kind === typescript_1.default.SyntaxKind.NullKeyword)
            return (0, lattice_js_1.constant)(null);
        if (expr.kind === typescript_1.default.SyntaxKind.UndefinedKeyword)
            return (0, lattice_js_1.constant)(undefined);
        // 2. Identifier reference
        if (typescript_1.default.isIdentifier(expr)) {
            return this.evaluateIdentifier(expr);
        }
        // 3. Property access on constant object
        if (typescript_1.default.isPropertyAccessExpression(expr)) {
            return this.evaluatePropertyAccess(expr);
        }
        // 4. Element access on constant array/object
        if (typescript_1.default.isElementAccessExpression(expr)) {
            return this.evaluateElementAccess(expr);
        }
        // 5. Binary expression
        if (typescript_1.default.isBinaryExpression(expr)) {
            return this.evaluateBinary(expr);
        }
        // 6. Unary prefix
        if (typescript_1.default.isPrefixUnaryExpression(expr)) {
            return this.evaluateUnaryPrefix(expr);
        }
        // 7. Conditional expression (ternary)
        if (typescript_1.default.isConditionalExpression(expr)) {
            return this.evaluateConditional(expr);
        }
        // 8. Template expression
        if (typescript_1.default.isTemplateExpression(expr)) {
            return this.evaluateTemplate(expr);
        }
        // 9. Object literal
        if (typescript_1.default.isObjectLiteralExpression(expr)) {
            return this.evaluateObjectLiteral(expr);
        }
        // 10. Array literal
        if (typescript_1.default.isArrayLiteralExpression(expr)) {
            return this.evaluateArrayLiteral(expr);
        }
        // 11. Call expression
        if (typescript_1.default.isCallExpression(expr)) {
            return this.evaluateCallExpression(expr);
        }
        // 12. typeof
        if (typescript_1.default.isTypeOfExpression(expr)) {
            return this.evaluateTypeOf(expr);
        }
        // 13. Spread element (handled by parent array/object evaluators)
        // 14. void expression
        if (typescript_1.default.isVoidExpression(expr)) {
            return (0, lattice_js_1.constant)(undefined);
        }
        return (0, lattice_js_1.bottom)(`Expression type '${typescript_1.default.SyntaxKind[expr.kind]}' is not foldable`, expr);
    }
    // -----------------------------------------------------------------------
    // Identifier
    // -----------------------------------------------------------------------
    evaluateIdentifier(expr) {
        // Special globals
        if (expr.text === 'undefined')
            return (0, lattice_js_1.constant)(undefined);
        if (expr.text === 'NaN')
            return (0, lattice_js_1.constant)(NaN);
        if (expr.text === 'Infinity')
            return (0, lattice_js_1.constant)(Infinity);
        const sym = this.checker.getSymbolAtLocation(expr);
        if (!sym)
            return (0, lattice_js_1.bottom)(`Unknown identifier '${expr.text}'`, expr);
        const value = this.resolveSymbol(sym);
        if ((0, lattice_js_1.isBottom)(value)) {
            return (0, lattice_js_1.propagateBottom)(`depends on '${expr.text}'`, value, expr);
        }
        return value;
    }
    // -----------------------------------------------------------------------
    // Property access
    // -----------------------------------------------------------------------
    evaluatePropertyAccess(expr) {
        const base = this.evaluate(expr.expression);
        const propName = expr.name.text;
        if ((0, lattice_js_1.isConstant)(base)) {
            const obj = base.value;
            if (obj !== null && obj !== undefined && typeof obj === 'object') {
                const val = obj[propName];
                return val !== undefined ? (0, lattice_js_1.constant)(val) : (0, lattice_js_1.bottom)(`Property '${propName}' not found on object`, expr);
            }
            // typeof on primitive
            if (typeof obj === 'string' && propName === 'length') {
                return (0, lattice_js_1.constant)(obj.length);
            }
            return (0, lattice_js_1.bottom)(`Property access on non-object constant`, expr);
        }
        if ((0, lattice_js_1.isBottom)(base)) {
            return (0, lattice_js_1.propagateBottom)(`property '${propName}' on unresolvable value`, base, expr);
        }
        return (0, lattice_js_1.bottom)(`Property access on non-constant value`, expr);
    }
    // -----------------------------------------------------------------------
    // Element access
    // -----------------------------------------------------------------------
    evaluateElementAccess(expr) {
        const base = this.evaluate(expr.expression);
        if (!expr.argumentExpression)
            return (0, lattice_js_1.bottom)('Missing element access argument', expr);
        const index = this.evaluate(expr.argumentExpression);
        if ((0, lattice_js_1.isConstant)(base) && (0, lattice_js_1.isConstant)(index)) {
            const result = base.value?.[index.value];
            return result !== undefined ? (0, lattice_js_1.constant)(result) : (0, lattice_js_1.bottom)('Element not found', expr);
        }
        if ((0, lattice_js_1.isBottom)(base))
            return (0, lattice_js_1.propagateBottom)('element access on unresolvable', base, expr);
        if ((0, lattice_js_1.isBottom)(index))
            return (0, lattice_js_1.propagateBottom)('unresolvable index', index, expr);
        return (0, lattice_js_1.bottom)('Element access on non-constant', expr);
    }
    // -----------------------------------------------------------------------
    // Binary expression
    // -----------------------------------------------------------------------
    evaluateBinary(expr) {
        const left = this.evaluate(expr.left);
        const right = this.evaluate(expr.right);
        if ((0, lattice_js_1.isBottom)(left))
            return (0, lattice_js_1.propagateBottom)('left operand unresolvable', left, expr);
        if ((0, lattice_js_1.isBottom)(right))
            return (0, lattice_js_1.propagateBottom)('right operand unresolvable', right, expr);
        if (!(0, lattice_js_1.isConstant)(left) || !(0, lattice_js_1.isConstant)(right))
            return (0, lattice_js_1.bottom)('Non-constant operands', expr);
        const l = left.value;
        const r = right.value;
        const op = expr.operatorToken.kind;
        switch (op) {
            case typescript_1.default.SyntaxKind.PlusToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l + r);
                if (typeof l === 'string' || typeof r === 'string')
                    return (0, lattice_js_1.constant)(String(l) + String(r));
                return (0, lattice_js_1.bottom)('+ on incompatible types', expr);
            case typescript_1.default.SyntaxKind.MinusToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l - r);
                return (0, lattice_js_1.bottom)('- on non-numbers', expr);
            case typescript_1.default.SyntaxKind.AsteriskToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l * r);
                return (0, lattice_js_1.bottom)('* on non-numbers', expr);
            case typescript_1.default.SyntaxKind.SlashToken:
                if (typeof l === 'number' && typeof r === 'number' && r !== 0)
                    return (0, lattice_js_1.constant)(l / r);
                return (0, lattice_js_1.bottom)('/ on non-numbers or division by zero', expr);
            case typescript_1.default.SyntaxKind.PercentToken:
                if (typeof l === 'number' && typeof r === 'number' && r !== 0)
                    return (0, lattice_js_1.constant)(l % r);
                return (0, lattice_js_1.bottom)('% on non-numbers or division by zero', expr);
            case typescript_1.default.SyntaxKind.AsteriskAsteriskToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l ** r);
                return (0, lattice_js_1.bottom)('** on non-numbers', expr);
            case typescript_1.default.SyntaxKind.AmpersandAmpersandToken:
                return (0, lattice_js_1.constant)(l && r);
            case typescript_1.default.SyntaxKind.BarBarToken:
                return (0, lattice_js_1.constant)(l || r);
            case typescript_1.default.SyntaxKind.QuestionQuestionToken:
                return (0, lattice_js_1.constant)(l ?? r);
            case typescript_1.default.SyntaxKind.EqualsEqualsEqualsToken:
                return (0, lattice_js_1.constant)(l === r);
            case typescript_1.default.SyntaxKind.ExclamationEqualsEqualsToken:
                return (0, lattice_js_1.constant)(l !== r);
            case typescript_1.default.SyntaxKind.EqualsEqualsToken:
                return (0, lattice_js_1.constant)(l == r);
            case typescript_1.default.SyntaxKind.ExclamationEqualsToken:
                return (0, lattice_js_1.constant)(l != r);
            case typescript_1.default.SyntaxKind.LessThanToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l < r);
                if (typeof l === 'string' && typeof r === 'string')
                    return (0, lattice_js_1.constant)(l < r);
                return (0, lattice_js_1.bottom)('< on incompatible types', expr);
            case typescript_1.default.SyntaxKind.LessThanEqualsToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l <= r);
                if (typeof l === 'string' && typeof r === 'string')
                    return (0, lattice_js_1.constant)(l <= r);
                return (0, lattice_js_1.bottom)('<= on incompatible types', expr);
            case typescript_1.default.SyntaxKind.GreaterThanToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l > r);
                if (typeof l === 'string' && typeof r === 'string')
                    return (0, lattice_js_1.constant)(l > r);
                return (0, lattice_js_1.bottom)('> on incompatible types', expr);
            case typescript_1.default.SyntaxKind.GreaterThanEqualsToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l >= r);
                if (typeof l === 'string' && typeof r === 'string')
                    return (0, lattice_js_1.constant)(l >= r);
                return (0, lattice_js_1.bottom)('>= on incompatible types', expr);
            case typescript_1.default.SyntaxKind.BarToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l | r);
                return (0, lattice_js_1.bottom)('| on non-numbers', expr);
            case typescript_1.default.SyntaxKind.AmpersandToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l & r);
                return (0, lattice_js_1.bottom)('& on non-numbers', expr);
            case typescript_1.default.SyntaxKind.CaretToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l ^ r);
                return (0, lattice_js_1.bottom)('^ on non-numbers', expr);
            case typescript_1.default.SyntaxKind.LessThanLessThanToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l << r);
                return (0, lattice_js_1.bottom)('<< on non-numbers', expr);
            case typescript_1.default.SyntaxKind.GreaterThanGreaterThanToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l >> r);
                return (0, lattice_js_1.bottom)('>> on non-numbers', expr);
            case typescript_1.default.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
                if (typeof l === 'number' && typeof r === 'number')
                    return (0, lattice_js_1.constant)(l >>> r);
                return (0, lattice_js_1.bottom)('>>> on non-numbers', expr);
            default:
                return (0, lattice_js_1.bottom)(`Unsupported binary operator`, expr);
        }
    }
    // -----------------------------------------------------------------------
    // Unary prefix
    // -----------------------------------------------------------------------
    evaluateUnaryPrefix(expr) {
        const operand = this.evaluate(expr.operand);
        if (!(0, lattice_js_1.isConstant)(operand)) {
            if ((0, lattice_js_1.isBottom)(operand))
                return (0, lattice_js_1.propagateBottom)('unary operand unresolvable', operand, expr);
            return (0, lattice_js_1.bottom)('Unary on non-constant', expr);
        }
        const v = operand.value;
        switch (expr.operator) {
            case typescript_1.default.SyntaxKind.MinusToken:
                return typeof v === 'number' ? (0, lattice_js_1.constant)(-v) : (0, lattice_js_1.bottom)('- on non-number', expr);
            case typescript_1.default.SyntaxKind.PlusToken:
                return typeof v === 'number' ? (0, lattice_js_1.constant)(+v) : (0, lattice_js_1.bottom)('+ on non-number', expr);
            case typescript_1.default.SyntaxKind.ExclamationToken:
                return (0, lattice_js_1.constant)(!v);
            case typescript_1.default.SyntaxKind.TildeToken:
                return typeof v === 'number' ? (0, lattice_js_1.constant)(~v) : (0, lattice_js_1.bottom)('~ on non-number', expr);
            default:
                return (0, lattice_js_1.bottom)('Unsupported unary operator', expr);
        }
    }
    // -----------------------------------------------------------------------
    // Conditional (ternary)
    // -----------------------------------------------------------------------
    evaluateConditional(expr) {
        const cond = this.evaluate(expr.condition);
        if (!(0, lattice_js_1.isConstant)(cond)) {
            if ((0, lattice_js_1.isBottom)(cond))
                return (0, lattice_js_1.propagateBottom)('ternary condition unresolvable', cond, expr);
            return (0, lattice_js_1.bottom)('Non-constant ternary condition', expr);
        }
        return cond.value ? this.evaluate(expr.whenTrue) : this.evaluate(expr.whenFalse);
    }
    // -----------------------------------------------------------------------
    // Template expression
    // -----------------------------------------------------------------------
    evaluateTemplate(expr) {
        let result = expr.head.text;
        for (const span of expr.templateSpans) {
            const val = this.evaluate(span.expression);
            if (!(0, lattice_js_1.isConstant)(val)) {
                if ((0, lattice_js_1.isBottom)(val))
                    return (0, lattice_js_1.propagateBottom)('template substitution unresolvable', val, expr);
                return (0, lattice_js_1.bottom)('Non-constant template substitution', expr);
            }
            result += String(val.value);
            result += span.literal.text;
        }
        return (0, lattice_js_1.constant)(result);
    }
    // -----------------------------------------------------------------------
    // Object literal
    // -----------------------------------------------------------------------
    evaluateObjectLiteral(expr) {
        const obj = {};
        for (const prop of expr.properties) {
            if (typescript_1.default.isPropertyAssignment(prop)) {
                let key;
                if (typescript_1.default.isIdentifier(prop.name)) {
                    key = prop.name.text;
                }
                else if (typescript_1.default.isStringLiteral(prop.name)) {
                    key = prop.name.text;
                }
                else if (typescript_1.default.isNumericLiteral(prop.name)) {
                    key = prop.name.text;
                }
                else {
                    return (0, lattice_js_1.bottom)('Computed property name in object literal', expr);
                }
                const val = this.evaluate(prop.initializer);
                if (!(0, lattice_js_1.isConstant)(val)) {
                    if ((0, lattice_js_1.isBottom)(val))
                        return (0, lattice_js_1.propagateBottom)(`property '${key}' unresolvable`, val, expr);
                    return (0, lattice_js_1.bottom)(`Non-constant property '${key}'`, expr);
                }
                obj[key] = val.value;
            }
            else if (typescript_1.default.isShorthandPropertyAssignment(prop)) {
                const key = prop.name.text;
                const val = this.evaluateIdentifier(prop.name);
                if (!(0, lattice_js_1.isConstant)(val)) {
                    if ((0, lattice_js_1.isBottom)(val))
                        return (0, lattice_js_1.propagateBottom)(`shorthand property '${key}' unresolvable`, val, expr);
                    return (0, lattice_js_1.bottom)(`Non-constant shorthand property '${key}'`, expr);
                }
                obj[key] = val.value;
            }
            else if (typescript_1.default.isSpreadAssignment(prop)) {
                const spread = this.evaluate(prop.expression);
                if (!(0, lattice_js_1.isConstant)(spread)) {
                    if ((0, lattice_js_1.isBottom)(spread))
                        return (0, lattice_js_1.propagateBottom)('spread value unresolvable', spread, expr);
                    return (0, lattice_js_1.bottom)('Non-constant spread value', expr);
                }
                if (typeof spread.value === 'object' && spread.value !== null) {
                    Object.assign(obj, spread.value);
                }
                else {
                    return (0, lattice_js_1.bottom)('Spread on non-object', expr);
                }
            }
            else {
                return (0, lattice_js_1.bottom)('Unsupported object literal member', expr);
            }
        }
        return (0, lattice_js_1.constant)(obj);
    }
    // -----------------------------------------------------------------------
    // Array literal
    // -----------------------------------------------------------------------
    evaluateArrayLiteral(expr) {
        const arr = [];
        for (const elem of expr.elements) {
            if (typescript_1.default.isSpreadElement(elem)) {
                const spread = this.evaluate(elem.expression);
                if (!(0, lattice_js_1.isConstant)(spread)) {
                    if ((0, lattice_js_1.isBottom)(spread))
                        return (0, lattice_js_1.propagateBottom)('array spread unresolvable', spread, expr);
                    return (0, lattice_js_1.bottom)('Non-constant array spread', expr);
                }
                if (Array.isArray(spread.value)) {
                    arr.push(...spread.value);
                }
                else {
                    return (0, lattice_js_1.bottom)('Spread of non-array', expr);
                }
            }
            else {
                const val = this.evaluate(elem);
                if (!(0, lattice_js_1.isConstant)(val)) {
                    if ((0, lattice_js_1.isBottom)(val))
                        return (0, lattice_js_1.propagateBottom)('array element unresolvable', val, expr);
                    return (0, lattice_js_1.bottom)('Non-constant array element', expr);
                }
                arr.push(val.value);
            }
        }
        return (0, lattice_js_1.constant)(arr);
    }
    // -----------------------------------------------------------------------
    // Call expression
    // -----------------------------------------------------------------------
    evaluateCallExpression(expr) {
        // Check for Steps.safeVar() wrapper
        if (this.isStepsSafeVarCall(expr)) {
            if (expr.arguments.length < 1)
                return (0, lattice_js_1.bottom)('Steps.safeVar() requires an argument', expr);
            const inner = this.evaluate(expr.arguments[0]);
            if ((0, lattice_js_1.isConstant)(inner))
                return inner; // no-op for constants
            if ((0, lattice_js_1.isBottom)(inner)) {
                return (0, lattice_js_1.safeBottom)(inner.reason, expr, inner.rootCause ?? inner);
            }
            return (0, lattice_js_1.safeBottom)('wrapped in Steps.safeVar()', expr);
        }
        // Delegate to external call resolver (pure function inlining)
        if (this.resolveCall) {
            const result = this.resolveCall(expr, this);
            if (result !== undefined)
                return result;
        }
        // Known pure built-in functions
        return this.evaluateBuiltinCall(expr);
    }
    isStepsSafeVarCall(expr) {
        if (!typescript_1.default.isPropertyAccessExpression(expr.expression))
            return false;
        const callee = expr.expression;
        if (!typescript_1.default.isIdentifier(callee.expression))
            return false;
        return callee.expression.text === 'Steps' && callee.name.text === 'safeVar';
    }
    evaluateBuiltinCall(expr) {
        // Property access calls: Math.floor(), JSON.parse(), etc.
        if (typescript_1.default.isPropertyAccessExpression(expr.expression)) {
            const obj = expr.expression.expression;
            const method = expr.expression.name.text;
            if (typescript_1.default.isIdentifier(obj)) {
                // Math.*
                if (obj.text === 'Math') {
                    return this.evaluateMathCall(method, expr);
                }
                // JSON.parse / JSON.stringify
                if (obj.text === 'JSON') {
                    return this.evaluateJsonCall(method, expr);
                }
                // Object.keys / values / entries
                if (obj.text === 'Object') {
                    return this.evaluateObjectCall(method, expr);
                }
                // Array.isArray
                if (obj.text === 'Array' && method === 'isArray' && expr.arguments.length === 1) {
                    const arg = this.evaluate(expr.arguments[0]);
                    if ((0, lattice_js_1.isConstant)(arg))
                        return (0, lattice_js_1.constant)(Array.isArray(arg.value));
                }
            }
        }
        // Direct function calls: String(), Number(), Boolean(), parseInt(), parseFloat()
        if (typescript_1.default.isIdentifier(expr.expression)) {
            const name = expr.expression.text;
            if (expr.arguments.length >= 1) {
                const arg = this.evaluate(expr.arguments[0]);
                if ((0, lattice_js_1.isConstant)(arg)) {
                    switch (name) {
                        case 'String': return (0, lattice_js_1.constant)(String(arg.value));
                        case 'Number': return (0, lattice_js_1.constant)(Number(arg.value));
                        case 'Boolean': return (0, lattice_js_1.constant)(Boolean(arg.value));
                        case 'parseInt': {
                            const radix = expr.arguments.length >= 2 ? this.evaluate(expr.arguments[1]) : undefined;
                            if (radix && !(0, lattice_js_1.isConstant)(radix))
                                break;
                            return (0, lattice_js_1.constant)(parseInt(String(arg.value), radix ? Number(radix.value) : undefined));
                        }
                        case 'parseFloat':
                            return (0, lattice_js_1.constant)(parseFloat(String(arg.value)));
                    }
                }
            }
        }
        return (0, lattice_js_1.bottom)('Function call not foldable', expr);
    }
    evaluateMathCall(method, expr) {
        const args = expr.arguments.map(a => this.evaluate(a));
        if (args.some(a => !(0, lattice_js_1.isConstant)(a))) {
            const firstBottom = args.find(lattice_js_1.isBottom);
            if (firstBottom)
                return (0, lattice_js_1.propagateBottom)(`Math.${method}() argument unresolvable`, firstBottom, expr);
            return (0, lattice_js_1.bottom)(`Non-constant argument to Math.${method}()`, expr);
        }
        const nums = args.map(a => Number(a.value));
        if (nums.some(n => isNaN(n) && typeof args[nums.indexOf(n)].value !== 'number')) {
            return (0, lattice_js_1.bottom)(`Non-numeric argument to Math.${method}()`, expr);
        }
        switch (method) {
            case 'floor': return (0, lattice_js_1.constant)(Math.floor(nums[0]));
            case 'ceil': return (0, lattice_js_1.constant)(Math.ceil(nums[0]));
            case 'round': return (0, lattice_js_1.constant)(Math.round(nums[0]));
            case 'abs': return (0, lattice_js_1.constant)(Math.abs(nums[0]));
            case 'min': return (0, lattice_js_1.constant)(Math.min(...nums));
            case 'max': return (0, lattice_js_1.constant)(Math.max(...nums));
            case 'pow': return (0, lattice_js_1.constant)(Math.pow(nums[0], nums[1]));
            case 'sqrt': return (0, lattice_js_1.constant)(Math.sqrt(nums[0]));
            case 'trunc': return (0, lattice_js_1.constant)(Math.trunc(nums[0]));
            case 'sign': return (0, lattice_js_1.constant)(Math.sign(nums[0]));
            case 'log': return (0, lattice_js_1.constant)(Math.log(nums[0]));
            case 'log2': return (0, lattice_js_1.constant)(Math.log2(nums[0]));
            case 'log10': return (0, lattice_js_1.constant)(Math.log10(nums[0]));
            default: return (0, lattice_js_1.bottom)(`Math.${method}() is not a recognized pure function`, expr);
        }
    }
    evaluateJsonCall(method, expr) {
        if (expr.arguments.length < 1)
            return (0, lattice_js_1.bottom)(`JSON.${method}() requires an argument`, expr);
        const arg = this.evaluate(expr.arguments[0]);
        if (!(0, lattice_js_1.isConstant)(arg)) {
            if ((0, lattice_js_1.isBottom)(arg))
                return (0, lattice_js_1.propagateBottom)(`JSON.${method}() argument unresolvable`, arg, expr);
            return (0, lattice_js_1.bottom)(`Non-constant argument to JSON.${method}()`, expr);
        }
        switch (method) {
            case 'stringify':
                try {
                    return (0, lattice_js_1.constant)(JSON.stringify(arg.value));
                }
                catch {
                    return (0, lattice_js_1.bottom)('JSON.stringify() failed', expr);
                }
            case 'parse':
                if (typeof arg.value !== 'string')
                    return (0, lattice_js_1.bottom)('JSON.parse() requires a string', expr);
                try {
                    return (0, lattice_js_1.constant)(JSON.parse(arg.value));
                }
                catch {
                    return (0, lattice_js_1.bottom)('JSON.parse() failed: invalid JSON', expr);
                }
            default:
                return (0, lattice_js_1.bottom)(`JSON.${method}() is not foldable`, expr);
        }
    }
    evaluateObjectCall(method, expr) {
        if (expr.arguments.length < 1)
            return (0, lattice_js_1.bottom)(`Object.${method}() requires an argument`, expr);
        const arg = this.evaluate(expr.arguments[0]);
        if (!(0, lattice_js_1.isConstant)(arg)) {
            if ((0, lattice_js_1.isBottom)(arg))
                return (0, lattice_js_1.propagateBottom)(`Object.${method}() argument unresolvable`, arg, expr);
            return (0, lattice_js_1.bottom)(`Non-constant argument to Object.${method}()`, expr);
        }
        if (typeof arg.value !== 'object' || arg.value === null) {
            return (0, lattice_js_1.bottom)(`Object.${method}() requires an object`, expr);
        }
        switch (method) {
            case 'keys': return (0, lattice_js_1.constant)(Object.keys(arg.value));
            case 'values': return (0, lattice_js_1.constant)(Object.values(arg.value));
            case 'entries': return (0, lattice_js_1.constant)(Object.entries(arg.value));
            case 'freeze': return (0, lattice_js_1.constant)(arg.value); // identity for our purposes
            case 'assign':
                // Object.assign({}, a, b, ...) — fold if all args are constants
                if (expr.arguments.length >= 2) {
                    const allArgs = expr.arguments.map(a => this.evaluate(a));
                    if (allArgs.every(lattice_js_1.isConstant)) {
                        const objs = allArgs.map(a => a.value);
                        return (0, lattice_js_1.constant)(Object.assign({}, ...objs));
                    }
                }
                return (0, lattice_js_1.bottom)('Non-constant argument to Object.assign()', expr);
            default:
                return (0, lattice_js_1.bottom)(`Object.${method}() is not foldable`, expr);
        }
    }
    // -----------------------------------------------------------------------
    // typeof
    // -----------------------------------------------------------------------
    evaluateTypeOf(expr) {
        const operand = this.evaluate(expr.expression);
        if ((0, lattice_js_1.isConstant)(operand)) {
            return (0, lattice_js_1.constant)(typeof operand.value);
        }
        if ((0, lattice_js_1.isBottom)(operand)) {
            return (0, lattice_js_1.propagateBottom)('typeof on unresolvable', operand, expr);
        }
        return (0, lattice_js_1.bottom)('typeof on non-constant', expr);
    }
}
exports.ExpressionEvaluator = ExpressionEvaluator;
//# sourceMappingURL=expressionEvaluator.js.map