@borgar/fx
Version:
Utilities for working with Excel formulas
135 lines (131 loc) • 3.47 kB
text/typescript
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, canEndPartialRange } from './canEndRange.ts';
function advA1Col (str: string, pos: number): number {
// [A-Z]{1,3}
const start = pos;
if (str.charCodeAt(pos) === 36) { // $
pos++;
}
const stop = pos + 3;
let col = 0;
do {
const c = str.charCodeAt(pos);
if (c >= 65 && c <= 90) { // A-Z
col = 26 * col + c - 64;
pos++;
}
else if (c >= 97 && c <= 122) { // a-z
col = 26 * col + c - 96;
pos++;
}
else {
break;
}
}
while (pos < stop && pos < str.length);
return (col && col <= MAX_COLS + 1) ? pos - start : 0;
}
function advA1Row (str: string, pos: number): number {
// [1-9][0-9]{0,6}
const start = pos;
if (str.charCodeAt(pos) === 36) { // $
pos++;
}
const stop = pos + 7;
let row = 0;
let c = str.charCodeAt(pos);
if (c >= 49 && c <= 57) { // 1-9
row = row * 10 + c - 48;
pos++;
do {
c = str.charCodeAt(pos);
if (c >= 48 && c <= 57) { // 0-9
row = row * 10 + c - 48;
pos++;
}
else {
break;
}
}
while (pos < stop && pos < str.length);
}
return (row && row <= MAX_ROWS + 1) ? pos - start : 0;
}
export function lexRangeA1 (
str: string,
pos: number,
options: { mergeRefs: boolean, allowTernary: boolean }
): Token | undefined {
let p = pos;
const left = advA1Col(str, p);
let right = 0;
let bottom = 0;
if (left) {
// TLBR: could be A1:A1
// TL R: could be A1:A (if allowTernary)
// TLB : could be A1:1 (if allowTernary)
// LBR: could be A:A1 (if allowTernary)
// L R: could be A:A
p += left;
const top = advA1Row(str, p);
p += top;
const op = advRangeOp(str, p);
const preOp = p;
if (op) {
p += op;
right = advA1Col(str, p);
p += right;
bottom = advA1Row(str, p);
p += bottom;
if (top && bottom && right) {
if (canEndRange(str, p) && options.mergeRefs) {
return { type: REF_RANGE, value: str.slice(pos, p) };
}
}
else if (!top && !bottom) {
if (canEndRange(str, p)) {
return { type: REF_BEAM, value: str.slice(pos, p) };
}
}
else if (options.allowTernary && (bottom || right)) {
if (canEndPartialRange(str, p)) {
return { type: REF_TERNARY, value: str.slice(pos, p) };
}
}
}
// LT : this is A1
if (top && canEndRange(str, preOp) && str.charCodeAt(preOp) !== 33) { // 33 = "!"
return { type: REF_RANGE, value: str.slice(pos, preOp) };
}
}
else {
// T B : could be 1:1
// T BR: could be 1:A1 (if allowTernary)
const top = advA1Row(str, p);
if (top) {
p += top;
const op = advRangeOp(str, p);
if (op) {
p += op;
right = advA1Col(str, p);
if (right) {
p += right;
}
bottom = advA1Row(str, p);
p += bottom;
if (right && bottom && options.allowTernary) {
if (canEndPartialRange(str, p)) {
return { type: REF_TERNARY, value: str.slice(pos, p) };
}
}
if (!right && bottom) {
if (canEndRange(str, p)) {
return { type: REF_BEAM, value: str.slice(pos, p) };
}
}
}
}
}
}