请问有没有好用的四则运算公式验证插件?

有个需求,表单验证输入框内的公式是否满足四则运算公式的正确写法,像是(R(n1)+R(n2))*R(n3),R()格式代表变量。

阅读 1.8k
2 个回答

先用正则过滤一遍, 保留()+-*/字母数字, 再建个new Function() 试试. 大差不差.

JavaScript 新手,试着写了个简单的语法分析(还不完善,抛砖引玉)

功能

  • 支持解析:整数、负数、小数、括号、四则运算、R(...) 式变量
  • 不使用 new Function()eval() 等,没有副作用,较为安全
  • 可以定位第一处出错的地方

结果

✔️ 1 / 0
✔️ (-1) / - 0.0
✔️ R(_1) - - 2
✔️ ((((((((0))))))))
✔️ -(-(-(-R(_)--1)--2)--3)--4
✔️ (R ( n1) +R(n2 ))* -R(n3)
✔️ (R(paper_money)+R(water_fee))*0.01
❌ 【n】 * 1
❌ 1 【乘】 1
❌ 1 *【*】 1
❌ 【alert】()
❌ 1 + 【/】* comment */ 2
❌ R(【)】
❌ R(【1】)
❌ 【r】(n)
❌ R(【(】n))
❌ R(n1 【-】 2)

代码

const tk_spec = {
    r: 'R',
    left: '\\(',
    right: '\\)',
    num: '\\d+(?:\\.\\d+)?',
    var: '[A-Za-z_]\\w*',
    sub: '-',
    div: '/',
    add: '\\+',
    mul: '\\*',
    ws: '\\s+',
    other: '.',
    end: '$',
};

const test = [
    // 合法表达式
    '1 / 0',
    '(-1) / - 0.0',
    'R(_1) - - 2',
    '((((((((0))))))))',
    '-(-(-(-R(_)--1)--2)--3)--4',
    '(R ( n1) +R(n2 ))* -R(n3)',
    '(R(paper_money)+R(water_fee))*0.01',

    // 非法表达式
    'n * 1',
    '1 乘 1',
    '1 ** 1',
    'alert()',
    '1 + /* comment */ 2',
    'R()',
    'R(1)',
    'r(n)',
    'R((n))',
    'R(n1 - 2)',
];

const tk_regex = new RegExp(Object.entries(tk_spec).map(([k, v]) => `(?<${k}>${v})`).join('|'), 'g');

/* 语法:
 * expr -> term { ('+' | '-') term }
 * term -> factor { ('*' | '/') factor }
 * factor -> ['-'] ( '(' expr ')' | 'R' '(' var ')' | num )
 */

class Parser {
    #tokens;
    #next;

    constructor(expr) {
        this.#tokens = (function *() {
            for (const {index, groups} of expr.matchAll(tk_regex)) {
                const [type, value] = Object.entries(groups).find(([k, v]) => v !== undefined);
                if (type !== 'ws')
                    yield {index, type, value};
            }
        })();
        this.#next = this.#tokens.next().value;
    }

    #match(...expects) {
        for (const expect of expects) {
            if (this.#next.type === expect)
                this.#next = this.#tokens.next().value;
            else if (typeof expect === 'function')
                expect.call(this);
            else
                throw new Error(expect);
        }
    }

    #expr() {
        this.#term();
        while (['add', 'sub'].includes(this.#next.type))
            this.#match(this.#next.type, this.#term);
    }

    #term() {
        this.#factor();
        while (['mul', 'div'].includes(this.#next.type))
            this.#match(this.#next.type, this.#factor);
    }

    #factor() {
        if (this.#next.type === 'sub')
            this.#match('sub');

        switch (this.#next.type) {
            case 'left':
                this.#match('left', this.#expr, 'right');
                break;
            case 'r':
                this.#match('r', 'left', 'var', 'right');
                break;
            default:
                this.#match('num');
        }
    }

    test() {
        try {
            this.#match(this.#expr, 'end');
            return true;
        }
        catch (err) {
            return this.#next;
        }
    }
}

test.forEach(expr => {
    const ret = new Parser(expr).test();
    if (ret === true)
        console.log(`✔️ ${expr}`);
    else
        console.log(`❌ ${expr.slice(0, ret.index)}【${ret.value}】${expr.slice(ret.index + ret.value.length)}`);
})
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