Парсер-комбинаторы безопасных типов в 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-классов через их неприменимые методы, но это также потребовало бы обширного рефакторинга кода. Еще один подход мог бы заключаться в применении семантики непосредственно во время синтаксического анализа, что позволило бы мне использовать только "стек" чисел с плавающей точкой, а затем иметь дело с операторами сразу после их синтаксического анализа. Это, конечно, полностью разрушило бы декларативную манеру работы синтаксических комбинаторов и стало бы преступлением против всего, что хорошо в мире.
Я, конечно, понимаю, что это придирки, но внутри каждого есть программист, пытающийся выбраться, и я готов последним предложением сделать пример идеальным. Есть идеи?:)