1

I am writing a parser combinator to parse simple control flow statements and execute some code. The structure of language is roughly this -

val resultId = 200
  val s = s"""(IF $resultId == 100 GOTO NODE-1-->NODE-2) (ELSE IF $resultId > 100 GOTO NODE-1-->NODE-3) (ELSE GOTO NODE-1-->NODE-4)""".stripMargin
  private val result= new ConditionalParserCombinator().run(s)

In above scenario for example I should get GOTO NODE-1-->NODE-3 instead I get false after evaluation of else expression, code of combinator outlined below:

final class ConditionalParserCombinator extends JavaTokenParsers with ParserCombinatorLike {
    def IF = "IF"
    def ELSE = "ELSE"
    def ELSEIF = ELSE ~ IF
    def NULL = "NULL"
    def GOTO = "GOTO"
    def node_id = wholeNumber | floatingPointNumber | stringLiteral
    def NODE = "NODE" ~ "-" ~ node_id ^^ (e ⇒ NodeExpression(e._2))
    def EDGE = NODE ~ "-->" ~ NODE ^^ (e ⇒ EdgeExpression(e._1._1, e._2))
    def lhs = ident | wholeNumber | floatingPointNumber | stringLiteral
    def rhs = ident | wholeNumber | floatingPointNumber | stringLiteral | NULL
    def operator = "==" | "*" | "/" | "||" | "&&" | ">" | "<" | ">=" | "<="
    def block = GOTO ~ EDGE
    def expression_block = lhs ~ operator ~ rhs ~ block ^^ {
      case lhs ~ operator ~ rhs ~ block ⇒ ExpressionBlock(lhs, rhs, operator, block._2)
    }
    def ifExpression = IF ~ expression_block ^^ (e ⇒ e._2.operator match {
      case "==" ⇒ if (e._2.lhs == e._2.rhs) Block(e._2.block) else false
      case ">" ⇒ if (e._2.lhs > e._2.rhs) Block(e._2.block) else false
      case "<" ⇒ if (e._2.lhs < e._2.rhs) Block(e._2.block) else false
      case _ ⇒ false
    })
    def elseIFExpression = ELSEIF ~ expression_block ^^ (e ⇒ e._2.operator match {
      case "==" ⇒ if (e._2.lhs == e._2.rhs) Block(e._2.block) else false
      case ">" ⇒ if (e._2.lhs > e._2.rhs) {
        println("matched elseif")
        Block(e._2.block)
      } else false
      case "<" ⇒ if (e._2.lhs < e._2.rhs) Block(e._2.block) else false
      case _ ⇒ false
    })
    def elseExpression = ELSE ~ block ^^ (e ⇒ Block(e._2._2))
    override def grammar = "(" ~> log(ifExpression)("ifexpression") <~ ")" ~!
      "(" ~> log(elseIFExpression)("elseifexpression") <~ ")" ~!
      "(" ~> log(elseExpression)("elseexpression") <~ ")"
  }

I am printing result.get and I see false as the result.

** Additional details - Block, ExpressionBlock are all case classes useful for a few things that I may do later on**

1 Answer 1

1

I think its cleaner to parse an expression to a type that you can understand (meaning I have custom Product/Case classes defined for it) and then Evaluate it - these are two different things. In hindsight not sure why I got both mixed up. Here's the logic that works -

