2011-12-19 5 views
1

Я хотел бы создать парсер для сложного выражения с порядком операций. У меня есть пример, но он работает очень медленно и исключает исключение OutOfMemoryError. Как я могу улучшить его?Scala. Сложный анализатор выражений. OutOfMemoryError

def expr: Parser[Expression] = term5 
def term5: Parser[Expression] = 
    (term4 ~ "OR" ~ term4) ^^ { case lhs~o~rhs => BinaryOp("OR", lhs, rhs) } | 
    term4 
def term4: Parser[Expression] = 
    (term3 ~ "AND" ~ term3) ^^ { case lhs~a~rhs => BinaryOp("AND", lhs, rhs) } | 
    term3 
def term3: Parser[Expression] = 
    (term2 ~ "<>" ~ term2) ^^ { case lhs~ne~rhs => BinaryOp("NE", lhs, rhs) } | 
    (term2 ~ "=" ~ term2) ^^ { case lhs~eq~rhs => BinaryOp("EQ", lhs, rhs) } | 
    (term2 ~ "NE" ~ term2) ^^ { case lhs~ne~rhs => BinaryOp("NE", lhs, rhs) } | 
    (term2 ~ "EQ" ~ term2) ^^ { case lhs~eq~rhs => BinaryOp("EQ", lhs, rhs) } | 
    term2 
def term2: Parser[Expression] = 
    (term1 ~ "<" ~ term1) ^^ { case lhs~lt~rhs => BinaryOp("LT", lhs, rhs) } | 
    (term1 ~ ">" ~ term1) ^^ { case lhs~gt~rhs => BinaryOp("GT", lhs, rhs) } | 
    (term1 ~ "<=" ~ term1) ^^ { case lhs~le~rhs => BinaryOp("LE", lhs, rhs) } | 
    (term1 ~ ">=" ~ term1) ^^ { case lhs~ge~rhs => BinaryOp("GE", lhs, rhs) } | 
    (term1 ~ "LT" ~ term1) ^^ { case lhs~lt~rhs => BinaryOp("LT", lhs, rhs) } | 
    (term1 ~ "GT" ~ term1) ^^ { case lhs~gt~rhs => BinaryOp("GT", lhs, rhs) } | 
    (term1 ~ "LE" ~ term1) ^^ { case lhs~le~rhs => BinaryOp("LE", lhs, rhs) } | 
    (term1 ~ "GE" ~ term1) ^^ { case lhs~ge~rhs => BinaryOp("GE", lhs, rhs) } | 
    term1 
def term1: Parser[Expression] = 
    (term ~ "+" ~ term) ^^ { case lhs~plus~rhs => BinaryOp("+", lhs, rhs) } | 
    (term ~ "-" ~ term) ^^ { case lhs~minus~rhs => BinaryOp("-", lhs, rhs) } | 
    (term ~ ":" ~ term) ^^ { case lhs~concat~rhs => BinaryOp(":", lhs, rhs) } | 
    term 
def term: Parser[Expression] = 
    (factor ~ "*" ~ factor) ^^ { case lhs~times~rhs => BinaryOp("*", lhs, rhs) } | 
    (factor ~ "/" ~ factor) ^^ { case lhs~div~rhs => BinaryOp("/", lhs, rhs) } | 
    (factor ~ "MOD" ~ factor) ^^ { case lhs~div~rhs => BinaryOp("MOD", lhs, rhs) } | 
    factor 
def factor: Parser[Expression] = 
    "(" ~> expr <~ ")" | 
    ("+" | "-") ~ factor ^^ { case op~rhs => UnaryOp(op, rhs) } | 
    function | 
    numericLit ^^ { case x => Number(x/*.toFloat*/) } | 
    stringLit ^^ { case s => Literal(s) } | 
    ident ^^ { case id => Variable(id) } 
+1

Что такое тестовый пример? А какой парсер вы используете? –

+0

Я использую StdTokenParsers. –

+0

Тестовый пример: Println (TestParser.parse ( \t "\ нА = \" MY ТЕСТ СТРОКА \ "" + \t "\ пВ = А: (1 + 2)" + \t "\ пС = 3" + \t "\ Nn = -4" + \t "\ п = 3 + 1/(5 - 4)" + \t "\ нФ = -5 + 1" + \t «\ NG = -3 + 1/(-5 - -3) "+ \t" \ nH = 4.99 "+ \t" \ nI = -4.99 ")) –

ответ

4

В основном, он медленный и потребляет слишком много памяти, потому что ваша грамматика невероятно неэффективна.

Рассмотрим вторую строку: B = A:(1+2). Он попытается проанализировать эту строку следующим образом:

  1. term4 OR term4 а затем term4.
  2. term3 AND term3 а затем срок3.
  3. term2 <> term2, затем =, затем NE затем EQ, а затем term2.
  4. срок1 8 различные операторы term1, then term1.
  5. термин + срок, срок - срок, срок : срок, а затем срок.
  6. фактор * фактор, фактор / коэффициент, фактор MOD коэффициент, а затем коэффициент.
  7. выражение в скобках, одинарный коэффициент, функция, числовой литерал, строковый литерал, идентификатор.

