Reputation: 572
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**
Upvotes: 1
Views: 394
Reputation: 572
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.
Upvotes: 1