def IF = "IF"
def ELSE = "ELSE"
def ELSEIF = ELSE ~ IF
def NULL = "NULL"
def GOTO = "GOTO"
def dataType: Parser[DataType] = "[" ~ "Integer" ~ "]" ^^ { e ⇒ DataType("", "Integer") }
def node_id = wholeNumber | floatingPointNumber | stringLiteral
def NODE = "NODE" ~ "-" ~ node_id ^^ (e ⇒ ParseableNode(e._2, DataType({}, "Unit")))
def EDGE = NODE ~ "-->" ~ NODE ^^ (e ⇒ EdgeExpression(e._1._1, e._2))
def lhs = ident | wholeNumber | floatingPointNumber | stringLiteral
def rhs = ident | wholeNumber | floatingPointNumber | stringLiteral | NULL
def operator = "==" | "*" | "/" | "||" | "&&" | ">" | "<" | ">=" | "<="
def block = GOTO ~ EDGE
def expression_block(expType: ConditionalKind) = dataType ~ lhs ~ operator ~ rhs ~ block ^^ {
  case dataType ~ lhs ~ operator ~ rhs ~ block ⇒ ExpressionBlock(ParseableNode(lhs, dataType), ParseableNode(rhs, dataType), operator, block._2, expType)
}
def ifExpression = IF ~ expression_block(ConditionalKind("IF")) ^^ {
  case "IF" ~ expression_block ⇒ ExpressionBlock(expression_block.lhs, expression_block.rhs, expression_block.operator, expression_block.block, expression_block.conditionalKind)
}
def elseIFExpression = ELSEIF ~ expression_block(ConditionalKind("ELSEIF")) ^^ {
  case "ELSE" ~ "IF" ~ expression_block ⇒ ExpressionBlock(expression_block.lhs, expression_block.rhs, expression_block.operator, expression_block.block, expression_block.conditionalKind)
}
def elseExpression = ELSE ~ block ^^ { case "ELSE" ~ block ⇒ Block(block._2) }
override def grammar = log(ifExpression)("ifexpression") ~ log(elseIFExpression)("elseifexpression") ~ log(elseExpression)("elseexpression") ^^ {
  case ifExpression ~ elseIFExpression ~ elseExpression ⇒
    ConditionalExpressions(List(ifExpression, elseIFExpression), elseExpression)
}

The above logic works after being evaluated like this -

object BasicSelectorExpressionEvaluator extends EvaluatorLike {

override def eval(parseable: Parseable) = parseable match {
  case ConditionalExpressions(ifElseIfs, otherwise) ⇒
    val mappedIfElseIfs: immutable.Seq[Block] = ifElseIfs.map { e ⇒
      println(s"e ==>$e")
      e.operator match {
        case "==" ⇒ if (e.lhs == e.rhs) {
          println("mached ==")
          Block(e.block)
        } else Block.Unit
        case "<" ⇒ if (e.lhs.value.toInt < e.rhs.value.toInt) {
          println("matched <")
          Block(e.block)
        } else Block.Unit
        case ">" ⇒ if (e.lhs.value.toInt > e.rhs.value.toInt) {
          println("matched >")
          Block(e.block)
        } else Block.Unit
        case "<=" ⇒ if (e.lhs.value.toInt <= e.rhs.value.toInt) {
          println("mached <=")
          Block(e.block)
        } else Block.Unit
        case ">=" ⇒ if (e.lhs.value.toInt >= e.rhs.value.toInt) {
          println("mached >=")
          Block(e.block)
        } else Block.Unit
      }
    }
    val filteredMappedIFElseIfs = mappedIfElseIfs.filterNot(e ⇒ e.equals(Block.Unit))
    println(s"filteredMappedIFElseIfs == $filteredMappedIFElseIfs")
    if (filteredMappedIFElseIfs.nonEmpty) PResult(filteredMappedIFElseIfs.head.block) else PResult(otherwise.block)
}

}

So the above can parse this grammar -

val s = s""" IF [Integer] $resultId == 100 GOTO NODE-1-->NODE-2 ELSE IF [Integer] $resultId > 100 GOTO NODE-1-->NODE-3 ELSE GOTO NODE-1-->NODE-4""".stripMargin

It could be done better, e.g. grammar seems to violate DRY by embedding data types on every If, but I suppose people can derive things out of it.

Edit - Also note - this toInt thing is a bit ugly, needs to be better designed, I will maybe post an update once I do so. I need to rework all grammar now that it all works - suggestions/improvements welcome, still learning.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.