Adding more documentation to types in the Script language class hierarchy

This commit is contained in:
Chris Stewart 2016-04-14 15:04:32 -05:00
parent 5b06e2e066
commit 9bb5f60bf4
4 changed files with 88 additions and 6 deletions

View file

@ -335,6 +335,10 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
logger.error("Cannot perform arithmetic operation on a number larger than 4 bytes, here is the number: " + s) logger.error("Cannot perform arithmetic operation on a number larger than 4 bytes, here is the number: " + s)
ScriptProgramFactory.factory(program,false) ScriptProgramFactory.factory(program,false)
} }
case s : ScriptConstant =>
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(s.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performUnaryArithmeticOperation(newProgram, op)
case s : ScriptToken => case s : ScriptToken =>
logger.error("Stack top must be a script number to perform an arithmetic operation") logger.error("Stack top must be a script number to perform an arithmetic operation")
ScriptProgramFactory.factory(program,false) ScriptProgramFactory.factory(program,false)
@ -364,9 +368,16 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack) val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op) performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptNumber, y : ScriptConstant) => case (x : ScriptNumber, y : ScriptConstant) =>
//interpret y as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex)) val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, x :: interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack) val newProgram = ScriptProgramFactory.factory(program, x :: interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op) performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptConstant, y : ScriptConstant) =>
//interpret x and y as a number
val interpretedNumberX = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val interpretedNumberY = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumberX :: interpretedNumberY :: program.stack.tail.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptToken, y : ScriptToken) => case (x : ScriptToken, y : ScriptToken) =>
logger.error("The top two stack items must be script numbers to perform an arithmetic operation") logger.error("The top two stack items must be script numbers to perform an arithmetic operation")
ScriptProgramFactory.factory(program,false) ScriptProgramFactory.factory(program,false)
@ -390,7 +401,22 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
logger.error("Cannot perform boolean operation on a number larger than 4 bytes, one of these two numbers is larger than 4 bytes: " + x + " " + y) logger.error("Cannot perform boolean operation on a number larger than 4 bytes, one of these two numbers is larger than 4 bytes: " + x + " " + y)
ScriptProgramFactory.factory(program,false) ScriptProgramFactory.factory(program,false)
} }
case (x : ScriptConstant, y : ScriptNumber) =>
//interpret x as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptNumber, y : ScriptConstant) =>
//interpret y as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, x :: interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptConstant, y : ScriptConstant) =>
//interpret x and y as a number
val interpretedNumberX = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val interpretedNumberY = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumberX :: interpretedNumberY :: program.stack.tail.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptToken, y : ScriptToken) => case (x : ScriptToken, y : ScriptToken) =>
logger.error("The top two stack items must be script numbers to perform an arithmetic operation") logger.error("The top two stack items must be script numbers to perform an arithmetic operation")
ScriptProgramFactory.factory(program,false) ScriptProgramFactory.factory(program,false)

View file

