Как я могу создать комбинатор парсера, в котором конечные строки значительны?

Я создаю DSL и использую библиотеку-комбайнер Scala для анализа DSL. DSL следует за простым, похожим на Ruby синтаксисом. Исходный файл может содержать ряд блоков, которые выглядят следующим образом:

create_model do
  at 0,0,0
end

Окончания строк значительны для DSL, так как они эффективно используются в качестве терминаторов операторов.

Я написал парсер Scala, который выглядит так:

class ML3D extends JavaTokenParsers {
  override val whiteSpace = """[ \t]+""".r

  def model: Parser[Any] = commandList
  def commandList: Parser[Any] = rep(commandBlock)
  def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"
  def eol: Parser[Any] = """(\r?\n)+""".r
  def command: Parser[Any] = commandName~opt(commandLabel)
  def commandName: Parser[Any] = ident
  def commandLabel: Parser[Any] = stringLiteral
  def statementList: Parser[Any] = rep(statement)
  def statement: Parser[Any] = functionName~argumentList~eol
  def functionName: Parser[Any] = ident
  def argumentList: Parser[Any] = repsep(argument, ",")
  def argument: Parser[Any] = stringLiteral | constant
  def constant: Parser[Any] = wholeNumber | floatingPointNumber
}

В связи с кончиками строк, я перевернул whiteSpace так, чтобы он обрабатывал пробелы и вкладки как пробельные символы (вместо того, чтобы обрабатывать новые строки как пробелы и, таким образом, игнорировать их).

Это работает, за исключением оператора "end" для commandBlock. Поскольку мой исходный файл содержит завершающую новую строку, синтаксический анализатор жалуется, что он ожидал только end, но получил новую строку после ключевого слова end.

Итак, я изменил определение commandBlock на следующее:

def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"~opt(eol)

(То есть, я добавил дополнительную строку после "end" ).

Но теперь, когда синтаксический анализ исходного файла, я получаю следующую ошибку:

[4.1] failure: `end' expected but `' found

Я думаю, что это связано с тем, что после того, как он сосет его в завершающей новой строке, синтаксический анализатор встречает пустую строку, которая, по ее мнению, является недопустимой, но я не уверен, почему она это делает.

Любые советы о том, как это исправить? Я мог бы пропустить неправильный синтаксический анализатор из Scala библиотеки комбинаторов парсеров, поэтому любые предложения о том, как создать определение языка со значительными новыми символами строки, также приветствуются.

Ответы

Ответ 1

Я получаю ту же ошибку в обоих направлениях, но я думаю, что вы неправильно интерпретируете ее. Говорят, что он ожидает end, но он уже достиг конца ввода.

И причина в том, что end читается как оператор. Теперь я уверен, что есть хороший способ решить эту проблему, но я недостаточно опытен с помощью парсеров Scala. Кажется, что было бы полезно использовать паренеры-маркеры со сканирующей частью, но я не мог понять, как заставить стандартный маркер токена не обрабатывать символы новой строки как пробельные символы.

Итак, вот альтернатива:

import scala.util.parsing.combinator.JavaTokenParsers

class ML3D extends JavaTokenParsers {
  override val whiteSpace = """[ \t]+""".r
  def keywords: Parser[Any] = "do" | "end"
  def identifier: Parser[Any] = not(keywords)~ident

  def model: Parser[Any] = commandList
  def commandList: Parser[Any] = rep(commandBlock)
  def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"~opt(eol)
  def eol: Parser[Any] = """(\r?\n)+""".r
  def command: Parser[Any] = commandName~opt(commandLabel)
  def commandName: Parser[Any] = identifier
  def commandLabel: Parser[Any] = stringLiteral
  def statementList: Parser[Any] = rep(statement)
  def statement: Parser[Any] = functionName~argumentList~eol
  def functionName: Parser[Any] = identifier
  def argumentList: Parser[Any] = repsep(argument, ",")
  def argument: Parser[Any] = stringLiteral | constant
  def constant: Parser[Any] = wholeNumber | floatingPointNumber
}

Ответ 2

Вы можете либо override protected val whiteSpace (Regex), по умолчанию это """\s+""".r или override метод protected def handleWhiteSpace(...), если вам нужно больше контроля, чем это легко достигается с помощью регулярного выражения. Оба эти элемента orginate в RegexParsers, который является базовым классом для JavaTokenParsers.