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;
}
}