@ -6,21 +6,53 @@ import org.scalacoin.util.{BitcoinSUtil}
* Created by chris on 1/6/16. * Created by chris on 1/6/16.
*/ */
/**
* This is the root class of Script. Every element in the Script language is a
* ScriptToken - think of this the same way you think about Object in Java.
*/
sealed trait ScriptToken { sealed trait ScriptToken {
/**
* The hexadecimal representation of this script token
* @return
*/
def hex : String def hex : String
/**
* The byte representation of this script token
* @return
*/
def bytes = BitcoinSUtil.decodeHex(hex) def bytes = BitcoinSUtil.decodeHex(hex)
/**
* The conversion from the byte representation of a token to a number
* @return
*/
def toLong = BitcoinSUtil.hexToLong(hex) def toLong = BitcoinSUtil.hexToLong(hex)
} }
/**
* A script operation is an instruction that takes an input and gives an output
* Think of these as functions
*/
trait ScriptOperation extends ScriptToken { trait ScriptOperation extends ScriptToken {
def opCode : Int def opCode : Int
override def hex : String = BitcoinSUtil.encodeHex(opCode.toByte) override def hex : String = BitcoinSUtil.encodeHex(opCode.toByte)
} }
/**
* A constant in the Script language for instance as String or a number
*/
sealed trait ScriptConstant extends ScriptToken sealed trait ScriptConstant extends ScriptToken
/**
* Represents a number in the Script language
*/
sealed trait ScriptNumber extends ScriptConstant { sealed trait ScriptNumber extends ScriptConstant {
/**
* The underlying number of the ScriptNumber
* @return
*/
def num : Long def num : Long
def + (that : ScriptNumber) : ScriptNumber = ScriptNumberFactory.fromNumber(num + that.num) def + (that : ScriptNumber) : ScriptNumber = ScriptNumberFactory.fromNumber(num + that.num)
@ -34,6 +66,13 @@ sealed trait ScriptNumber extends ScriptConstant {
def > (that : ScriptNumber) : Boolean = num > that.num def > (that : ScriptNumber) : Boolean = num > that.num
def >= (that : ScriptNumber) : Boolean = num >= that.num def >= (that : ScriptNumber) : Boolean = num >= that.num
/**
* This equality just checks that the underlying scala numbers are equivalent, NOT if the numbers
* are bitwise equivalent in Script. For instance ScriptNumber(0x01).numEqual(ScriptNumber(0x00000000001)) == true
* but (ScriptNumber(0x01) == (ScriptNumber(0x00000000001))) == false
* @param that
* @return
*/
def numEqual(that : ScriptNumber) : Boolean = num == that.num def numEqual(that : ScriptNumber) : Boolean = num == that.num
} }
@ -46,11 +85,16 @@ sealed trait ScriptNumber extends ScriptConstant {
case class ScriptNumberImpl(num : Long, override val hex : String) extends ScriptNumber case class ScriptNumberImpl(num : Long, override val hex : String) extends ScriptNumber
/**
* Companion object for ScriptNumberImpl that gives us access to more constructor types for the
* ScriptNumberImpl case class
*/
object ScriptNumberImpl { object ScriptNumberImpl {
def apply(num : Long) : ScriptNumber = ScriptNumberImpl(num, BitcoinSUtil.longToHex(num)) def apply(num : Long) : ScriptNumber = ScriptNumberImpl(num, BitcoinSUtil.longToHex(num))
def apply(hex : String) : ScriptNumber = ScriptNumberImpl(BitcoinSUtil.hexToLong(hex), hex) def apply(hex : String) : ScriptNumber = ScriptNumberImpl(BitcoinSUtil.hexToLong(hex), hex)
def apply(bytes : Seq[Byte]) : ScriptNumber = ScriptNumberImpl(BitcoinSUtil.encodeHex(bytes)) def apply(bytes : Seq[Byte]) : ScriptNumber = ScriptNumberImpl(BitcoinSUtil.encodeHex(bytes))
} }
sealed trait ScriptBoolean extends ScriptNumber sealed trait ScriptBoolean extends ScriptNumber
//TODO: Need to remove ScriptTrue & ScriptFalse - make OP_TRUE/FALSE inherit from ScriptBoolean //TODO: Need to remove ScriptTrue & ScriptFalse - make OP_TRUE/FALSE inherit from ScriptBoolean

View file

@ -390,4 +390,16 @@ class ArithmeticInterpreterTest extends FlatSpec with MustMatchers with Arithmet
} }
it must "interpret two script constants as numbers and then add them" in {
val scriptConstant1 = ScriptConstantFactory.fromHex("ffffffff")
val scriptConstant2 = ScriptConstantFactory.fromHex("ffffff7f")
val stack = List(scriptConstant1, scriptConstant2)
val script = List(OP_ADD)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack, script)
val newProgram = opAdd(program)
newProgram.stack must be (List(OP_0))
newProgram.script.isEmpty must be (true)
}
} }

View file

@ -29,12 +29,12 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
val source = scala.io.Source.fromFile("src/test/scala/org/scalacoin/script/interpreter/script_valid.json") val source = scala.io.Source.fromFile("src/test/scala/org/scalacoin/script/interpreter/script_valid.json")
//use this to represent a single test case from script_valid.json //use this to represent a single test case from script_valid.json
/* val lines = val lines =
""" """
| |
|[["0x4c 0x00","0 EQUAL", "P2SH,STRICTENC"]] |[["''", "RIPEMD160 0x14 0x9c1185a5c5e9fc54612808977ee8f548b2258d31 EQUAL", "P2SH,STRICTENC"]]
""".stripMargin*/ """.stripMargin
val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close() //val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
val json = lines.parseJson val json = lines.parseJson
val testCasesOpt : Seq[Option[CoreTestCase]] = json.convertTo[Seq[Option[CoreTestCase]]] val testCasesOpt : Seq[Option[CoreTestCase]] = json.convertTo[Seq[Option[CoreTestCase]]]
val testCases : Seq[CoreTestCase] = testCasesOpt.flatten val testCases : Seq[CoreTestCase] = testCasesOpt.flatten