Преобразование регулярного выражения токенизации Java в Javascript

В качестве ответа на мой вопрос маркировка инфиксной строки в Java я получил регулярное выражение (?<=[^\.a-zA-Z\d])|(?=[^\.a-zA-Z\d]. Однако теперь я пишу тот же код на Javascript и не понимаю, как заставить регулярное выражение Javascript делать то же самое.

Например, если у меня есть строка sin(4+3)*2, мне нужно разобрать ее на ["sin","(","4","+","3",")","*","2"].

Какое регулярное выражение я бы использовал для токенизации строки в каждую отдельную часть.

Раньше я просто заменял строку всех возможных токенов и помещал вокруг него пробел, а затем разделял его на этот пробел. Однако этот код быстро стал очень раздутым.

Операторы, на которые мне нужно было бы разделить, были бы стандартными математическими операторами (+,-,*,/,^), а также именами функций (sin,cos,tan,abs,etc...) и запятыми

Как это сделать быстро и эффективно?


person scrblnrd3    schedule 01.03.2014    source источник


Ответы (3)


Для этого можно воспользоваться группировкой регулярных выражений. Вам нужно регулярное выражение, которое объединяет различные возможные токены, и вы применяете его неоднократно.

Мне нравится отделять разные части; это упрощает обслуживание и расширение:

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
comment
Извините, но когда я запускаю это на jsfiddle.net/P5vmY, он продолжает выдавать синтаксическую ошибку. Я неправильно это реализовал? - person scrblnrd3; 02.03.2014
comment
@ scrblnrd3 ну, я не включил ничего, что соответствовало бы x. Это должен быть немного другой набор шаблонов токенов; вместо того, чтобы явно искать грех и т. д., вы просто искали бы идентификаторы и квалифицировали их отдельно в синтаксическом анализаторе. - person Pointy; 02.03.2014
comment
Спасибо. Я использовал небольшую модификацию этого, где я добавил атрибут, который указывал, было ли что-то числом, переменной, оператором, скобкой и т. д. - person scrblnrd3; 02.03.2014
comment
@scrblnrd3 ага, отличная идея! Всегда делайте код, который знает что-то, передает материал другому коду!! - person Pointy; 02.03.2014

Я не знаю, сделает ли это все того, чего вы хотите достичь, но это работает для меня:

'sin(4+3)*2'.match(/\d+\.?\d*|[a-zA-Z]+|\S/g);

// ["sin", "(", "4", "+", "3", ")", "*", "2"]

Вы можете заменить часть [a-zA-Z]+ на sin|cos|tan|etc, чтобы поддерживать только математические функции.

person VisioN    schedule 01.03.2014

Просто предложите несколько возможностей:

[a-zA-Z]+|\d+(?:\.\d+)?|.
person Ry-♦    schedule 01.03.2014