UNPKG

@borgar/fx

Version:

Utilities for working with Excel formulas

148 lines (141 loc) 3.53 kB
import { REF_RANGE, REF_BEAM, REF_TERNARY, MAX_COLS, MAX_ROWS } from '../constants.ts'; import type { Token } from '../types.ts'; import { advRangeOp } from './advRangeOp.ts'; import { canEndRange } from './canEndRange.ts'; const BR_OPEN = 91; // [ const BR_CLOSE = 93; // ] const UC_R = 82; const LC_R = 114; const UC_C = 67; const LC_C = 99; const PLUS = 43; const MINUS = 45; const EXCL = 33; // C // C\[[+-]?\d+\] // C[1-9][0-9]{0,4} // R // R\[[+-]?\d+\] // R[1-9][0-9]{0,6} function lexR1C1Part (str: string, pos: number, isRow: boolean = false): number { const start = pos; const c0 = str.charCodeAt(pos); if ((isRow ? c0 === UC_R || c0 === LC_R : c0 === UC_C || c0 === LC_C)) { pos++; let digits = 0; let value = 0; let stop = str.length; const c1 = str.charCodeAt(pos); let c; let sign = 1; const relative = c1 === BR_OPEN; if (relative) { stop = Math.min(stop, pos + (isRow ? 8 : 6)); pos++; // allow +- c = str.charCodeAt(pos); if (c === PLUS || c === MINUS) { pos++; stop++; sign = c === MINUS ? -1 : 1; } } else if (c1 < 49 || c1 > 57 || isNaN(c1)) { // char must be 1-9, or part is either just "R" or "C" return 1; } do { const c = str.charCodeAt(pos); if (c >= 48 && c <= 57) { // 0-9 value = value * 10 + c - 48; digits++; pos++; } else { break; } } while (pos < stop); const MAX = isRow ? MAX_ROWS : MAX_COLS; if (relative) { const c = str.charCodeAt(pos); if (c !== BR_CLOSE) { return 0; } // isRow: next char must not be a number! pos++; value *= sign; return (digits && (-MAX <= value) && (value <= MAX)) ? pos - start : 0; } // isRow: next char must not be a number! return (digits && value <= (MAX + 1)) ? pos - start : 0; } return 0; } export function lexRangeR1C1 ( str: string, pos: number, options: { allowTernary: boolean } ): Token | undefined { let p = pos; // C1 // C1:C1 // C1:R1C1 --partial // R1 // R1:R1 // R1:R1C1 --partial // R1C1 // R1C1:C1 --partial // R1C1:R1 --partial const r1 = lexR1C1Part(str, p, true); p += r1; const c1 = lexR1C1Part(str, p); p += c1; if ((c1 || r1) && str.charCodeAt(p) !== EXCL) { const op = advRangeOp(str, p); const preOp = p; if (op) { p += op; const r2 = lexR1C1Part(str, p, true); // R1 p += r2; const c2 = lexR1C1Part(str, p); // C1 p += c2; // C1:R2C2 --partial // R1:R2C2 --partial // R1C1:C2 --partial // R1C1:R2 --partial if ( (r1 && !c1 && r2 && c2) || (!r1 && c1 && r2 && c2) || (r1 && c1 && r2 && !c2) || (r1 && c1 && !r2 && c2) ) { if (options.allowTernary && canEndRange(str, p)) { return { type: REF_TERNARY, value: str.slice(pos, p) }; } } // C1:C2 -- beam // R1:R2 -- beam else if ( (c1 && c2 && !r1 && !r2) || (!c1 && !c2 && r1 && r2) ) { if (canEndRange(str, p)) { return { type: REF_BEAM, value: str.slice(pos, p) }; } } // Note: we do not capture R1C1:R1C1, mergeRefTokens will join the parts } // R1 // C1 // R1C1 if (canEndRange(str, preOp)) { return { type: (r1 && c1) ? REF_RANGE : REF_BEAM, value: str.slice(pos, preOp) }; } } }