"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FfiLibraryObj = exports.NULL = exports.FALSE = exports.TRUE = exports.Evaluator = exports.Environment = exports.FunctionObj = exports.BuiltinObj = exports.HashObj = exports.ArrayObj = exports.ErrorObj = exports.ContinueSignal = exports.BreakSignal = exports.ReturnValue = exports.NullObj = exports.BooleanObj = exports.StringObj = exports.IntegerObj = void 0;
exports.runSource = runSource;
exports.runSourceAsync = runSourceAsync;
const lexer_1 = require("./lexer");
const parser_1 = require("./parser");
const values_1 = require("./values");
Object.defineProperty(exports, "ArrayObj", { enumerable: true, get: function () { return values_1.ArrayObj; } });
Object.defineProperty(exports, "BooleanObj", { enumerable: true, get: function () { return values_1.BooleanObj; } });
Object.defineProperty(exports, "BreakSignal", { enumerable: true, get: function () { return values_1.BreakSignal; } });
Object.defineProperty(exports, "ContinueSignal", { enumerable: true, get: function () { return values_1.ContinueSignal; } });
Object.defineProperty(exports, "ErrorObj", { enumerable: true, get: function () { return values_1.ErrorObj; } });
Object.defineProperty(exports, "HashObj", { enumerable: true, get: function () { return values_1.HashObj; } });
Object.defineProperty(exports, "IntegerObj", { enumerable: true, get: function () { return values_1.IntegerObj; } });
Object.defineProperty(exports, "NullObj", { enumerable: true, get: function () { return values_1.NullObj; } });
Object.defineProperty(exports, "ReturnValue", { enumerable: true, get: function () { return values_1.ReturnValue; } });
Object.defineProperty(exports, "StringObj", { enumerable: true, get: function () { return values_1.StringObj; } });
Object.defineProperty(exports, "FALSE", { enumerable: true, get: function () { return values_1.FALSE_OBJ; } });
Object.defineProperty(exports, "NULL", { enumerable: true, get: function () { return values_1.NULL_OBJ; } });
Object.defineProperty(exports, "TRUE", { enumerable: true, get: function () { return values_1.TRUE_OBJ; } });
const builtins_1 = require("./builtins");
Object.defineProperty(exports, "BuiltinObj", { enumerable: true, get: function () { return builtins_1.BuiltinObj; } });
const runtime_1 = require("./runtime");
const effects_1 = require("./effects");
const regions_1 = require("./regions");
const memory_1 = require("../runtime/memory");
const ffi_1 = require("./ffi");
Object.defineProperty(exports, "FfiLibraryObj", { enumerable: true, get: function () { return ffi_1.FfiLibraryObj; } });
const modules_1 = require("../runtime/modules");
const syntax_1 = require("./syntax");
const path = __importStar(require("path"));
class FunctionObj {
    parameters;
    body;
    env;
    isAsync;
    type = "FUNCTION";
    constructor(parameters, body, env, isAsync = false) {
        this.parameters = parameters;
        this.body = body;
        this.env = env;
        this.isAsync = isAsync;
    }
    inspect() {
        const params = this.parameters.map((p) => p.value).join(", ");
        return `${this.isAsync ? "async " : ""}fn(${params}) { ... }`;
    }
}
exports.FunctionObj = FunctionObj;
class Environment {
    outer;
    store = new Map();
    constructor(outer = null) {
        this.outer = outer;
    }
    get(name) {
        const value = this.store.get(name);
        if (value !== undefined) {
            return value;
        }
        return this.outer?.get(name);
    }
    set(name, value) {
        this.store.set(name, value);
        return value;
    }
}
exports.Environment = Environment;
/**
 * Async tree-walking interpreter with effects, regions, channels, and FFI hooks.
 */
