Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 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'),
- UNKNOWN = Symbol('unknown'),
- END_OF_FILE = Symbol('eof');
- function* tokenize(input) {
- 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: END_OF_FILE, re: /^$/ }
- ];
- let pos = 0;
- let unknownFrom = -1;
- while (input) {
- const posBefore = pos;
- for (const { type, re } of matchers) {
- const 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;
- }
- 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 {
- #stream;
- #token;
- constructor(input) {
- this.#stream = tokenize(input);
- this.#next();
- }
- calc() {
- const result = this.#expression();
- 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);
- }
- #accept(type) {
- let match;
- if (this.#token?.type === 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 = parseFloat(text);
- } else if (this.#accept(PLUS)) { // unary plus
- result = +this.#factor();
- } else if (this.#accept(MINUS)) { // unary minus
- result = -this.#factor();
- } else if (text = this.#accept(IDENTIFIER)) { // identifiers (named constants and function calls)
- text = text.toLowerCase();
- let func;
- if (text === 'pi') result = Math.PI;
- else if (text === 'e') result = Math.E;
- else if (Math[text] && typeof (func = Math[text]) === 'function') {
- this.#expect(LEFT_PAR);
- result = func(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)) result *= this.#factor();
- else if (this.#accept(DIVIDE)) result /= this.#factor();
- else if (this.#accept(MOD)) result %= this.#factor();
- else break;
- }
- return result;
- }
- #expression() {
- let result = this.#term();
- while (true) {
- if (this.#accept(PLUS)) result += this.#term();
- else if (this.#accept(MINUS)) result -= this.#term();
- else break;
- }
- return result;
- }
- }
- console.log(new ExpressionCalc('2+2').calc() === 4);
- console.log(new ExpressionCalc('2+3-4').calc() === 1);
- console.log(new ExpressionCalc('2+2*2').calc() === 6);
- console.log(new ExpressionCalc('3+4*5').calc() === 23);
- console.log(new ExpressionCalc('3/2+4*5').calc() === 21.5);
- console.log(new ExpressionCalc('(2+2)*2').calc() === 8);
- console.log(new ExpressionCalc('(02. + 0002.) × 002.000').calc() === 8);
- console.log(new ExpressionCalc('3%2').calc() === 1);
- console.log(new ExpressionCalc('+1').calc() === 1);
- console.log(new ExpressionCalc('-(2+3)').calc() === -5);
- console.log(new ExpressionCalc('cos(2*pi)').calc() === 1);
- 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