Advertisement
Guest User

Untitled

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