您可以利用正则表达式分组来做到这一点。您需要一个组合不同可能标记的正则表达式,然后重复应用它。
我喜欢把不同的部分分开;它使维护和扩展更容易:
var tokens = [
"sin",
"cos",
"tan",
"\\(",
"\\)",
"\\+",
"-",
"\\*",
"/",
"\\d+(?:\\.\\d*)?"
];
你将它们粘合成一个大的正则表达式,在每个标记之间使用|:
var rtok = new RegExp( "\\s*(?:(" + tokens.join(")|(") + "))\\s*", "g" );
然后您可以对源字符串使用正则表达式操作进行标记化:
function tokenize( expression ) {
var toks = [], p;
rtok.lastIndex = p = 0; // reset the regex
while (rtok.lastIndex < expression.length) {
var match = rtok.exec(expression);
// Make sure we found a token, and that we found
// one without skipping garbage
if (!match || rtok.lastIndex - match[0].length !== p)
throw "Oops - syntax error";
// Figure out which token we matched by finding the non-null group
for (var i = 1; i < match.length; ++i) {
if (match[i]) {
toks.push({
type: i,
txt: match[i]
});
// remember the new position in the string
p = rtok.lastIndex;
break;
}
}
}
return toks;
}
这只是重复将标记正则表达式与字符串匹配。正则表达式是使用“g”标志创建的,因此正则表达式机器将在我们进行每次匹配后自动跟踪从哪里开始匹配。当它没有看到匹配时,或者当它看到但必须跳过无效的东西来找到它时,我们知道存在语法错误。当它匹配时,它会在令牌数组中记录它匹配的令牌(非空组的索引)和匹配的文本。通过记住匹配的标记索引,它省去了在标记化后必须弄清楚每个标记字符串 含义 的麻烦;你只需要做一个简单的数字比较。
因此调用tokenize( "sin(4+3) * cos(25 / 3)" ) 返回:
[ { type: 1, txt: 'sin' },
{ type: 4, txt: '(' },
{ type: 10, txt: '4' },
{ type: 6, txt: '+' },
{ type: 10, txt: '3' },
{ type: 5, txt: ')' },
{ type: 8, txt: '*' },
{ type: 2, txt: 'cos' },
{ type: 4, txt: '(' },
{ type: 10, txt: '25' },
{ type: 9, txt: '/' },
{ type: 10, txt: '3' },
{ type: 5, txt: ')' } ]
令牌类型 1 是 sin 函数,类型 4 是左括号,类型 10 是数字,等等。
edit——如果你想匹配像“x”和“y”这样的标识符,那么我可能会使用一组不同的标记模式,其中一个只是为了匹配任何标识符。这意味着解析器不会直接从词法分析器中找到“sin”和“cos”等,但这没关系。以下是令牌模式的替代列表:
var tokens = [
"[A-Za-z_][A-Za-z_\d]*",
"\\(",
"\\)",
"\\+",
"-",
"\\*",
"/",
"\\d+(?:\\.\\d*)?"
];
现在任何标识符都将是类型 1 令牌。