Почему синтаксический анализатор expr может анализировать только первый элемент?

у меня Празер

package app
import scala.util.parsing.combinator._


class MyParser extends JavaTokenParsers {
  import MyParser._
  
  def expr =
    plus | sub | multi | divide | num
  
  def num = floatingPointNumber ^^ (x => Value(x.toDouble).e)

  def plus = num ~ rep("+" ~> num) ^^ {
    case num ~ nums => nums.foldLeft(num.e) {
      (x, y) => Operation("+", x, y)
    }
  }

  def sub = num ~ rep("-" ~> num) ^^ {
    case num ~ nums => nums.foldLeft(num.e){
      (x, y) => Operation("-", x, y)
    }
  }

  def multi = num ~ rep("*" ~> num) ^^ {
    case num ~ nums => nums.foldLeft(num.e){
      (x, y) => Operation("*", x, y)
    }
  }

  def divide = num ~ rep("/" ~> num) ^^ {
    case num ~ nums => nums.foldLeft(num.e){
      (x, y) => Operation("/", x, y)
    }
  }
}

object MyParser {
  sealed trait Expr {
    def e = this.asInstanceOf[Expr]
    def compute: Double = this match {
      case Value(x) => x
      case Operation(op, left, right) => (op : @unchecked) match {
        case "+" => left.compute + right.compute
        case "-" => left.compute - right.compute
        case "*" => left.compute * right.compute
        case "/" => left.compute / right.compute
      }
    }
  }

  case class Value(x: Double) extends Expr
  case class Operation(op: String, left: Expr, right: Expr) extends Expr
}

и я использую его для разбора выражения something

package app

object Runner extends App {
  val p = new MyParser
  println(p.parseAll(p.expr, "1 * 11"))
}

он печатает

[1.3] failure: end of input expected

1 * 11
  ^

но если я разберу выражение 1 + 11, он преуспеет в его разборе.

[1.7] parsed: Operation(+,Value(1.0),Value(11.0))

и я могу разобрать что-то через комбинатор plus, multi, divide, num, sub, но комбинатор expr может разобрать только первый элемент комбинатора or. так почему он может анализировать только первый элемент парсера expr? И как я могу изменить определение парсеров, чтобы синтаксический анализ был успешным?


person L l    schedule 04.02.2021    source источник


Ответы (2)


Проблема в том, что вы используете rep, который соответствует ноль или более раз.

def rep[T](p: => Parser[T]): Parser[List[T]] = rep1(p) | success(List())

вместо этого вам нужно использовать rep1, для чего потребуется хотя бы одно совпадение.

Если вы замените все rep на rep1, ваш код будет работать.

Ознакомьтесь с изменениями на scastie.

person Ivan Stanislavciuc    schedule 04.02.2021

Проведите эксперимент:

println(p.parseAll(p.expr, "1 + 11"))
println(p.parseAll(p.expr, "1 - 11"))
println(p.parseAll(p.expr, "1 * 11"))
println(p.parseAll(p.expr, "1 / 11"))

Что случится?

[1.7] parsed: Operation(+,Value(1.0),Value(11.0))
[1.3] failure: end of input expected
1 - 11
  ^
[1.3] failure: end of input expected
1 * 11
  ^
[1.3] failure: end of input expected
1 / 11

+ потребляется, но все остальное терпит неудачу. Давайте изменим def expr определение

  def expr =
    multi | plus | sub | divide | num
[1.3] failure: end of input expected
1 + 11
  ^
[1.3] failure: end of input expected
1 - 11
  ^
[1.7] parsed: Operation(*,Value(1.0),Value(11.0))
[1.3] failure: end of input expected
1 / 11
  ^

Переместив multi в начало, * кейс прошел, а + не прошел.

  def expr =
    num | multi | plus | sub | divide
[1.3] failure: end of input expected
1 + 11
  ^
[1.3] failure: end of input expected
1 - 11
  ^
[1.3] failure: end of input expected
1 * 11
  ^
[1.3] failure: end of input expected
1 / 11

С num в качестве первого случая все терпит неудачу. Теперь очевидно, что этот код

num | multi | plus | sub | divide

НЕ совпадет, если совпадет любая из его частей, но только если совпадет первая.

Что говорят об этом документы?

   /** A parser combinator for alternative composition.
     *
     *  `p | q` succeeds if `p` succeeds or `q` succeeds.
     *   Note that `q` is only tried if `p`s failure is non-fatal (i.e., back-tracking is allowed).
     *
     * @param q a parser that will be executed if `p` (this parser) fails (and allows back-tracking)
     * @return a `Parser` that returns the result of the first parser to succeed (out of `p` and `q`)
     *         The resulting parser succeeds if (and only if)
     *         - `p` succeeds, ''or''
     *         - if `p` fails allowing back-tracking and `q` succeeds.
     */
  def | [U >: T](q: => Parser[U]): Parser[U] = append(q).named("|")

Важное примечание: должно быть разрешено обратное отслеживание. Если это не так, то несоответствие первому синтаксическому анализатору приведет к сбою альтернативы без попытки второго синтаксического анализатора вообще.

Как сделать так, чтобы ваш парсер работал с возвратом? Что ж, вам придется использовать PackratParsers, так как это единственный синтаксический анализатор в библиотеке, поддерживающий поиск с возвратом. Или перепишите свой код, чтобы он вообще не полагался на поиск с возвратом.

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

person Mateusz Kubuszok    schedule 04.02.2021