From aa5b934ef57d97a1a6895dc67110ba870a64fdbf Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Fri, 29 Jan 2016 14:10:33 -0600 Subject: [PATCH] All ControlOperations tests are passing - parsing binary tree correctly --- .../ControlOperationsInterpreter.scala | 160 +++++++++++++----- .../scala/org/scalacoin/util/BinaryTree.scala | 2 + .../ControlOperationsInterpreterTest.scala | 55 +++++- 3 files changed, 167 insertions(+), 50 deletions(-) diff --git a/src/main/scala/org/scalacoin/script/control/ControlOperationsInterpreter.scala b/src/main/scala/org/scalacoin/script/control/ControlOperationsInterpreter.scala index 4b4d02955c..2e916f163b 100644 --- a/src/main/scala/org/scalacoin/script/control/ControlOperationsInterpreter.scala +++ b/src/main/scala/org/scalacoin/script/control/ControlOperationsInterpreter.scala @@ -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 diff --git a/src/main/scala/org/scalacoin/util/BinaryTree.scala b/src/main/scala/org/scalacoin/util/BinaryTree.scala index 19d80e491b..7a847746f7 100644 --- a/src/main/scala/org/scalacoin/util/BinaryTree.scala +++ b/src/main/scala/org/scalacoin/util/BinaryTree.scala @@ -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] diff --git a/src/test/scala/org/scalacoin/script/control/ControlOperationsInterpreterTest.scala b/src/test/scala/org/scalacoin/script/control/ControlOperationsInterpreterTest.scala index f2e61586f7..0d754d5764 100644 --- a/src/test/scala/org/scalacoin/script/control/ControlOperationsInterpreterTest.scala +++ b/src/test/scala/org/scalacoin/script/control/ControlOperationsInterpreterTest.scala @@ -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)) + + } }