Parsing fixing bug with parsing nested OP_ELSES into binary tree, removing first OP_ELSE in the tree

This commit is contained in:
Chris Stewart 2016-01-29 10:00:53 -06:00
parent 171053bf94
commit 4bd236cf9f
3 changed files with 84 additions and 65 deletions

View File

@ -32,62 +32,18 @@ trait ControlOperationsInterpreter {
* @param script
* @return
*/
/*
def opIf(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken], List[ScriptToken]) = {
require(script.headOption.isDefined && script.head == OP_IF, "Script top was not OP_IF")
val (opElseIndex,opEndIfIndex) = findFirstIndexesOpElseOpEndIf(script)
stack.head match {
case OP_0 =>
//means that we need to execute the OP_ELSE statement if one exists
//need to remove the OP_IF expression from the script
//since it should not be executed
require(opEndIfIndex.isDefined,"Every OP_IF must have a matching OP_ENDIF statement")
//means that we have an else statement which needs to be executed
if (opElseIndex.isDefined) {
//removes the OP_ELSE as well
val newScript = script.slice(opElseIndex.get,script.size)
opElse(stack.tail,newScript)
} else {
//means that we do not have an OP_ELSE statement
//removes the OP_ENDIF as well
val newScript = script.slice(opEndIfIndex.get+1,script.size)
(stack.tail,newScript)
}
case _ =>
//means that we need to execute the OP_IF expression
//and delete its corresponding OP_ELSE if one exists
if (opElseIndex.isDefined) {
logger.debug("OP_ELSE index: " + opElseIndex.get)
logger.debug("OP_ENDIF index: " + opEndIfIndex.get)
//means we have an OP_ELSE expression that needs to be removed
//start at index 1 to remove the OP_IF
val scriptPart1 = script.slice(1,opElseIndex.get)
val scriptWithoutOpElse = script.zipWithIndex.filter(_._2 != opElseIndex.get).map(_._1)
val newOpElseIndex = findFirstOpElse(scriptWithoutOpElse)
//means that we have another OP_ELSE before our OP_ENDIF.
val scriptPart2 = if (newOpElseIndex.isDefined && newOpElseIndex.get < opEndIfIndex.get) {
//the +1 is because we removed the OP_ELSE
script.slice(newOpElseIndex.get+1,script.size)
} else script.slice(opEndIfIndex.get,script.size)
val newScript = scriptPart1 ++ scriptPart2
(stack.tail,newScript)
} else (stack.tail,script.tail)
}
}
*/
def opIf(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken], List[ScriptToken]) = {
require(script.headOption.isDefined && script.head == OP_IF, "Script top was not OP_IF")
val binaryTree = parseBinaryTree(script)
logger.debug("Binary tree: " + binaryTree)
if (stack.head != OP_0) {
//remove the OP_ELSE if one exists
val newTree : Option[BinaryTree[ScriptToken]] = binaryTree.left
if (newTree.isDefined) (stack.tail,newTree.get.toSeq.toList) else (stack.tail,List())
//remove OP_ELSE from binary tree
val newTreeWithoutOpElse = if (newTree.isDefined) Some(removeFirstOpElse(newTree.get)) else None
if (newTreeWithoutOpElse.isDefined) (stack.tail,newTreeWithoutOpElse.get.toSeq.toList) else (stack.tail,List())
} else {
//remove the OP_IF
val scriptWithoutOpIf = removeFirstOpIf(script)
@ -164,9 +120,7 @@ trait ControlOperationsInterpreter {
case OP_IF :: t =>
val lastOpEndIfIndex = findLastOpEndIf(t)
val lastOpElseIndex = findLastOpElse(t)
if (lastOpEndIfIndex.isDefined && !t.contains(OP_IF)) {
val opElseIndex : Option[Int] = findFirstOpElse(t)
val opIfExpression = if (opElseIndex.isDefined) t.slice(0,opElseIndex.get) else t.slice(0, lastOpEndIfIndex.get)
val restOfScript = if (opElseIndex.isDefined) t.slice(opElseIndex.get, script.size) else t.slice(lastOpEndIfIndex.get, script.size)
@ -180,10 +134,16 @@ trait ControlOperationsInterpreter {
} else Node(OP_IF,loop(t),Empty)
case OP_ELSE :: t =>
val lastOpEndIf = findLastOpEndIf(t)
if (lastOpEndIf.isDefined) {
val opElseExpression = t.slice(0,lastOpEndIf.get)
val restOfScript = t.slice(lastOpEndIf.get,t.size)
val nestedOpElseIndex = findFirstOpElse(t)
val lastOpEndIfIndex = findLastOpEndIf(t)
if (nestedOpElseIndex.isDefined && lastOpEndIfIndex.isDefined && nestedOpElseIndex.get < lastOpEndIfIndex.get) {
val opElseExpression = t.slice(0,nestedOpElseIndex.get)
val nestedOpElseExpression = t.slice(nestedOpElseIndex.get,t.size)
Node(OP_ELSE, loop(opElseExpression), loop(nestedOpElseExpression))
} else if (lastOpEndIfIndex.isDefined) {
val opElseExpression = t.slice(0,lastOpEndIfIndex.get)
val restOfScript = t.slice(lastOpEndIfIndex.get,t.size)
Node(OP_ELSE, loop(opElseExpression), loop(restOfScript))
} else Node(OP_ELSE,loop(t),Empty)
@ -266,7 +226,18 @@ trait ControlOperationsInterpreter {
}
/**
* Removes the first op else in a binary tree
* @param tree
* @tparam T
* @return
*/
def removeFirstOpElse[T](tree : BinaryTree[T]) : BinaryTree[T] = {
logger.debug("Binary tree: " + tree)
if (tree.right.isDefined && tree.right.get.value == Some(OP_ELSE)) {
Node(tree.value.get,tree.left.getOrElse(Empty),tree.right.get.right.getOrElse(Empty))
} else tree
}
/**
* Removes the first OP_IF { expression } encountered in the script
* @param script

View File

@ -32,7 +32,6 @@ trait BinaryTree[+T] {
* @return
*/
def toSeqLeafValues : Seq[T] = {
//TODO: Optimize this into a tailrec function
def loop(tree : BinaryTree[T],accum : List[T]) : Seq[T] = tree match {
case Leaf(x) => x :: accum

View File

@ -4,6 +4,7 @@ import org.scalacoin.script.arithmetic.OP_ADD
import org.scalacoin.script.bitwise.OP_EQUAL
import org.scalacoin.script.constant._
import org.scalacoin.script.reserved.{OP_VER, OP_RESERVED}
import org.scalacoin.util.Leaf
import org.scalatest.{MustMatchers, FlatSpec}
/**
@ -84,6 +85,20 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
removeFirstOpElse(List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)) must be (List(OP_IF, OP_1, OP_ELSE, OP_3, OP_ENDIF))
}
it must "remove the first OP_ELSE in a binary tree" in {
val script1 = List(OP_IF,OP_ELSE,OP_ENDIF)
val bTree1 = parseBinaryTree(script1)
removeFirstOpElse(bTree1).toSeq must be (List(OP_IF,OP_ENDIF))
val script2 = List(OP_IF,OP_ENDIF)
val bTree2 = parseBinaryTree(script2)
removeFirstOpElse(bTree2).toSeq must be (script2)
val script3 = List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)
val bTree3 = parseBinaryTree(script3)
removeFirstOpElse(bTree3).toSeq must be (List(OP_IF, OP_1, OP_ELSE, OP_3, OP_ENDIF))
}
it must "find a matching OP_ENDIF for an OP_IF" in {
//https://gist.github.com/Christewart/381dc1dbbb07e62501c3
val script = List(OP_IF, OP_1, OP_IF, OP_RETURN, OP_ELSE, OP_RETURN, OP_ELSE, OP_RETURN, OP_ENDIF,
@ -110,7 +125,6 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
it must "parse a script as a binary tree then convert it back to the original list" in {
val script0 = List(OP_IF,OP_ENDIF)
parseBinaryTree(script0).toSeq must be (script0)
@ -125,8 +139,11 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
val bTree3 = parseBinaryTree(script3)
bTree3.toSeq must be (script3)
val script = List(OP_IF, OP_IF, OP_0, OP_ELSE, OP_1, OP_ENDIF, OP_ELSE, OP_IF, OP_2, OP_ELSE, OP_3, OP_ENDIF, OP_ENDIF)
parseBinaryTree(script).toSeq must be (script)
val script4 = List(OP_IF, OP_IF, OP_0, OP_ELSE, OP_1, OP_ENDIF, OP_ELSE, OP_IF, OP_2, OP_ELSE, OP_3, OP_ENDIF, OP_ENDIF)
parseBinaryTree(script4).toSeq must be (script4)
val script5 = List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)
parseBinaryTree(script5).toSeq must be (script5)
}
it must "parse a script into a binary tree and have the OP_IF expression on the left branch and the OP_ELSE expression on the right branch"in {
@ -148,25 +165,57 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
bTree.right.get.right.get.value must be (Some(OP_ENDIF))
}
/* it must "evaluate an OP_IF block correctly if the stack top is true" in {
it must "parse nested OP_ELSE statements into the same branch" in {
val script = List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)
val bTree = parseBinaryTree(script)
println(bTree)
bTree.value.get must be (OP_IF)
bTree.left.isDefined must be (true)
bTree.left.get.value must be (Some(OP_1))
bTree.right.isDefined must be (true)
bTree.right.get.value must be (Some(OP_ELSE))
bTree.right.get.left.isDefined must be (true)
bTree.right.get.left.get.value must be (Some(OP_2))
bTree.right.get.right.isDefined must be (true)
bTree.right.get.right.get.value must be (Some(OP_ELSE))
bTree.right.get.right.get.left.isDefined must be (true)
bTree.right.get.right.get.left.get.value must be (Some(OP_3))
bTree.right.get.right.get.right.isDefined must be (true)
bTree.right.get.right.get.right.get.value must be (Some(OP_ENDIF))
bTree.right.get.right.get.left.isDefined must be (true)
bTree.right.get.right.get.left.get.value must be (Some(OP_3))
bTree.right.get.right.get.right.isDefined must be (true)
bTree.right.get.right.get.right must be (Some(Leaf(OP_ENDIF)))
}
/*it must "evaluate an OP_IF block correctly if the stack top is true" in {
val stack = List(OP_1)
val script = List(OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF)
val (newStack,newScript) = opIf(stack,script)
newStack must be (List())
newScript must be (List(OP_1, OP_ENDIF))
}*/
newScript must be (List(OP_1))
}
/*it must "evaluate a weird case using multiple OP_ELSEs" in {
it must "evaluate a weird case using multiple OP_ELSEs" in {
val stack = List(ScriptNumberImpl(1))
val script = List(OP_IF, OP_ELSE, OP_0, OP_ELSE, OP_1, OP_ENDIF)
val (newStack,newScript) = opIf(stack,script)
newScript must be (List(OP_ELSE,OP_1,OP_ENDIF))
}
}*/
it must "evaluate this OP_IF OP_ELSE block correctly" in {
/*it must "evaluate this OP_IF OP_ELSE block correctly" in {
//https://gist.github.com/Christewart/381dc1dbbb07e62501c3
val stack = List(OP_0)
val script = List(OP_IF, OP_1, OP_IF, OP_RETURN, OP_ELSE, OP_RETURN, OP_ELSE, OP_RETURN, OP_ENDIF,