Как я могу создать комбинатор синтаксического анализатора, в котором окончания строки имеют большое значение? - PullRequest
16 голосов
/ 04 марта 2010

Я создаю 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, поэтому любые предложения о том, как создать определение языка со значительными символами новой строки, также приветствуются.

Ответы [ 2 ]

9 голосов
/ 05 марта 2010

Я получаю одну и ту же ошибку в обоих случаях, но я думаю, что вы ее неправильно интерпретируете. Он говорит, что ожидает 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
}
0 голосов
/ 05 марта 2010

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

...