Использование Alex в Haskell для создания лексера, анализирующего Dice Rolls

Я делаю парсер для DSL на Haskell, используя Alex + Happy. Мой DSL использует броски костей как часть возможных выражений.

Иногда у меня есть выражение, которое я хочу проанализировать, которое выглядит так:

[some code...]  3D6  [... rest of the code]

Что должно примерно переводиться как:

TokenInt {... value = 3}, TokenD, TokenInt {... value = 6}

Мой DSL также использует переменные (в основном строки), поэтому у меня есть специальный токен, который обрабатывает имена переменных. Итак, с этими токенами:

"D"                                 { \pos str -> TokenD pos }
$alpha [$alpha $digit \_ \']*       { \pos str -> TokenName pos str}
$digit+                             { \pos str -> TokenInt pos (read str) }

Результат, который я получаю при использовании моего синтаксического анализа сейчас:

TokenInt {... value = 3}, TokenName { ... , name = "D6"}

Это означает, что мой лексер считывает целое число и переменную с именем D6.

Я пробовал много вещей, например, я изменил токен D на:

$digit "D" $digit                   { \pos str -> TokenD pos }

Но это просто потребляет цифры :(

  • Могу ли я разобрать бросок костей с числами?
  • Или хотя бы парсить TokenInt-TokenD-TokenInt?

PS: я использую PosN в качестве оболочки, не уверен, что это актуально.


person Zeb    schedule 13.07.2020    source источник
comment
Хороший вопрос, хотя ИМХО, вероятно, лучший способ сделать это в этом случае - просто накатить лексер вручную. Для таких маленьких языков инструменты могут сделать работу сложнее, чем просто делать что-то вручную. Однако это может легко возникнуть и на более широком языке, так что это хороший вопрос, который можно задать в любом случае.   -  person Cubic    schedule 13.07.2020
comment
На самом деле вы могли бы продолжать лексировать их по отдельности и анализировать Dn как постфиксный оператор, который повторяет n-сторонний бросок кубика по своему операнду; или даже разобрать D как оператор infix со счетчиком кубиков слева и значением кубика справа. Тогда бросок не ограничивается фиксированными кубиками, и вы можете разрешить динамические выражения, такие как x D y (или стилизованные, например, как x D(y)) ≅ replicateM x (rollD y)   -  person Jon Purdy    schedule 13.07.2020


Ответы (2)


Я бы хотел расширить тип TokenD до TokenD Int Int, поэтому, используя оболочку basic для удобства, я бы сделал

$digit+ D $digit+ { dice }
...
dice :: String -> Token
dice s = TokenD (read $ head ls) (read $ last ls)
  where ls = split 'D' s

split можно найти здесь.

Это дополнительный шаг, который обычно делается во время синтаксического анализа, но здесь он не сильно помешает.

Также я не могу заставить Алекса разобрать $alpha на TokenD вместо TokenName. Если бы у нас было Di вместо D, это не было бы проблемой. Из документов Алекса:

Когда входной поток соответствует более чем одному правилу, выигрывает правило, которое соответствует самому длинному префиксу входного потока. Если есть еще несколько правил, которые соответствуют равному количеству символов, то выигрывает правило, которое появляется в файле раньше.

Но тогда ваш код должен работать. Я не знаю, проблема ли это с Алексом.

person Mihalis    schedule 13.07.2020
comment
Я хочу сохранить оболочку PosN, так как я могу использовать ее, чтобы отметить позицию ошибки синтаксического анализа. Однако мне нравится идея, я думаю, что я могу использовать это. Спасибо! - person Zeb; 14.07.2020
comment
Да, вы все еще можете использовать posn. Идея остается неизменной с любой оберткой. - person Mihalis; 14.07.2020

Я решил, что смогу выжить с переменными, начинающимися с строчных букв (например, с переменными Haskell), поэтому я изменил свой лексер, чтобы анализировать переменные только в том случае, если они начинаются со строчной буквы. Это также решило некоторые возможные проблемы с некоторыми другими зарезервированными словами.

Мне все еще любопытно узнать, были ли другие решения, но проблема сама по себе была решена.

Спасибо вам всем!

person Zeb    schedule 14.07.2020