Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- const Decimal = require('decimal.js');
- const
- WS = Symbol('ws'),
- NUMBER = Symbol('num'),
- PLUS = Symbol('plus'),
- MINUS = Symbol('minus'),
- MULTIPLY = Symbol('mul'),
- DIVIDE = Symbol('div'),
- MOD = Symbol('mod'),
- LEFT_PAR = Symbol('lpar'),
- RIGHT_PAR = Symbol('rpar'),
- IDENTIFIER = Symbol('id'),
- EQUAL = Symbol('eq'),
- SEMICOLON = Symbol('semicolon'),
- UNKNOWN = Symbol('unknown'),
- END_OF_FILE = Symbol('eof');
- function* tokenize(input, sym = {}) {
- const matchers = [
- { type: NUMBER, re: /^[0-9\.]+/ },
- { type: PLUS, re: /^\+/ },
- { type: MINUS, re: /^[-−]/ },
- { type: MULTIPLY, re: /^[*×⋅·]/ },
- { type: DIVIDE, re: /^\// },
- { type: MOD, re: /^%/ },
- { type: WS, re: /^[\s]+/ },
- { type: LEFT_PAR, re: /^\(/ },
- { type: RIGHT_PAR, re: /^\)/ },
- { type: IDENTIFIER, re: /^[_a-zA-Z][_a-zA-Z0-9]*/ },
- { type: EQUAL, re: /^=/ },
- { type: SEMICOLON, re: /^;/ },
- { type: END_OF_FILE, re: /^$/ }
- ];
- let pos = 0;
- let unknownFrom = -1;
- while (input) {
- const posBefore = pos;
- for (const { type, re } of matchers) {
- let match = re.exec(input.slice(pos))?.[0];
- if (typeof(match) === 'string') {
- if (unknownFrom >= 0) {
- // flush the unknown input as one token
- yield {
- type: UNKNOWN,
- pos: unknownFrom,
- match: input.slice(unknownFrom, pos),
- };
- unknownFrom = -1;
- }
- if (type === IDENTIFIER) {
- for (const name in sym) {
- if (match.startsWith(name)) match = name;
- }
- }
- yield { type, pos, match };
- if (type === END_OF_FILE) input = null; // breaks outer loop
- pos += match.length;
- break;
- }
- }
- if (input && posBefore === pos) {
- // nothing matched, track unknown input
- if (unknownFrom < 0) unknownFrom = pos;
- pos++;
- }
- }
- }
- class ExpressionCalc {
- #symbols = {
- pi: new Decimal(Math.PI),
- e: new Decimal(Math.E),
- sin: d => d.sin(),
- cos: d => d.cos(),
- };
- #stream;
- #token;
- constructor(input) {
- this.#stream = tokenize(input, this.#symbols);
- this.#next();
- }
- calc() {
- let result;
- do {
- result = this.#expression();
- } while (this.#accept(SEMICOLON));
- this.#expect(END_OF_FILE);
- return result;
- }
- #error(err) {
- throw new Error(`${err}, pos=${this.#token?.pos}, token=${this.#token.match}, type=${this.#token.type.description}`);
- }
- #next() {
- do {
- this.#token = this.#stream.next()?.value;
- } while (this.#token?.type === WS);
- }
- #check(...types) {
- return types.includes(this.#token?.type);
- }
- #accept(type) {
- let match;
- if (this.#check(type)) {
- match = this.#token.match;
- this.#next();
- return match || true;
- }
- return false;
- }
- #expect(type) {
- return this.#accept(type) || this.#error(`expected ${type.description}`);
- }
- #factor() {
- let result;
- let text;
- if (this.#accept(LEFT_PAR)) { // parenthesis (but not function calls)
- result = this.#expression();
- this.#expect(RIGHT_PAR);
- } else if (text = this.#accept(NUMBER)) { // numeric literals
- result = new Decimal(text);
- } else if (this.#accept(PLUS)) { // unary plus
- result = this.#factor();
- } else if (this.#accept(MINUS)) { // unary minus
- result = this.#factor().mul(new Decimal(-1));
- } else if (text = this.#accept(IDENTIFIER)) { // identifiers (named constants and function calls)
- text = text.toLowerCase();
- if (this.#accept(EQUAL)) {
- this.#symbols[text] = this.#expression();
- }
- const sym = this.#symbols[text];
- if (sym instanceof Decimal) result = sym;
- else if (typeof sym === 'function') {
- this.#expect(LEFT_PAR);
- result = sym(this.#expression());
- this.#expect(RIGHT_PAR);
- }
- else this.#error(`unknown id ${text}`);
- } else {
- this.#error('unexpected input');
- }
- return result
- }
- #term() {
- let result = this.#factor();
- while (true) {
- if (this.#accept(MULTIPLY) || this.#check(NUMBER, IDENTIFIER, LEFT_PAR)) result = result.mul(this.#factor());
- else if (this.#accept(DIVIDE)) result = result.div(this.#factor());
- else if (this.#accept(MOD)) result = result.mod(this.#factor());
- else break;
- }
- return result;
- }
- #expression() {
- let result = this.#term();
- while (true) {
- if (this.#accept(PLUS)) result = result.add(this.#term());
- else if (this.#accept(MINUS)) result = result.sub(this.#term());
- else break;
- }
- return result;
- }
- }
- console.log(new ExpressionCalc('2+2').calc().eq(new Decimal('4')));
- console.log(new ExpressionCalc('2+3-4').calc().eq(new Decimal('1')));
- console.log(new ExpressionCalc('2+2*2').calc().eq(new Decimal('6')));
- console.log(new ExpressionCalc('3+4*5').calc().eq(new Decimal('23')));
- console.log(new ExpressionCalc('3/2+4*5').calc().eq(new Decimal('21.5')));
- console.log(new ExpressionCalc('(2+2)*2').calc().eq(new Decimal('8')));
- console.log(new ExpressionCalc('(02. + 0002.) × 002.000').calc().eq(new Decimal('8')));
- console.log(new ExpressionCalc('3%2').calc().eq(new Decimal('1')));
- console.log(new ExpressionCalc('+1').calc().eq(new Decimal('1')));
- console.log(new ExpressionCalc('-(2+3)').calc().eq(new Decimal('-5')));
- console.log(new ExpressionCalc('cos(2*pi)').calc().eq(new Decimal('1')));
- console.log(new ExpressionCalc('-2.1+ .355 / (cos(pi % 3) + sin(0.311))').calc().eq(new Decimal('-1.8260809473359577169')));
- console.log(new ExpressionCalc('2pi').calc().eq(new Decimal(Math.PI * 2)));
- console.log(new ExpressionCalc('3(5cos(1)2)').calc().eq(new Decimal('16.209069176044191522')));
- console.log(new ExpressionCalc('2 2').calc().eq(new Decimal('4')));
- console.log(new ExpressionCalc('(2 + 3) 2').calc().eq(new Decimal('10')));
- console.log(new ExpressionCalc('(2)(2)3(4)\t3').calc().eq(new Decimal('144')));
- console.log(new ExpressionCalc('a=7').calc().eq(new Decimal('7')))
- console.log(new ExpressionCalc('a=b=c=7').calc().eq(new Decimal('7')))
- console.log(new ExpressionCalc('2*((a=1)+(b=1))').calc().eq(new Decimal('4')));
- console.log(new ExpressionCalc('a=1;b=2').calc().eq(new Decimal('2')));
- console.log(new ExpressionCalc('a=1; b=2; x=pi/3; a*b*cos(x)').calc().eq(new Decimal('1.0000000000000001376')));
- console.log(new ExpressionCalc('a=1; b=2; x=pi/3; abcos(x)').calc().eq(new Decimal('1.0000000000000001376')));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement