Advertisement
Guest User

Untitled

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