Advertisement
Guest User

Untitled

a guest
Aug 22nd, 2023
84
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const Decimal = require('decimal.js');
  2.  
  3. const
  4.   WS = Symbol('ws'),
  5.   NUMBER = Symbol('num'),
  6.   PLUS = Symbol('plus'),
  7.   MINUS = Symbol('minus'),
  8.   MULTIPLY = Symbol('mul'),
  9.   DIVIDE = Symbol('div'),
  10.   MOD = Symbol('mod'),
  11.   LEFT_PAR = Symbol('lpar'),
  12.   RIGHT_PAR = Symbol('rpar'),
  13.   IDENTIFIER = Symbol('id'),
  14.   EQUAL = Symbol('eq'),
  15.   SEMICOLON = Symbol('semicolon'),
  16.   UNKNOWN = Symbol('unknown'),
  17.   END_OF_FILE = Symbol('eof');
  18.  
  19. function* tokenize(input, sym = {}) {
  20.   const matchers = [
  21.     { type: NUMBER, re: /^[0-9\.]+/ },
  22.     { type: PLUS, re: /^\+/ },
  23.     { type: MINUS, re: /^[-−]/ },
  24.     { type: MULTIPLY, re: /^[*×⋅·]/ },
  25.     { type: DIVIDE, re: /^\// },
  26.     { type: MOD, re: /^%/ },
  27.     { type: WS, re: /^[\s]+/ },
  28.     { type: LEFT_PAR, re: /^\(/ },
  29.     { type: RIGHT_PAR, re: /^\)/ },
  30.     { type: IDENTIFIER, re: /^[_a-zA-Z][_a-zA-Z0-9]*/ },
  31.     { type: EQUAL, re: /^=/ },
  32.     { type: SEMICOLON, re: /^;/ },
  33.     { type: END_OF_FILE, re: /^$/ }
  34.   ];
  35.   let pos = 0;
  36.   let unknownFrom = -1;
  37.   while (input) {
  38.     const posBefore = pos;
  39.     for (const { type, re } of matchers) {
  40.       let match = re.exec(input.slice(pos))?.[0];
  41.       if (typeof(match) === 'string') {
  42.         if (unknownFrom >= 0) {
  43.           // flush the unknown input as one token
  44.           yield {
  45.             type: UNKNOWN,
  46.             pos: unknownFrom,
  47.             match: input.slice(unknownFrom, pos),
  48.           };
  49.           unknownFrom = -1;
  50.         }
  51.         if (type === IDENTIFIER) {
  52.           for (const name in sym) {
  53.             if (match.startsWith(name)) match = name;
  54.           }
  55.         }
  56.         yield { type, pos, match };
  57.         if (type === END_OF_FILE) input = null; // breaks outer loop
  58.         pos += match.length;
  59.         break;
  60.       }
  61.     }
  62.     if (input && posBefore === pos) {
  63.       // nothing matched, track unknown input
  64.       if (unknownFrom < 0) unknownFrom = pos;
  65.       pos++;
  66.     }
  67.   }
  68. }
  69.  
  70. class ExpressionCalc {
  71.   #symbols = {
  72.     pi: new Decimal(Math.PI),
  73.     e: new Decimal(Math.E),
  74.     sin: d => d.sin(),
  75.     cos: d => d.cos(),
  76.   };
  77.   #stream;
  78.   #token;
  79.  
  80.   constructor(input) {
  81.     this.#stream = tokenize(input, this.#symbols);
  82.     this.#next();
  83.   }
  84.  
  85.   calc() {
  86.     let result;
  87.     do {
  88.       result = this.#expression();
  89.     } while (this.#accept(SEMICOLON));
  90.     this.#expect(END_OF_FILE);
  91.     return result;
  92.   }
  93.  
  94.   #error(err) {
  95.     throw new Error(`${err}, pos=${this.#token?.pos}, token=${this.#token.match}, type=${this.#token.type.description}`);
  96.   }
  97.  
  98.   #next() {
  99.     do {
  100.       this.#token = this.#stream.next()?.value;
  101.     } while (this.#token?.type === WS);
  102.   }
  103.  
  104.   #check(...types) {
  105.     return types.includes(this.#token?.type);
  106.   }
  107.  
  108.   #accept(type) {
  109.     let match;
  110.     if (this.#check(type)) {
  111.       match = this.#token.match;
  112.       this.#next();
  113.       return match || true;
  114.     }
  115.     return false;
  116.   }
  117.  
  118.   #expect(type) {
  119.     return this.#accept(type) || this.#error(`expected ${type.description}`);
  120.   }
  121.  
  122.   #factor() {
  123.     let result;
  124.  
  125.     let text;
  126.     if (this.#accept(LEFT_PAR)) { // parenthesis (but not function calls)
  127.       result = this.#expression();
  128.       this.#expect(RIGHT_PAR);
  129.     } else if (text = this.#accept(NUMBER)) { // numeric literals
  130.       result = new Decimal(text);
  131.     } else if (this.#accept(PLUS)) { // unary plus
  132.       result = this.#factor();
  133.     } else if (this.#accept(MINUS)) { // unary minus
  134.       result = this.#factor().mul(new Decimal(-1));
  135.     } else if (text = this.#accept(IDENTIFIER)) { // identifiers (named constants and function calls)
  136.       text = text.toLowerCase();
  137.       if (this.#accept(EQUAL)) {
  138.         this.#symbols[text] = this.#expression();
  139.       }
  140.       const sym = this.#symbols[text];
  141.       if (sym instanceof Decimal) result = sym;
  142.       else if (typeof sym === 'function') {
  143.         this.#expect(LEFT_PAR);
  144.         result = sym(this.#expression());
  145.         this.#expect(RIGHT_PAR);
  146.       }
  147.       else this.#error(`unknown id ${text}`);
  148.     } else {
  149.       this.#error('unexpected input');
  150.     }
  151.  
  152.     return result
  153.   }
  154.  
  155.   #term() {
  156.     let result = this.#factor();
  157.  
  158.     while (true) {
  159.       if (this.#accept(MULTIPLY) || this.#check(NUMBER, IDENTIFIER, LEFT_PAR)) result = result.mul(this.#factor());
  160.       else if (this.#accept(DIVIDE)) result = result.div(this.#factor());
  161.       else if (this.#accept(MOD)) result = result.mod(this.#factor());
  162.       else break;
  163.     }
  164.  
  165.     return result;
  166.   }
  167.  
  168.   #expression() {
  169.     let result = this.#term();
  170.  
  171.     while (true) {
  172.       if (this.#accept(PLUS)) result = result.add(this.#term());
  173.       else if (this.#accept(MINUS)) result = result.sub(this.#term());
  174.       else break;
  175.     }
  176.  
  177.     return result;
  178.   }
  179. }
  180.  
  181. console.log(new ExpressionCalc('2+2').calc().eq(new Decimal('4')));
  182. console.log(new ExpressionCalc('2+3-4').calc().eq(new Decimal('1')));
  183. console.log(new ExpressionCalc('2+2*2').calc().eq(new Decimal('6')));
  184. console.log(new ExpressionCalc('3+4*5').calc().eq(new Decimal('23')));
  185. console.log(new ExpressionCalc('3/2+4*5').calc().eq(new Decimal('21.5')));
  186. console.log(new ExpressionCalc('(2+2)*2').calc().eq(new Decimal('8')));
  187. console.log(new ExpressionCalc('(02. + 0002.) × 002.000').calc().eq(new Decimal('8')));
  188. console.log(new ExpressionCalc('3%2').calc().eq(new Decimal('1')));
  189. console.log(new ExpressionCalc('+1').calc().eq(new Decimal('1')));
  190. console.log(new ExpressionCalc('-(2+3)').calc().eq(new Decimal('-5')));
  191. console.log(new ExpressionCalc('cos(2*pi)').calc().eq(new Decimal('1')));
  192. console.log(new ExpressionCalc('-2.1+ .355 / (cos(pi % 3) + sin(0.311))').calc().eq(new Decimal('-1.8260809473359577169')));
  193. console.log(new ExpressionCalc('2pi').calc().eq(new Decimal(Math.PI * 2)));
  194. console.log(new ExpressionCalc('3(5cos(1)2)').calc().eq(new Decimal('16.209069176044191522')));
  195. console.log(new ExpressionCalc('2 2').calc().eq(new Decimal('4')));
  196. console.log(new ExpressionCalc('(2 + 3) 2').calc().eq(new Decimal('10')));
  197. console.log(new ExpressionCalc('(2)(2)3(4)\t3').calc().eq(new Decimal('144')));
  198. console.log(new ExpressionCalc('a=7').calc().eq(new Decimal('7')))
  199. console.log(new ExpressionCalc('a=b=c=7').calc().eq(new Decimal('7')))
  200. console.log(new ExpressionCalc('2*((a=1)+(b=1))').calc().eq(new Decimal('4')));
  201. console.log(new ExpressionCalc('a=1;b=2').calc().eq(new Decimal('2')));
  202. console.log(new ExpressionCalc('a=1; b=2; x=pi/3; a*b*cos(x)').calc().eq(new Decimal('1.0000000000000001376')));
  203. 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