"use strict";
/**
 * Interactive Nexus REPL (tree-walk or bytecode VM).
 */
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.runRepl = runRepl;
const readline = __importStar(require("readline"));
const path = __importStar(require("path"));
const engine_1 = require("./compiler/engine");
const modules_1 = require("./runtime/modules");
const compiler_1 = require("./vm/compiler");
const vm_1 = require("./vm/vm");
const builtins_1 = require("./language/builtins");
const symbols_1 = require("./vm/symbols");
const values_1 = require("./language/values");
const lexer_1 = require("./language/lexer");
const parser_1 = require("./language/parser");
const syntax_1 = require("./language/syntax");
const macro_1 = require("./language/macro");
async function runRepl(cfg = {}) {
    const engine = cfg.engine ?? "eval";
    const rootDir = cfg.rootDir ?? process.cwd();
    const stdlibDir = cfg.stdlibDir !== undefined ? cfg.stdlibDir : (0, modules_1.findStdlibDir)(rootDir);
    const prompt = cfg.prompt ?? "nex> ";
    const input = cfg.input ?? process.stdin;
    const output = cfg.output ?? process.stdout;
    output.write("Nexus REPL — :help for commands, :quit to exit\n");
    output.write(`engine=${engine}\n`);
    let pending = "";
    let currentEngine = engine;
    // VM persistent state
    let constants = [];
    let globals = new Array(65536);
    let symbolTable = new symbols_1.SymbolTable();
    for (let i = 0; i < builtins_1.BUILTIN_NAMES.length; i++) {
        symbolTable.defineBuiltin(i, builtins_1.BUILTIN_NAMES[i]);
    }
    const rl = readline.createInterface({ input, output, terminal: true });
    const ask = () => new Promise((resolve) => {
        rl.question(pending ? "...  " : prompt, (line) => {
            resolve(line);
        });
        rl.once("close", () => resolve(null));
    });
    while (true) {
        const line = await ask();
        if (line === null) {
            output.write("\n");
            break;
        }
        const trim = line.trim();
        if (!pending && trim.startsWith(":")) {
            if (trim === ":quit" || trim === ":exit") {
                break;
            }
            if (trim === ":help") {
                output.write("Commands: :help :quit :engine eval|vm :clear\n");
                continue;
            }
            if (trim.startsWith(":engine")) {
                const parts = trim.split(/\s+/);
                const next = parts[1];
                if (next === "eval" || next === "vm") {
                    currentEngine = next;
                    output.write(`engine=${currentEngine}\n`);
                }
                else {
                    output.write("usage: :engine eval|vm\n");
                }
                continue;
            }
            if (trim === ":clear") {
                pending = "";
                constants = [];
                globals = new Array(65536);
                symbolTable = new symbols_1.SymbolTable();
                for (let i = 0; i < builtins_1.BUILTIN_NAMES.length; i++) {
                    symbolTable.defineBuiltin(i, builtins_1.BUILTIN_NAMES[i]);
                }
                output.write("state cleared\n");
                continue;
            }
            output.write(`unknown command ${trim}\n`);
            continue;
        }
        pending = pending ? `${pending}\n${line}` : line;
        if (!isBalanced(pending)) {
            continue;
        }
        const source = pending;
        pending = "";
        try {
            if (currentEngine === "vm") {
                const lowered = (0, syntax_1.lowerSyntax)(source);
                const lexer = new lexer_1.Lexer(lowered);
                const parser = new parser_1.Parser(lexer);
                let program = parser.parseProgram();
                if (parser.getErrors().length > 0) {
                    output.write(parser.getErrors().join("\n") + "\n");
                    continue;
                }
                program = new macro_1.MacroExpander().expand(program).program;
                const compiler = new compiler_1.BytecodeCompiler(symbolTable, constants);
                const err = compiler.compile(program);
                if (err) {
                    output.write(err + "\n");
                    continue;
                }
                const bytecode = compiler.bytecode();
                constants = bytecode.constants;
                symbolTable = compiler.symbolTable;
                const vm = new vm_1.VirtualMachine(bytecode, globals);
                const runErr = vm.run();
                globals = vm.globals;
                for (const o of vm.output) {
                    output.write(o + "\n");
                }
                if (runErr) {
                    output.write(`ERROR: ${runErr}\n`);
                }
                else {
                    const v = vm.lastPoppedStackElem();
                    if (v !== values_1.NULL_OBJ && v.type !== "NULL") {
                        output.write(v.inspect() + "\n");
                    }
                }
            }
            else {
                const result = await (0, engine_1.evaluate)(source, {
                    tier: "eval",
                    rootDir,
                    modulesDir: path.join(rootDir, modules_1.MODULES_DIRNAME),
                    stdlibDir,
                    checkOwnership: false,
                    enableEffects: false,
                });
                for (const o of result.output) {
                    output.write(o + "\n");
                }
                if (result.value.type === "ERROR") {
                    output.write(result.value.inspect() + "\n");
                }
                else if (result.value.type !== "NULL") {
                    output.write(result.value.inspect() + "\n");
                }
            }
        }
        catch (err) {
            output.write(`ERROR: ${err instanceof Error ? err.message : String(err)}\n`);
        }
    }
    rl.close();
}
function isBalanced(source) {
    let braces = 0;
    let parens = 0;
    let brackets = 0;
    let inString = false;
    for (let i = 0; i < source.length; i++) {
        const ch = source[i];
        if (ch === '"' && source[i - 1] !== "\\") {
            inString = !inString;
            continue;
        }
        if (inString) {
            continue;
        }
        if (ch === "{") {
            braces++;
        }
        else if (ch === "}") {
            braces--;
        }
        else if (ch === "(") {
            parens++;
        }
        else if (ch === ")") {
            parens--;
        }
        else if (ch === "[") {
            brackets++;
        }
        else if (ch === "]") {
            brackets--;
        }
    }
    return braces <= 0 && parens <= 0 && brackets <= 0;
}
//# sourceMappingURL=repl.js.map