有个需求,表单验证输入框内的公式是否满足四则运算公式的正确写法,像是(R(n1)+R(n2))*R(n3)
,R()格式代表变量。
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)}`);
})
6 回答5.4k 阅读✓ 已解决
9 回答9.6k 阅读
5 回答3.8k 阅读✓ 已解决
3 回答10.6k 阅读✓ 已解决
4 回答8.1k 阅读✓ 已解决
7 回答10.2k 阅读
4 回答7.5k 阅读
先用正则过滤一遍, 保留
()+-*/字母数字
, 再建个new Function()
试试. 大差不差.