function rebuild()

in packages/core/micro/src/sheetlet.ts [69:237]


function rebuild(chain: FormulaCell<CellValue>[], host: BuildHost): FormulaCell<CellValue>[] {
    const outChain: FormulaCell<CellValue>[] = [];
    const stack: Fiber<CellValue>[] = [];
    const end = chain.length;

    let chainIdx = 0;
    let pc = 0;

    while (true) {
        if (stack.length > 0) {
            const fiber = stack.pop()!;
            if (isFormulaFiber(fiber)) {
                if (fiber.state === CalcState.InCalc) {
                    assert(pc > 0, "PC should be non-zero if we find a InCalc cell");
                    pc--;
                }
                switch (fiber.state) {
                    case CalcState.Clean:
                        return assert.fail("Clean fibers should not be in the stack.");

                    case CalcState.InCalc:
                    case CalcState.Dirty:
                        const result = evalCell(fiber, host);
                        if (result === true) {
                            fiber.flags &= ~CalcFlags.InStack;
                            outChain.push(fiber);
                            continue;
                        }
                        rescheduleRoot(fiber);
                        result.forEach(f => pushFiberIfOut(f.fiber));
                        continue;

                    case CalcState.Invalid:
                        const cell = host.refresh(fiber);
                        if (cell && isFormulaCell(cell) && cell.state === CalcState.Dirty) {
                            pushFiberIfOut(cell);
                        }
                        continue;

                    default:
                        return assert.fail("TODO");
                }
            }

            /** Function Fiber */
            const { row, column, range } = fiber;
            const startC = range.tlCol;
            const endR = range.tlRow + range.height;
            const endC = range.tlCol + range.width;
            for (let j = column; j < endC; j += 1) {
                const cell = host.readCache(row, j);
                if (cell && isFormulaCell(cell) && (cell.state === CalcState.Dirty || cell.state === CalcState.Invalid)) {
                    pushFiberIfOut(cell);
                }
            }
            for (let i = row + 1; i < endR; i += 1) {
                for (let j = startC; j < endC; j += 1) {
                    const cell = host.readCache(i, j);
                    if (cell && isFormulaCell(cell) && (cell.state === CalcState.Dirty || cell.state === CalcState.Invalid)) {
                        pushFiberIfOut(cell);
                    }
                }
            }
            continue;
        }

        if (chainIdx < end) {
            const fiber = chain[chainIdx];
            switch (fiber.state) {
                case CalcState.Clean:
                    chainIdx++;
                    outChain.push(fiber);
                    continue;

                case CalcState.Dirty:
                    chainIdx++;
                    const result = evalCell(fiber, host);
                    if (result === true) {
                        outChain.push(fiber);
                        continue;
                    }
                    assert(stack.length === 0, "Fiber stack should be empty when pushing from chain.");
                    assert(pc === 0, "PC should be zero when pushing from chain.");
                    pc = 1;
                    pushFiberFromChain(fiber);
                    result.forEach(f => pushFiberFromChain(f.fiber));
                    continue;

                case CalcState.Invalid:
                    const cell = host.refresh(fiber);
                    if (cell && isFormulaCell(cell)) {
                        chain[chainIdx] = cell;
                        continue;
                    }
                    chainIdx++;
                    continue;

                case CalcState.InCalc:
                default:
                    chainIdx++;
                    return assert.fail("Cells in the main chain should not be in-calc");
            }
        }
        break;
    }

    return outChain;

    function rescheduleRoot(fiber: FormulaCell<CellValue>) {
        assert(fiber.state === CalcState.InCalc, "Rescheduled should be in flight");
        assert((fiber.flags & CalcFlags.InStack) !== 0, "Rescheduled root should be marked in stack");
        if (pc === stack.length) {
            stack.push(fiber);
            pc++;
            return;
        }
        assert(pc < stack.length, "PC should be less than stack len.");
        const existing = stack[pc];
        assert(existing.state !== CalcState.InCalc, "Top of the pc should not be in flight");
        assert((existing.flags & CalcFlags.InStack) !== 0, "Top of the pc should be in stack");
        stack[pc] = fiber;
        stack.push(existing);
        pc++;
        return;
    }

    function pushFiberIfOut(fiber: Fiber<CellValue>) {
        if ((fiber.flags & CalcFlags.InStack) === 0) {
            fiber.flags |= CalcFlags.InStack;
            stack.push(fiber);
        }
    }

    function pushFiberFromChain(fiber: Fiber<CellValue>) {
        assert((fiber.flags & CalcFlags.InStack) === 0, "Fiber should not be evaluated from chain while in stack.")
        fiber.flags |= CalcFlags.InStack;
        stack.push(fiber);
    }

    function evalCell(cell: FormulaCell<CellValue>, host: BuildHost): true | PendingValue[] {
        // TODO: We don't cache the contexts for the cells. Maybe we should?
        const { parser, evaluate, dereference } = host;
        if (cell.node === undefined) {
            const [hasError, node] = parser(cell.formula);
            if (hasError) {
                cell.state = CalcState.Clean;
                cell.flags |= CalcFlags.PendingNotification;
                cell.value = errors.parseFailure;
                return true;
            }
            cell.node = node;
        }
        cell.state = CalcState.InCalc;
        const result = evaluate(cell.row, cell.col, cell.node);
        if (Array.isArray(result)) {
            const tasks = result.filter(isPendingTask);
            assert(tasks.length === result.length, "Unknown pending value");
            return tasks;
        }
        const nonRange = dereference(cell.row, cell.col, result);
        if (isPendingTask(nonRange)) {
            return [nonRange];
        }
        cell.state = CalcState.Clean;
        cell.flags |= CalcFlags.PendingNotification;
        cell.value = nonRange;
        return true;
    }
}