x-data-spreadsheet
Version:
a javascript xpreadsheet
224 lines (218 loc) • 6.85 kB
JavaScript
import { expr2xy, xy2expr } from './alphabet';
import { numberCalc } from './helper';
// Converting infix expression to a suffix expression
// src: AVERAGE(SUM(A1,A2), B1) + 50 + B20
// return: [A1, A2], SUM[, B1],AVERAGE,50,+,B20,+
const infixExprToSuffixExpr = (src) => {
const operatorStack = [];
const stack = [];
let subStrs = []; // SUM, A1, B2, 50 ...
let fnArgType = 0; // 1 => , 2 => :
let fnArgOperator = '';
let fnArgsLen = 1; // A1,A2,A3...
let oldc = '';
for (let i = 0; i < src.length; i += 1) {
const c = src.charAt(i);
if (c !== ' ') {
if (c >= 'a' && c <= 'z') {
subStrs.push(c.toUpperCase());
} else if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c === '.') {
subStrs.push(c);
} else if (c === '"') {
i += 1;
while (src.charAt(i) !== '"') {
subStrs.push(src.charAt(i));
i += 1;
}
stack.push(`"${subStrs.join('')}`);
subStrs = [];
} else if (c === '-' && /[+\-*/,(]/.test(oldc)) {
subStrs.push(c);
} else {
// console.log('subStrs:', subStrs.join(''), stack);
if (c !== '(' && subStrs.length > 0) {
stack.push(subStrs.join(''));
}
if (c === ')') {
let c1 = operatorStack.pop();
if (fnArgType === 2) {
// fn argument range => A1:B5
try {
const [ex, ey] = expr2xy(stack.pop());
const [sx, sy] = expr2xy(stack.pop());
// console.log('::', sx, sy, ex, ey);
let rangelen = 0;
for (let x = sx; x <= ex; x += 1) {
for (let y = sy; y <= ey; y += 1) {
stack.push(xy2expr(x, y));
rangelen += 1;
}
}
stack.push([c1, rangelen]);
} catch (e) {
// console.log(e);
}
} else if (fnArgType === 1 || fnArgType === 3) {
if (fnArgType === 3) stack.push(fnArgOperator);
// fn argument => A1,A2,B5
stack.push([c1, fnArgsLen]);
fnArgsLen = 1;
} else {
// console.log('c1:', c1, fnArgType, stack, operatorStack);
while (c1 !== '(') {
stack.push(c1);
if (operatorStack.length <= 0) break;
c1 = operatorStack.pop();
}
}
fnArgType = 0;
} else if (c === '=' || c === '>' || c === '<') {
const nc = src.charAt(i + 1);
fnArgOperator = c;
if (nc === '=' || nc === '-') {
fnArgOperator += nc;
i += 1;
}
fnArgType = 3;
} else if (c === ':') {
fnArgType = 2;
} else if (c === ',') {
if (fnArgType === 3) {
stack.push(fnArgOperator);
}
fnArgType = 1;
fnArgsLen += 1;
} else if (c === '(' && subStrs.length > 0) {
// function
operatorStack.push(subStrs.join(''));
} else {
// priority: */ > +-
// console.log(operatorStack, c, stack);
if (operatorStack.length > 0 && (c === '+' || c === '-')) {
let top = operatorStack[operatorStack.length - 1];
if (top !== '(') stack.push(operatorStack.pop());
if (top === '*' || top === '/') {
while (operatorStack.length > 0) {
top = operatorStack[operatorStack.length - 1];
if (top !== '(') stack.push(operatorStack.pop());
else break;
}
}
}
operatorStack.push(c);
}
subStrs = [];
}
oldc = c;
}
}
if (subStrs.length > 0) {
stack.push(subStrs.join(''));
}
while (operatorStack.length > 0) {
stack.push(operatorStack.pop());
}
return stack;
};
const evalSubExpr = (subExpr, cellRender) => {
const [fl] = subExpr;
let expr = subExpr;
if (fl === '"') {
return subExpr.substring(1);
}
let ret = 1;
if (fl === '-') {
expr = subExpr.substring(1);
ret = -1;
}
if (expr[0] >= '0' && expr[0] <= '9') {
return ret * Number(expr);
}
const [x, y] = expr2xy(expr);
return ret * cellRender(x, y);
};
// evaluate the suffix expression
// srcStack: <= infixExprToSufixExpr
// formulaMap: {'SUM': {}, ...}
// cellRender: (x, y) => {}
const evalSuffixExpr = (srcStack, formulaMap, cellRender, cellList) => {
const stack = [];
// console.log(':::::formulaMap:', formulaMap);
for (let i = 0; i < srcStack.length; i += 1) {
// console.log(':::>>>', srcStack[i]);
const expr = srcStack[i];
const fc = expr[0];
if (expr === '+') {
const top = stack.pop();
stack.push(numberCalc('+', stack.pop(), top));
} else if (expr === '-') {
if (stack.length === 1) {
const top = stack.pop();
stack.push(numberCalc('*', top, -1));
} else {
const top = stack.pop();
stack.push(numberCalc('-', stack.pop(), top));
}
} else if (expr === '*') {
stack.push(numberCalc('*', stack.pop(), stack.pop()));
} else if (expr === '/') {
const top = stack.pop();
stack.push(numberCalc('/', stack.pop(), top));
} else if (fc === '=' || fc === '>' || fc === '<') {
let top = stack.pop();
if (!Number.isNaN(top)) top = Number(top);
let left = stack.pop();
if (!Number.isNaN(left)) left = Number(left);
let ret = false;
if (fc === '=') {
ret = (left === top);
} else if (expr === '>') {
ret = (left > top);
} else if (expr === '>=') {
ret = (left >= top);
} else if (expr === '<') {
ret = (left < top);
} else if (expr === '<=') {
ret = (left <= top);
}
stack.push(ret);
} else if (Array.isArray(expr)) {
const [formula, len] = expr;
const params = [];
for (let j = 0; j < len; j += 1) {
params.push(stack.pop());
}
stack.push(formulaMap[formula].render(params.reverse()));
} else {
if (cellList.includes(expr)) {
return 0;
}
if ((fc >= 'a' && fc <= 'z') || (fc >= 'A' && fc <= 'Z')) {
cellList.push(expr);
}
stack.push(evalSubExpr(expr, cellRender));
cellList.pop();
}
// console.log('stack:', stack);
}
return stack[0];
};
const cellRender = (src, formulaMap, getCellText, cellList = []) => {
if (src[0] === '=') {
const stack = infixExprToSuffixExpr(src.substring(1));
if (stack.length <= 0) return src;
return evalSuffixExpr(
stack,
formulaMap,
(x, y) => cellRender(getCellText(x, y), formulaMap, getCellText, cellList),
cellList,
);
}
return src;
};
export default {
render: cellRender,
};
export {
infixExprToSuffixExpr,
};