expression-calculator
Version:
Calculate expressions without using `eval()`, using LL(1) syntax analyzer.
234 lines (232 loc) • 8.24 kB
JavaScript
import assert from 'assert';
import {
NUM,
VAR,
OPER
} from './types';
import RPN from './rpn';
describe('Basic RPN test',function(){
it('Calculate 1+1',function(){
assert.strictEqual(new RPN('1+1').calc(),2);
});
it('RPN of 1+1',function(){
assert.deepStrictEqual(new RPN('1+1').getRPN(),[
{type:NUM, value:1},
{type:NUM, value:1},
{type:OPER, value:'+'},
]);
});
it('RPN of 1+2-3',function(){
assert.deepStrictEqual(new RPN('1+2-3').getRPN(),[
{type:NUM, value:1},
{type:NUM, value:2},
{type:OPER, value:'+'},
{type:NUM, value:3},
{type:OPER, value:'-'},
]);
});
it('RPN of 1+2-3 for JSON serialization',function(){
assert.strictEqual(JSON.stringify(new RPN('1+2-3')),JSON.stringify([
{type:NUM, value:1},
{type:NUM, value:2},
{type:OPER, value:'+'},
{type:NUM, value:3},
{type:OPER, value:'-'},
]));
});
it('Calculate 1+2*(3-4)',function(){
assert.strictEqual(new RPN('1+2*(3-4)').calc(),-1);
});
it('RPN of 1+2*(3-4)',function(){
assert.deepStrictEqual(new RPN('1+2*(3-4)').getRPN(),[
{type:NUM, value:1},
{type:NUM, value:2},
{type:NUM, value:3},
{type:NUM, value:4},
{type:OPER, value:'-'},
{type:OPER, value:'*'},
{type:OPER, value:'+'},
]);
});
it('Calculate 1*1+2*(3-4)',function(){
assert.strictEqual(new RPN('1*1+2*(3-4)').calc(),-1);
});
it('RPN of 1*1+2*(3-4)',function(){
assert.deepStrictEqual(new RPN().compile('1*1+2*(3-4)').getRPN(),[
{type:NUM, value:1},
{type:NUM, value:1},
{type:OPER, value:'*'},
{type:NUM, value:2},
{type:NUM, value:3},
{type:NUM, value:4},
{type:OPER, value:'-'},
{type:OPER, value:'*'},
{type:OPER, value:'+'},
]);
});
it('RPN of -1*1+2*(-3-4)',function(){
assert.deepStrictEqual(new RPN().compile('-1*1+2*(-3-4)').getRPN(),[
{type:NUM, value:1},
{type:OPER, value:'NEG'},
{type:NUM, value:1},
{type:OPER, value:'*'},
{type:NUM, value:2},
{type:NUM, value:3},
{type:OPER, value:'NEG'},
{type:NUM, value:4},
{type:OPER, value:'-'},
{type:OPER, value:'*'},
{type:OPER, value:'+'},
]);
});
it('RPN compress',function(){
assert.deepStrictEqual(new RPN('1*1+2*(3-4)').compress().getRPN(),[
{type:NUM, value:-1},
]);
});
it('RPN with variable',function(){
assert.deepStrictEqual(new RPN('(a+3)*2-1').getRPN(),[
{type:VAR, value:'a'},
{type:NUM, value:3},
{type:OPER, value:'+'},
{type:NUM, value:2},
{type:OPER, value:'*'},
{type:NUM, value:1},
{type:OPER, value:'-'},
]);
});
it('RPN with variable calculation',function(){
assert.deepStrictEqual(new RPN('(a+3)*2-1*8').calc({a:666}),(666+3)*2-1*8);
});
it('RPN with variable calculation but without value specified',function(){
assert.throws(()=>new RPN('(TEST+3)*2-1').calc(),
new ReferenceError('Value of variable TEST not specified.'));
});
it('RPN with variable compression',function(){
assert.deepStrictEqual(new RPN('(a+3)*2-1*8').getRPN(),[
{type:VAR, value:'a'},
{type:NUM, value:3},
{type:OPER, value:'+'},
{type:NUM, value:2},
{type:OPER, value:'*'},
{type:NUM, value:1},
{type:NUM, value:8},
{type:OPER, value:'*'},
{type:OPER, value:'-'},
]);
assert.deepStrictEqual(new RPN('(a+3)*2-1*8').compress().getRPN(),[
{type:VAR, value:'a'},
{type:NUM, value:3},
{type:OPER, value:'+'},
{type:NUM, value:2},
{type:OPER, value:'*'},
{type:NUM, value:8},
{type:OPER, value:'-'},
]);
});
it('RPN get & set',function(){
let template=[
{type:VAR, value:'a'},
{type:OPER, value:'NEG'},
{type:NUM, value:3},
{type:OPER, value:'+'},
{type:NUM, value:2748},
{type:OPER, value:'*'},
{type:NUM, value:1},
{type:NUM, value:8},
{type:OPER, value:'NEG'},
{type:OPER, value:'*'},
{type:OPER, value:'-'},
];
let rpn=new RPN('(-a+3)*0xabc-1*-8');
let rpn2=new RPN();
let rpn3=new RPN(rpn);
rpn2.setRPN(rpn.getRPN());
assert.deepStrictEqual(rpn.getRPN(),template);
assert.deepStrictEqual(rpn2.getRPN(),template);
assert.deepStrictEqual(rpn3.getRPN(),template);
});
it('Empty RPN',function(){
let emptyRPN=new RPN().setRPN([]);
assert.deepStrictEqual(emptyRPN.getRPN(),null);
assert.deepStrictEqual(emptyRPN.calc(),null);
});
it('Exponentiation test',function(){
assert.strictEqual(new RPN('2**2**3').calc(),256);
});
it('Exponentiation test 2',function(){
assert.strictEqual(new RPN('2*2**(1+1)**(4-1)').calc(),512);
});
});
describe('Malformed RPN test',function(){
it('Non-array',function(){
assert.throws(()=>new RPN().setRPN('aaa'),
TypeError('Input must be an array with objects contains key "type" and "value"'));
});
it('Malformed array item',function(){
assert.throws(()=>new RPN().setRPN([
{value:23333},
{type:NUM,value:666},
{type:OPER,value:'+'},
]),TypeError(`Invalid RPN token at position 0`));
assert.throws(()=>new RPN().setRPN([
{type:NUM,value:666},
null,
{type:OPER,value:'+'},
]),TypeError(`Invalid RPN token at position 1`));
});
it('Invalid RPN variable',function(){
assert.throws(()=>new RPN().setRPN([
{type:VAR,value:23333},
{type:NUM,value:666},
{type:OPER,value:'+'},
]),TypeError(`Invalid RPN token at position 0`));
assert.throws(()=>new RPN().setRPN([
{type:NUM,value:666},
{type:VAR,value:23333},
{type:OPER,value:'+'},
]),TypeError(`Invalid RPN token at position 1`));
});
it('Invalid RPN number',function(){
assert.throws(()=>new RPN().setRPN([
{type:VAR,value:'var'},
{type:NUM,value:'#####'},
{type:OPER,value:'+'},
]),TypeError(`Invalid number at position 1`));
assert.throws(()=>new RPN().setRPN([
{type:NUM,value:'#####'},
{type:VAR,value:'var'},
{type:OPER,value:'+'},
]),TypeError(`Invalid number at position 0`));
});
it('Invalid RPN operand',function(){
assert.throws(()=>new RPN().setRPN([
{type:VAR,value:'var'},
{type:NUM,value:2333},
{type:OPER},
]),TypeError(`Invalid operand at position 2`));
assert.throws(()=>new RPN([
{type:NUM,value:2333},
{type:VAR,value:'var'},
{type:OPER,value:'雷'},
]),TypeError(`Invalid operand at position 2`));
});
it('Invalid RPN',function(){
assert.throws(()=>new RPN().setRPN([
{type:VAR,value:'var'},
{type:OPER,value:'NEG'},
{type:OPER,value:'+'},
]),SyntaxError(`Invalid RPN`));
assert.throws(()=>new RPN().setRPN([
{type:OPER,value:'NEG'},
{type:OPER,value:'+'},
]),SyntaxError(`Invalid RPN`));
assert.throws(()=>new RPN().setRPN([
{type:NUM,value:2333},
{type:NUM,value:2333},
{type:NUM,value:2333},
{type:OPER,value:'NEG'},
{type:OPER,value:'+'},
]),SyntaxError(`Invalid RPN`));
});
});