Первая попытка, как это:

ident * factor + term < term1 <> term2 AND term3 OR term4 

я пропущу скобка, унарный, функция, числовые и строковые литералы, потому что они не соответствуют A - хотя function, вероятно, делает, но это определение недоступно. Теперь, так как : не соответствует *, он будет пытаться дальше:

ident/factor + term < term1 <> term2 AND term3 OR term4 
ident MOD factor + term < term1 <> term2 AND term3 OR term4 
ident + term < term1 <> term2 AND term3 OR term4 

Теперь он идет к следующему term1:

ident * factor - term < term1 <> term2 AND term3 OR term4 
ident/factor - term < term1 <> term2 AND term3 OR term4 
ident MOD factor - term < term1 <> term2 AND term3 OR term4 
ident - term < term1 <> term2 AND term3 OR term4 

И дальше:

ident * factor : term < term1 <> term2 AND term3 OR term4 
ident/factor : term < term1 <> term2 AND term3 OR term4 
ident MOD factor : term < term1 <> term2 AND term3 OR term4 
ident : term < term1 <> term2 AND term3 OR term4 

Aha! Наконец-то мы получили матч на term1! Но ( не соответствует <, поэтому он должен попробовать следующий term2:

ident * factor + term > term1 <> term2 AND term3 OR term4 

и т.д ...

Все потому, что первый член в каждой строке для каждого члена всегда будет соответствовать! Чтобы соответствовать простому номеру, он должен разобрать factor 2 * 2 * 5 * 9 * 4 * 4 = 2880 раз!

Но это не половина истории! Видите ли, потому что termX повторяется дважды, он будет повторять все это на и сторон.Например, первый матч за A:(1+2) это:

ident : term < term1 <> term2 AND term3 OR term4 
where ident = A 
and term = (1+2) 

Что неправильно, поэтому он будет пытаться > вместо <, а затем <= и т.д., и т.д.

Я ставлю версию протоколирования этого анализатора ниже. Попробуйте запустить его и увидеть все, что он пытается проанализировать.

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

sbaz install scala-devel-docs 

Тогда загляните в doc/scala-devel-docs/examples/parsing каталоге дистрибутива Scala, и вы найдете несколько примеров.

Вот версия вашего синтаксического анализатора (без function), который регистрирует все, что пытается:

sealed trait Expression 
case class Variable(id: String) extends Expression 
case class Literal(s: String) extends Expression 
case class Number(x: String) extends Expression 
case class UnaryOp(op: String, rhs: Expression) extends Expression 
case class BinaryOp(op: String, lhs: Expression, rhs: Expression) extends Expression 

object TestParser extends scala.util.parsing.combinator.syntactical.StdTokenParsers { 
    import scala.util.parsing.combinator.lexical.StdLexical 
    type Tokens = StdLexical 
    val lexical = new StdLexical 
    lexical.delimiters ++= List("(", ")", "+", "-", "*", "/", "=", "OR", "AND", "NE", "EQ", "LT", "GT", "LE", "GE", ":", "MOD") 
    def stmts: Parser[Any] = log(expr.*)("stmts") 
    def stmt: Parser[Expression] = log(expr <~ "\n")("stmt") 
    def expr: Parser[Expression] = log(term5)("expr") 
    def term5: Parser[Expression] = (
     log((term4 ~ "OR" ~ term4) ^^ { case lhs~o~rhs => BinaryOp("OR", lhs, rhs) })("term5 OR") 
     | log(term4)("term5 term4") 
    ) 
    def term4: Parser[Expression] = (
     log((term3 ~ "AND" ~ term3) ^^ { case lhs~a~rhs => BinaryOp("AND", lhs, rhs) })("term4 AND") 
     | log(term3)("term4 term3") 
    ) 
    def term3: Parser[Expression] = (
     log((term2 ~ "<>" ~ term2) ^^ { case lhs~ne~rhs => BinaryOp("NE", lhs, rhs) })("term3 <>") 
     | log((term2 ~ "=" ~ term2) ^^ { case lhs~eq~rhs => BinaryOp("EQ", lhs, rhs) })("term3 =") 
     | log((term2 ~ "NE" ~ term2) ^^ { case lhs~ne~rhs => BinaryOp("NE", lhs, rhs) })("term3 NE") 
     | log((term2 ~ "EQ" ~ term2) ^^ { case lhs~eq~rhs => BinaryOp("EQ", lhs, rhs) })("term3 EQ") 
     | log(term2)("term3 term2") 
    ) 
    def term2: Parser[Expression] = (
     log((term1 ~ "<" ~ term1) ^^ { case lhs~lt~rhs => BinaryOp("LT", lhs, rhs) })("term2 <") 
     | log((term1 ~ ">" ~ term1) ^^ { case lhs~gt~rhs => BinaryOp("GT", lhs, rhs) })("term2 >") 
     | log((term1 ~ "<=" ~ term1) ^^ { case lhs~le~rhs => BinaryOp("LE", lhs, rhs) })("term2 <=") 
     | log((term1 ~ ">=" ~ term1) ^^ { case lhs~ge~rhs => BinaryOp("GE", lhs, rhs) })("term2 >=") 
     | log((term1 ~ "LT" ~ term1) ^^ { case lhs~lt~rhs => BinaryOp("LT", lhs, rhs) })("term2 LT") 
     | log((term1 ~ "GT" ~ term1) ^^ { case lhs~gt~rhs => BinaryOp("GT", lhs, rhs) })("term2 GT") 
     | log((term1 ~ "LE" ~ term1) ^^ { case lhs~le~rhs => BinaryOp("LE", lhs, rhs) })("term2 LE") 
     | log((term1 ~ "GE" ~ term1) ^^ { case lhs~ge~rhs => BinaryOp("GE", lhs, rhs) })("term2 GE") 
     | log(term1)("term2 term1") 
    ) 
    def term1: Parser[Expression] = (
     log((term ~ "+" ~ term) ^^ { case lhs~plus~rhs => BinaryOp("+", lhs, rhs) })("term1 +") 
     | log((term ~ "-" ~ term) ^^ { case lhs~minus~rhs => BinaryOp("-", lhs, rhs) })("term1 -") 
     | log((term ~ ":" ~ term) ^^ { case lhs~concat~rhs => BinaryOp(":", lhs, rhs) })("term1 :") 
     | log(term)("term1 term") 
    ) 
    def term: Parser[Expression] = (
     log((factor ~ "*" ~ factor) ^^ { case lhs~times~rhs => BinaryOp("*", lhs, rhs) })("term *") 
     | log((factor ~ "/" ~ factor) ^^ { case lhs~div~rhs => BinaryOp("/", lhs, rhs) })("term /") 
     | log((factor ~ "MOD" ~ factor) ^^ { case lhs~div~rhs => BinaryOp("MOD", lhs, rhs) })("term MOD") 
     | log(factor)("term factor") 
    ) 
    def factor: Parser[Expression] = (
     log("(" ~> expr <~ ")")("factor (expr)") 
     | log(("+" | "-") ~ factor ^^ { case op~rhs => UnaryOp(op, rhs) })("factor +-") 
     //| function | 
     | log(numericLit ^^ { case x => Number(x/*.toFloat*/) })("factor numericLit") 
     | log(stringLit ^^ { case s => Literal(s) })("factor stringLit") 
     | log(ident ^^ { case id => Variable(id) })("factor ident") 
    ) 
    def parse(s: String) = stmts(new lexical.Scanner(s)) 
} 
0

Моего первое улучшение было похоже, что:

def term3: Parser[Expression] = 
log((term2 ~ ("<>" | "=" | "NE" | "EQ") ~ term2) ^^ { case lhs~op~rhs => BinaryOp(op, lhs, rhs) })("term3 <>,=,NE,EQ") | 
log(term2)("term3 term2") 

Он работает без OutOfMemoryError но замедляться. После просмотра док/SCALA-разви-документы/примеры/разбор/лямбда/TestParser.scala Я получил этот источник:

def expr: Parser[Expression] = term5 
def term5: Parser[Expression] = 
    log(chainl1(term4, term5, "OR" ^^ {o => (a: Expression, b: Expression) => BinaryOp(o, a, b)}))("term5 OR") 
def term4: Parser[Expression] = 
    log(chainl1(term3, term4, "AND" ^^ {o => (a: Expression, b: Expression) => BinaryOp(o, a, b)}))("term4 AND") 
def term3: Parser[Expression] = 
    log(chainl1(term2, term3, ("<>" | "=" | "NE" | "EQ") ^^ {o => (a: Expression, b: Expression) => BinaryOp(o, a, b)}))("term3 <>,=,NE,EQ") 
def term2: Parser[Expression] = 
    log(chainl1(term1, term2, ("<" | ">" | "<=" | ">=" | "LT" | "GT" | "LE" | "GE") ^^ {o => (a: Expression, b: Expression) => BinaryOp(o, a, b)}))("term2 <,>,...") 
def term1: Parser[Expression] = 
    log(chainl1(term, term1, ("+" | "-" | ":") ^^ {o => (a: Expression, b: Expression) => BinaryOp(o, a, b)}))("term1 +,-,:") 
def term: Parser[Expression] = 
    log(chainl1(factor, term, ("*" | "/" | "MOD") ^^ {o => (a: Expression, b: Expression) => BinaryOp(o, a, b)}))("term *,/,MOD") 
def factor: Parser[Expression] = 
    log("(" ~> expr <~ ")")("factor()") | 
    log(("+" | "-") ~ factor ^^ { case op~rhs => UnaryOp(op, rhs) })("factor unary") | 
    log(function)("factor function") | 
    log(numericLit ^^ { case x => Number(x/*.toFloat*/) })("factor numLit") | 
    log(stringLit ^^ { case s => Literal(s) })("factor strLit") | 
    log(ident ^^ { case id => Variable(id) })("factor ident") 

Он работает быстро. Извините, но я не понимаю, как функция chainl1 улучшает мой источник. Я не понимаю, как это работает.

Смежные вопросы