All ControlOperations tests are passing - parsing binary tree correctly

This commit is contained in:
Chris Stewart 2016-01-29 14:10:33 -06:00
parent 07a691fab5
commit aa5b934ef5
3 changed files with 167 additions and 50 deletions

View file

@ -39,11 +39,10 @@ trait ControlOperationsInterpreter {
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
//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())
val newTreeWithoutOpElse = removeFirstOpElse(binaryTree)
val newScript = newTreeWithoutOpElse.toList
(stack.tail,newScript.tail)
} else {
//remove the OP_IF
val scriptWithoutOpIf = removeFirstOpIf(script)
@ -114,49 +113,124 @@ trait ControlOperationsInterpreter {
* @return
*/
def parseBinaryTree(script : List[ScriptToken]) : BinaryTree[ScriptToken] = {
def loop(script : List[ScriptToken]) : BinaryTree[ScriptToken] = script match {
case OP_ENDIF :: t => Leaf(OP_ENDIF)
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)
logger.debug("OP_IF Expression: " + opIfExpression)
logger.debug("rest of script: " + restOfScript)
Node(OP_IF, loop(opIfExpression), loop(restOfScript))
} else if (lastOpElseIndex.isDefined) {
val opIfExpression = t.slice(0,lastOpElseIndex.get)
val restOfScript = t.slice(lastOpElseIndex.get,script.size)
Node(OP_IF,loop(opIfExpression),loop(restOfScript))
} else Node(OP_IF,loop(t),Empty)
case OP_ELSE :: t =>
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)
case (x : ScriptConstant) :: t => Node(x,loop(t),Empty)
case (x : ScriptNumber) :: t => Node(x,loop(t),Empty)
case scriptToken :: t => Node(scriptToken,loop(t),Empty)
case Nil => Empty
}
val bTree = loop(script)
bTree
}
private def loop(script : List[ScriptToken]) : BinaryTree[ScriptToken] = script match {
case OP_IF :: t => parseOpIf(script)
case OP_ELSE :: t => parseOpElse(script)
case OP_ENDIF :: t => Leaf(OP_ENDIF)
case (x : ScriptConstant) :: t => Node(x,loop(t),Empty)
case (x : ScriptNumber) :: t => Node(x,loop(t),Empty)
case scriptToken :: t => Node(scriptToken,loop(t),Empty)
case Nil => Empty
}
/**
* Parses an OP_IF expression in Script
* @param t
* @return
*/
private def parseOpIf(script : List[ScriptToken]) : BinaryTree[ScriptToken] = {
logger.debug("script : " + script)
//require(checkMatchingOpIfOpEndIf(script), "Every OP_IF must have a matching OP_ENDIF")
val _ :: t = script
val lastOpEndIfIndex = findLastOpEndIf(t)
val lastOpElseIndex = findLastOpElse(t)
if (!t.contains(OP_IF) && lastOpEndIfIndex.isDefined) {
//means that we do not have any nested OP_IF expressions
val opElseIndex : Option[Int] = findFirstOpElse(t)
//slice off the entire OP_IF expression
val opIfExpression = if (opElseIndex.isDefined) t.slice(0,opElseIndex.get) else t.slice(0, lastOpEndIfIndex.get)
//slice off everything that isn't the OP_IF expression
val restOfScript = if (opElseIndex.isDefined) t.slice(opElseIndex.get, t.size) else t.slice(lastOpEndIfIndex.get, t.size)
//build the OP_IF expression as the left subtree, the rest of the script as the right subtree
Node(OP_IF, loop(opIfExpression), loop(restOfScript))
} else if (t.contains(OP_IF) && lastOpEndIfIndex.isDefined) {
//means that we have a nested OP_IF expresion
//we need to parse the inner OP_IF expression
val nestedOpIfIndex = findFirstOpIf(t).get
//if we have multiple nested OP_IFs we need this to get the correct OP_ENDIF
val nextNestedOpIfIndex = findFirstOpIf(t.slice(nestedOpIfIndex+1,t.size))
//find the nested OP_ENDIF matching our nested OP_IF
val nestedLastOpEndIfIndex = nextNestedOpIfIndex.isDefined match {
//means that we need to search t until the next nested OP_IF index
case true => findLastOpEndIf(t.slice(0,nextNestedOpIfIndex.get))
//means that we can search all of t until the last OP_ENDIF
case false => findLastOpEndIf(t.slice(0,lastOpEndIfIndex.get))
}
//every OP_IF must be matched with a OP_ENDIF
require(nestedLastOpEndIfIndex.isDefined,"Every OP_IF must have a matching OP_ENDIF")
//slice off the nested OP_IF expression
val firstNestedOpIfExpression = t.slice(nestedOpIfIndex,nestedLastOpEndIfIndex.get+1)
val restOfScript = t.slice(nestedLastOpEndIfIndex.get+1,t.size)
logger.debug("Rest of script: " + restOfScript)
//parse the nested OP_IF expression as the left subtree
//the rest of the parent OP_IF as the right subtree
Node(OP_IF,loop(firstNestedOpIfExpression),loop(restOfScript))
} else Node(OP_IF,loop(t),Empty)
}
def checkMatchingOpIfOpEndIf(script : List[ScriptToken]) : Boolean = {
script.count(_ == OP_IF) == script.count(_ == OP_ENDIF)
}
private def parseOpElse(script : List[ScriptToken]) : BinaryTree[ScriptToken] = {
logger.debug("Script parse OP_ELSE: " + script)
val _ :: t = script
val nestedOpElseIndex = findFirstOpElse(t)
val lastOpEndIfIndex = findLastOpEndIf(t)
if (t.contains(OP_IF)) {
//check to see if there is a nested OP_IF inside of the OP_ELSE
//find the index of the nested OP_IF
logger.debug("Last OP_ENDIF index: " + lastOpEndIfIndex)
val firstOpIfIndex = findFirstOpIf(t)
logger.debug("First OP_IF index: " + firstOpIfIndex)
//find the nested OP_IFs corresponding OP_ENDIF
val nestedOpEndIfIndex = findLastOpEndIf(t.slice(firstOpIfIndex.get,lastOpEndIfIndex.get))
logger.debug("Nested OP_ENDIF: " + nestedOpEndIfIndex)
//slice off the nested OP_IF expression
val nestedOpIfExpression = t.slice(firstOpIfIndex.get,nestedOpEndIfIndex.get+1)
Node(OP_ELSE,loop(nestedOpIfExpression),loop(t.slice(nestedOpEndIfIndex.get+1,t.size)))
} else if (nestedOpElseIndex.isDefined && lastOpEndIfIndex.isDefined && nestedOpElseIndex.get < lastOpEndIfIndex.get) {
//if we have a nested OP_ELSE before our last OP_ENDIF index
val opElseExpression = t.slice(0,nestedOpElseIndex.get)
val restOfScript = t.slice(nestedOpElseIndex.get,t.size)
Node(OP_ELSE, loop(opElseExpression), loop(restOfScript))
} 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)
}
def findFirstOpIf(script : List[ScriptToken]) : Option[Int] = findFirstScriptToken(script,OP_IF)
private def findFirstScriptToken(script : List[ScriptToken], scriptToken : ScriptToken) : Option[Int] = {
val index = script.indexOf(scriptToken)
index match {
case -1 => None
case _ => Some(index)
}
}
private def findLastScriptToken(script : List[ScriptToken], scriptToken : ScriptToken) = {
val index = script.reverse.indexOf(scriptToken)
index match {
case -1 => None
case _ => Some(script.size - index)
}
}
/**
* Returns the first index of an OP_ENDIF
* @param script

View file

@ -52,6 +52,8 @@ trait BinaryTree[+T] {
}
loop(this,List())
}
def toList : List[T] = toSeq.toList
}
case class Node[T](v: T, l: BinaryTree[T], r: BinaryTree[T]) extends BinaryTree[T]

View file

@ -96,6 +96,9 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
val script3 = List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)
val bTree3 = parseBinaryTree(script3)
println(bTree3)
//Node(OP_IF,Node(OP_1,Empty,Empty),Node(OP_ELSE,Node(OP_2,Node(OP_ELSE,Node(OP_3,Empty,Empty),Empty),Empty),Leaf(OP_ENDIF)))
removeFirstOpElse(bTree3).toSeq must be (List(OP_IF, OP_1, OP_ELSE, OP_3, OP_ENDIF))
}
@ -123,6 +126,20 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
newScript must be (List(OP_ELSE,OP_1,OP_ENDIF))
}
it must "check that every OP_IF has a matching OP_ENDIF" in {
val script0 = List()
checkMatchingOpIfOpEndIf(script0) must be (true)
val script1 = List(OP_IF, OP_ENDIF)
checkMatchingOpIfOpEndIf(script1) must be (true)
val script2 = List(OP_IF)
checkMatchingOpIfOpEndIf(script2) must be (false)
val script3 = List(OP_IF,OP_IF,OP_IF,OP_ELSE,OP_ELSE,OP_ELSE,OP_ENDIF,OP_ENDIF,OP_ENDIF)
checkMatchingOpIfOpEndIf(script3) must be (true)
}
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)
@ -140,7 +157,10 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
bTree3.toSeq must be (script3)
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 bTree4 = parseBinaryTree(script4)
println(bTree4)
bTree4.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)
@ -168,7 +188,6 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
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)
@ -197,13 +216,33 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
}
it must "parse a binary tree from a script with nested OP_IFs and OP_ELSES on both branches" in {
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)
val bTree = parseBinaryTree(script)
bTree.value must be (Some(OP_IF))
bTree.left.get.value must be (Some(OP_IF))
bTree.left.get.right.get.value must be (Some(OP_ELSE))
bTree.left.get.right.get.left.get.value must be (Some(OP_1))
bTree.left.get.right.get.right.get.value must be (Some(OP_ENDIF))
bTree.right.get.value must be (Some(OP_ELSE))
bTree.right.get.left.get.value must be (Some(OP_IF))
bTree.right.get.left.get.left.get.value must be (Some(OP_2))
bTree.right.get.left.get.right.get.value must be (Some(OP_ELSE))
bTree.right.get.left.get.right.get.left.get.value must be (Some(OP_3))
bTree.right.get.left.get.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 {
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))
newScript must be (List(OP_1,OP_ENDIF))
}
it must "evaluate a weird case using multiple OP_ELSEs" in {
@ -213,9 +252,10 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
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,
@ -229,12 +269,13 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
it must "evalute nested OP_IFS correctly" in {
val stack = List(OP_1,OP_1)
val script = List(OP_IF, OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF, OP_ELSE, OP_IF, OP_0, OP_ELSE, OP_1, OP_ENDIF, OP_ENDIF)
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)
val (newStack,newScript) = opIf(stack,script)
newStack.head must be (OP_1)
newScript must be (List(OP_IF,OP_1,OP_ELSE,OP_0,OP_ENDIF,OP_ENDIF))
}*/
newScript must be (List(OP_IF,OP_0,OP_ELSE,OP_1,OP_ENDIF,OP_ENDIF))
}
}