string — PHP regex parsing — расщепление токенов на моем родном языке. Есть ли способ лучше?

Я создаю свой собственный язык.

Цель состоит в том, чтобы «скомпилировать» его в PHP или Javascript и, в конечном счете, интерпретировать и запустить его на одном языке, чтобы он выглядел как язык «среднего уровня».

Прямо сейчас я сосредотачиваюсь на аспекте его интерпретации в PHP и запуска его.

В настоящее время я использую регулярное выражение для разделения строки и извлечения нескольких токенов.

Это регулярное выражение, которое я имею:

/\:((?:cons@(?:\d+(?:\.\d+)?|(?:"(?:(?:\\\\)+"|[^"]|(?:\r\n|\r|\n))*")))|(?:[a-z]+(?:@[a-z]+)?|\^?[\~\&](?:[a-z]+|\d+|\-1)))/g

Это довольно сложно читать и поддерживать, даже если это работает.

Есть ли лучший способ сделать это?

Вот пример кода для моего языка:

:define:&0:factorial
:param:~0:static
:case
:lower@equal:cons@1
:case:end
:scope
:return:cons@1
:scope:end
:scope
:define:~0:static
:define:~1:static
:require:static
:call:static@sub:^~0:~1 :store:~0
:call:&-1:~0 :store:~1
:call:static@sum:^~0:~1 :store:~0
:return:~0
:scope:end
:define:end

Это определяет рекурсивную функцию для вычисления факториала (не так хорошо написано, что не важно).

Цель состоит в том, чтобы получить то, что после :, в том числе @, :static@sub это целый токен, сохраняя его без :,

Все тоже самое, кроме токена :cons, который может принимать значение после. Значение является числовым значением (integer или же float, называется static или же dynamic на языке, соответственно) или строка, которая должна начинаться и заканчиваться "поддерживая побег, как \", Многострочные строки не поддерживаются.

Переменные с ~0, с помощью ^ прежде чем получит значение к выше :scope,

Функции похожи, используются &0 вместо и &-1 указывает на текущую функцию (нет необходимости в ^&-1 Вот).

Сказал это: есть ли лучший способ получить токены?

Здесь вы можете увидеть это в действии: http://regex101.com/r/nF7oF9/2

0

Решение

[Обновить] Чтобы выпустить сложный шаблон и ремонтопригодность, вы можете разделить его с помощью PCRE_EXTENDED, а также Комментарии:

preg_match('/
# read constant (?)
\:((?:cons@(?:\d+(?:\.\d+)?|
# read a string (?)
(?:"(?:(?:\\\\)+"|[^"]|(?:\r\n|\r|\n))*")))|
# read an identifier (?)
(?:[a-z]+(?:@[a-z]+)?|
# read whatever
\^?[\~\&](?:[a-z]+|\d+|\-1)))
/gx
', $input)

Помните, что все пространство игнорируется, за исключением определенных условий (\n обычно «безопасно»).


Теперь, если вы хотите улучшить свой лексер и парсер, прочитайте это:

Что делает (f) lex [GNU-эквивалент LEX] — это просто позволить вам передать список регулярных выражений и, в конечном итоге, «группу». Вы также можете попробовать ANTLR а также PHP Target Runtime чтобы сделать работу.

Что касается вашей просьбы, я сделал лексер в прошлом, следуя принципу FLEX. Идея состоит в том, чтобы циклически проходить регулярные выражения, как это делает FLEX:

$regexp = [reg1 => STRING, reg2 => ID, reg3 => WS];
$input = ...;
$tokens = [];
while ($input) {
$best = null;
$k = null;
for ($regexp as $re => $kind) {
if (preg_match($re, $input, $match)) {
$best = $match[0];
$k = $kind;
break;
}
}

if (null === $best) {
throw new Exception("could not analyze input, invalid token");
}

$tokens[] = ['kind' => $kind, 'value' => $best];

$input = substr($input, strlen($best)); // move.
}

Поскольку FLEX и Yacc / Bison интегрируются, обычным шаблоном является чтение до следующего токена (то есть они не выполняют цикл, который читает весь ввод перед анализом).

$regexp массив может быть чем угодно, я ожидал, что это будет "regexp" => "kind" ключ / значение, но вы также можете массив, как это:

$regexp = [['reg' => '...', 'kind' => STRING], ...]

Вы также можете включить / отключить регулярное выражение, используя группы (как работает группа FLEX): например, рассмотрите следующий код:

class Foobar {
const FOOBAR = "arg";
function x() {...}
}

Нет необходимости активировать строковое регулярное выражение до тех пор, пока вам не понадобится прочитать выражение (здесь выражение — это то, что следует после «=»). И нет необходимости активировать class идентификатор, когда вы на самом деле в class,

Группа FLEX позволяет читать комментарии, используя первое регулярное выражение, активируя некоторую группу, которая будет игнорировать другое регулярное выражение, пока не будет выполнено некоторое совпадение (например, «*/«).

Обратите внимание, что этот подход является наивным: лексер, подобный FLEX, фактически сгенерирует автомат, который использует другое состояние для представления ваших потребностей (регулярное выражение само является автоматом).

При этом используется алгоритм упакованных индексов или что-то подобное (я использовал наивное «для каждого», потому что я недостаточно разбирался в алгоритме), который эффективно использует память и скорость.

Как я уже сказал, это было то, что я сделал в прошлом — что-то вроде 6/7 лет назад.

  • Это было на Windows.
  • Это было не особенно быстро (ну, это O (N²) из-за двух петель).
  • Я также думаю, что PHP компилировал регулярные выражения каждый раз. Теперь, когда я использую Java, я использую реализацию Pattern, которая один раз компилирует регулярное выражение и позволяет вам использовать его повторно. Я не знаю, что PHP делает то же самое, сначала заглядывая в кеш регулярных выражений, если уже было скомпилированное регулярное выражение.
  • Я использовал preg_match со смещением, чтобы избежать substr($input, ...) в конце.

Вы должны попытаться использовать ANTLR3 PHP Code Generation Target, поскольку редактор грамматики ANTLR довольно прост в использовании, и у вас будет действительно более читаемый / поддерживаемый код 🙂

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]