expression-calculator
Version:
Calculate expressions without using `eval()`, using LL(1) syntax analyzer.
198 lines (194 loc) • 6.22 kB
JavaScript
import {
NUM,
VAR,
OPER
} from './types';
import OPERAND_ALL from './operands';
import SyntaxChecker from "./syntax";
export default class RPN{
__data=[];
constructor(input){
if(input instanceof RPN){
this.setRPN(input.getRPN());
}if('string'==typeof(input)){
this.compile(input);
}else if(Array.isArray(input) && input.length){
this.setRPN(input);
}
}
__numConv(numStr){
if(isNaN(numStr)){
throw new TypeError('Invalid number format');
}
return Number(numStr);
}
compile(input){
let stackMain=[], stackOper=[];
for(let token of new SyntaxChecker(input)){
if(NUM==token.type){
token.value = this.__numConv(token.value);
stackMain.push(token);
}else if(VAR==token.type){
stackMain.push(token);
}else if(OPER==token.type) {
if (!stackOper.length || token.value == '(') {
stackOper.push(token);
} else if (token.value == ')') {
let token_pop=null;
while (token_pop = stackOper.pop()) {
if (token_pop.value == '(') {
break;
}
stackMain.push(token_pop);
}
} else {
while (stackOper.length) {
let stackOperTop=stackOper[stackOper.length-1].value,
operandTop=OPERAND_ALL[stackOperTop],
operandCurrent=OPERAND_ALL[token.value];
if('('==stackOperTop
|| operandTop.priority < operandCurrent.priority
|| (operandTop.rightToLeft && operandCurrent.rightToLeft)){
break;
}
stackMain.push(stackOper.pop());
}
stackOper.push(token);
}
}
}
while(stackOper.length){
stackMain.push(stackOper.pop());
}
this.__data=stackMain;
return this;
}
compress(){
let result=[];
for(let token of this.__data){
//非操作符,直接入栈
if(OPER!=token.type){
result.push(token);
continue;
}
let operand = OPERAND_ALL[token.value];
if (operand.binocular
&& result.length > 1
&& result[result.length-1].type == NUM
&& result[result.length-2].type == NUM) {
var b = result.pop().value, a = result.pop().value;
result.push({
type: NUM,
value: operand.processor(a,b),
});
} else if (!operand.binocular
&& result.length > 0
&& result[result.length-1].type == NUM) {
result.push({
type: NUM,
value: operand.processor(operand.pop().value),
});
} else {
result.push(token);
}
}
this.__data=result;
return this;
}
getRPN(){
if(!this.__data.length){
return null;
}
return this.__data.map((item)=>({...item}));
}
toJSON(){ //Ailas of getRPN()
return this.getRPN();
}
setRPN(input){
if(!Array.isArray(input)){
throw new TypeError('Input must be an array with objects contains key "type" and "value"');
}
if(!input.length){
this.__data=[];
return this;
}
let result=[],stack=0;
input.forEach((token,i)=>{
let type, value;
try{
type=token.type, value=token.value;
}catch(e){
throw new TypeError(`Invalid RPN token at position ${i}`);
}
if(VAR===type){
if('string'!=typeof(value) || !value){
throw new TypeError(`Invalid RPN token at position ${i}`);
}
stack++;
}else if(NUM===type){
try{
value=this.__numConv(value);
}catch(e){
throw new TypeError(`Invalid number at position ${i}`);
}
stack++;
}else if(OPER===type){
let operand=OPERAND_ALL[value];
if(!operand){
throw new TypeError(`Invalid operand at position ${i}`);
}
if((!operand.binocular && stack<1)
|| (operand.binocular && stack<2)){
throw new SyntaxError(`Invalid RPN`);
}
if(operand.binocular){
stack--;
}
}else{
throw new TypeError(`Invalid RPN token at position ${i}`);
}
//All the validation for current token passed, write to result.
result.push({
type,
value
});
});
if(1!=stack){
throw new SyntaxError(`Invalid RPN`);
}
this.__data=result;
return this;
}
calc(valueTable={}){
if(!this.__data.length){
return null;
}
let stack=[];
for(let token of this.__data){
if(NUM==token.type){
stack.push(token.value);
continue;
}
if(VAR==token.type){
if(!valueTable.hasOwnProperty(token.value)){
throw new ReferenceError(`Value of variable ${token.value} not specified.`);
}
stack.push(this.__numConv(valueTable[token.value]));
continue;
}
if(OPER==token.type){
let operand = OPERAND_ALL[token.value];
if(operand.binocular){
let b = stack.pop(), a = stack.pop();
stack.push(operand.processor(a,b));
}else{
stack.push(operand.processor(stack.pop()));
}
}
}
if(1!=stack.length){
throw new SyntaxError('Malformed RPN');
}
return stack[0];
}
}