Парсер-комбинаторы безопасных типов в Scala

Я был вдохновлен на использование обратной польской записи в качестве примера комбинаторов синтаксического анализа для курса, который я буду преподавать, однако мое решение заканчивается использованием типа List[Any] хранить числа с плавающей запятой и бинарные операторы соответственно. В конце я рекурсивно деконструирую список и применяю бинарные операторы всякий раз, когда встречаю их. Вся реализация здесь:

import scala.util.parsing.combinator._

trait Ops {
  type Op = (Float,Float) => Float

  def add(x: Float, y: Float) = x + y
  def sub(x: Float, y: Float) = x - y
  def mul(x: Float, y: Float) = x * y
  def div(x: Float, y: Float) = x / y
}

trait PolishParser extends Ops with JavaTokenParsers {

  // Converts a floating point number as a String to Float
  def num: Parser[Float] = floatingPointNumber ^^ (_.toFloat)
  // Parses an operator and converts it to the underlying function it logically maps to
  def operator: Parser[Op] = ("*" | "/" | "+" | "-") ^^ {
    case "+" => add
    case "-" => sub
    case "*" => mul
    case "/" => div
  }

}

trait PolishSemantics extends PolishParser {

  def polish:Parser[Float] = rep(num | operator) ^^ ( xs => reduce(xs).head )

  def pop2(xs:List[Float],f:Op) = (xs take 2 reduce f) :: (xs drop 2)

  def reduce(input:List[Any],stack:List[Float] = Nil):List[Float] = input match {
    case (f:Op) :: xs => reduce(xs,pop2(stack,f))
    case (x:Float) :: xs => reduce(xs,x :: stack)
    case Nil => stack
    case _ => sys.error("Unexpected input")
  }

}

class PolishInterpreter extends PolishParser with PolishSemantics {
  // Parse an expression and return the calculated result as a String
  def interpret(expression: String) = parseAll(polish, expression)
}

object Calculator extends PolishSemantics {

  def main(args: Array[String]) {
    val pi = new PolishInterpreter
    println("input: " + args(0))
    println("result: " + pi.interpret(args(0)))
  }

}

Чего я хочу добиться, так это не использовать шаблон типа в функции сокращения. Одним из решений, конечно, является создание пользовательской иерархии типов в следующем смысле:

trait Elem
case class Floating(f:Float) extends Elem
case class Operator(o: (Float,Float) => Float) extends Elem

Таким образом, я смогу использовать сопоставление с образцом для case-классов через их неприменимые методы, но это также потребовало бы обширного рефакторинга кода. Еще один подход мог бы заключаться в применении семантики непосредственно во время синтаксического анализа, что позволило бы мне использовать только "стек" чисел с плавающей точкой, а затем иметь дело с операторами сразу после их синтаксического анализа. Это, конечно, полностью разрушило бы декларативную манеру работы синтаксических комбинаторов и стало бы преступлением против всего, что хорошо в мире.

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

0 ответов

Другие вопросы по тегам