class Evaluator {
    output = [];
    runtime = new runtime_1.NexusRuntime();
    effects = new effects_1.EffectRuntime();
    regions = new regions_1.RegionStack();
    enums = new Map();
    structs = new Map();
    program = null;
    loadedModules = new Set();
    rootDir = process.cwd();
    modulesDir = "";
    stdlibDir = "";
    constructor() {
        this.modulesDir = path.join(this.rootDir, modules_1.MODULES_DIRNAME);
        this.stdlibDir = (0, modules_1.findStdlibDir)(this.rootDir);
    }
    configureModules(options) {
        if (options.rootDir) {
            this.rootDir = options.rootDir;
        }
        this.modulesDir =
            options.modulesDir ?? path.join(this.rootDir, modules_1.MODULES_DIRNAME);
        this.stdlibDir =
            options.stdlibDir !== undefined
                ? options.stdlibDir
                : (0, modules_1.findStdlibDir)(this.rootDir);
    }
    async eval(node, env = new Environment()) {
        if (node.type === "Program") {
            this.program = node;
            this.collectDecls(this.program);
        }
        this.ensureBuiltins(env);
        const value = await this.evalNode(node, env);
        await this.runtime.drain();
        return { value, output: [...this.output] };
    }
    writeOutput(line) {
        this.output.push(line);
    }
    ensureBuiltins(env) {
        if (env.get("puts")) {
            return;
        }
        (0, builtins_1.installCoreBuiltins)(env, {
            writeOutput: (line) => this.writeOutput(line),
            applyFunction: (fn, args) => this.applyFunctionSync(fn, args),
        });
    }
    /** Synchronous apply used by map/filter builtins and the HTTP host. */
    applyFunctionSync(fn, args) {
        if (fn instanceof builtins_1.BuiltinObj) {
            return fn.fn(...args);
        }
        if (fn instanceof FunctionObj) {
            if (args.length !== fn.parameters.length) {
                return (0, values_1.newError)(`wrong number of arguments: got=${args.length}, want=${fn.parameters.length}`);
            }
            const extended = new Environment(fn.env);
            for (let i = 0; i < fn.parameters.length; i++) {
                extended.set(fn.parameters[i].value, args[i]);
            }
            // Use a minimal sync walk for function bodies inside map/filter.
            return syncEvalNode(fn.body, extended, this);
        }
        return (0, values_1.newError)(`not a function: ${fn.type}`);
    }
    collectDecls(program) {
        for (const stmt of program.statements) {
            if (stmt.type === "EnumDeclaration") {
                const d = stmt;
                this.enums.set(d.name.value, new Set(d.variants.map((v) => v.name.value)));
            }
            if (stmt.type === "StructDeclaration") {
                const d = stmt;
                this.structs.set(d.name.value, d.fields.map((f) => f.name.value));
            }
        }
    }
    async evalNode(node, env) {
        switch (node.type) {
            case "Program":
                return this.evalProgram(node, env);
            case "BlockStatement":
                return this.evalBlock(node, env);
            case "ExpressionStatement": {
                const es = node;
                return es.expression ? this.evalNode(es.expression, env) : values_1.NULL_OBJ;
            }
            case "ReturnStatement": {
                const rs = node;
                const val = rs.returnValue
                    ? await this.evalNode(rs.returnValue, env)
                    : values_1.NULL_OBJ;
                if ((0, values_1.isError)(val)) {
                    return val;
                }
                return new values_1.ReturnValue(val);
            }
            case "BreakStatement":
                return values_1.BREAK_SIGNAL;
            case "ContinueStatement":
                return values_1.CONTINUE_SIGNAL;
            case "LetStatement": {
                const ls = node;
                const val = ls.value ? await this.evalNode(ls.value, env) : values_1.NULL_OBJ;
                if ((0, values_1.isError)(val)) {
                    return val;
                }
                // Track heap for non-primitives
                if (!(val instanceof values_1.IntegerObj) &&
                    !(val instanceof values_1.BooleanObj) &&
                    !(val instanceof values_1.StringObj) &&
                    !(val instanceof values_1.NullObj)) {
                    const id = (0, memory_1.getMemoryEngine)().alloc(val);
                    (0, memory_1.getMemoryEngine)().retain(id);
                    const arena = this.regions.current();
                    if (arena) {
                        arena.alloc(ls.name.value, () => (0, memory_1.getMemoryEngine)().release(id));
                    }
                }
                env.set(ls.name.value, val);
                return val;
            }
            case "EnumDeclaration":
            case "StructDeclaration":
            case "EffectDeclaration":
            case "MacroDefinition":
            case "ExternDeclaration":
                return values_1.NULL_OBJ;
            case "ImportStatement":
                return this.evalImport(node, env);
            case "WhileStatement":
                return this.evalWhile(node, env);
            case "IntegerLiteral":
                return new values_1.IntegerObj(node.value);
            case "StringLiteral":
                return new values_1.StringObj(node.value);
            case "BooleanLiteral":
                return (0, values_1.nativeBool)(node.value);
            case "NullLiteral":
                return values_1.NULL_OBJ;
            case "PrefixExpression":
                return this.evalPrefix(node, env);
            case "InfixExpression":
                return this.evalInfix(node, env);
            case "IfExpression":
                return this.evalIf(node, env);
            case "Identifier":
                return this.evalIdentifier(node, env);
            case "FunctionLiteral": {
                const fl = node;
                return new FunctionObj(fl.parameters, fl.body, env, fl.isAsync);
            }
            case "CallExpression":
                return this.evalCall(node, env);
            case "ConstructorExpression":
                return this.evalConstructor(node, env);
            case "ArrayLiteral":
                return this.evalArray(node, env);
            case "HashLiteral":
                return this.evalHash(node, env);
            case "IndexExpression":
                return this.evalIndex(node, env);
            case "MemberExpression":
                return this.evalMember(node, env);
            case "AssignExpression":
                return this.evalAssign(node, env);
            case "PipeExpression":
                return this.evalPipe(node, env);
            case "MatchExpression":
                return this.evalMatch(node, env);
            case "RefExpression":
                return this.evalRef(node, env);
            case "MoveExpression": {
                const m = node;
                return m.value ? this.evalNode(m.value, env) : values_1.NULL_OBJ;
            }
            case "AsyncExpression":
                return this.evalAsync(node, env);
            case "AwaitExpression":
                return this.evalAwait(node, env);
            case "SpawnExpression":
                return this.evalSpawn(node, env);
            case "ChanExpression":
                return this.evalChan(node, env);
            case "PerformExpression":
                return this.evalPerform(node, env);
            case "HandleExpression":
                return this.evalHandle(node, env);
            case "RegionExpression":
                return this.evalRegion(node, env);
            case "ReflectExpression":
                return this.evalReflect(node);
            default:
                return (0, values_1.newError)(`unknown node type: ${node.type}`);
        }
    }
    async evalProgram(program, env) {
        let result = values_1.NULL_OBJ;
        for (const statement of program.statements) {
            result = await this.evalNode(statement, env);
            if (result instanceof values_1.ReturnValue) {
                return result.value;
            }
            if (result instanceof values_1.ErrorObj) {
                return result;
            }
        }
        return result;
    }
    async evalBlock(block, env) {
        let result = values_1.NULL_OBJ;
        for (const statement of block.statements) {
            result = await this.evalNode(statement, env);
            if (result instanceof values_1.ReturnValue ||
                result instanceof values_1.ErrorObj ||
                result instanceof values_1.BreakSignal ||
                result instanceof values_1.ContinueSignal) {
                return result;
            }
        }
        return result;
    }
    async evalPrefix(node, env) {
        if (!node.right) {
            return (0, values_1.newError)("prefix expression missing operand");
        }
        const right = await this.evalNode(node.right, env);
        if ((0, values_1.isError)(right)) {
            return right;
        }
        switch (node.operator) {
            case "!":
                return (0, values_1.nativeBool)(!(0, values_1.isTruthy)(right));
            case "-":
                if (!(right instanceof values_1.IntegerObj)) {
                    return (0, values_1.newError)(`unknown operator: -${right.type}`);
                }
                return new values_1.IntegerObj(-right.value);
            default:
                return (0, values_1.newError)(`unknown operator: ${node.operator}`);
        }
    }
    async evalInfix(node, env) {
        const left = await this.evalNode(node.left, env);
        if ((0, values_1.isError)(left)) {
            return left;
        }
        if (!node.right) {
            return (0, values_1.newError)("infix expression missing right operand");
        }
        // Short-circuit logical operators (&& / || / and / or)
        if (node.operator === "&&" ||
            node.operator === "AND" ||
            node.operator === "and") {
            if (!(0, values_1.isTruthy)(left)) {
                return left;
            }
            return this.evalNode(node.right, env);
        }
        if (node.operator === "||" ||
            node.operator === "OR" ||
            node.operator === "or") {
            if ((0, values_1.isTruthy)(left)) {
                return left;
            }
            return this.evalNode(node.right, env);
        }
        const right = await this.evalNode(node.right, env);
        if ((0, values_1.isError)(right)) {
            return right;
        }
        if (left instanceof values_1.IntegerObj && right instanceof values_1.IntegerObj) {
            return this.evalIntegerInfix(node.operator, left, right);
        }
        if (left instanceof values_1.StringObj && right instanceof values_1.StringObj) {
            if (node.operator === "+") {
                return new values_1.StringObj(left.value + right.value);
            }
            if (node.operator === "==") {
                return (0, values_1.nativeBool)(left.value === right.value);
            }
            if (node.operator === "!=") {
                return (0, values_1.nativeBool)(left.value !== right.value);
            }
        }
        if (node.operator === "==") {
            return (0, values_1.nativeBool)(left === right);
        }
        if (node.operator === "!=") {
            return (0, values_1.nativeBool)(left !== right);
        }
        return (0, values_1.newError)(`unknown operator: ${left.type} ${node.operator} ${right.type}`);
    }
    evalIntegerInfix(operator, left, right) {
        switch (operator) {
            case "+":
                return new values_1.IntegerObj(left.value + right.value);
            case "-":
                return new values_1.IntegerObj(left.value - right.value);
            case "*":
                return new values_1.IntegerObj(left.value * right.value);
            case "/":
                if (right.value === 0) {
                    return (0, values_1.newError)("division by zero");
                }
                return new values_1.IntegerObj(Math.trunc(left.value / right.value));
            case "%":
                if (right.value === 0) {
                    return (0, values_1.newError)("modulo by zero");
                }
                return new values_1.IntegerObj(left.value % right.value);
            case "<":
                return (0, values_1.nativeBool)(left.value < right.value);
            case ">":
                return (0, values_1.nativeBool)(left.value > right.value);
            case "<=":
                return (0, values_1.nativeBool)(left.value <= right.value);
            case ">=":
                return (0, values_1.nativeBool)(left.value >= right.value);
            case "==":
                return (0, values_1.nativeBool)(left.value === right.value);
            case "!=":
                return (0, values_1.nativeBool)(left.value !== right.value);
            default:
                return (0, values_1.newError)(`unknown operator: INTEGER ${operator} INTEGER`);
        }
    }
    async evalIf(node, env) {
        if (!node.condition) {
            return (0, values_1.newError)("if expression missing condition");
        }
        const condition = await this.evalNode(node.condition, env);
        if ((0, values_1.isError)(condition)) {
            return condition;
        }
        if ((0, values_1.isTruthy)(condition)) {
            return this.evalNode(node.consequence, env);
        }
        if (node.alternative) {
            return this.evalNode(node.alternative, env);
        }
        return values_1.NULL_OBJ;
    }
    evalIdentifier(node, env) {
        const value = env.get(node.value);
        if (value) {
            return value;
        }
        const b = this.runtimeBuiltins()[node.value];
        if (b) {
            return b;
        }
        return (0, values_1.newError)(`identifier not found: ${node.value}`);
    }
    async evalCall(node, env) {
        const fn = await this.evalNode(node.function, env);
        if ((0, values_1.isError)(fn)) {
            return fn;
        }
        const args = [];
        for (const arg of node.arguments) {
            const evaluated = await this.evalNode(arg, env);
            if ((0, values_1.isError)(evaluated)) {
                return evaluated;
            }
            args.push(evaluated);
        }
        return this.applyFunction(fn, args);
    }
    /** Apply a user function (used by HTTP host and call expressions). */
    async applyFunction(fn, args) {
        if (fn instanceof FunctionObj) {
            if (args.length !== fn.parameters.length) {
                return (0, values_1.newError)(`wrong number of arguments: got=${args.length}, want=${fn.parameters.length}`);
            }
            const extended = new Environment(fn.env);
            for (let i = 0; i < fn.parameters.length; i++) {
                extended.set(fn.parameters[i].value, args[i]);
            }
            const result = await this.evalNode(fn.body, extended);
            if (result instanceof values_1.ReturnValue) {
                return result.value;
            }
            return result;
        }
        if (fn instanceof builtins_1.BuiltinObj) {
            return fn.fn(...args);
        }
        if (fn instanceof runtime_1.ExternFnObj) {
            return fn.call(...args);
        }
        return (0, values_1.newError)(`not a function: ${fn.type}`);
    }
    async evalConstructor(node, env) {
        const fields = [];
        for (const arg of node.arguments) {
            const v = await this.evalNode(arg, env);
            if ((0, values_1.isError)(v)) {
                return v;
            }
            fields.push(v);
        }
        const structFields = this.structs.get(node.variant.value);
        if (structFields && !node.enumName) {
            if (fields.length !== structFields.length) {
                return (0, values_1.newError)(`${node.variant.value} expects ${structFields.length} fields, got ${fields.length}`);
            }
            const hash = new values_1.HashObj();
            hash.setString("__struct", new values_1.StringObj(node.variant.value));
            for (let i = 0; i < structFields.length; i++) {
                hash.setString(structFields[i], fields[i]);
            }
            return hash;
        }
        return new runtime_1.EnumValueObj(node.enumName, node.variant.value, fields);
    }
    async evalImport(node, env) {
        const fromDirObj = env.get("__dir__");
        const fromDir = fromDirObj instanceof values_1.StringObj ? fromDirObj.value : this.rootDir;
        const resolved = (0, modules_1.resolveModulePath)(node.path, {
            rootDir: this.rootDir,
            modulesDir: this.modulesDir,
            stdlibDir: this.stdlibDir,
            fromDir,
        });
        if (!resolved) {
            return (0, values_1.newError)(`import failed: cannot resolve ${node.path}`);
        }
        if (this.loadedModules.has(resolved)) {
            return values_1.NULL_OBJ;
        }
        this.loadedModules.add(resolved);
        let source;
        try {
            source = (0, modules_1.readModuleSource)(resolved);
        }
        catch (err) {
            this.loadedModules.delete(resolved);
            return (0, values_1.newError)(`import failed: ${err instanceof Error ? err.message : String(err)}`);
        }
        source = (0, syntax_1.lowerSyntax)(source);
        const prevFile = env.get("__file__");
        const prevDir = env.get("__dir__");
        env.set("__file__", new values_1.StringObj(resolved));
        env.set("__dir__", new values_1.StringObj(path.dirname(resolved)));
        const lexer = new lexer_1.Lexer(source);
        const parser = new parser_1.Parser(lexer);
        const program = parser.parseProgram();
        if (parser.getErrors().length > 0) {
            return (0, values_1.newError)(`parse error in ${resolved}: ${parser.getErrors().join("; ")}`);
        }
        this.collectDecls(program);
        const result = await this.evalNode(program, env);
        if (prevFile) {
            env.set("__file__", prevFile);
        }
        if (prevDir) {
            env.set("__dir__", prevDir);
        }
        return result;
    }
    async evalWhile(node, env) {
        let result = values_1.NULL_OBJ;
        while (true) {
            if (!node.condition) {
                return (0, values_1.newError)("while missing condition");
            }
            const cond = await this.evalNode(node.condition, env);
            if ((0, values_1.isError)(cond)) {
                return cond;
            }
            if (!(0, values_1.isTruthy)(cond)) {
                break;
            }
            result = await this.evalNode(node.body, env);
            if (result instanceof values_1.ReturnValue || (0, values_1.isError)(result)) {
                return result;
            }
            if (result instanceof values_1.BreakSignal) {
                return values_1.NULL_OBJ;
            }
            if (result instanceof values_1.ContinueSignal) {
                continue;
            }
        }
        return result;
    }
    async evalArray(node, env) {
        const elements = [];
        for (const el of node.elements) {
            const v = await this.evalNode(el, env);
            if ((0, values_1.isError)(v)) {
                return v;
            }
            elements.push(v);
        }
        return new values_1.ArrayObj(elements);
    }
    async evalHash(node, env) {
        const hash = new values_1.HashObj();
        for (const { key, value } of node.pairs) {
            const k = await this.evalNode(key, env);
            if ((0, values_1.isError)(k)) {
                return k;
            }
            const v = await this.evalNode(value, env);
            if ((0, values_1.isError)(v)) {
                return v;
            }
            hash.set(k, v);
        }
        return hash;
    }
    async evalIndex(node, env) {
        const left = await this.evalNode(node.left, env);
        if ((0, values_1.isError)(left)) {
            return left;
        }
        if (!node.index) {
            return (0, values_1.newError)("index missing");
        }
        const index = await this.evalNode(node.index, env);
        if ((0, values_1.isError)(index)) {
            return index;
        }
        if (left instanceof values_1.ArrayObj && index instanceof values_1.IntegerObj) {
            return left.elements[index.value] ?? values_1.NULL_OBJ;
        }
        if (left instanceof values_1.HashObj) {
            return left.get(index);
        }
        if (left instanceof values_1.StringObj && index instanceof values_1.IntegerObj) {
            if (index.value < 0 || index.value >= left.value.length) {
                return values_1.NULL_OBJ;
            }
            return new values_1.StringObj(left.value[index.value]);
        }
        return (0, values_1.newError)(`index operator not supported: ${left.type}`);
    }
    async evalMember(node, env) {
        const obj = await this.evalNode(node.object, env);
        if ((0, values_1.isError)(obj)) {
            return obj;
        }
        if (obj instanceof values_1.HashObj) {
            return obj.getString(node.property.value);
        }
        return (0, values_1.newError)(`member access on non-hash: ${obj.type}`);
    }
    async evalAssign(node, env) {
        if (!node.value) {
            return (0, values_1.newError)("assign missing value");
        }
        const val = await this.evalNode(node.value, env);
        if ((0, values_1.isError)(val)) {
            return val;
        }
        if (node.name.type === "Identifier") {
            env.set(node.name.value, val);
            return val;
        }
        if (node.name.type === "IndexExpression") {
            const ix = node.name;
            const left = await this.evalNode(ix.left, env);
            if ((0, values_1.isError)(left)) {
                return left;
            }
            if (!ix.index) {
                return (0, values_1.newError)("index missing");
            }
            const index = await this.evalNode(ix.index, env);
            if ((0, values_1.isError)(index)) {
                return index;
            }
            if (left instanceof values_1.ArrayObj && index instanceof values_1.IntegerObj) {
                if (index.value < 0 || index.value >= left.elements.length) {
                    return (0, values_1.newError)("array index out of bounds");
                }
                left.elements[index.value] = val;
                return val;
            }
            if (left instanceof values_1.HashObj) {
                left.set(index, val);
                return val;
            }
            return (0, values_1.newError)(`index assignment not supported on ${left.type}`);
        }
        return (0, values_1.newError)("invalid assignment target");
    }
    async evalPipe(node, env) {
        const left = await this.evalNode(node.left, env);
        if ((0, values_1.isError)(left)) {
            return left;
        }
        if (!node.right) {
            return (0, values_1.newError)("pipe missing right-hand side");
        }
        if (node.right.type === "CallExpression") {
            const call = node.right;
            const fn = await this.evalNode(call.function, env);
            if ((0, values_1.isError)(fn)) {
                return fn;
            }
            const args = [left];
            for (const arg of call.arguments) {
                const evaluated = await this.evalNode(arg, env);
                if ((0, values_1.isError)(evaluated)) {
                    return evaluated;
                }
                args.push(evaluated);
            }
            return this.applyFunction(fn, args);
        }
        const fn = await this.evalNode(node.right, env);
        if ((0, values_1.isError)(fn)) {
            return fn;
        }
        return this.applyFunction(fn, [left]);
    }
    async evalMatch(node, env) {
        if (!node.scrutinee) {
            return (0, values_1.newError)("match missing scrutinee");
        }
        const value = await this.evalNode(node.scrutinee, env);
        if ((0, values_1.isError)(value)) {
            return value;
        }
        for (const arm of node.arms) {
            const matched = this.matchPattern(arm.pattern, value, env);
            if (matched) {
                return this.evalNode(arm.body, matched);
            }
        }
        return (0, values_1.newError)("non-exhaustive match at runtime");
    }
    matchPattern(pattern, value, env) {
        switch (pattern.kind) {
            case "wildcard":
                return env;
            case "ident": {
                const extended = new Environment(env);
                extended.set(pattern.name.value, value);
                return extended;
            }
            case "literal": {
                if (pattern.value.type === "IntegerLiteral" &&
                    value instanceof values_1.IntegerObj &&
                    pattern.value.value === value.value) {
                    return env;
                }
                if (pattern.value.type === "StringLiteral" &&
                    value instanceof values_1.StringObj &&
                    pattern.value.value === value.value) {
                    return env;
                }
                if (pattern.value.type === "BooleanLiteral" &&
                    value instanceof values_1.BooleanObj &&
                    pattern.value.value === value.value) {
                    return env;
                }
                return null;
            }
            case "variant": {
                if (!(0, runtime_1.isEnumValue)(value)) {
                    return null;
                }
                if (value.variant !== pattern.variant.value) {
                    return null;
                }
                if (pattern.enumName &&
                    value.enumName &&
                    pattern.enumName !== value.enumName) {
                    return null;
                }
                if (pattern.fields.length !== value.fields.length) {
                    return null;
                }
                let extended = env;
                for (let i = 0; i < pattern.fields.length; i++) {
                    const nested = this.matchPattern(pattern.fields[i], value.fields[i], extended);
                    if (!nested) {
                        return null;
                    }
                    extended = nested;
                }
                return extended;
            }
            default:
                return null;
        }
    }
    async evalRef(node, env) {
        if (!node.value) {
            return (0, values_1.newError)("ref missing operand");
        }
        const val = await this.evalNode(node.value, env);
        if ((0, values_1.isError)(val)) {
            return val;
        }
        return new runtime_1.RefObj(val, node.mutable);
    }
    evalAsync(node, env) {
        const promise = (async () => {
            if (node.body.type === "BlockStatement") {
                return this.evalNode(node.body, env);
            }
            return this.evalNode(node.body, env);
        })();
        return new runtime_1.PromiseObj(promise);
    }
    async evalAwait(node, env) {
        if (!node.argument) {
            return (0, values_1.newError)("await missing argument");
        }
        const val = await this.evalNode(node.argument, env);
        if ((0, values_1.isError)(val)) {
            return val;
        }
        if ((0, runtime_1.isPromiseObj)(val)) {
            return val.awaitValue();
        }
        return val;
    }
    async evalSpawn(node, env) {
        if (!node.argument) {
            return (0, values_1.newError)("spawn missing argument");
        }
        const arg = node.argument;
        const handle = this.runtime.spawn(async () => {
            const fn = await this.evalNode(arg, env);
            if (fn instanceof FunctionObj) {
                await this.applyFunction(fn, []);
            }
            else if ((0, runtime_1.isPromiseObj)(fn)) {
                await fn.awaitValue();
            }
        });
        return new values_1.IntegerObj(handle.id);
    }
    async evalChan(node, env) {
        let capacity = 0;
        if (node.capacity) {
            const c = await this.evalNode(node.capacity, env);
            if (c instanceof values_1.IntegerObj) {
                capacity = c.value;
            }
        }
        return this.runtime.createChannel(capacity);
    }
    async evalPerform(node, env) {
        const args = [];
        for (const a of node.arguments) {
            const v = await this.evalNode(a, env);
            if ((0, values_1.isError)(v)) {
                return v;
            }
            args.push(v);
        }
        try {
            const result = this.effects.perform(node.effectName, node.operation.value, args);
            return result ?? values_1.NULL_OBJ;
        }
        catch (err) {
            return (0, values_1.newError)(err instanceof Error ? err.message : String(err));
        }
    }
    async evalHandle(node, env) {
        const handlers = new Map();
        for (const arm of node.handlers) {
            handlers.set(arm.operation.value, {
                parameters: arm.parameters.map((p) => p.value),
                invoke: (args, cont) => {
                    const extended = new Environment(env);
                    for (let i = 0; i < arm.parameters.length; i++) {
                        extended.set(arm.parameters[i].value, args[i] ?? values_1.NULL_OBJ);
                    }
                    // resume builtin for handlers
                    extended.set("resume", new builtins_1.BuiltinObj((...resumeArgs) => {
                        const v = resumeArgs[0] ?? values_1.NULL_OBJ;
                        cont.resume(v);
                        return v;
                    }));
                    // Synchronous bridge: kick async eval without awaiting (handlers
                    // are expected to call resume).
                    let result = values_1.NULL_OBJ;
                    void this.evalNode(arm.body, extended).then((r) => {
                        result = r;
                        if (!extended.get("resume")) {
                            cont.resume(r);
                        }
                    });
                    return result;
                },
            });
        }
        this.effects.push({
            effectName: node.effectName.value,
            handlers,
        });
        try {
            return await this.evalNode(node.body, env);
        }
        finally {
            this.effects.pop();
        }
    }
    async evalRegion(node, env) {
        this.regions.enter();
        try {
            return await this.evalNode(node.body, env);
        }
        finally {
            this.regions.exit();
        }
    }
    evalReflect(node) {
        const name = node.target.value;
        const fields = this.structs.get(name);
        if (fields) {
            return new values_1.StringObj(JSON.stringify({ kind: "struct", name, fields }));
        }
        const variants = this.enums.get(name);
        if (variants) {
            return new values_1.StringObj(JSON.stringify({
                kind: "enum",
                name,
                variants: [...variants],
            }));
        }
        return (0, values_1.newError)(`reflect: unknown type ${name}`);
    }
    runtimeBuiltins() {
        return {
            send: new builtins_1.BuiltinObj((...args) => {
                if (args.length !== 2 || !(0, runtime_1.isChannel)(args[0])) {
                    return (0, values_1.newError)("send: want channel, value");
                }
                return new runtime_1.PromiseObj(args[0].send(args[1]).then(() => values_1.NULL_OBJ));
            }),
            recv: new builtins_1.BuiltinObj((...args) => {
                if (args.length !== 1 || !(0, runtime_1.isChannel)(args[0])) {
                    return (0, values_1.newError)("recv: want channel");
                }
                return new runtime_1.PromiseObj(args[0].recv());
            }),
            deref: new builtins_1.BuiltinObj((...args) => {
                if (args.length !== 1 || !(0, runtime_1.isRefObj)(args[0])) {
                    return (0, values_1.newError)("deref: want ref");
                }
                return args[0].target;
            }),
            reflect: new builtins_1.BuiltinObj((...args) => {
                if (args[0] instanceof values_1.StringObj) {
                    return this.evalReflect({
                        kind: "expression",
                        type: "ReflectExpression",
                        token: {
                            type: "REFLECT",
                            literal: "reflect",
                            line: 0,
                            column: 0,
                        },
                        target: {
                            kind: "expression",
                            type: "Identifier",
                            token: {
                                type: "IDENT",
                                literal: args[0].value,
                                line: 0,
                                column: 0,
                            },
                            value: args[0].value,
                            tokenLiteral: () => args[0].inspect(),
                        },
                        tokenLiteral: () => "reflect",
                    });
                }
                return (0, values_1.newError)("reflect: want type name string");
            }),
        };
    }
}
exports.Evaluator = Evaluator;
/** Sync walk for map/filter callbacks (subset of statements/expressions). */
function syncEvalNode(node, env, host) {
    switch (node.type) {
        case "BlockStatement": {
            let result = values_1.NULL_OBJ;
            for (const s of node.statements) {
                result = syncEvalNode(s, env, host);
                if (result instanceof values_1.ReturnValue) {
                    return result.value;
                }
                if ((0, values_1.isError)(result)) {
                    return result;
                }
            }
            return result;
        }
        case "ExpressionStatement": {
            const es = node;
            return es.expression ? syncEvalNode(es.expression, env, host) : values_1.NULL_OBJ;
        }
        case "ReturnStatement": {
            const rs = node;
            const val = rs.returnValue
                ? syncEvalNode(rs.returnValue, env, host)
                : values_1.NULL_OBJ;
            return (0, values_1.isError)(val) ? val : new values_1.ReturnValue(val);
        }
        case "IntegerLiteral":
            return new values_1.IntegerObj(node.value);
        case "StringLiteral":
            return new values_1.StringObj(node.value);
        case "BooleanLiteral":
            return (0, values_1.nativeBool)(node.value);
        case "NullLiteral":
            return values_1.NULL_OBJ;
        case "Identifier": {
            const name = node.value;
            return env.get(name) ?? (0, values_1.newError)(`identifier not found: ${name}`);
        }
        case "InfixExpression": {
            const inf = node;
            const left = syncEvalNode(inf.left, env, host);
            if (!inf.right) {
                return (0, values_1.newError)("missing rhs");
            }
            if (inf.operator === "&&" ||
                inf.operator === "AND" ||
                inf.operator === "and") {
                if (!(0, values_1.isTruthy)(left)) {
                    return left;
                }
                return syncEvalNode(inf.right, env, host);
            }
            if (inf.operator === "||" ||
                inf.operator === "OR" ||
                inf.operator === "or") {
                if ((0, values_1.isTruthy)(left)) {
                    return left;
                }
                return syncEvalNode(inf.right, env, host);
            }
            const right = syncEvalNode(inf.right, env, host);
            if (left instanceof values_1.IntegerObj && right instanceof values_1.IntegerObj) {
                switch (inf.operator) {
                    case "+":
                        return new values_1.IntegerObj(left.value + right.value);
                    case "-":
                        return new values_1.IntegerObj(left.value - right.value);
                    case "*":
                        return new values_1.IntegerObj(left.value * right.value);
                    case "/":
                        return right.value === 0
                            ? (0, values_1.newError)("division by zero")
                            : new values_1.IntegerObj(Math.trunc(left.value / right.value));
                    case "==":
                        return (0, values_1.nativeBool)(left.value === right.value);
                    case "!=":
                        return (0, values_1.nativeBool)(left.value !== right.value);
                    case "<":
                        return (0, values_1.nativeBool)(left.value < right.value);
                    case ">":
                        return (0, values_1.nativeBool)(left.value > right.value);
                    default:
                        return (0, values_1.newError)(`unknown op ${inf.operator}`);
                }
            }
            if (left instanceof values_1.StringObj &&
                right instanceof values_1.StringObj &&
                inf.operator === "+") {
                return new values_1.StringObj(left.value + right.value);
            }
            if (inf.operator === "==") {
                return (0, values_1.nativeBool)(left === right);
            }
            if (inf.operator === "!=") {
                return (0, values_1.nativeBool)(left !== right);
            }
            return (0, values_1.newError)("type mismatch");
        }
        case "CallExpression": {
            const call = node;
            const fn = syncEvalNode(call.function, env, host);
            const args = call.arguments.map((a) => syncEvalNode(a, env, host));
            return host.applyFunctionSync(fn, args);
        }
        case "PrefixExpression": {
            const p = node;
            if (!p.right) {
                return (0, values_1.newError)("missing operand");
            }
            const right = syncEvalNode(p.right, env, host);
            if (p.operator === "!") {
                return (0, values_1.nativeBool)(!(0, values_1.isTruthy)(right));
            }
            if (p.operator === "-" && right instanceof values_1.IntegerObj) {
                return new values_1.IntegerObj(-right.value);
            }
            return (0, values_1.newError)(`unknown prefix ${p.operator}`);
        }
        default:
            return (0, values_1.newError)(`sync eval unsupported for ${node.type}`);
    }
}
/** Synchronous convenience for simple (non-async) scripts. */
function runSource(source) {
    const lexer = new lexer_1.Lexer(source);
    const parser = new parser_1.Parser(lexer);
    let program = parser.parseProgram();
    if (parser.getErrors().length > 0) {
        return {
            value: new values_1.ErrorObj(`parse errors:\n  ${parser.getErrors().join("\n  ")}`),
            output: [],
        };
    }
    const { MacroExpander } = require("./macro");
    const expanded = new MacroExpander().expand(program);
    if (expanded.errors.length > 0) {
        return {
            value: new values_1.ErrorObj(`macro errors:\n  ${expanded.errors.join("\n  ")}`),
            output: [],
        };
    }
    program = expanded.program;
    return syncEvalProgram(program, new Environment());
}
function syncEvalProgram(program, env) {
    const output = [];
    const builtins = {
        puts: new builtins_1.BuiltinObj((...args) => {
            for (const a of args) {
                output.push(a.inspect());
            }
            return values_1.NULL_OBJ;
        }),
        len: new builtins_1.BuiltinObj((...args) => {
            if (args.length === 1 && args[0] instanceof values_1.StringObj) {
                return new values_1.IntegerObj(args[0].value.length);
            }
            return (0, values_1.newError)("len: want string");
        }),
    };
    const evalExpr = (node, localEnv) => {
        switch (node.type) {
            case "Program": {
                let result = values_1.NULL_OBJ;
                for (const s of node.statements) {
                    result = evalExpr(s, localEnv);
                    if (result instanceof values_1.ReturnValue) {
                        return result.value;
                    }
                    if (result instanceof values_1.ErrorObj) {
                        return result;
                    }
                }
                return result;
            }
            case "BlockStatement": {
                let result = values_1.NULL_OBJ;
                for (const s of node.statements) {
                    result = evalExpr(s, localEnv);
                    if (result instanceof values_1.ReturnValue || result instanceof values_1.ErrorObj) {
                        return result;
                    }
                }
                return result;
            }
            case "ExpressionStatement": {
                const es = node;
                return es.expression ? evalExpr(es.expression, localEnv) : values_1.NULL_OBJ;
            }
            case "LetStatement": {
                const ls = node;
                const val = ls.value ? evalExpr(ls.value, localEnv) : values_1.NULL_OBJ;
                if ((0, values_1.isError)(val)) {
                    return val;
                }
                localEnv.set(ls.name.value, val);
                return val;
            }
            case "ReturnStatement": {
                const rs = node;
                const val = rs.returnValue ? evalExpr(rs.returnValue, localEnv) : values_1.NULL_OBJ;
                return (0, values_1.isError)(val) ? val : new values_1.ReturnValue(val);
            }
            case "IntegerLiteral":
                return new values_1.IntegerObj(node.value);
            case "StringLiteral":
                return new values_1.StringObj(node.value);
            case "BooleanLiteral":
                return (0, values_1.nativeBool)(node.value);
            case "Identifier": {
                const name = node.value;
                return localEnv.get(name) ?? builtins[name] ?? (0, values_1.newError)(`identifier not found: ${name}`);
            }
            case "PrefixExpression": {
                const p = node;
                if (!p.right) {
                    return (0, values_1.newError)("missing operand");
                }
                const right = evalExpr(p.right, localEnv);
                if (p.operator === "!") {
                    return (0, values_1.nativeBool)(!(0, values_1.isTruthy)(right));
                }
                if (p.operator === "-" && right instanceof values_1.IntegerObj) {
                    return new values_1.IntegerObj(-right.value);
                }
                return (0, values_1.newError)(`unknown prefix ${p.operator}`);
            }
            case "InfixExpression": {
                const inf = node;
                const left = evalExpr(inf.left, localEnv);
                if (!inf.right) {
                    return (0, values_1.newError)("missing rhs");
                }
                const right = evalExpr(inf.right, localEnv);
                if (left instanceof values_1.IntegerObj && right instanceof values_1.IntegerObj) {
                    switch (inf.operator) {
                        case "+":
                            return new values_1.IntegerObj(left.value + right.value);
                        case "-":
                            return new values_1.IntegerObj(left.value - right.value);
                        case "*":
                            return new values_1.IntegerObj(left.value * right.value);
                        case "/":
                            return right.value === 0
                                ? (0, values_1.newError)("division by zero")
                                : new values_1.IntegerObj(Math.trunc(left.value / right.value));
                        case "==":
                            return (0, values_1.nativeBool)(left.value === right.value);
                        case "!=":
                            return (0, values_1.nativeBool)(left.value !== right.value);
                        case "<":
                            return (0, values_1.nativeBool)(left.value < right.value);
                        case ">":
                            return (0, values_1.nativeBool)(left.value > right.value);
                        default:
                            return (0, values_1.newError)(`unknown op ${inf.operator}`);
                    }
                }
                if (left instanceof values_1.StringObj &&
                    right instanceof values_1.StringObj &&
                    inf.operator === "+") {
                    return new values_1.StringObj(left.value + right.value);
                }
                return (0, values_1.newError)("type mismatch");
            }
            case "IfExpression": {
                const iff = node;
                if (!iff.condition) {
                    return values_1.NULL_OBJ;
                }
                const cond = evalExpr(iff.condition, localEnv);
                if ((0, values_1.isTruthy)(cond)) {
                    return evalExpr(iff.consequence, localEnv);
                }
                return iff.alternative ? evalExpr(iff.alternative, localEnv) : values_1.NULL_OBJ;
            }
            case "FunctionLiteral": {
                const fl = node;
                return new FunctionObj(fl.parameters, fl.body, localEnv, fl.isAsync ?? false);
            }
            case "CallExpression": {
                const call = node;
                const fn = evalExpr(call.function, localEnv);
                const args = call.arguments.map((a) => evalExpr(a, localEnv));
                if (fn instanceof FunctionObj) {
                    const extended = new Environment(fn.env);
                    for (let i = 0; i < fn.parameters.length; i++) {
                        extended.set(fn.parameters[i].value, args[i]);
                    }
                    const result = evalExpr(fn.body, extended);
                    return result instanceof values_1.ReturnValue ? result.value : result;
                }
                if (fn instanceof builtins_1.BuiltinObj) {
                    return fn.fn(...args);
                }
                return (0, values_1.newError)(`not a function: ${fn.type}`);
            }
            case "ConstructorExpression": {
                const ctor = node;
                const fields = ctor.arguments.map((a) => evalExpr(a, localEnv));
                return new runtime_1.EnumValueObj(ctor.enumName, ctor.variant.value, fields);
            }
            case "MatchExpression": {
                const m = node;
                if (!m.scrutinee) {
                    return (0, values_1.newError)("match missing scrutinee");
                }
                const value = evalExpr(m.scrutinee, localEnv);
                for (const arm of m.arms) {
                    const matched = syncMatchPattern(arm.pattern, value, localEnv);
                    if (matched) {
                        return evalExpr(arm.body, matched);
                    }
                }
                return (0, values_1.newError)("non-exhaustive match at runtime");
            }
            case "MoveExpression": {
                const mv = node;
                return mv.value ? evalExpr(mv.value, localEnv) : values_1.NULL_OBJ;
            }
            case "EnumDeclaration":
            case "StructDeclaration":
            case "EffectDeclaration":
            case "MacroDefinition":
            case "ExternDeclaration":
                return values_1.NULL_OBJ;
            default:
                return (0, values_1.newError)(`sync eval unsupported for ${node.type}; use runSourceAsync`);
        }
    };
    return { value: evalExpr(program, env), output };
}
function syncMatchPattern(pattern, value, env) {
    switch (pattern.kind) {
        case "wildcard":
            return env;
        case "ident": {
            const extended = new Environment(env);
            extended.set(pattern.name.value, value);
            return extended;
        }
        case "literal":
            return null;
        case "variant": {
            if (!(0, runtime_1.isEnumValue)(value)) {
                return null;
            }
            if (value.variant !== pattern.variant.value) {
                return null;
            }
            if (pattern.enumName &&
                value.enumName &&
                pattern.enumName !== value.enumName) {
                return null;
            }
            if (pattern.fields.length !== value.fields.length) {
                return null;
            }
            let extended = env;
            for (let i = 0; i < pattern.fields.length; i++) {
                const nested = syncMatchPattern(pattern.fields[i], value.fields[i], extended);
                if (!nested) {
                    return null;
                }
                extended = nested;
            }
            return extended;
        }
        default:
            return null;
    }
}
async function runSourceAsync(source, env, preparsed, options) {
    let program = preparsed;
    if (!program) {
        const lexer = new lexer_1.Lexer(source);
        const parser = new parser_1.Parser(lexer);
        program = parser.parseProgram();
        if (parser.getErrors().length > 0) {
            return {
                value: new values_1.ErrorObj(`parse errors:\n  ${parser.getErrors().join("\n  ")}`),
                output: [],
            };
        }
    }
    const evaluator = new Evaluator();
    if (options) {
        evaluator.configureModules(options);
    }
    const environment = env ?? new Environment();
    if (options?.filePath) {
        environment.set("__file__", new values_1.StringObj(options.filePath));
        environment.set("__dir__", new values_1.StringObj(path.dirname(options.filePath)));
    }
    else if (!environment.get("__dir__")) {
        environment.set("__dir__", new values_1.StringObj(evaluator.rootDir));
        environment.set("__file__", new values_1.StringObj(path.join(evaluator.rootDir, "<stdin>")));
    }
    options?.onReady?.({ env: environment, evaluator });
    return evaluator.eval(program, environment);
}
//# sourceMappingURL=evaluator.js.map