Для этого можно воспользоваться группировкой регулярных выражений. Вам нужно регулярное выражение, которое объединяет различные возможные токены, и вы применяете его неоднократно.
Мне нравится отделять разные части; это упрощает обслуживание и расширение:
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 — число и т. д.
редактировать, если вы хотите сопоставить такие идентификаторы, как "x" и "y", я бы, вероятно, использовал другой набор шаблонов токенов, один из которых соответствовал бы любым идентификаторам. Это означало бы, что синтаксический анализатор не узнает напрямую о «sin», «cos» и т. д. от лексера, но это нормально. Вот альтернативный список шаблонов токенов:
var tokens = [
"[A-Za-z_][A-Za-z_\d]*",
"\\(",
"\\)",
"\\+",
"-",
"\\*",
"/",
"\\d+(?:\\.\\d*)?"
];
Теперь любой идентификатор будет токеном типа 1.
person
Pointy
schedule
01.03.2014