Scala парсер комбинаторы: разбирать либо целое число, либо число с плавающей запятой

РЕДАКТИРОВАТЬ: решено, см. "FIX" ниже

Я пытаюсь настроить комбинатор парсера Scala для анализа числа с плавающей запятой или целого числа в зависимости от сложности числа. Вот что у меня сейчас:

import scala.util.parsing.combinator.JavaTokenParsers

trait NumberLiteral
case class IntegerLiteral(i:Int) extends NumberLiteral
case class FloatLiteral(f:Float) extends NumberLiteral

class Parser extends JavaTokenParsers {

  def integer:Parser[IntegerLiteral] = wholeNumber ^^ {i => new IntegerLiteral(i.toInt)}
  def float:Parser[FloatLiteral] = floatingPointNumber ^^ {f => new FloatLiteral(f.toFloat)}
  //FIX: def float:Parser[FloatLiteral] = """[+-]?[0-9]*((\.[0-9]+([eE][+-]?[0-9]+)?[fF]?)|([fF])|([eE][+-]?[0-9]+))\b""".r ^^ {f => new FloatLiteral(f.toFloat)} 

  def number:Parser[NumberLiteral] = integer | float;
  //FIX: def number:Parser[NumberLiteral] = float | integer;

}

Я настроил scalatest для тестирования как целочисленного, так и плавающего парсеров, и они оба работают. Вот как выглядит мой класс тестирования:

import org.scalatest._

class ParserSpec extends FlatSpec with Matchers {

  val parser = new Parser()

  "Parser" should "parse IntegerLiteral" in {
    parser.parseAll(parser.integer, "0").get should equal (new IntegerLiteral(0))
    parser.parseAll(parser.integer, "4").get should equal (new IntegerLiteral(4))
    parser.parseAll(parser.integer, "4448338").get should equal (new IntegerLiteral(4448338))
    parser.parseAll(parser.integer, "-33").get should equal (new IntegerLiteral(-33))
    parser.parseAll(parser.integer, "-10101010").get should equal (new IntegerLiteral(-10101010))
    parser.parseAll(parser.integer, "004").get should equal (new IntegerLiteral(4))
  }
  it should "parse FloatLiteral" in {
    parser.parseAll(parser.float, "1.0").get should equal (new FloatLiteral(1.0f))
    parser.parseAll(parser.float, "0").get should equal (new FloatLiteral(0))
    parser.parseAll(parser.float, "32.3").get should equal (new FloatLiteral(32.3f))
    parser.parseAll(parser.float, "3.4e3").get should equal (new FloatLiteral(3400))
    parser.parseAll(parser.float, "-10").get should equal (new FloatLiteral(-10))
    parser.parseAll(parser.float, "-4e-4").get should equal (new FloatLiteral(-0.0004f))
    parser.parseAll(parser.float, "003.4").get should equal (new FloatLiteral(3.4f))
    parser.parseAll(parser.float, "4f").get should equal (new FloatLiteral(4))
  }
  it should "parse NumberLiteral" in {
    parser.parseAll(parser.number, "32").get should equal (new IntegerLiteral(32))
    parser.parseAll(parser.number, "32.3").get should equal (new FloatLiteral(32.3f))
    parser.parseAll(parser.number, "32f").get should equal (new FloatLiteral(32))
    parser.parseAll(parser.number, "0.33").get should equal (new FloatLiteral(0.33f))
    parser.parseAll(parser.number, "32e2").get should equal (new IntegerLiteral(3200))
    parser.parseAll(parser.number, "0").get should equal (new IntegerLiteral(32))
    parser.parseAll(parser.number, "32.3e1").get should equal (new IntegerLiteral(323))
  }

}

Оба IntegerLiteral а также FloatLiteral тесты работают отлично. Как видите, я хочу разобрать номер как IntegerLiteral или FloatLiteral в зависимости от того, может ли он быть проанализирован как int или float. Первая строка в NumberLiteral тест работает, однако я получаю следующую ошибку во второй строке: java.lang.RuntimeException: no result when parsing failed, Я не могу понять, почему анализатор выдает эту ошибку, так как анализатор с плавающей точкой может анализировать 32.3, Я делаю что-то не так в парсере чисел с integer | float?

1 ответ

Решение

Просто поменяйте их местами:

...
def number:Parser[NumberLiteral] = float | integer //float first
...

Пример:

scala> parser.parseAll(parser.number, "32.3").get
res0: NumberLiteral = FloatLiteral(32.3)

Причина, по которой это не сработало в первую очередь, заключается в том, что анализатор проанализировал "32" из "32.3" как целочисленный, а неразобранный хвост ".3" вызвал ошибку. Вы можете легко увидеть это с parse:

...
def number:Parser[NumberLiteral] = integer | float //integer first
...

scala> parser.parse(parser.number, "32.3")
res3: parser.ParseResult[NumberLiteral] = [1.3] parsed: IntegerLiteral(32)

//And here is how to get unparsed tail (".3"): 

scala> val pointer = parser.parse(parser.number, "32.3").next
pointer: parser.Input = scala.util.parsing.input.CharSequenceReader@34a2d29d

scala> pointer.source.toString.drop(pointer.pos.column - 1)
res15: String = .3
Другие вопросы по тегам