Successfuly parsing a script to a binary tree, then converting it back to a seq

This commit is contained in:
Chris Stewart 2016-01-28 16:32:57 -06:00
parent 25714cd03b
commit caaf98c43e
4 changed files with 147 additions and 46 deletions

View File

@ -1,6 +1,7 @@
package org.scalacoin.script.control
import org.scalacoin.script.constant._
import org.scalacoin.util.{Leaf, Node, Empty, BinaryTree}
import org.slf4j.LoggerFactory
import scala.annotation.tailrec
@ -82,10 +83,11 @@ trait ControlOperationsInterpreter {
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)
if (stack.head != OP_0) {
//remove the OP_ELSE if one exists
val scriptWithoutOpElse = removeOpElse(script)
(stack.tail,scriptWithoutOpElse.tail)
val newTree : Option[BinaryTree[ScriptToken]] = binaryTree.left
if (newTree.isDefined) (stack.tail,newTree.get.toSeq.toList) else (stack.tail,List())
} else {
//remove the OP_IF
val scriptWithoutOpIf = removeFirstOpIf(script)
@ -94,6 +96,7 @@ trait ControlOperationsInterpreter {
(scriptWithoutOpEndIf._1.tail, scriptWithoutOpEndIf._2)
} else (stack.tail,scriptWithoutOpIf)
}
}
/**
* Evaluates the OP_ELSE operator
@ -148,6 +151,48 @@ trait ControlOperationsInterpreter {
false
}
/**
* Parses a list of script tokens into its corresponding binary tree
* @param script
* @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 opIfExpression = t.slice(0, lastOpEndIfIndex.get)
val restOfScript = t.slice(lastOpEndIfIndex.get, script.size)
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 lastOpEndIf = findLastOpEndIf(t)
if (lastOpEndIf.isDefined) {
val opElseExpression = t.slice(0,lastOpEndIf.get)
val restOfScript = t.slice(lastOpEndIf.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
}
/**
* Returns the first index of an OP_ENDIF
* @param script
@ -197,15 +242,6 @@ trait ControlOperationsInterpreter {
}
/**
* Removes the last OP_ELSE if there are nested OP_IF statements,
* else removes the first OP_ELSE
* @param script
* @return
*/
def removeOpElse(script : List[ScriptToken]) : List[ScriptToken] = {
if (script.filter(_ == OP_IF).size > 1) removeCorrespondingOpElse(script) else removeFirstOpElse(script)
}
/**
* Removes the first OP_ELSE expression encountered in the script
* @param script
@ -226,32 +262,6 @@ trait ControlOperationsInterpreter {
}
/**
* Removes the last OP_ELSE expression in a script
* @param script
* @return
*/
def removeCorrespondingOpElse(script : List[ScriptToken]) : List[ScriptToken] = {
@tailrec
def loop(script : List[ScriptToken]) : List[ScriptToken] = {
if (!script.tail.contains(OP_IF)) {
val opElseIndex = findFirstOpElse(script)
if (opElseIndex.isDefined) script.slice(opElseIndex.get,script.size-1) else script
} else {
val nestedOpIfIndex = script.tail.indexOf(OP_IF)
val nestedOpEndIfIndex = findFirstOpEndIf(script.tail)
loop(script.slice(nestedOpEndIfIndex.get+1,nestedOpEndIfIndex.get+1))
}
}
val lastOpElseIndex : Option[Int] = findLastOpElse(script)
val lastOpEndIfIndex = findLastOpEndIf(script).get
if (lastOpElseIndex.isDefined) {
val scriptPart1 = script.slice(0,lastOpElseIndex.get)
val scriptPart2 = script.slice(lastOpEndIfIndex,script.size)
scriptPart1 ++ scriptPart2
} else script
}
/**
* Removes the first OP_IF { expression } encountered in the script
@ -295,7 +305,7 @@ trait ControlOperationsInterpreter {
*/
def findMatchingOpEndIf(script : List[ScriptToken]) : Int = {
val matchingOpEndIfIndex = findLastOpEndIf(script)
require(matchingOpEndIfIndex.isDefined, "Every OP_IF must have a matching OP_ENDIF")
require(matchingOpEndIfIndex.isDefined, "Every OP_IF must have a matching OP_ENDIF: " + script)
matchingOpEndIfIndex.get
}
}

