打字稿-计算算术表达式

时间:2019-03-22 14:51:12

标签: typescript

如何计算Typescript中的算术表达式?一个示例是'(3 + 5 *(-3 + -1))'。

Eval(...)被禁止。

运行时不接受建议的解决方案:

let input = '(3+5)';
let resultNumber = (new Function( 'return (' + input + ')'))();

错误是:

  

SyntaxError:无效或意外的令牌               在新功能()

对于我来说,具有136kb占用空间(压缩)的Math.js太大,无法评估简单表达式。可以通过限制功能进行自定义。

那么,您是否有一个小的打字稿文件/服务可以评估算术表达式?当然,一元减号/加号应该可以正常工作。

2 个答案:

答案 0 :(得分:2)

由于@Mathyn,我发现了@Boann创建的一段不错的Java代码。我将其迁移到Typescript,就在那里。

下面还找到了Karma测试代码,因此可以看到可能的结果:+,-,(一元),*,^,/,花括号,(值),sin,cos,tan,sqrt等。

如何使用?在Angular中,您可以通过依赖注入来访问它。否则,您可以创建一个对象。您可以通过布尔值(此处为“ true”)进行指定,以将结果作为整数获取。

arithmeticExpressionEvaluator.evaluate('10 + 2 * 6')   // shows 22.0
arithmeticExpressionEvaluator.evaluate('10 + 2 * 6', true)   // shows 22 (integer only)

完整的Typescript源代码为:

export class ArithmeticExpressionEvaluator {
    static INVALID_NUMBER = -1234567.654;
    str: string;
    pos = -1;
    ch: string;

    evaluate(expression: string): number {
        return this.evaluateAll(expression, false);
    }

    evaluateAll(expression: string, resultIsInteger: boolean): number {
        this.str = expression;
        pos = -1;
        const outcome = this.parse();
        if (resultIsInteger) {
            return Math.round(outcome);
        }
        return outcome;
    }

    nextChar() {
        this.ch = (++this.pos < this.str.length) ? this.str.charAt(this.pos) : null;
    }

    eat(charToEat: string): boolean {
        while (this.ch === ' ') {
            this.nextChar();
        }
        if (this.ch === charToEat) {
            this.nextChar();
            return true;
        }
        return false;
    }

    parse(): number {
        this.nextChar();
        const x = this.parseExpression();
        if (this.pos < this.str.length) {
            return ArithmeticExpressionEvaluator.INVALID_NUMBER;
        }
        return x;
    }

    parseExpression(): number {
        let x = this.parseTerm();
        for (; ; ) {
            if (this.eat('+')) {  // addition
                x += this.parseTerm();
            } else if (this.eat('-')) {  // subtraction
                x -= this.parseTerm();
            } else {
                return x;
            }
        }
    }

    parseTerm(): number {
        let x = this.parseFactor();
        for (; ;) {
            if (this.eat('*')) {  // multiplication
                x *= this.parseFactor();
            } else if (this.eat('/')) {  // division
                x /= this.parseFactor();
            } else {
                return x;
            }
        }
    }

    parseFactor(): number {
        if (this.eat('+')) {  // unary plus
            return this.parseFactor();
        }
        if (this.eat('-')) { // unary minus
            return -this.parseFactor();
        }
        let x;
        const startPos = this.pos;
        if (this.eat('(')) { // parentheses
            x = this.parseExpression();
            this.eat(')');
        } else if ((this.ch >= '0' && this.ch <= '9') || this.ch === '.') { // numbers
            while ((this.ch >= '0' && this.ch <= '9') || this.ch === '.') {
                this.nextChar();
            }
            x = parseFloat(this.str.substring(startPos, this.pos));
        } else if (this.ch >= 'a' && this.ch <= 'z') { // functions
            while (this.ch >= 'a' && this.ch <= 'z') {
                this.nextChar();
            }
            const func = this.str.substring(startPos, this.pos);
            x = this.parseFactor();
            if (func === 'sqrt') {
                x = Math.sqrt(x);
            } else if (func === 'sin') {
                x = Math.sin(this.degreesToRadians(x));
            } else if (func === 'cos') {
                x = Math.cos(this.degreesToRadians(x));
            } else if (func === 'tan') {
                x = Math.tan(this.degreesToRadians(x));
            } else {
                return ArithmeticExpressionEvaluator.INVALID_NUMBER;
            }
        } else {
            return ArithmeticExpressionEvaluator.INVALID_NUMBER;
        }
        if (this.eat('^')) {  // exponentiation
            x = Math.pow(x, this.parseFactor());
        }
        return x;
    }

    degreesToRadians(degrees: number): number {
        const pi = Math.PI;
        return degrees * (pi / 180);
    }
}

业力测试代码为:

import {ArithmeticExpressionEvaluator} from './arithmetic-expression-evaluator.service';

describe('Arithmetic Expression Evaluation', () => {
    let arithmeticExpressionEvaluator: ArithmeticExpressionEvaluator;
    beforeEach(() => {
        arithmeticExpressionEvaluator = new ArithmeticExpressionEvaluator();
    });
    it('Arithmetic Expression Evaluation - double result', () => {
        expect(arithmeticExpressionEvaluator.evaluate('10 + 2 * 6')).toBe(22.0);
        expect(arithmeticExpressionEvaluator.evaluate('100 * 2 + 12')).toBe(212.0);
        expect(arithmeticExpressionEvaluator.evaluate('100 * 2 + -12')).toBe(188.0);
        expect(arithmeticExpressionEvaluator.evaluate('100 * (2) + -12')).toBe(188.0);
        expect(arithmeticExpressionEvaluator.evaluate('-100 * 2 + 12')).toBe(-188.0);
        expect(arithmeticExpressionEvaluator.evaluate('100 * 2 ^ 12')).toBe(409600.0);
        expect(arithmeticExpressionEvaluator.evaluate('100 * ( 2 + 12 )')).toBe(1400.0);
        expect(arithmeticExpressionEvaluator.evaluate('(100) * (( 2 ) + (12) )')).toBe(1400.0);
        expect(arithmeticExpressionEvaluator.evaluate('100 * ( 2 + 12 ) / 14')).toBe(100.0);
    });
    it('Arithmetic Expression Evaluation - integer result', () => {
        expect(arithmeticExpressionEvaluator.evaluateAll('10 + 2 * 6',  true)).toBe(22);
        expect(arithmeticExpressionEvaluator.evaluateAll('100 * 2 + 12' , true)).toBe(212);
        expect(arithmeticExpressionEvaluator.evaluateAll('100 * 2 + -12', true)).toBe(188);
        expect(arithmeticExpressionEvaluator.evaluateAll('100 * (2) + -12', true)).toBe(188);
        expect(arithmeticExpressionEvaluator.evaluateAll('-100 * 2 + 12' , true)).toBe(-188);
        expect(arithmeticExpressionEvaluator.evaluateAll('100 * 2 ^ 12', true)).toBe(409600);
        expect(arithmeticExpressionEvaluator.evaluateAll('100 * ( 2 + 12 )', true)).toBe(1400);
        expect(arithmeticExpressionEvaluator.evaluateAll('(100) * (( 2 ) + (12) )', true)).toBe(1400);
        expect(arithmeticExpressionEvaluator.evaluateAll('100 * ( 2 + 12 ) / 14', true)).toBe(100);
    });
});

答案 1 :(得分:0)

如果eval()不在桌面上,则需要编写自定义DSL并编写自己的解析器。在某种程度上,您将构建eval()已经为您完成的工作,但是可能是一个受限制的版本,没有所有的javascript功能。

或者找到现有的NPM软件包。