← Spreadsheet results

Spreadsheet

inkling Small · typescript

Initial view of Spreadsheet

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 44,257 bytes · SHA-256 fecc8b411555
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spreadsheet</title>
<style>
/* ── css: layout ── */
*{box-sizing:border-box}
html,body{margin:0;padding:0;height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:#f3f4f6;color:#1f2937}
#app{display:flex;flex-direction:column;height:100vh;overflow:hidden}
.header-bar{display:flex;align-items:center;gap:12px;padding:10px 16px;background:#fff;border-bottom:1px solid #e5e7eb;box-shadow:0 1px 3px rgba(0,0,0,.04)}
.ref-label{font-weight:700;color:#2563eb;width:60px;text-align:center;font-size:13px}
.formula-bar{flex:1;display:flex;align-items:center;background:#f9fafb;border:1px solid #d1d5db;border-radius:8px;padding:6px 10px;gap:8px;font-family:ui-monospace,SFMono-Regular,monospace;font-size:13px;color:#374151;min-height:36px}
.fx{color:#ff9800;font-weight:700;font-size:12px;width:20px;text-align:center}
.toolbar{display:flex;gap:6px;padding:6px 14px;background:#fff;border-bottom:1px solid #e5e7eb}
.toolbar button{padding:6px 14px;border:1px solid #d1d5db;border-radius:6px;background:#fff;font-size:12px;cursor:pointer;color:#374151}
.toolbar button:hover{background:#f3f4f6;border-color:#9ca3af}
.grid-wrap{flex:1;overflow:auto;background:#fff;position:relative}
.sheet-table{border-collapse:collapse;table-layout:fixed;color:#1f2937;font-size:12px}
.sheet-table th{position:sticky;top:0;left:0;background:#f8fafc;z-index:3;border-right:1px solid #cbd5e1;border-bottom:1px solid #cbd5e1;font-weight:600;color:#475569;text-align:center;padding:4px 2px;min-width:80px;min-height:24px;user-select:none}
.sheet-table th:first-child{z-index:5;background:#e2e8f0;min-width:48px}
.sheet-table th.row-head{position:sticky;left:0;z-index:4;background:#e2e8f0}
.sheet-table td{border-right:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;padding:0;text-align:right;vertical-align:middle;min-width:80px;height:24px;position:relative;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;cursor:cell}
.sheet-table td:first-child{border-left:1px solid #e5e7eb;min-width:48px;background:#f8fafc}
.sheet-table td.active{outline:2px solid #2563eb;outline-offset:-2px}
.sheet-table td.error-text{color:#dc2626;font-weight:500}
.sheet-table td.text-cell{text-align:left}
.sheet-table td.num-cell{font-variant-numeric:tabular-nums}
</style>
</head>
<body>
<div id="app">
<div class="header-bar"><div class="ref-label" id="ref-label">A1</div><div class="formula-bar" id="formula-bar"><span class="fx">fx</span><input type="text" id="formula-input" style="flex:1;border:none;background:transparent;outline:none;font-family:inherit;font-size:inherit;color:inherit" value=""></div></div>
<div class="toolbar"><button onclick="window.sheetInsertRow()">Insert Row</button><button onclick="window.sheetDeleteRow()">Delete Row</button><button onclick="window.sheetInsertCol()">Insert Col</button><button onclick="window.sheetDeleteCol()">Delete Col</button><button onclick="window.sheetClear()">Clear</button></div>
<div class="grid-wrap" id="grid-wrap"></div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./constants":"src/constants.ts","./parser":"src/parser.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
const constants_1 = require("./constants");
const parser_1 = require("./parser");
let formulas = new Map();
let computed = new Map();
let cycleSet = new Set();
let selected = { col: 0, row: 0 };
let editMode = false;
let editValue = '';
let depGraph = new Map();
function k(c, r) { return `${c}-${r}`; }
function initSheet() {
    try {
        const s = localStorage.getItem(constants_1.STORAGE_KEY);
        if (s) {
            const o = JSON.parse(s);
            if (o && o.formulas)
                formulas = new Map(Object.entries(o.formulas));
        }
    }
    catch (e) { }
    if (formulas.size === 0) {
        formulas.set(k(0, 0), '5');
        formulas.set(k(1, 0), '=A1*2');
        formulas.set(k(2, 0), '=B1+1');
    }
    rebuild();
}
function saveSheet() { try {
    localStorage.setItem(constants_1.STORAGE_KEY, JSON.stringify({ formulas: Object.fromEntries(formulas) }));
}
catch (e) { } }
function rebuild() { depGraph = (0, parser_1.computeDependencies)(formulas); cycleSet = (0, parser_1.detectCycles)(formulas); computed = (0, parser_1.buildFullComputed)(formulas, cycleSet); renderGrid(); updateHeader(); updateFormulaBar(); saveSheet(); }
function renderGrid() {
    const wrap = document.getElementById('grid-wrap');
    const table = document.createElement('table');
    table.className = 'sheet-table';
    const thead = document.createElement('thead');
    const tr = document.createElement('tr');
    const corner = document.createElement('th');
    corner.style.minWidth = '48px';
    tr.appendChild(corner);
    for (let c = 0; c < constants_1.COLS; c++) {
        const th = document.createElement('th');
        th.textContent = (0, parser_1.cellToString)({ col: c, row: -1 });
        th.style.width = constants_1.COL_WIDTH + 'px';
        tr.appendChild(th);
    }
    thead.appendChild(tr);
    table.appendChild(thead);
    const tbody = document.createElement('tbody');
    for (let r = 0; r < constants_1.ROWS; r++) {
        const tr = document.createElement('tr');
        const rh = document.createElement('th');
        rh.className = 'row-head';
        rh.textContent = String(r + 1);
        rh.style.width = '48px';
        tr.appendChild(rh);
        for (let c = 0; c < constants_1.COLS; c++) {
            const td = document.createElement('td');
            td.style.width = constants_1.COL_WIDTH + 'px';
            td.style.height = constants_1.ROW_HEIGHT + 'px';
            const key = k(c, r);
            const raw = formulas.get(key) || '';
            const val = computed.get(key);
            if (c === selected.col && r === selected.row)
                td.classList.add('active');
            if (cycleSet.has(key)) {
                td.classList.add('error-text');
                td.textContent = '#CYCLE!';
            }
            else if (typeof val === 'string' && val.startsWith('#')) {
                td.classList.add('error-text');
                td.textContent = val;
            }
            else {
                if ((0, parser_1.isFormula)(raw)) {
                    td.classList.add('num-cell');
                    td.textContent = (0, parser_1.formatValue)(val);
                }
                else {
                    const n = parseFloat(raw);
                    if (!isNaN(n) && raw.trim() !== '') {
                        td.classList.add('num-cell');
                        td.textContent = (0, parser_1.formatValue)(val ?? n);
                    }
                    else {
                        td.classList.add('text-cell');
                        td.textContent = raw;
                    }
                }
            }
            td.addEventListener('click', () => selectCell(c, r));
            td.addEventListener('dblclick', () => enterEdit(c, r));
            tr.appendChild(td);
        }
        tbody.appendChild(tr);
    }
    table.appendChild(tbody);
    wrap.innerHTML = '';
    wrap.appendChild(table);
}
function updateHeader() { document.getElementById('ref-label').textContent = (0, parser_1.cellToString)({ col: selected.col, row: selected.row }); }
function updateFormulaBar() { const input = document.getElementById('formula-input'); input.value = editMode ? editValue : (formulas.get(k(selected.col, selected.row)) || ''); }
function selectCell(c, r) { selected = { col: c, row: r }; editMode = false; editValue = ''; renderGrid(); updateHeader(); updateFormulaBar(); const wrap = document.getElementById('grid-wrap'); const td = wrap.querySelectorAll('td')[r * constants_1.COLS + c]; if (td)
    td.scrollIntoView({ block: 'center', inline: 'center' }); }
function enterEdit(c, r) { selectCell(c, r); editMode = true; editValue = formulas.get(k(c, r)) || ''; updateFormulaBar(); setTimeout(() => { document.getElementById('formula-input').focus(); }, 50); }
function commitEdit(value) { formulas.set(k(selected.col, selected.row), value); editMode = false; editValue = ''; rebuild(); }
function setupFormulaInput() {
    const input = document.getElementById('formula-input');
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            commitEdit(input.value);
            if (selected.row < constants_1.ROWS - 1) {
                selected.row++;
                rebuild();
                selectCell(selected.col, selected.row);
            }
        }
        else if (e.key === 'Tab') {
            e.preventDefault();
            commitEdit(input.value);
            if (selected.col < constants_1.COLS - 1) {
                selected.col++;
                rebuild();
                selectCell(selected.col, selected.row);
            }
        }
        else if (e.key === 'Escape') {
            editMode = false;
            editValue = '';
            updateFormulaBar();
        }
    });
    input.addEventListener('input', () => { editValue = input.value; });
}
function onKeyDown(e) {
    if (editMode)
        return;
    if (e.key === 'ArrowDown') {
        if (selected.row < constants_1.ROWS - 1) {
            selected.row++;
            rebuild();
            selectCell(selected.col, selected.row);
            e.preventDefault();
        }
    }
    else if (e.key === 'ArrowUp') {
        if (selected.row > 0) {
            selected.row--;
            rebuild();
            selectCell(selected.col, selected.row);
            e.preventDefault();
        }
    }
    else if (e.key === 'ArrowRight') {
        if (selected.col < constants_1.COLS - 1) {
            selected.col++;
            rebuild();
            selectCell(selected.col, selected.row);
            e.preventDefault();
        }
    }
    else if (e.key === 'ArrowLeft') {
        if (selected.col > 0) {
            selected.col--;
            rebuild();
            selectCell(selected.col, selected.row);
            e.preventDefault();
        }
    }
    else if (e.key === 'Enter') {
        enterEdit(selected.col, selected.row);
        e.preventDefault();
    }
    else if (e.key === 'Delete') {
        formulas.set(k(selected.col, selected.row), '');
        rebuild();
    }
    else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
        enterEdit(selected.col, selected.row);
        editValue = e.key;
        updateFormulaBar();
        setTimeout(() => { const inp = document.getElementById('formula-input'); inp.value = editValue; inp.focus(); inp.setSelectionRange(1, 1); }, 10);
    }
}
window.sheetInsertRow = () => { const r = selected.row; for (let rr = constants_1.ROWS - 1; rr > r; rr--)
    for (let c = 0; c < constants_1.COLS; c++)
        formulas.set(k(c, rr), formulas.get(k(c, rr - 1)) || ''); for (let c = 0; c < constants_1.COLS; c++)
    formulas.set(k(c, r), ''); for (const [key, raw] of formulas)
    if (raw.startsWith('='))
        formulas.set(key, (0, parser_1.rewriteFormula)(raw, 'insertRow', r + 1)); rebuild(); };
window.sheetDeleteRow = () => { const r = selected.row; for (let rr = r; rr < constants_1.ROWS - 1; rr++)
    for (let c = 0; c < constants_1.COLS; c++)
        formulas.set(k(c, rr), formulas.get(k(c, rr + 1)) || ''); for (let c = 0; c < constants_1.COLS; c++)
    formulas.set(k(c, constants_1.ROWS - 1), ''); for (const [key, raw] of formulas)
    if (raw.startsWith('='))
        formulas.set(key, (0, parser_1.rewriteFormula)(raw, 'deleteRow', r + 1)); rebuild(); };
window.sheetInsertCol = () => { const c = selected.col; for (let cc = constants_1.COLS - 1; cc > c; cc--)
    for (let r = 0; r < constants_1.ROWS; r++)
        formulas.set(k(cc, r), formulas.get(k(cc - 1, r)) || ''); for (let r = 0; r < constants_1.ROWS; r++)
    formulas.set(k(c, r), ''); for (const [key, raw] of formulas)
    if (raw.startsWith('='))
        formulas.set(key, (0, parser_1.rewriteFormula)(raw, 'insertCol', c)); rebuild(); };
window.sheetDeleteCol = () => { const c = selected.col; for (let cc = c; cc < constants_1.COLS - 1; cc++)
    for (let r = 0; r < constants_1.ROWS; r++)
        formulas.set(k(cc, r), formulas.get(k(cc + 1, r)) || ''); for (let r = 0; r < constants_1.ROWS; r++)
    formulas.set(k(constants_1.COLS - 1, r), ''); for (const [key, raw] of formulas)
    if (raw.startsWith('='))
        formulas.set(key, (0, parser_1.rewriteFormula)(raw, 'deleteCol', c)); rebuild(); };
window.sheetClear = () => { formulas.set(k(selected.col, selected.row), ''); rebuild(); };
function start() { document.addEventListener('keydown', onKeyDown); setupFormulaInput(); initSheet(); }
window.getSheetFormulas = () => formulas;
window.getComputed = () => computed;
window.selectCell = selectCell;
window.getSelected = () => selected;
window.commitEdit = commitEdit;
window.rebuild = rebuild;
window.enterEdit = enterEdit;
window.setFormula = (k, v) => { formulas.set(k, v); rebuild(); };
window.getFormulaString = (k) => formulas.get(k) || '';
window.getCellValueString = (k) => (0, parser_1.formatValue)(computed.get(k));
window.clearCycle = () => { cycleSet.clear(); rebuild(); };
window.resetSheet = () => { formulas.clear(); initSheet(); };
window.setCell = (k, v) => { formulas.set(k, v); rebuild(); };
window.getKey = (c, r) => k(c, r);
window.getCOLS = () => constants_1.COLS;
window.getROWS = () => constants_1.ROWS;
window.getFormulas = () => Array.from(formulas.entries());
window.buildFullComputed = () => { depGraph = (0, parser_1.computeDependencies)(formulas); cycleSet = (0, parser_1.detectCycles)(formulas); computed = (0, parser_1.buildFullComputed)(formulas, cycleSet); };
window.recalcAll = () => rebuild();
window.makeCycle = () => { formulas.set('0-0', '=B1'); formulas.set('1-0', '=A1'); rebuild(); };
window.clearCycleDirect = () => { formulas.set('0-0', ''); formulas.set('1-0', ''); rebuild(); };
window.getComputedValue = (k) => computed.get(k);
window.getFormula = (k) => formulas.get(k);
window.enterEditDirect = (c, r) => enterEdit(c, r);
window.commitEditDirect = () => commitEdit(document.getElementById('formula-input').value);
window.verifyAll = async () => {
    formulas.clear();
    formulas.set('0-0', '5');
    formulas.set('1-0', '=A1*2');
    formulas.set('2-0', '=B1+1');
    rebuild();
    const r = [];
    r.push({ name: 'basic', pass: computed.get('2-0') === 11 });
    formulas.set('0-0', '10');
    rebuild();
    r.push({ name: 'cascade', pass: computed.get('2-0') === 21 });
    formulas.set('0-0', '1');
    formulas.set('1-0', '2');
    formulas.set('2-0', '3');
    formulas.set('3-0', '=SUM(A1:A3)');
    rebuild();
    r.push({ name: 'sum', pass: computed.get('3-0') === 6 });
    formulas.set('4-0', '=AVG(A1:A3)');
    rebuild();
    r.push({ name: 'avg', pass: computed.get('4-0') === 2 });
    formulas.set('5-0', '=1/0');
    rebuild();
    r.push({ name: 'div0', pass: computed.get('5-0') === '#DIV/0!' });
    formulas.set('6-0', '=SUM(');
    rebuild();
    r.push({ name: 'err', pass: computed.get('6-0') === '#ERR!' });
    formulas.set('0-7', '=B8');
    formulas.set('1-7', '=A8');
    rebuild();
    r.push({ name: 'cycle', pass: computed.get('0-7') === '#CYCLE!' && computed.get('1-7') === '#CYCLE!' });
    formulas.set('0-7', '');
    formulas.set('1-7', '');
    rebuild();
    r.push({ name: 'cycle_clear', pass: computed.get('0-7') !== '#CYCLE!' });
    return r;
};
start();
exports.default = start;
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.COLORS = exports.STORAGE_KEY = exports.ROW_HEIGHT = exports.COL_WIDTH = exports.ROWS = exports.COLS = void 0;
exports.COLS = 26;
exports.ROWS = 100;
exports.COL_WIDTH = 90;
exports.ROW_HEIGHT = 26;
exports.STORAGE_KEY = 'spreadsheet_state';
exports.COLORS = { bg: '#f3f4f6', grid: '#fff', select: '#2563eb' };
};

// ── module: src/parser.ts ──
__mods["src/parser.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
exports.parseRef = parseRef;
exports.compileFormula = compileFormula;
exports.evalExpr = evalExpr;
exports.evaluateAst = evalExpr;
exports.getRangeValues = getRangeValues;
exports.formatValue = formatValue;
exports.parseCell = parseCell;
exports.cellToString = cellToString;
exports.isFormula = isFormula;
exports.extractRefs = extractRefs;
exports.extractRangeRefs = extractRangeRefs;
exports.rewriteFormula = rewriteFormula;
exports.findCycles = findCycles;
exports.getRefsInString = getRefsInString;
exports.getRangeInString = getRangeInString;
exports.getAllRefs = getAllRefs;
exports.computeDependencies = computeDependencies;
exports.topologicalOrder = topologicalOrder;
exports.evaluateCell = evaluateCell;
exports.getRangeForFunc = getRangeForFunc;
exports.computeCell = computeCell;
exports.recalcCells = recalcCells;
exports.recalcDependents = recalcDependents;
exports.detectCycles = detectCycles;
exports.buildFullComputed = buildFullComputed;
exports.getCellValueString = getCellValueString;
exports.getFormulaString = getFormulaString;
function tokenize(s) {
    const t = [];
    let i = 0;
    while (i < s.length) {
        const c = s[i];
        if (/\s/.test(c)) {
            i++;
            continue;
        }
        if (c === '(') {
            t.push({ type: 'LPAREN', val: '(', raw: '(', pos: i });
            i++;
            continue;
        }
        if (c === ')') {
            t.push({ type: 'RPAREN', val: ')', raw: ')', pos: i });
            i++;
            continue;
        }
        if (c === ',') {
            t.push({ type: 'COMMA', val: ',', raw: ',', pos: i });
            i++;
            continue;
        }
        if (c === '+') {
            t.push({ type: 'PLUS', val: '+', raw: '+', pos: i });
            i++;
            continue;
        }
        if (c === '-') {
            t.push({ type: 'MINUS', val: '-', raw: '-', pos: i });
            i++;
            continue;
        }
        if (c === '*') {
            t.push({ type: 'MUL', val: '*', raw: '*', pos: i });
            i++;
            continue;
        }
        if (c === '/') {
            t.push({ type: 'DIV', val: '/', raw: '/', pos: i });
            i++;
            continue;
        }
        if (/\d/.test(c)) {
            let v = '';
            while (i < s.length && /\d/.test(s[i]))
                v += s[i++];
            if (i < s.length && s[i] === '.') {
                v += s[i++];
                while (i < s.length && /\d/.test(s[i]))
                    v += s[i++];
            }
            t.push({ type: 'NUM', val: v, raw: v, pos: i - v.length });
            continue;
        }
        if (/[A-Za-z]/.test(c)) {
            let v = '';
            while (i < s.length && /[A-Za-z]/.test(s[i]))
                v += s[i++];
            while (i < s.length && /\d/.test(s[i]))
                v += s[i++];
            const up = v.toUpperCase();
            if (['SUM', 'AVG', 'AVERAGE', 'MIN', 'MAX', 'COUNT'].includes(up)) {
                t.push({ type: 'FUNC', val: up, raw: v, pos: i - v.length });
            }
            else {
                // could be part of reference? References start with letter then digits
                t.push({ type: 'REF', val: v, raw: v, pos: i - v.length });
            }
            continue;
        }
        // If colon after reference -> range token
        if (c === ':') {
            t.push({ type: 'RANGE', val: ':', raw: ':', pos: i });
            i++;
            continue;
        }
        i++;
    }
    // Post-process ranges: if REF RANGE REF => merge to RANGE token with full value
    for (let k = 0; k < t.length - 2; k++) {
        if (t[k].type === 'REF' && t[k + 1].type === 'RANGE' && t[k + 2].type === 'REF') {
            const combined = t[k].val + ':' + t[k + 2].val;
            t.splice(k, 3, { type: 'RANGE', val: combined, raw: combined, pos: t[k].pos });
        }
    }
    t.push({ type: 'EOF', val: '', raw: '', pos: i });
    return t;
}
function parseRef(str) {
    const m = str.match(/^([A-Z]+)(\d+)$/);
    if (!m)
        return null;
    let col = 0;
    for (let i = 0; i < m[1].length; i++)
        col = col * 26 + (m[1].charCodeAt(i) - 64);
    return { col: col - 1, row: parseInt(m[2], 10) - 1 };
}
function refToStr(r) {
    let s = '';
    let n = r.col + 1;
    while (n > 0) {
        s = String.fromCharCode(65 + ((n - 1) % 26)) + s;
        n = Math.floor((n - 1) / 26);
    }
    return s + (r.row + 1);
}
class Parser {
    constructor(src) {
        this.pos = 0;
        this.tok = tokenize(src);
    }
    cur() { return this.tok[this.pos]; }
    eat(t) {
        const c = this.cur();
        if (t && c.type !== t)
            throw new Error('Expected ' + t);
        this.pos++;
        return c;
    }
    parse() {
        return this.parseExpr();
    }
    parseExpr() {
        let left = this.parseTerm();
        while (this.cur().type === 'PLUS' || this.cur().type === 'MINUS') {
            const op = this.eat().type === 'PLUS' ? 'add' : 'sub';
            const right = this.parseTerm();
            left = { op, left, right };
        }
        return left;
    }
    parseTerm() {
        let left = this.parseFactor();
        while (this.cur().type === 'MUL' || this.cur().type === 'DIV') {
            const op = this.eat().type === 'MUL' ? 'mul' : 'div';
            const right = this.parseFactor();
            left = { op, left, right };
        }
        return left;
    }
    parseFactor() {
        if (this.cur().type === 'PLUS') {
            this.eat();
            return this.parseFactor();
        }
        if (this.cur().type === 'MINUS') {
            this.eat();
            return { op: 'neg', left: this.parseFactor() };
        }
        return this.parsePrimary();
    }
    parsePrimary() {
        const c = this.cur();
        if (c.type === 'NUM') {
            this.eat();
            return { op: 'num', val: parseFloat(c.val) };
        }
        if (c.type === 'REF') {
            this.eat();
            return { op: 'ref', val: parseRef(c.val) };
        }
        if (c.type === 'RANGE') {
            this.eat();
            const p = parseRef(c.val.split(':')[0]);
            const q = parseRef(c.val.split(':')[1]);
            return { op: 'range', start: p, end: q };
        }
        if (c.type === 'FUNC') {
            const fn = this.eat().val;
            this.eat('LPAREN');
            const args = [];
            if (this.cur().type !== 'RPAREN') {
                args.push(this.parseExpr());
                while (this.cur().type === 'COMMA') {
                    this.eat();
                    args.push(this.parseExpr());
                }
            }
            this.eat('RPAREN');
            return { op: 'func', fn, args };
        }
        if (c.type === 'LPAREN') {
            this.eat();
            const e = this.parseExpr();
            this.eat('RPAREN');
            return e;
        }
        throw new Error('Unexpected token ' + c.type);
    }
}
exports.Parser = Parser;
function compileFormula(src) {
    // Remove leading = if present
    const s = src.startsWith('=') ? src.slice(1) : src;
    const p = new Parser(s);
    return p.parse();
}
function evalExpr(node, getValue, getRange) {
    // Returns computed value or error string
    try {
        if (!node)
            return 0;
        if (node.op === 'num')
            return node.val;
        if (node.op === 'ref') {
            const v = getValue(node.val);
            if (v === null)
                return '#REF!';
            if (typeof v === 'string' && v.startsWith('#'))
                return v;
            return v;
        }
        if (node.op === 'range') {
            return getRange(node.start, node.end);
        }
        if (node.op === 'neg') {
            const v = evalExpr(node.left, getValue, getRange);
            if (typeof v === 'string')
                return v;
            return -v;
        }
        if (node.op === 'add') {
            const a = evalExpr(node.left, getValue, getRange);
            if (typeof a === 'string')
                return a;
            const b = evalExpr(node.right, getValue, getRange);
            if (typeof b === 'string')
                return b;
            return a + b;
        }
        if (node.op === 'sub') {
            const a = evalExpr(node.left, getValue, getRange);
            if (typeof a === 'string')
                return a;
            const b = evalExpr(node.right, getValue, getRange);
            if (typeof b === 'string')
                return b;
            return a - b;
        }
        if (node.op === 'mul') {
            const a = evalExpr(node.left, getValue, getRange);
            if (typeof a === 'string')
                return a;
            const b = evalExpr(node.right, getValue, getRange);
            if (typeof b === 'string')
                return b;
            return a * b;
        }
        if (node.op === 'div') {
            const a = evalExpr(node.left, getValue, getRange);
            if (typeof a === 'string')
                return a;
            const b = evalExpr(node.right, getValue, getRange);
            if (typeof b === 'string')
                return b;
            if (b === 0)
                return '#DIV/0!';
            return a / b;
        }
        if (node.op === 'func') {
            const fn = node.fn;
            const args = node.args.map((a) => evalExpr(a, getValue, getRange));
            // Handle ranges inside args: if arg result is array, use it
            let vals = [];
            for (const arg of args) {
                if (Array.isArray(arg))
                    vals.push(...arg);
                else if (typeof arg === 'number')
                    vals.push(arg);
                else if (typeof arg === 'string' && arg.startsWith('#'))
                    return arg;
            }
            if (fn === 'SUM')
                return vals.reduce((s, v) => s + v, 0);
            if (fn === 'AVG' || fn === 'AVERAGE')
                return vals.length ? vals.reduce((s, v) => s + v, 0) / vals.length : 0;
            if (fn === 'MIN')
                return vals.length ? Math.min(...vals) : 0;
            if (fn === 'MAX')
                return vals.length ? Math.max(...vals) : 0;
            if (fn === 'COUNT')
                return vals.length;
            return '#ERR!';
        }
        return '#ERR!';
    }
    catch (e) {
        return '#ERR!';
    }
}
// Helper for functions to extract range from args that are range nodes
function getRangeValues(start, end, cellMap) {
    const res = [];
    for (let r = start.row; r <= end.row; r++) {
        for (let c = start.col; c <= end.col; c++) {
            const key = `${c}-${r}`;
            const raw = cellMap.get(key);
            if (raw === undefined)
                continue;
            if (raw.startsWith('=')) {
                // evaluate formula for numeric result; ignore non-numeric/errors
                try {
                    const compiled = compileFormula(raw);
                    // We don't have eval context here directly; this is used in engine
                }
                catch { }
            }
            else {
                const n = parseFloat(raw);
                if (!isNaN(n))
                    res.push(n);
            }
        }
    }
    return res;
}
function formatValue(v) {
    if (v === null || v === undefined)
        return '';
    if (typeof v === 'number') {
        if (Number.isInteger(v))
            return String(v);
        return v.toFixed(4).replace(/\.?0+$/, '');
    }
    return String(v);
}
function parseCell(str) {
    // Return column index 0-based, row 0-based from A1-style
    const m = str.match(/^([A-Z]+)(\d+)$/i);
    if (!m)
        return null;
    let c = 0;
    for (let i = 0; i < m[1].length; i++)
        c = c * 26 + (m[1].charCodeAt(i) - (m[1].charCodeAt(i) >= 97 ? 97 : 65));
    return { col: c - 1, row: parseInt(m[2], 10) - 1 };
}
function cellToString(c) {
    let s = '';
    let n = c.col + 1;
    while (n > 0) {
        s = String.fromCharCode(65 + ((n - 1) % 26)) + s;
        n = Math.floor((n - 1) / 26);
    }
    return s + (c.row + 1);
}
function isFormula(str) { return str.startsWith('='); }
function extractRefs(node) {
    const refs = [];
    function walk(n) {
        if (!n)
            return;
        if (n.op === 'ref')
            refs.push(cellToString(n.val));
        if (n.op === 'func')
            n.args.forEach(walk);
        else {
            if (n.left)
                walk(n.left);
            if (n.right)
                walk(n.right);
        }
    }
    walk(node);
    return refs;
}
function extractRangeRefs(node) {
    const refs = [];
    function walk(n) {
        if (!n)
            return;
        if (n.op === 'range')
            refs.push(cellToString(n.start) + ':' + cellToString(n.end));
        if (n.op === 'func')
            n.args.forEach(walk);
        else {
            if (n.left)
                walk(n.left);
            if (n.right)
                walk(n.right);
        }
    }
    walk(node);
    return refs;
}
// For dependency graph updates when rows/cols inserted/deleted
function rewriteFormula(raw, op, idx) {
    if (!raw.startsWith('='))
        return raw;
    // Simplified: only handle references that shift; ranges also shift
    // We'll do a regex-based rewrite for common cases
    let s = raw.slice(1);
    // Insert row before idx: all row refs >= idx increase by 1
    // Delete row at idx: all refs >= idx shift down by 1 (if target exists, else #REF!)
    // For simplicity, parse and rewrite refs
    // Actually do basic regex on references
    const refRe = /([A-Z]+)(\d+)/g;
    s = s.replace(refRe, (_m, col, rowStr) => {
        const r = parseInt(rowStr, 10);
        if (op === 'insertRow' && r >= idx + 1)
            return col + (r + 1);
        if (op === 'deleteRow' && r > idx + 1)
            return col + (r - 1);
        if (op === 'deleteRow' && r === idx + 1)
            return col + r; // might become invalid; handled by engine
        if (op === 'insertCol') {
            const c = colToIndex(col);
            if (c >= idx)
                return indexToCol(c + 1) + rowStr;
        }
        if (op === 'deleteCol') {
            const c = colToIndex(col);
            if (c > idx)
                return indexToCol(c - 1) + rowStr;
        }
        return _m;
    });
    // For ranges A1:B2
    s = s.replace(/([A-Z]+\d+):([A-Z]+\d+)/g, (m, a, b) => {
        const aa = parseCell(a);
        const bb = parseCell(b);
        if (!aa || !bb)
            return m;
        let na = a, nb = b;
        if (op === 'insertRow') {
            if (aa.row >= idx)
                na = a[0] + a.slice(1); // rough; skip detailed range rewrite for simplicity
            if (bb.row >= idx)
                nb = b[0] + b.slice(1); // actually do numeric
            // Simple numeric rewrite for range ends
            const ar = parseInt(a.match(/\d+/)[0], 10);
            const br = parseInt(b.match(/\d+/)[0], 10);
            na = a.replace(/\d+/, String(ar >= idx + 1 ? ar + 1 : ar));
            nb = b.replace(/\d+/, String(br >= idx + 1 ? br + 1 : br));
        }
        else if (op === 'deleteRow') {
            const ar = parseInt(a.match(/\d+/)[0], 10);
            const br = parseInt(b.match(/\d+/)[0], 10);
            na = a.replace(/\d+/, String(ar > idx + 1 ? ar - 1 : ar));
            nb = b.replace(/\d+/, String(br > idx + 1 ? br - 1 : br));
        }
        else if (op === 'insertCol') {
            const ac = colToIndex(a.replace(/\d+$/, ''));
            const bc = colToIndex(b.replace(/\d+$/, ''));
            if (ac >= idx)
                na = indexToCol(ac + 1) + a.replace(/^[A-Z]+/, '');
            if (bc >= idx)
                nb = indexToCol(bc + 1) + b.replace(/^[A-Z]+/, '');
        }
        else if (op === 'deleteCol') {
            const ac = colToIndex(a.replace(/\d+$/, ''));
            const bc = colToIndex(b.replace(/\d+$/, ''));
            if (ac > idx)
                na = indexToCol(ac - 1) + a.replace(/^[A-Z]+/, '');
            if (bc > idx)
                nb = indexToCol(bc - 1) + b.replace(/^[A-Z]+/, '');
        }
        return na + ':' + nb;
    });
    return '=' + s;
}
function colToIndex(col) {
    let n = 0;
    for (let i = 0; i < col.length; i++)
        n = n * 26 + (col.charCodeAt(i) - 64);
    return n - 1;
}
function indexToCol(n) {
    let s = '';
    let x = n + 1;
    while (x > 0) {
        s = String.fromCharCode(65 + ((x - 1) % 26)) + s;
        x = Math.floor((x - 1) / 26);
    }
    return s;
}
function findCycles(allCells, refsMap) {
    const visited = new Set();
    const recStack = new Set();
    const cycleNodes = new Set();
    function dfs(key) {
        visited.add(key);
        recStack.add(key);
        const refs = refsMap.get(key) || [];
        for (const r of refs) {
            if (!visited.has(r))
                dfs(r);
            else if (recStack.has(r)) {
                // found cycle; mark all in current path that are part of it
                // For simplicity, just mark all nodes that reach back; mark r and current
                cycleNodes.add(r);
                cycleNodes.add(key);
            }
        }
        recStack.delete(key);
    }
    for (const k of allCells.keys()) {
        if (!visited.has(k))
            dfs(k);
    }
    return cycleNodes;
}
function getRefsInString(str) {
    const refs = [];
    if (!str.startsWith('='))
        return refs;
    try {
        const p = compileFormula(str);
        const arr = extractRefs(p);
        for (const r of arr)
            refs.push(r);
    }
    catch { }
    return refs;
}
function getRangeInString(str) {
    const refs = [];
    if (!str.startsWith('='))
        return refs;
    try {
        const p = compileFormula(str);
        const arr = extractRangeRefs(p);
        refs.push(...arr);
    }
    catch { }
    return refs;
}
function getAllRefs(str) {
    return [...getRefsInString(str), ...getRangeInString(str).flatMap(r => {
            const [s, e] = r.split(':');
            const res = [];
            const cs = parseCell(s);
            const ce = parseCell(e);
            if (cs && ce) {
                for (let rr = cs.row; rr <= ce.row; rr++) {
                    for (let cc = cs.col; cc <= ce.col; cc++) {
                        res.push(cellToString({ col: cc, row: rr }));
                    }
                }
            }
            return res;
        })];
}
function computeDependencies(formulas) {
    const graph = new Map();
    for (const [k, raw] of formulas) {
        if (raw.startsWith('=')) {
            graph.set(k, getAllRefs(raw));
        }
        else {
            graph.set(k, []);
        }
    }
    return graph;
}
function topologicalOrder(needsRecalc, graph) {
    const visited = new Set();
    const order = [];
    function visit(n) {
        if (visited.has(n))
            return;
        visited.add(n);
        for (const dep of (graph.get(n) || [])) {
            if (needsRecalc.has(dep))
                visit(dep);
        }
        order.push(n);
    }
    for (const n of needsRecalc)
        visit(n);
    return order;
}
// For engine to evaluate a cell given current formula map and cached values
function evaluateCell(key, formulas, computed, cycleSet) {
    if (cycleSet.has(key))
        return '#CYCLE!';
    const raw = formulas.get(key);
    if (raw === undefined || raw === '')
        return '';
    if (!raw.startsWith('=')) {
        const n = parseFloat(raw);
        return isNaN(n) ? raw : n;
    }
    // Check if any dependency is error/cycle
    const refs = getRefsInString(raw);
    for (const r of refs) {
        if (cycleSet.has(r))
            return '#CYCLE!';
    }
    try {
        const compiled = compileFormula(raw);
        // Build getValue using computed cache; if missing compute recursively
        const getValue = (ref) => {
            const rk = `${ref.col}-${ref.row}`;
            const v = computed.get(rk);
            if (v === undefined) {
                const subRaw = formulas.get(rk);
                if (subRaw === undefined || subRaw === '')
                    return 0; // empty -> 0 for arithmetic
                if (subRaw.startsWith('=')) {
                    const val = evaluateCell(rk, formulas, computed, cycleSet);
                    if (typeof val === 'string' && val.startsWith('#'))
                        return val;
                    computed.set(rk, val);
                    return val;
                }
                else {
                    const n = parseFloat(subRaw);
                    computed.set(rk, isNaN(n) ? subRaw : n);
                    return isNaN(n) ? 0 : n;
                }
            }
            if (typeof v === 'string' && v.startsWith('#'))
                return v;
            return v;
        };
        const getRange = (start, end) => {
            const arr = [];
            for (let rr = start.row; rr <= end.row; rr++) {
                for (let cc = start.col; cc <= end.col; cc++) {
                    const rk = `${cc}-${rr}`;
                    const subRaw = formulas.get(rk);
                    if (subRaw === undefined || subRaw === '')
                        continue;
                    let val = 0;
                    if (subRaw.startsWith('=')) {
                        val = evaluateCell(rk, formulas, computed, cycleSet);
                    }
                    else {
                        const n = parseFloat(subRaw);
                        val = isNaN(n) ? subRaw : n;
                    }
                    if (typeof val === 'string' && val.startsWith('#'))
                        return val; // propagate error
                    if (typeof val === 'number')
                        arr.push(val);
                }
            }
            return arr;
        };
        // We need to handle range args inside evalExpr. Modify evalExpr to use getRange directly.
        // Actually our evalExpr handles range nodes by calling getRange, but it needs context.
        // Let's call our own evaluate that passes getValue/getRange.
        // For simplicity, we'll expand evalExpr directly here with closures.
        const result = evalExpr(compiled, getValue, getRange);
        if (typeof result === 'string')
            return result;
        return result;
    }
    catch (e) {
        return '#ERR!';
    }
}
// Simple version of getValue for range args in engine
function getRangeForFunc(start, end, formulas, computed, cycleSet) {
    const arr = [];
    for (let rr = start.row; rr <= end.row; rr++) {
        for (let cc = start.col; cc <= end.col; cc++) {
            const rk = `${cc}-${rr}`;
            const subRaw = formulas.get(rk);
            if (subRaw === undefined || subRaw === '')
                continue;
            let val = 0;
            if (subRaw.startsWith('=')) {
                val = evaluateCell(rk, formulas, computed, cycleSet);
            }
            else {
                const n = parseFloat(subRaw);
                val = isNaN(n) ? subRaw : n;
            }
            if (typeof val === 'string' && val.startsWith('#'))
                return [];
            if (typeof val === 'number')
                arr.push(val);
        }
    }
    return arr;
}
function computeCell(key, formulas, computed, cycleSet) {
    return evaluateCell(key, formulas, computed, cycleSet);
}
// Rebuild computed values for needed cells
function recalcCells(changedKey, formulas, computed, depGraph, cycleSet) {
    const allKeys = Array.from(formulas.keys());
    const dependents = new Set();
    // For simplicity, just recompute all cells in dependency order (topological over whole graph)
    // This is acceptable for 2600 cells.
    const order = topologicalOrder(new Set(allKeys), depGraph);
    for (const k of order) {
        computed.set(k, evaluateCell(k, formulas, computed, cycleSet));
    }
}
// Fast targeted recalc only for dependents
function recalcDependents(startKey, formulas, computed, depGraph, cycleSet) {
    // Find all cells that depend (directly or indirectly) on startKey
    const affected = new Set();
    const rev = new Map();
    for (const [k, refs] of depGraph) {
        for (const r of refs) {
            if (!rev.has(r))
                rev.set(r, []);
            rev.get(r).push(k);
        }
    }
    const stack = [startKey];
    while (stack.length) {
        const cur = stack.pop();
        for (const d of (rev.get(cur) || [])) {
            if (!affected.has(d)) {
                affected.add(d);
                stack.push(d);
            }
        }
    }
    affected.add(startKey);
    const order = topologicalOrder(affected, depGraph);
    for (const k of order) {
        computed.set(k, evaluateCell(k, formulas, computed, cycleSet));
    }
}
function detectCycles(formulas) {
    const refsMap = new Map();
    for (const [k, v] of formulas) {
        if (v.startsWith('='))
            refsMap.set(k, getRefsInString(v));
        else
            refsMap.set(k, []);
    }
    return findCycles(formulas, refsMap);
}
function getRangeVals(start, end, formulas, computed, cycleSet) {
    const arr = [];
    for (let rr = start.row; rr <= end.row; rr++) {
        for (let cc = start.col; cc <= end.col; cc++) {
            const rk = `${cc}-${rr}`;
            const subRaw = formulas.get(rk);
            if (subRaw === undefined || subRaw === '')
                continue;
            let val = 0;
            if (subRaw.startsWith('=')) {
                val = evaluateCell(rk, formulas, computed, cycleSet);
            }
            else {
                const n = parseFloat(subRaw);
                val = isNaN(n) ? subRaw : n;
            }
            if (typeof val === 'string' && val.startsWith('#'))
                return val; // propagate error
            if (typeof val === 'number')
                arr.push(val);
        }
    }
    return arr;
}
// Monkey-patch evalExpr to use closures; since evalExpr is exported and takes getRange, we'll rely on engine to pass correct closures.
// Already implemented above.
// Additional helper for full sheet rebuild
function buildFullComputed(formulas, cycleSet) {
    const computed = new Map();
    const depGraph = computeDependencies(formulas);
    const order = topologicalOrder(new Set(formulas.keys()), depGraph);
    for (const k of order) {
        computed.set(k, evaluateCell(k, formulas, computed, cycleSet));
    }
    return computed;
}
function getCellValueString(key, formulas, computed, cycleSet) {
    const v = computed.get(key);
    if (v === undefined) {
        if (!formulas.has(key))
            return '';
        return formatValue(evaluateCell(key, formulas, computed, cycleSet));
    }
    return formatValue(v);
}
function getFormulaString(key, formulas) {
    return formulas.get(key) || '';
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingmachines/inkling-small","provider":"openrouter","persona":"typescript","sessionId":"3a219f58-eb0a-4b64-b876-9ce23b1cf009","tokensIn":3292091,"tokensOut":34210,"tokensTotal":3326301,"cost":0.4005439,"turns":73,"toolCalls":72,"failedToolCalls":7,"timestamp":"2026-08-01T17:22:48.608Z"} -->