View File

@ -1,5 +1,7 @@
package org.scalacoin.util
import scala.annotation.tailrec
/**
* Created by chris on 1/27/16.
*/
@ -21,6 +23,36 @@ trait BinaryTree[+T] {
case l: Leaf[T] => None
case Empty => None
}
/**
* Creates a sequence with only the leaf values
* evaluates as depth first from left to right
* @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
case Empty => accum
case Node(_,l,r) => loop(l,List()) ++ loop(r,List())
}
loop(this,List())
}
def toSeq : Seq[T] = {
//TODO: Optimize this into a tailrec function
def loop(tree : BinaryTree[T],accum : List[T]) : List[T] = tree match {
case Leaf(x) => x :: accum
case Empty => accum
case Node(v,l,r) => v :: loop(l,List()) ++ loop(r,List())
}
loop(this,List())
}
}
case class Node[T](v: T, l: BinaryTree[T], r: BinaryTree[T]) extends BinaryTree[T]

View File

@ -84,12 +84,6 @@ 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 last OP_ELSE expression in a script" in {
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)
removeCorrespondingOpElse(script) must be (List(OP_IF, OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF, 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,
@ -114,6 +108,44 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
newScript must be (List(OP_ELSE,OP_1,OP_ENDIF))
}
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)
val script1 = List(OP_IF,OP_0,OP_ELSE,OP_1,OP_ENDIF)
parseBinaryTree(script1).toSeq must be (script1)
val script2 = List(OP_IF,OP_ELSE, OP_ELSE,OP_ENDIF)
parseBinaryTree(script2).toSeq must be (script2)
val script3 = List(OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF)
parseBinaryTree(script3).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)
}
/* 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 {
val script = List(OP_IF,OP_0,OP_ELSE,OP_1,OP_ENDIF)
val bTree = parseBinaryTree(script)
bTree.value.get must be (OP_IF)
bTree.left.isDefined must be (true)
bTree.left.get must be (OP_0)
bTree.right.isDefined must be (true)
bTree.right.get must be (OP_ELSE)
bTree.right.get.left.isDefined must be (true)
bTree.right.get.left.get must be (OP_1)
bTree.right.get.right.get must be (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)
@ -123,7 +155,7 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
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 {
val stack = List(ScriptNumberImpl(1))
val script = List(OP_IF, OP_ELSE, OP_0, OP_ELSE, OP_1, OP_ENDIF)
@ -151,7 +183,7 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
newStack.head must be (OP_1)
newScript must be (List(OP_IF,OP_1,OP_ELSE,OP_0,OP_ENDIF,OP_ENDIF))
}
}*/
}

View File

@ -0,0 +1,27 @@
package org.scalacoin.util
import org.scalacoin.script.constant.{ScriptToken, OP_0, OP_1}
import org.scalacoin.script.control.{OP_ENDIF, OP_ELSE, OP_IF}
import org.scalatest.{FlatSpec, MustMatchers}
/**
* Created by chris on 1/27/16.
*/
class BinaryTreeTest extends FlatSpec with MustMatchers {
"BinaryTree" must "convert a binary tree to a list with only leaf values" in {
val bTree = Node(-1,Node(-1,Leaf(0),Leaf(1)),Node(-1,Leaf(2),Leaf(3)))
bTree.toSeqLeafValues must be (Seq(0,1,2,3))
}
it must "convert a binary tree to to a list with node values" in {
//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 bTree : BinaryTree[ScriptToken] =
Node[ScriptToken](OP_IF,Node(OP_IF,Leaf(OP_1),Node(OP_ELSE,Leaf(OP_0),Leaf(OP_ENDIF))),
Node(OP_ELSE,Node(OP_IF,Leaf(OP_0), Node(OP_ELSE,Leaf(OP_1),Leaf(OP_ENDIF))),Leaf(OP_ENDIF)))
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)
bTree.toSeq.size must be (script.size)
bTree.toSeq must be (script)
}
}