Я создаю свой собственный язык.
Цель состоит в том, чтобы «скомпилировать» его в 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
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 лет назад.
preg_match
со смещением, чтобы избежать substr($input, ...)
в конце. Вы должны попытаться использовать ANTLR3 PHP Code Generation Target, поскольку редактор грамматики ANTLR довольно прост в использовании, и у вас будет действительно более читаемый / поддерживаемый код 🙂
Других решений пока нет …