Scala - Создание базового динамического парсера функций - PullRequest
8 голосов
/ 20 марта 2012

Я новичок в Scala, но мне было интересно, что можно реализовать простой анализатор уравнений на языке.

Скажем, у меня есть несколько функций (очень похожих на функции Excel):

IF(Cond a=b, val_true, val_false)

MID(String, Start_pos, num_chars) - извлечение строки

LEN(String) - длина строки

OR(cond1, cond2, ... condn)

AND(cond1, cond2, ... condn)

Таким образом, идея заключается в том, что я мог бы передать формулу во время выполнения в виде строки от пользователя в качестве аргумента командной строки вместе с любыми другими параметрами, скажем IF(LEN(param1)=4,MID(param1,2,1), MID(param1,0,LEN(param1)))

Идея состоит в том, чтобы оценить функцию, поэтому, если пользователь предоставит приведенную выше формулу и строку «scat», то результатом будет «a». Если задана строка «scala», то результатом будет «scala» ...

Насколько легко это реализовать в Scala? Какой лучший дизайнерский подход? Я знаю, что нет указателей на функции (в C я бы разбил строку формулы в набор точек функции и пошел бы оттуда) ...

Буду признателен за любые советы о том, как подойти к этому в эффективном стиле Scala.

Ура!

1 Ответ

8 голосов
/ 20 марта 2012

Этот вопрос побудил поэкспериментировать с комбинаторными парсерами . Имеются следующие алгебраические типы данных, представляющие подмножество ваших выражений:

import scala.util.parsing.combinator._
object Expr { type VARS = Map[String, Any] }
import Expr._
sealed trait Expr { def eval(v: VARS) : Any } 

case class If(cond: Cond, ifTrue: Expr, ifFalse: Expr) extends Expr {
  def eval(v: VARS) = 
    if (cond.eval(v)) ifTrue.eval(v) else ifFalse.eval(v)
}
case class Cond(left: Expr, right: Expr) extends Expr {
  def eval(v: VARS) = left.eval(v) == right.eval(v)
}
case class Len(ident: String) extends Expr { 
  def eval(v: VARS) = v(ident).toString.size
}
case class Mid(ident: String, start: Expr, count: Expr) extends Expr {
  def eval(v: VARS) = {
    val s = start.eval(v).asInstanceOf[Int]
    val e = s + count.eval(v).asInstanceOf[Int]
    v(ident).asInstanceOf[String].substring(s, e)
  }
}
case class Ident(ident: String) extends Expr   { def eval(v:VARS) = v(ident) }
case class StringLit(value: String) extends Expr { def eval(v:VARS) = value }
case class Number(value: String) extends Expr  { def eval(v:VARS) = value.toInt }

Следующее определение синтаксического анализатора проанализирует данное выражение и возвратит объект Expr:

class Equation extends JavaTokenParsers {
  def IF: Parser[If] = "IF" ~ "(" ~ booleanExpr ~","~ expr ~","~ expr ~ ")" ^^ {
    case "IF" ~ "(" ~ booleanExpr ~ "," ~ ifTrue ~ "," ~ ifFalse ~ ")" =>
      If(booleanExpr, ifTrue, ifFalse)
  }
  def LEN: Parser[Len] = "LEN" ~> "(" ~> ident <~ ")" ^^ (Len(_))
  def MID: Parser[Mid] = "MID" ~ "(" ~ ident ~ "," ~ expr ~ "," ~ expr ~ ")" ^^ {
    case "MID" ~ "(" ~ ident ~ "," ~ expr1 ~ "," ~ expr2 ~ ")" =>
      Mid(ident, expr1, expr2) 
  }
  def expr: Parser[Expr] = (
    stringLiteral ^^ (StringLit(_))
    | wholeNumber  ^^ (Number(_))
    | LEN
    | MID 
    | IF 
    | ident ^^ (Ident(_))
  )
  def booleanExpr: Parser[Cond] = expr ~ "=" ~ expr ^^ {
    case expr1 ~ "=" ~ expr2 => Cond(expr1, expr2)
  }
}

Затем анализ и оценку результатов можно выполнить следующим образом:

val equation = new Equation
val parsed = equation.parseAll(equation.expr,
   """IF(LEN(param1)=4,MID(param1,2,1), MID(param1,0,LEN(param1)))""")
parsed match {
  case equation.Success(expr, _) =>
    println(expr)
    // If(Cond(Len(param1),Number(4)),
    //   Mid(param1,Number(2),Number(1)),
    //   Mid(param1,Number(0),Len(param1)))
    println(expr.eval(Map("param1" -> "scala"))) // prints scala
    println(expr.eval(Map("param1" -> "scat")))  // prints a
  case _ =>
    println("cannot parse")
}

Обратите внимание, что приведенная мною грамматика является лишь минимумом для анализа вашего примера, и в нем абсолютно отсутствует управление ошибками или проверка типов. Что касается процесса, я сначала придумал грамматику без производной ^^ ..., которая бы анализировала ваш пример, затем добавил типы Expr, но без метода eval, затем с производственной ^^ ..., затем я наконец добавил методы eval к Expr черта и подклассы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...