mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
All ControlOperations tests are passing - parsing binary tree correctly
This commit is contained in:
parent
07a691fab5
commit
aa5b934ef5
3 changed files with 167 additions and 50 deletions
|
@ -39,11 +39,10 @@ trait ControlOperationsInterpreter {
|
||||||
val binaryTree = parseBinaryTree(script)
|
val binaryTree = parseBinaryTree(script)
|
||||||
logger.debug("Binary tree: " + binaryTree)
|
logger.debug("Binary tree: " + binaryTree)
|
||||||
if (stack.head != OP_0) {
|
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
|
//remove OP_ELSE from binary tree
|
||||||
val newTreeWithoutOpElse = if (newTree.isDefined) Some(removeFirstOpElse(newTree.get)) else None
|
val newTreeWithoutOpElse = removeFirstOpElse(binaryTree)
|
||||||
if (newTreeWithoutOpElse.isDefined) (stack.tail,newTreeWithoutOpElse.get.toSeq.toList) else (stack.tail,List())
|
val newScript = newTreeWithoutOpElse.toList
|
||||||
|
(stack.tail,newScript.tail)
|
||||||
} else {
|
} else {
|
||||||
//remove the OP_IF
|
//remove the OP_IF
|
||||||
val scriptWithoutOpIf = removeFirstOpIf(script)
|
val scriptWithoutOpIf = removeFirstOpIf(script)
|
||||||
|
@ -114,49 +113,124 @@ trait ControlOperationsInterpreter {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
def parseBinaryTree(script : List[ScriptToken]) : BinaryTree[ScriptToken] = {
|
def parseBinaryTree(script : List[ScriptToken]) : BinaryTree[ScriptToken] = {
|
||||||
|
val bTree = loop(script)
|
||||||
|
bTree
|
||||||
|
}
|
||||||
|
|
||||||
def loop(script : List[ScriptToken]) : BinaryTree[ScriptToken] = script match {
|
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 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 : ScriptConstant) :: t => Node(x,loop(t),Empty)
|
||||||
case (x : ScriptNumber) :: t => Node(x,loop(t),Empty)
|
case (x : ScriptNumber) :: t => Node(x,loop(t),Empty)
|
||||||
case scriptToken :: t => Node(scriptToken,loop(t),Empty)
|
case scriptToken :: t => Node(scriptToken,loop(t),Empty)
|
||||||
case Nil => Empty
|
case Nil => Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
val bTree = loop(script)
|
|
||||||
bTree
|
/**
|
||||||
|
* 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
|
* Returns the first index of an OP_ENDIF
|
||||||
* @param script
|
* @param script
|
||||||
|
|
|
@ -52,6 +52,8 @@ trait BinaryTree[+T] {
|
||||||
}
|
}
|
||||||
loop(this,List())
|
loop(this,List())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def toList : List[T] = toSeq.toList
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Node[T](v: T, l: BinaryTree[T], r: BinaryTree[T]) extends BinaryTree[T]
|
case class Node[T](v: T, l: BinaryTree[T], r: BinaryTree[T]) extends BinaryTree[T]
|
||||||
|
|
|
@ -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 script3 = List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)
|
||||||
val bTree3 = parseBinaryTree(script3)
|
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))
|
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))
|
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 {
|
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)
|
val script0 = List(OP_IF,OP_ENDIF)
|
||||||
|
@ -140,7 +157,10 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
|
||||||
bTree3.toSeq must be (script3)
|
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)
|
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)
|
val script5 = List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)
|
||||||
parseBinaryTree(script5).toSeq must be (script5)
|
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 {
|
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 script = List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)
|
||||||
val bTree = parseBinaryTree(script)
|
val bTree = parseBinaryTree(script)
|
||||||
println(bTree)
|
|
||||||
bTree.value.get must be (OP_IF)
|
bTree.value.get must be (OP_IF)
|
||||||
|
|
||||||
bTree.left.isDefined must be (true)
|
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 {
|
it must "evaluate an OP_IF block correctly if the stack top is true" in {
|
||||||
val stack = List(OP_1)
|
val stack = List(OP_1)
|
||||||
val script = List(OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF)
|
val script = List(OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF)
|
||||||
val (newStack,newScript) = opIf(stack,script)
|
val (newStack,newScript) = opIf(stack,script)
|
||||||
|
|
||||||
newStack must be (List())
|
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 {
|
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)
|
val (newStack,newScript) = opIf(stack,script)
|
||||||
|
|
||||||
newScript must be (List(OP_ELSE,OP_1,OP_ENDIF))
|
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
|
//https://gist.github.com/Christewart/381dc1dbbb07e62501c3
|
||||||
val stack = List(OP_0)
|
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,
|
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 {
|
it must "evalute nested OP_IFS correctly" in {
|
||||||
val stack = List(OP_1,OP_1)
|
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)
|
val (newStack,newScript) = opIf(stack,script)
|
||||||
|
|
||||||
newStack.head must be (OP_1)
|
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))
|
||||||
}*/
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue