strings and arrays are now stored in a 'heap' instead of in the value itself, to reflect how the target platform will store them

This commit is contained in:
Irmen de Jong 2018-09-29 17:33:59 +02:00
parent d4232721fc
commit 6b89bb7be5
21 changed files with 1502 additions and 1298 deletions

View File

@ -0,0 +1,76 @@
%output prg
%import c64lib
%import mathlib
~ main {
sub start() -> () {
str name = "?" * 80
str guess = "?" * 80
byte secretnumber = 0
byte attempts_left = 10
memory word freadstr_arg = $22 ; argument for FREADSTR
c64.init_system()
c64.VMCSB |= 2 ; activate lowercase charset
; greeting
c64scr.print_string("Enter your name: ")
Y = c64scr.input_chars(name)
c64.CHROUT("\n")
c64.CHROUT("\n")
c64scr.print_string("Hello, ")
c64scr.print_string(name)
c64.CHROUT(".")
c64.CHROUT("\n")
; create a secret random number from 1-100
c64.RNDA(0) ; fac = rnd(0)
c64.MUL10() ; fac *= 10
c64.MUL10() ; .. and now *100
c64.FADDH() ; add 0.5..
c64.FADDH() ; and again, so +1 total
AY = c64flt.GETADRAY()
secretnumber = A
;A=math.randbyte()
;A+=c64.RASTER
;A-=c64.TIME_LO
;X,secretnumber=math.divmod_bytes(A, 99)
c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n")
ask_guess:
c64scr.print_string("\nYou have ")
c64scr.print_byte_decimal(attempts_left)
c64scr.print_string(" guess")
if(attempts_left>0) c64scr.print_string("es")
c64scr.print_string(" left.\nWhat is your next guess? ")
Y = c64scr.input_chars(guess)
c64.CHROUT("\n")
freadstr_arg = guess
c64.FREADSTR(A)
AY = c64flt.GETADRAY()
if(A==secretnumber) {
c64scr.print_string("\nThat's my number, impressive!\n")
goto goodbye
}
c64scr.print_string("That is too ")
if(A > secretnumber)
c64scr.print_string("low!\n")
else
c64scr.print_string("high!\n")
attempts_left--
if(attempts_left>0) goto ask_guess
; more efficient: if_nz goto ask_guess
; game over.
c64scr.print_string("\nToo bad! It was: ")
c64scr.print_byte_decimal(secretnumber)
c64.CHROUT("\n")
goodbye:
c64scr.print_string("\nThanks for playing. Bye!\n")
return
}
}

View File

@ -1,76 +1,72 @@
%output prg
%import c64lib
%import mathlib
~ main {
sub start() -> () {
str name = "?" * 80
str guess = "?" * 80
str name = "?" * 20
str guess = "?" * 20
byte secretnumber = 0
byte attempts_left = 10
memory word freadstr_arg = $22 ; argument for FREADSTR
c64.init_system()
c64.VMCSB |= 2 ; activate lowercase charset
; greeting
c64scr.print_string("Enter your name: ")
Y = c64scr.input_chars(name)
c64.CHROUT("\n")
c64.CHROUT("\n")
c64scr.print_string("Hello, ")
c64scr.print_string(name)
c64.CHROUT(".")
c64.CHROUT("\n")
_vm_write_str("Enter your name: ")
; _vm_input_str(name)
_vm_write_char($8d)
_vm_write_char($8d)
_vm_write_str("Hello, ")
_vm_write_str(name)
_vm_write_char($2e)
_vm_write_char($8d)
; create a secret random number from 1-100
c64.RNDA(0) ; fac = rnd(0)
c64.MUL10() ; fac *= 10
c64.MUL10() ; .. and now *100
c64.FADDH() ; add 0.5..
c64.FADDH() ; and again, so +1 total
AY = c64flt.GETADRAY()
secretnumber = A
;A=math.randbyte()
;A+=c64.RASTER
;A-=c64.TIME_LO
;X,secretnumber=math.divmod_bytes(A, 99)
c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n")
ask_guess:
c64scr.print_string("\nYou have ")
c64scr.print_byte_decimal(attempts_left)
c64scr.print_string(" guess")
if(attempts_left>0) c64scr.print_string("es")
c64scr.print_string(" left.\nWhat is your next guess? ")
Y = c64scr.input_chars(guess)
c64.CHROUT("\n")
freadstr_arg = guess
c64.FREADSTR(A)
AY = c64flt.GETADRAY()
if(A==secretnumber) {
c64scr.print_string("\nThat's my number, impressive!\n")
goto goodbye
}
c64scr.print_string("That is too ")
if(A > secretnumber)
c64scr.print_string("low!\n")
else
c64scr.print_string("high!\n")
attempts_left--
if(attempts_left>0) goto ask_guess
; more efficient: if_nz goto ask_guess
; game over.
c64scr.print_string("\nToo bad! It was: ")
c64scr.print_byte_decimal(secretnumber)
c64.CHROUT("\n")
goodbye:
c64scr.print_string("\nThanks for playing. Bye!\n")
return
; ; create a secret random number from 1-100
; c64.RNDA(0) ; fac = rnd(0)
; c64.MUL10() ; fac *= 10
; c64.MUL10() ; .. and now *100
; c64.FADDH() ; add 0.5..
; c64.FADDH() ; and again, so +1 total
; AY = c64flt.GETADRAY()
; secretnumber = A
; ;A=math.randbyte()
; ;A+=c64.RASTER
; ;A-=c64.TIME_LO
; ;X,secretnumber=math.divmod_bytes(A, 99)
;
; c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n")
;
;ask_guess:
; c64scr.print_string("\nYou have ")
; c64scr.print_byte_decimal(attempts_left)
; c64scr.print_string(" guess")
; if(attempts_left>0) c64scr.print_string("es")
;
; c64scr.print_string(" left.\nWhat is your next guess? ")
; Y = c64scr.input_chars(guess)
; c64.CHROUT("\n")
; freadstr_arg = guess
; c64.FREADSTR(A)
; AY = c64flt.GETADRAY()
; if(A==secretnumber) {
; c64scr.print_string("\nThat's my number, impressive!\n")
; goto goodbye
; }
; c64scr.print_string("That is too ")
; if(A > secretnumber)
; c64scr.print_string("low!\n")
; else
; c64scr.print_string("high!\n")
;
; attempts_left--
; if(attempts_left>0) goto ask_guess
; ; more efficient: if_nz goto ask_guess
;
; ; game over.
; c64scr.print_string("\nToo bad! It was: ")
; c64scr.print_byte_decimal(secretnumber)
; c64.CHROUT("\n")
;
;goodbye:
; c64scr.print_string("\nThanks for playing. Bye!\n")
; return
}
}

View File

@ -1,39 +0,0 @@
; source code for a stackvm program
; init memory bytes/words/strings
%memory
0400 01 02 03 04 05 06 07 08 09 22 33 44 55 66
0500 1111 2222 3333 4444
1000 "Hello world!\n"
%end_memory
; init global var table with bytes/words/floats/strings
%variables
main.var1 str "This is main.var1"
main.var2 byte aa
main.var3 word ea44
main.var4 float 3.1415927
main.textcolor byte 0
input.prompt str "Enter a number: "
input.result word 0
%end_variables
; instructions and labels
%instructions
nop
syscall WRITE_MEMSTR w:1000
loop:
inc_var main.textcolor
syscall RANDOM
syscall RANDOM
push_var main.textcolor
syscall GFX_PIXEL
; syscall WRITE_VAR "input.prompt"
; push b:10
; syscall INPUT_STR
; pop_var input.result
; syscall WRITE_VAR "input.result"
; push b:8d
; syscall WRITE_CHAR
jump loop
%end_instructions

View File

@ -2,17 +2,45 @@
~ main {
sub start() -> () {
str msg1 = "abc"
str msg2 = "abc"
str msg3 = "abc123"
str_p msg4 = "abc"
byte[5] array1 = 0
byte[5] array2 = 222
byte[6] array3 = [1,2,3,4,5,66]
word[5] array4 = 333
word[5] array5 = [1,2,3,4,59999]
word[5] array6 = [1,2,3,4,5]
word[5] array7 = [1,2,3,4,999+22/33]
byte[2,3] matrix1 = [1,2, 3,4, 5,6]
byte[2,3] matrix2 = [1,2, 3,4, 5,6]
byte[2,3] matrix3 = [11,22, 33,44, 55,66]
str message = "Calculating Mandelbrot Fractal..."
_vm_gfx_text(5, 5, 7, message)
_vm_gfx_text(5, 5, 7, "Calculating Mandelbrot Fractal...")
A = calcIt(12345, 99)
;A = 99/5 + lsb(12345)
_vm_write_num(A)
_vm_write_char($8d)
return
A= len("abcdef")
A= len([4,5,99/X])
A= max([4,5,99])
A= min([4,5,99])
A= avg([4,5,99])
A= sum([4,5,99])
A= any([4,5,99])
A= all([4,5,99])
A= len(msg3)
float xx
A= len(array3)
A= max(array3)
A= min(array3)
xx= avg(array3)
A= sum(array3)
A= any(array3)
A= all(array3)
}
sub calcIt(length: XY, control: A) -> (Y) {
return control/5 +lsb(length)
}
}

View File

@ -55,27 +55,28 @@ fun main(args: Array<String>) {
moduleAst.checkIdentifiers()
println("Optimizing...")
moduleAst.constantFold(namespace)
moduleAst.checkValid(namespace, compilerOptions) // check if tree is valid
val heap = HeapValues()
moduleAst.constantFold(namespace, heap)
moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers()
while(true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = moduleAst.simplifyExpressions(namespace)
val optsDone2 = moduleAst.optimizeStatements(namespace)
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
if(optsDone1 + optsDone2 == 0)
break
}
StatementReorderer().process(moduleAst) // reorder statements to please the compiler later
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime
moduleAst.checkValid(namespace, compilerOptions) // check if final tree is valid
moduleAst.checkValid(namespace, compilerOptions, heap) // check if final tree is valid
moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
// namespace.debugPrint()
// compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(compilerOptions)
val intermediate = compiler.compile(moduleAst)
val intermediate = compiler.compile(moduleAst, heap)
intermediate.optimize()
val stackVmFilename = intermediate.name + "_stackvm.txt"

View File

@ -2,6 +2,7 @@ package prog8.ast
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import prog8.functions.*
import prog8.parser.prog8Parser
@ -677,11 +678,11 @@ data class AssignTarget(val register: Register?, val identifier: IdentifierRefer
interface IExpression: Node {
fun isIterable(namespace: INameScope): Boolean
fun constValue(namespace: INameScope): LiteralValue?
fun isIterable(namespace: INameScope, heap: HeapValues): Boolean
fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue?
fun process(processor: IAstProcessor): IExpression
fun referencesIdentifier(name: String): Boolean
fun resultingDatatype(namespace: INameScope): DataType?
fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType?
}
@ -695,11 +696,11 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri
expression.linkParents(this)
}
override fun constValue(namespace: INameScope): LiteralValue? = null
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? = expression.resultingDatatype(namespace)
override fun isIterable(namespace: INameScope) = false
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? = expression.resultingDatatype(namespace, heap)
override fun isIterable(namespace: INameScope, heap: HeapValues) = false
}
@ -713,13 +714,13 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
}
// binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(namespace: INameScope): LiteralValue? = null
override fun isIterable(namespace: INameScope) = false
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
override fun isIterable(namespace: INameScope, heap: HeapValues) = false
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? {
val leftDt = left.resultingDatatype(namespace)
val rightDt = right.resultingDatatype(namespace)
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
val leftDt = left.resultingDatatype(namespace, heap)
val rightDt = right.resultingDatatype(namespace, heap)
return when(operator) {
"+", "-", "*", "**", "%" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt)
"//" -> if(leftDt==null || rightDt==null) null else integerDivisionOpDt(leftDt, rightDt)
@ -731,7 +732,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
"<=", ">=",
"==", "!=" -> DataType.BYTE
"/" -> {
val rightNum = right.constValue(namespace)?.asNumericValue?.toDouble()
val rightNum = right.constValue(namespace, heap)?.asNumericValue?.toDouble()
if(rightNum!=null) {
when(leftDt) {
DataType.BYTE ->
@ -807,8 +808,10 @@ class LiteralValue(val type: DataType,
val floatvalue: Double? = null,
val strvalue: String? = null,
val arrayvalue: Array<IExpression>? = null,
val heapId: Int? =null,
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false
val isString = type==DataType.STR || type==DataType.STR_P || type==DataType.STR_S || type==DataType.STR_PS
@ -858,13 +861,13 @@ class LiteralValue(val type: DataType,
DataType.BYTE -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
DataType.WORD -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
if(strvalue==null) throw FatalAstException("literal value missing strvalue")
}
DataType.ARRAY, DataType.ARRAY_W -> if(arrayvalue==null) throw FatalAstException("literal value missing arrayvalue")
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS ->
if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
DataType.ARRAY, DataType.ARRAY_W ->
if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
DataType.MATRIX -> TODO("matrix literalvalue? for now, arrays are good enough for this")
}
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null)
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null)
throw FatalAstException("literal value without actual value")
}
@ -893,7 +896,7 @@ class LiteralValue(val type: DataType,
arrayvalue?.forEach {it.linkParents(this)}
}
override fun constValue(namespace: INameScope): LiteralValue? = this
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = this
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
@ -901,16 +904,25 @@ class LiteralValue(val type: DataType,
DataType.BYTE -> "byte:$bytevalue"
DataType.WORD -> "word:$wordvalue"
DataType.FLOAT -> "float:$floatvalue"
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> "str:$strvalue"
DataType.ARRAY, DataType.ARRAY_W -> "array:$arrayvalue"
DataType.MATRIX -> "matrix:$arrayvalue"
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> {
if(heapId!=null) "str:#$heapId"
else "str:$strvalue"
}
DataType.ARRAY, DataType.ARRAY_W -> {
if(heapId!=null) "array:#$heapId"
else "array:$arrayvalue"
}
DataType.MATRIX -> {
if(heapId!=null) "matrix:#$heapId"
else "matrix:$arrayvalue"
}
}
return "LiteralValue($vstr)"
}
override fun resultingDatatype(namespace: INameScope) = type
override fun resultingDatatype(namespace: INameScope, heap: HeapValues) = type
override fun isIterable(namespace: INameScope): Boolean = IterableDatatypes.contains(type)
override fun isIterable(namespace: INameScope, heap: HeapValues): Boolean = IterableDatatypes.contains(type)
override fun hashCode(): Int {
val bh = bytevalue?.hashCode() ?: 0x10001234
@ -954,13 +966,13 @@ class RangeExpr(var from: IExpression,
step.linkParents(this)
}
override fun constValue(namespace: INameScope): LiteralValue? = null
override fun isIterable(namespace: INameScope) = true
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
override fun isIterable(namespace: INameScope, heap: HeapValues) = true
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? {
val fromDt=from.resultingDatatype(namespace)
val toDt=to.resultingDatatype(namespace)
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
val fromDt=from.resultingDatatype(namespace, heap)
val toDt=to.resultingDatatype(namespace, heap)
return when {
fromDt==null || toDt==null -> null
fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD
@ -1023,15 +1035,15 @@ class RegisterExpr(val register: Register, override val position: Position) : IE
this.parent = parent
}
override fun constValue(namespace: INameScope): LiteralValue? = null
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
override fun process(processor: IAstProcessor) = this
override fun referencesIdentifier(name: String): Boolean = false
override fun isIterable(namespace: INameScope) = false
override fun isIterable(namespace: INameScope, heap: HeapValues) = false
override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)"
}
override fun resultingDatatype(namespace: INameScope): DataType? {
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
return when(register){
Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD
@ -1053,7 +1065,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
this.parent = parent
}
override fun constValue(namespace: INameScope): LiteralValue? {
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? {
val node = namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl
@ -1062,7 +1074,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
} else if(vardecl.type!=VarDeclType.CONST) {
return null
}
return vardecl.value?.constValue(namespace)
return vardecl.value?.constValue(namespace, heap)
}
override fun toString(): String {
@ -1072,7 +1084,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time?
override fun resultingDatatype(namespace: INameScope): DataType? {
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
val targetStmt = targetStatement(namespace)
if(targetStmt is VarDecl) {
return targetStmt.datatype
@ -1081,7 +1093,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
}
}
override fun isIterable(namespace: INameScope): Boolean = IterableDatatypes.contains(resultingDatatype(namespace))
override fun isIterable(namespace: INameScope, heap: HeapValues): Boolean = IterableDatatypes.contains(resultingDatatype(namespace, heap))
}
@ -1131,45 +1143,45 @@ class FunctionCall(override var target: IdentifierReference,
arglist.forEach { it.linkParents(this) }
}
override fun constValue(namespace: INameScope) = constValue(namespace, true)
override fun constValue(namespace: INameScope, heap: HeapValues) = constValue(namespace, heap, true)
private fun constValue(namespace: INameScope, withDatatypeCheck: Boolean): LiteralValue? {
private fun constValue(namespace: INameScope, heap: HeapValues, withDatatypeCheck: Boolean): LiteralValue? {
// if the function is a built-in function and the args are consts, should try to const-evaluate!
if(target.nameInSource.size>1) return null
try {
val resultValue = when (target.nameInSource[0]) {
"sin" -> builtinSin(arglist, position, namespace)
"cos" -> builtinCos(arglist, position, namespace)
"abs" -> builtinAbs(arglist, position, namespace)
"acos" -> builtinAcos(arglist, position, namespace)
"asin" -> builtinAsin(arglist, position, namespace)
"tan" -> builtinTan(arglist, position, namespace)
"atan" -> builtinAtan(arglist, position, namespace)
"ln" -> builtinLn(arglist, position, namespace)
"log2" -> builtinLog2(arglist, position, namespace)
"log10" -> builtinLog10(arglist, position, namespace)
"sqrt" -> builtinSqrt(arglist, position, namespace)
"max" -> builtinMax(arglist, position, namespace)
"min" -> builtinMin(arglist, position, namespace)
"round" -> builtinRound(arglist, position, namespace)
"rad" -> builtinRad(arglist, position, namespace)
"deg" -> builtinDeg(arglist, position, namespace)
"sum" -> builtinSum(arglist, position, namespace)
"avg" -> builtinAvg(arglist, position, namespace)
"len" -> builtinLen(arglist, position, namespace)
"lsb" -> builtinLsb(arglist, position, namespace)
"msb" -> builtinMsb(arglist, position, namespace)
"flt" -> builtinFlt(arglist, position, namespace)
"any" -> builtinAny(arglist, position, namespace)
"all" -> builtinAll(arglist, position, namespace)
"floor" -> builtinFloor(arglist, position, namespace)
"ceil" -> builtinCeil(arglist, position, namespace)
"sin" -> builtinSin(arglist, position, namespace, heap)
"cos" -> builtinCos(arglist, position, namespace, heap)
"abs" -> builtinAbs(arglist, position, namespace, heap)
"acos" -> builtinAcos(arglist, position, namespace, heap)
"asin" -> builtinAsin(arglist, position, namespace, heap)
"tan" -> builtinTan(arglist, position, namespace, heap)
"atan" -> builtinAtan(arglist, position, namespace, heap)
"ln" -> builtinLn(arglist, position, namespace, heap)
"log2" -> builtinLog2(arglist, position, namespace, heap)
"log10" -> builtinLog10(arglist, position, namespace, heap)
"sqrt" -> builtinSqrt(arglist, position, namespace, heap)
"max" -> builtinMax(arglist, position, namespace, heap)
"min" -> builtinMin(arglist, position, namespace, heap)
"round" -> builtinRound(arglist, position, namespace, heap)
"rad" -> builtinRad(arglist, position, namespace, heap)
"deg" -> builtinDeg(arglist, position, namespace, heap)
"sum" -> builtinSum(arglist, position, namespace, heap)
"avg" -> builtinAvg(arglist, position, namespace, heap)
"len" -> builtinLen(arglist, position, namespace, heap)
"lsb" -> builtinLsb(arglist, position, namespace, heap)
"msb" -> builtinMsb(arglist, position, namespace, heap)
"flt" -> builtinFlt(arglist, position, namespace, heap)
"any" -> builtinAny(arglist, position, namespace, heap)
"all" -> builtinAll(arglist, position, namespace, heap)
"floor" -> builtinFloor(arglist, position, namespace, heap)
"ceil" -> builtinCeil(arglist, position, namespace, heap)
"lsl", "lsr", "rol", "rol2", "ror", "ror2", "set_carry", "clear_carry", "set_irqd", "clear_irqd" ->
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used in expressions because it doesn't return a value", position)
else -> null
}
if(withDatatypeCheck) {
val resultDt = this.resultingDatatype(namespace)
val resultDt = this.resultingDatatype(namespace, heap)
if(resultValue==null || resultDt == resultValue.type)
return resultValue
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
@ -1190,10 +1202,10 @@ class FunctionCall(override var target: IdentifierReference,
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
override fun resultingDatatype(namespace: INameScope): DataType? {
val constVal = constValue(namespace, false)
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
val constVal = constValue(namespace, heap,false)
if(constVal!=null)
return constVal.resultingDatatype(namespace)
return constVal.resultingDatatype(namespace, heap)
val stmt = target.targetStatement(namespace) ?: return null
when (stmt) {
is BuiltinFunctionStatementPlaceholder -> {
@ -1201,7 +1213,7 @@ class FunctionCall(override var target: IdentifierReference,
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return null // these have no return value
}
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace)
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace, heap)
}
is Subroutine -> {
if(stmt.returnvalues.isEmpty()) {
@ -1225,7 +1237,7 @@ class FunctionCall(override var target: IdentifierReference,
TODO("datatype of functioncall to $stmt")
}
override fun isIterable(namespace: INameScope) : Boolean {
override fun isIterable(namespace: INameScope, heap: HeapValues) : Boolean {
TODO("isIterable of function call result")
}
}

View File

@ -1,6 +1,7 @@
package prog8.ast
import prog8.compiler.CompilationOptions
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctionNames
import prog8.parser.ParsingFailedError
@ -8,8 +9,8 @@ import prog8.parser.ParsingFailedError
* General checks on the Ast
*/
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions) {
val checker = AstChecker(globalNamespace, compilerOptions)
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) {
val checker = AstChecker(globalNamespace, compilerOptions, heap)
this.process(checker)
printErrors(checker.result(), name)
}
@ -38,7 +39,9 @@ fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
}
class AstChecker(private val namespace: INameScope, private val compilerOptions: CompilationOptions) : IAstProcessor {
class AstChecker(private val namespace: INameScope,
private val compilerOptions: CompilationOptions,
private val heap: HeapValues) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf()
fun result(): List<AstException> {
@ -87,10 +90,10 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
override fun process(forLoop: ForLoop): IStatement {
if(forLoop.body.isEmpty())
printWarning("for loop body is empty", forLoop.position)
if(!forLoop.iterable.isIterable(namespace)) {
if(!forLoop.iterable.isIterable(namespace, heap)) {
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else {
val iterableDt = forLoop.iterable.resultingDatatype(namespace)
val iterableDt = forLoop.iterable.resultingDatatype(namespace, heap)
if (forLoop.loopRegister != null) {
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
// loop register
@ -281,11 +284,11 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
}
val targetDatatype = assignment.target.determineDatatype(namespace, assignment)
val constVal = assignment.value.constValue(namespace)
val constVal = assignment.value.constValue(namespace, heap)
if(constVal!=null) {
checkValueTypeAndRange(targetDatatype, null, constVal)
checkValueTypeAndRange(targetDatatype, null, constVal, heap)
} else {
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace)
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap)
if(sourceDatatype==null) {
if(assignment.value is FunctionCall)
checkResult.add(ExpressionError("function call doesn't return a value to use in assignment", assignment.value.position))
@ -340,7 +343,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
}
when {
decl.value is RangeExpr -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as RangeExpr)
decl.value is LiteralValue -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as LiteralValue)
decl.value is LiteralValue -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as LiteralValue, heap)
else -> {
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}")
return super.process(decl)
@ -433,14 +436,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) {
checkResult.add(SyntaxError("floating point value used, but floating point is not enabled via options", literalValue.position))
}
checkValueTypeAndRange(literalValue.type, null, literalValue)
checkValueTypeAndRange(literalValue.type, null, literalValue, heap)
return super.process(literalValue)
}
override fun process(expr: BinaryExpression): IExpression {
when(expr.operator){
"/", "//", "%" -> {
val numeric = expr.right.constValue(namespace)?.asNumericValue?.toDouble()
val numeric = expr.right.constValue(namespace, heap)?.asNumericValue?.toDouble()
if(numeric==0.0)
checkResult.add(ExpressionError("division by zero", expr.right.position))
}
@ -453,9 +456,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
checkResult.add(SyntaxError(msg, range.position))
}
super.process(range)
val from = range.from.constValue(namespace)
val to = range.to.constValue(namespace)
val stepLv = range.step.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position)
val from = range.from.constValue(namespace, heap)
val to = range.to.constValue(namespace, heap)
val stepLv = range.step.constValue(namespace, heap) ?: LiteralValue(DataType.BYTE, 1, position = range.position)
if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) {
err("range step must be an integer != 0")
return range
@ -555,8 +558,8 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, range: RangeExpr) : Boolean {
val from = range.from.constValue(namespace)
val to = range.to.constValue(namespace)
val from = range.from.constValue(namespace, heap)
val to = range.to.constValue(namespace, heap)
if(from==null || to==null) {
checkResult.add(SyntaxError("range from and to values must be constants", range.position))
return false
@ -596,7 +599,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
}
}
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, value: LiteralValue) : Boolean {
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, value: LiteralValue, heap: HeapValues) : Boolean {
fun err(msg: String) : Boolean {
checkResult.add(ExpressionError(msg, value.position))
return false
@ -629,37 +632,21 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
return err("value '$number' out of range for unsigned word")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = value.strvalue
?: return err("string value expected")
val str = value.strvalue ?: heap.get(value.heapId!!).str!!
if (str.isEmpty() || str.length > 255)
return err("string length must be 1 to 255")
}
DataType.ARRAY -> {
// value may be either a single byte, or a byte array
// value may be either a single byte, or a byte array (of all constant values)
if(value.type==DataType.ARRAY) {
val array = heap.get(value.heapId!!).array!!
if(arrayspec!=null) {
// arrayspec is not always known when checking
val constX = arrayspec.x.constValue(namespace)
val constX = arrayspec.x.constValue(namespace, heap)
if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue
if (value.arrayvalue!!.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
}
for (av in value.arrayvalue!!) {
if(av is LiteralValue) {
val number = av.bytevalue
?: return err("array must be all bytes")
if (number < 0 || number > 255)
return err("value '$number' in array is out of range for unsigned byte")
} else if(av is RegisterExpr) {
if(av.register!=Register.A && av.register!=Register.X && av.register!=Register.Y)
return err("register '$av' in byte array is not a single register")
} else {
val avDt = av.resultingDatatype(namespace)
if(avDt!=DataType.BYTE)
return err("array must be all bytes")
}
if (array.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${array.size})")
}
} else if(value.type==DataType.ARRAY_W) {
return err("initialization value must be an array of bytes")
@ -675,26 +662,15 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
DataType.ARRAY_W -> {
// value may be either a single word, or a word array
if(value.type==DataType.ARRAY || value.type==DataType.ARRAY_W) {
val array = heap.get(value.heapId!!).array!!
if(arrayspec!=null) {
// arrayspec is not always known when checking
val constX = arrayspec.x.constValue(namespace)
val constX = arrayspec.x.constValue(namespace, heap)
if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue
if (value.arrayvalue!!.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
}
for (av in value.arrayvalue!!) {
if(av is LiteralValue) {
val number = av.asIntegerValue
?: return err("array must be all integers")
if (number < 0 || number > 65535)
return err("value '$number' in array is out of range for unsigned word")
} else {
val avDt = av.resultingDatatype(namespace)
if(avDt!=DataType.BYTE && avDt!=DataType.WORD)
return err("array must be all integers")
}
if (array.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${array.size})")
}
} else {
val number = value.asIntegerValue ?: return if (value.floatvalue!=null)
@ -708,21 +684,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
DataType.MATRIX -> {
// value can only be a single byte, or a byte array (which represents the matrix)
if(value.type==DataType.ARRAY) {
for (av in value.arrayvalue!!) {
val number = (av as LiteralValue).bytevalue
?: return err("array must be all bytes")
val constX = arrayspec!!.x.constValue(namespace)
val constY = arrayspec.y!!.constValue(namespace)
if(constX?.asIntegerValue==null || constY?.asIntegerValue==null)
return err("matrix size specifiers must be constant integer values")
val expectedSize = constX.asIntegerValue * constY.asIntegerValue
if (value.arrayvalue.size != expectedSize)
return err("initializer matrix size mismatch (expecting $expectedSize, got ${value.arrayvalue.size} elements)")
if (number < 0 || number > 255)
return err("value '$number' in byte array is out of range for unsigned byte")
}
val constX = arrayspec!!.x.constValue(namespace, heap)
val constY = arrayspec.y!!.constValue(namespace, heap)
if(constX?.asIntegerValue==null || constY?.asIntegerValue==null)
return err("matrix size specifiers must be constant integer values")
val matrix = heap.get(value.heapId!!).array!!
val expectedSize = constX.asIntegerValue * constY.asIntegerValue
if (matrix.size != expectedSize)
return err("initializer matrix size mismatch (expecting $expectedSize, got ${matrix.size} elements)")
} else {
val number = value.bytevalue
?: return err("unsigned byte value expected")

View File

@ -24,8 +24,52 @@ fun Number.toHex(): String {
}
class HeapValues {
class HeapValue(val type: DataType, val str: String?, val array: IntArray?) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as HeapValue
return type==other.type && str==other.str && Arrays.equals(array, other.array)
}
class StackVmProgram(val name: String) {
override fun hashCode(): Int {
var result = type.hashCode()
result = 31 * result + (str?.hashCode() ?: 0)
result = 31 * result + (array?.let { Arrays.hashCode(it) } ?: 0)
return result
}
}
private val heap = mutableListOf<HeapValue>()
fun add(type: DataType, str: String): Int {
if (str.isEmpty() || str.length > 255)
throw IllegalArgumentException("string length must be 1-255")
// strings are 'interned' and shared if they're the same
val value = HeapValue(type, str, null)
val existing = heap.indexOf(value)
if(existing>=0)
return existing
heap.add(value)
return heap.size-1
}
fun add(type: DataType, array: IntArray): Int {
// arrays are never shared
heap.add(HeapValue(type, null, array))
return heap.size-1
}
fun get(heapId: Int): HeapValue = heap[heapId]
fun allStrings() = heap.asSequence().withIndex().filter { it.value.str!=null }.toList()
fun allArrays() = heap.asSequence().withIndex().filter { it.value.array!=null }.toList()
}
class StackVmProgram(val name: String, val heap: HeapValues) {
private val instructions = mutableListOf<Instruction>()
private val variables = mutableMapOf<String, MutableMap<String, Value>>()
private val memory = mutableMapOf<Int, List<Value>>()
@ -44,9 +88,19 @@ class StackVmProgram(val name: String) {
fun blockvar(scopedname: String, decl: VarDecl) {
val value = when(decl.datatype) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue)
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> Value(decl.datatype, null, (decl.value as LiteralValue).strvalue)
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> TODO("array/matrix variable values")
DataType.BYTE, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
throw CompilerException("string should already be in the heap")
Value(decl.datatype, litval.heapId)
}
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
throw CompilerException("array/matrix should already be in the heap")
Value(decl.datatype, litval.heapId)
}
}
// We keep the block structure intact: vars are stored per block. This is needed eventually for the actual 6502 code generation later...
@ -57,7 +111,7 @@ class StackVmProgram(val name: String) {
}
fun writeAsText(out: PrintStream) {
Program(name, instructions, labels, variables, memory).print(out)
Program(name, instructions, labels, variables, memory, heap).print(out)
}
fun instr(opcode: Opcode, arg: Value? = null, callLabel: String? = null) {
@ -71,7 +125,7 @@ class StackVmProgram(val name: String) {
}
fun line(position: Position) {
instructions.add(Instruction(Opcode.LINE, Value(DataType.STR, null, "${position.line} ${position.file}")))
instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}"))
}
}
@ -99,18 +153,18 @@ data class CompilationOptions(val output: OutputType,
class Compiler(private val options: CompilationOptions) {
fun compile(module: Module) : StackVmProgram {
fun compile(module: Module, heap: HeapValues) : StackVmProgram {
println("\nCreating stackVM code...")
val namespace = module.definingScope()
val intermediate = StackVmProgram(module.name)
val intermediate = StackVmProgram(module.name, heap)
// create the pool of all variables used in all blocks and scopes
// create the heap of all variables used in all blocks and scopes
val varGather = VarGatherer(intermediate)
varGather.process(module)
println(" ${intermediate.numVariables} allocated variables and constants")
val translator = StatementTranslator(intermediate, namespace)
val translator = StatementTranslator(intermediate, namespace, heap)
translator.process(module)
println(" ${translator.stmtUniqueSequenceNr} source statements, ${intermediate.numInstructions} resulting instructions")
@ -130,7 +184,9 @@ class Compiler(private val options: CompilationOptions) {
}
private class StatementTranslator(private val stackvmProg: StackVmProgram, private val namespace: INameScope): IAstProcessor {
private class StatementTranslator(private val stackvmProg: StackVmProgram,
private val namespace: INameScope,
private val heap: HeapValues): IAstProcessor {
var stmtUniqueSequenceNr = 0
private set
@ -290,8 +346,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
}
private fun checkForFloatPrecisionProblem(left: IExpression, right: IExpression) {
val leftDt = left.resultingDatatype(namespace)
val rightDt = right.resultingDatatype(namespace)
val leftDt = left.resultingDatatype(namespace, heap)
val rightDt = right.resultingDatatype(namespace, heap)
if (leftDt == DataType.BYTE || leftDt == DataType.WORD) {
if(rightDt==DataType.FLOAT)
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit flt() conversion or revert to byte/word arithmetic", left.position)
@ -301,7 +357,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
private fun translate(expr: IExpression) {
when(expr) {
is RegisterExpr -> {
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, expr.register.toString()))
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = expr.register.toString())
}
is PrefixExpression -> {
translate(expr.expression)
@ -333,14 +389,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
is VarDecl -> {
when(target.type) {
VarDeclType.VAR ->
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname))
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = target.scopedname)
VarDeclType.CONST ->
throw CompilerException("const ref should have been const-folded away")
VarDeclType.MEMORY -> {
when(target.datatype){
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH_MEM, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue))
DataType.WORD -> stackvmProg.instr(Opcode.PUSH_MEM_W, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue))
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH_MEM_F, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue))
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH_MEM, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!))
DataType.WORD -> stackvmProg.instr(Opcode.PUSH_MEM_W, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!))
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH_MEM_F, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!))
else -> TODO("invalid datatype for memory variable expression: $target")
}
}
@ -354,17 +410,13 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
TODO("TRANSLATE range $expr")
}
else -> {
val lv = expr.constValue(namespace) ?: throw CompilerException("constant expression required, not $expr")
val lv = expr.constValue(namespace, heap) ?: throw CompilerException("constant expression required, not $expr")
when(lv.type) {
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue))
DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue))
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH, Value(DataType.FLOAT, lv.floatvalue))
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> stackvmProg.instr(Opcode.PUSH, Value(DataType.STR,null, lv.strvalue))
DataType.ARRAY, DataType.ARRAY_W -> {
lv.arrayvalue?.forEach { translate(it) }
stackvmProg.instr(Opcode.ARRAY, Value(DataType.WORD, lv.arrayvalue!!.size))
}
DataType.MATRIX -> TODO("matrix type")
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue!!))
DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue!!))
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH, Value(DataType.FLOAT, lv.floatvalue!!))
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> stackvmProg.instr(Opcode.PUSH, Value(lv.type, lv.heapId!!))
}
}
}
@ -396,7 +448,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
"flt" -> {
// 1 argument, type determines the exact opcode to use
val arg = args.single()
when (arg.resultingDatatype(namespace)) {
when (arg.resultingDatatype(namespace, heap)) {
DataType.BYTE -> stackvmProg.instr(Opcode.B2FLOAT)
DataType.WORD -> stackvmProg.instr(Opcode.W2FLOAT)
DataType.FLOAT -> stackvmProg.instr(Opcode.NOP)
@ -507,14 +559,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
stackvmProg.line(stmt.position)
if(stmt.target.register!=null) {
when(stmt.operator) {
"++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
"--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
"++" -> stackvmProg.instr(Opcode.INC_VAR, callLabel = stmt.target.register.toString())
"--" -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = stmt.target.register.toString())
}
} else {
val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl
when(stmt.operator) {
"++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, targetStatement.scopedname))
"--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, targetStatement.scopedname))
"++" -> stackvmProg.instr(Opcode.INC_VAR, callLabel = targetStatement.scopedname)
"--" -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = targetStatement.scopedname)
}
}
}
@ -522,7 +574,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
private fun translate(stmt: Assignment) {
stackvmProg.line(stmt.position)
translate(stmt.value)
val valueDt = stmt.value.resultingDatatype(namespace)
val valueDt = stmt.value.resultingDatatype(namespace, heap)
val targetDt = stmt.target.determineDatatype(namespace, stmt)
if(valueDt!=targetDt) {
// convert value to target datatype if possible
@ -552,11 +604,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
if(stmt.target.identifier!=null) {
val target = stmt.target.identifier!!.targetStatement(namespace)!!
when(target) {
is VarDecl -> stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname))
is VarDecl -> stackvmProg.instr(Opcode.PUSH_VAR, callLabel = target.scopedname)
else -> throw CompilerException("invalid assignment target type ${target::class}")
}
} else if(stmt.target.register!=null) {
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = stmt.target.register.toString())
}
translateAugAssignOperator(stmt.aug_op)
}
@ -565,11 +617,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
if(stmt.target.identifier!=null) {
val target = stmt.target.identifier!!.targetStatement(namespace)!!
when(target) {
is VarDecl -> stackvmProg.instr(Opcode.POP_VAR, Value(DataType.STR, null, target.scopedname))
is VarDecl -> stackvmProg.instr(Opcode.POP_VAR, callLabel = target.scopedname)
else -> throw CompilerException("invalid assignment target type ${target::class}")
}
} else if(stmt.target.register!=null) {
stackvmProg.instr(Opcode.POP_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
stackvmProg.instr(Opcode.POP_VAR, callLabel = stmt.target.register.toString())
}
}
@ -704,32 +756,31 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
continueStmtLabelStack.push(continueLabel)
breakStmtLabelStack.push(breakLabel)
val varValue = Value(DataType.STR, null, varname)
stackvmProg.instr(Opcode.PUSH, Value(varDt, range.first))
stackvmProg.instr(Opcode.POP_VAR, varValue)
stackvmProg.instr(Opcode.POP_VAR, callLabel = varname)
stackvmProg.label(loopLabel)
translate(body)
stackvmProg.label(continueLabel)
when {
range.step==1 -> stackvmProg.instr(Opcode.INC_VAR, varValue)
range.step==-1 -> stackvmProg.instr(Opcode.DEC_VAR, varValue)
range.step==1 -> stackvmProg.instr(Opcode.INC_VAR, callLabel = varname)
range.step==-1 -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = varname)
range.step>1 -> {
stackvmProg.instr(Opcode.PUSH_VAR, varValue)
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname)
stackvmProg.instr(Opcode.PUSH, Value(varDt, range.step))
stackvmProg.instr(Opcode.ADD)
stackvmProg.instr(Opcode.POP_VAR, varValue)
stackvmProg.instr(Opcode.POP_VAR, callLabel = varname)
}
range.step<1 -> {
stackvmProg.instr(Opcode.PUSH_VAR, varValue)
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname)
stackvmProg.instr(Opcode.PUSH, Value(varDt, abs(range.step)))
stackvmProg.instr(Opcode.SUB)
stackvmProg.instr(Opcode.POP_VAR, varValue)
stackvmProg.instr(Opcode.POP_VAR, callLabel = varname)
}
}
// TODO: optimize edge cases if last value = 255 or 0 (for bytes) etc. to avoid PUSH / SUB opcodes and make use of the wrapping around of the value.
stackvmProg.instr(Opcode.PUSH, Value(varDt, range.last+range.step))
stackvmProg.instr(Opcode.PUSH_VAR, varValue)
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname)
stackvmProg.instr(Opcode.SUB)
stackvmProg.instr(Opcode.BNZ, callLabel = loopLabel)
@ -838,9 +889,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
postIncr.linkParents(range.parent)
translate(postIncr)
if(lvTarget.register!=null)
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stringvalue=lvTarget.register.toString()))
stackvmProg.instr(Opcode.PUSH_VAR, callLabel =lvTarget.register.toString())
else
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stringvalue=targetStatement!!.scopedname))
stackvmProg.instr(Opcode.PUSH_VAR, callLabel =targetStatement!!.scopedname)
val branch = BranchStatement(
BranchCondition.NZ,
listOf(Jump(null, null, loopLabel, range.position)),

View File

@ -1,6 +1,7 @@
package prog8.functions
import prog8.ast.*
import prog8.compiler.HeapValues
import kotlin.math.log2
@ -19,9 +20,9 @@ val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf(
"_vm_write_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text")
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope): DataType? {
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope, heap: HeapValues): DataType? {
fun integerDatatypeFromArg(arg: IExpression): DataType {
val dt = arg.resultingDatatype(namespace)
val dt = arg.resultingDatatype(namespace, heap)
return when(dt) {
DataType.BYTE -> DataType.BYTE
DataType.WORD -> DataType.WORD
@ -33,7 +34,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
fun datatypeFromListArg(arglist: IExpression): DataType {
if(arglist is LiteralValue) {
if(arglist.type==DataType.ARRAY || arglist.type==DataType.ARRAY_W || arglist.type==DataType.MATRIX) {
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace)}
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)}
if(dt.any { it!=DataType.BYTE && it!=DataType.WORD && it!=DataType.FLOAT}) {
throw FatalAstException("fuction $function only accepts array of numeric values")
}
@ -51,7 +52,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
"lsb", "msb", "any", "all", "rnd" -> DataType.BYTE
"rndw" -> DataType.WORD
"rol", "rol2", "ror", "ror2", "lsl", "lsr", "set_carry", "clear_carry", "set_irqd", "clear_irqd" -> null // no return value so no datatype
"abs" -> args.single().resultingDatatype(namespace)
"abs" -> args.single().resultingDatatype(namespace, heap)
"max", "min" -> datatypeFromListArg(args.single())
"round", "floor", "ceil" -> integerDatatypeFromArg(args.single())
"sum" -> {
@ -96,10 +97,10 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
class NotConstArgumentException: AstException("not a const argument to a built-in function")
private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position)
@ -107,10 +108,10 @@ private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:
return numericLiteral(function(float), args[0].position)
}
private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val float: Double = when(constval.type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> constval.asNumericValue!!.toDouble()
else -> throw SyntaxError("built-in function requires one floating point argument", position)
@ -118,10 +119,10 @@ private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position, n
return numericLiteral(function(float).toInt(), args[0].position)
}
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Int)->Number): LiteralValue {
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Int)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
if(constval.type!=DataType.BYTE && constval.type!=DataType.WORD)
throw SyntaxError("built-in function requires one integer argument", position)
@ -129,95 +130,117 @@ private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, name
return numericLiteral(function(integer).toInt(), args[0].position)
}
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position, namespace:INameScope,
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
namespace:INameScope, heap: HeapValues,
function: (arg: Collection<Double>)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace)
if(iterable?.arrayvalue == null)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
val result = function(constants.map { it!!.toDouble() }).toDouble()
var iterable = args[0].constValue(namespace, heap)
if(iterable==null) {
if(args[0] !is IdentifierReference)
throw SyntaxError("function over weird argument ${args[0]}", position)
iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap)
?: throw SyntaxError("function over weird argument ${args[0]}", position)
}
val result = if(iterable.arrayvalue != null) {
val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
function(constants.map { it!!.toDouble() }).toDouble()
} else {
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array/matrix argument", position)
function(array.map { it.toDouble() })
}
return numericLiteral(result, args[0].position)
}
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position, namespace:INameScope,
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
namespace:INameScope, heap: HeapValues,
function: (arg: Collection<Double>)->Boolean): LiteralValue {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace)
if(iterable?.arrayvalue == null)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
val result = function(constants.map { it?.toDouble()!! })
var iterable = args[0].constValue(namespace, heap)
if(iterable==null) {
if(args[0] !is IdentifierReference)
throw SyntaxError("function over weird argument ${args[0]}", position)
iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap)
?: throw SyntaxError("function over weird argument ${args[0]}", position)
}
val result = if(iterable.arrayvalue != null) {
val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
function(constants.map { it!!.toDouble() })
} else {
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array/matrix argument", position)
function(array.map { it.toDouble() })
}
return LiteralValue.fromBoolean(result, position)
}
fun builtinRound(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, Math::round)
fun builtinRound(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, heap, Math::round)
fun builtinFloor(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, Math::floor)
fun builtinFloor(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, heap, Math::floor)
fun builtinCeil(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, Math::ceil)
fun builtinCeil(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArgOutputInt(args, position, namespace, heap, Math::ceil)
fun builtinSin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::sin)
fun builtinSin(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::sin)
fun builtinCos(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::cos)
fun builtinCos(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::cos)
fun builtinAcos(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::acos)
fun builtinAcos(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::acos)
fun builtinAsin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::asin)
fun builtinAsin(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::asin)
fun builtinTan(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::tan)
fun builtinTan(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::tan)
fun builtinAtan(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::atan)
fun builtinAtan(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::atan)
fun builtinLn(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::log)
fun builtinLn(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::log)
fun builtinLog2(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, ::log2)
fun builtinLog2(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, ::log2)
fun builtinLog10(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::log10)
fun builtinLog10(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::log10)
fun builtinSqrt(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::sqrt)
fun builtinSqrt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::sqrt)
fun builtinRad(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::toRadians)
fun builtinRad(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::toRadians)
fun builtinDeg(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneDoubleArg(args, position, namespace, Math::toDegrees)
fun builtinDeg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneDoubleArg(args, position, namespace, heap, Math::toDegrees)
fun builtinFlt(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
fun builtinFlt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
// 1 numeric arg, convert to float
if(args.size!=1)
throw SyntaxError("flt requires one numeric argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val number = constval.asNumericValue ?: throw SyntaxError("flt requires one numeric argument", position)
return LiteralValue(DataType.FLOAT, floatvalue = number.toDouble(), position = position)
}
fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
// 1 arg, type = float or int, result type= same as argument type
if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val number = constval.asNumericValue
return when (number) {
is Int, is Byte, is Short -> numericLiteral(Math.abs(number.toInt()), args[0].position)
@ -227,50 +250,73 @@ fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope
}
fun builtinLsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 }
fun builtinLsb(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneIntArgOutputInt(args, position, namespace, heap) { x: Int -> x and 255 }
fun builtinMsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 8 and 255}
fun builtinMsb(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= oneIntArgOutputInt(args, position, namespace, heap) { x: Int -> x ushr 8 and 255}
fun builtinMin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputNumber(args, position, namespace) { it.min()!! }
fun builtinMin(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= collectionArgOutputNumber(args, position, namespace, heap) { it.min()!! }
fun builtinMax(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputNumber(args, position, namespace) { it.max()!! }
fun builtinMax(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= collectionArgOutputNumber(args, position, namespace, heap) { it.max()!! }
fun builtinSum(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputNumber(args, position, namespace) { it.sum() }
fun builtinSum(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= collectionArgOutputNumber(args, position, namespace, heap) { it.sum() }
fun builtinAvg(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
fun builtinAvg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
if(args.size!=1)
throw SyntaxError("avg requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace)
if(iterable?.arrayvalue == null)
throw SyntaxError("avg requires one non-scalar argument", position)
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
val result = (constants.map { it!!.toDouble() }).average()
throw SyntaxError("avg requires array/matrix argument", position)
var iterable = args[0].constValue(namespace, heap)
if(iterable==null) {
if(args[0] !is IdentifierReference)
throw SyntaxError("avg over weird argument ${args[0]}", position)
iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap)
?: throw SyntaxError("avg over weird argument ${args[0]}", position)
}
val result = if(iterable.arrayvalue!=null) {
val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue }
if (constants.contains(null))
throw NotConstArgumentException()
(constants.map { it!!.toDouble() }).average()
}
else {
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("avg requires array/matrix argument", position)
array.average()
}
return numericLiteral(result, args[0].position)
}
fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val argument = args[0].constValue(namespace) ?: throw NotConstArgumentException()
var argument = args[0].constValue(namespace, heap)
if(argument==null) {
if(args[0] !is IdentifierReference)
throw SyntaxError("len over weird argument ${args[0]}", position)
argument = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap)
?: throw SyntaxError("len over weird argument ${args[0]}", position)
}
return when(argument.type) {
DataType.ARRAY, DataType.ARRAY_W -> numericLiteral(argument.arrayvalue!!.size, args[0].position)
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> numericLiteral(argument.strvalue!!.length, args[0].position)
DataType.ARRAY, DataType.ARRAY_W -> {
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).array!!.size
numericLiteral(arraySize, args[0].position)
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = argument.strvalue ?: heap.get(argument.heapId!!).str!!
numericLiteral(str.length, args[0].position)
}
else -> throw SyntaxError("len of weird argument ${args[0]}", position)
}
}
fun builtinAny(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputBoolean(args, position, namespace) { it.any { v -> v != 0.0} }
fun builtinAny(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= collectionArgOutputBoolean(args, position, namespace, heap) { it.any { v -> v != 0.0} }
fun builtinAll(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputBoolean(args, position, namespace) { it.all { v -> v != 0.0} }
fun builtinAll(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
= collectionArgOutputBoolean(args, position, namespace, heap) { it.all { v -> v != 0.0} }
private fun numericLiteral(value: Number, position: Position): LiteralValue {

View File

@ -184,7 +184,7 @@ class ConstExprEvaluator {
}
private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue {
val error = "cannot multiply $left and $right"
val error = "cannot multiply ${left.type} and ${right.type}"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position)
@ -200,13 +200,6 @@ class ConstExprEvaluator {
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.floatvalue, position = left.position)
else -> throw ExpressionError(error, left.position)
}
left.strvalue!=null -> when {
right.asIntegerValue!=null -> {
if(left.strvalue.length * right.asIntegerValue > 255) throw ExpressionError("string too long", left.position)
LiteralValue(DataType.STR, strvalue = left.strvalue.repeat(right.asIntegerValue), position = left.position)
}
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
}
}

View File

@ -1,10 +1,11 @@
package prog8.optimizing
import prog8.ast.*
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
class ConstantFolding(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf()
@ -94,8 +95,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
*/
override fun process(identifier: IdentifierReference): IExpression {
return try {
val cval = identifier.constValue(namespace) ?: return identifier
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, cval.strvalue, cval.arrayvalue, identifier.position)
val cval = identifier.constValue(namespace, heap) ?: return identifier
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, cval.strvalue, cval.arrayvalue, position=identifier.position)
copy.parent = identifier.parent
return copy
} catch (ax: AstException) {
@ -107,7 +108,7 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
override fun process(functionCall: FunctionCall): IExpression {
return try {
super.process(functionCall)
functionCall.constValue(namespace) ?: functionCall
functionCall.constValue(namespace, heap) ?: functionCall
} catch (ax: AstException) {
addError(ax)
functionCall
@ -183,8 +184,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
override fun process(expr: BinaryExpression): IExpression {
return try {
super.process(expr)
val leftconst = expr.left.constValue(namespace)
val rightconst = expr.right.constValue(namespace)
val leftconst = expr.left.constValue(namespace, heap)
val rightconst = expr.right.constValue(namespace, heap)
val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression
@ -192,8 +193,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
else -> null
}
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(namespace)
val subrightconst = subExpr.right.constValue(namespace)
val subleftconst = subExpr.left.constValue(namespace, heap)
val subrightconst = subExpr.right.constValue(namespace, heap)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null))
// try reordering.
return groupTwoConstsTogether(expr, subExpr,
@ -286,12 +287,23 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
}
override fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.arrayvalue!=null) {
if(literalValue.strvalue!=null) {
// intern the string; move it into the heap
if(literalValue.strvalue.length !in 1..255)
addError(ExpressionError("string literal length must be between 1 and 255", literalValue.position))
else {
val heapId = heap.add(literalValue.type, literalValue.strvalue) // TODO: we don't know the actual string type yet, STR != STR_P etc...
val newValue = LiteralValue(literalValue.type, heapId = heapId, position = literalValue.position)
return super.process(newValue)
}
} else if(literalValue.arrayvalue!=null) {
val newArray = literalValue.arrayvalue.map { it.process(this) }.toTypedArray()
// determine if the values are all bytes or that we need a word array instead
var arrayDt = DataType.ARRAY
var allElementsAreConstant = true
for (expr in newArray) {
val valueDt = expr.resultingDatatype(namespace)
allElementsAreConstant = allElementsAreConstant and (expr is LiteralValue)
val valueDt = expr.resultingDatatype(namespace, heap)
if(valueDt==DataType.BYTE)
continue
else {
@ -299,6 +311,37 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
break
}
}
// if the values are all constants, the array is moved to the heap
if(allElementsAreConstant) {
val array = newArray.map {
val litval = it as? LiteralValue
if(litval==null) {
addError(ExpressionError("array elements must all be constants", literalValue.position))
return super.process(literalValue)
}
if(litval.bytevalue==null && litval.wordvalue==null) {
if(arrayDt==DataType.ARRAY)
addError(ExpressionError("byte array elements must all be integers 0..255", literalValue.position))
else
addError(ExpressionError("word array elements must all be integers 0..65535", literalValue.position))
return super.process(literalValue)
}
val integer = litval.asIntegerValue!!
if(arrayDt==DataType.ARRAY && integer !in 0..255) {
addError(ExpressionError("byte array elements must all be integers 0..255", literalValue.position))
return super.process(literalValue)
} else if(arrayDt==DataType.ARRAY_W && integer !in 0..65535) {
addError(ExpressionError("word array elements must all be integers 0..65535", literalValue.position))
return super.process(literalValue)
}
integer
}.toIntArray()
val heapId = heap.add(arrayDt, array)
val newValue = LiteralValue(arrayDt, heapId=heapId, position = literalValue.position)
return super.process(newValue)
}
val newValue = LiteralValue(arrayDt, arrayvalue = newArray, position = literalValue.position)
return super.process(newValue)
}

View File

@ -3,17 +3,12 @@ package prog8.optimizing
import prog8.ast.AstException
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.compiler.HeapValues
import prog8.parser.ParsingFailedError
/**
* TODO: array, matrix, string and float constants should be put into a constant-pool,
* so that they're only stored once instead of replicated everywhere.
* Note that initial constant folding of them is fine: it's needed to be able to
* optimize the expressions. But as a final step, they should be consolidated again
*/
fun Module.constantFold(globalNamespace: INameScope) {
val optimizer = ConstantFolding(globalNamespace)
fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) {
val optimizer = ConstantFolding(globalNamespace, heap)
try {
this.process(optimizer)
} catch (ax: AstException) {
@ -35,8 +30,8 @@ fun Module.constantFold(globalNamespace: INameScope) {
}
fun Module.optimizeStatements(globalNamespace: INameScope): Int {
val optimizer = StatementOptimizer(globalNamespace)
fun Module.optimizeStatements(globalNamespace: INameScope, heap: HeapValues): Int {
val optimizer = StatementOptimizer(globalNamespace, heap)
this.process(optimizer)
if(optimizer.optimizationsDone > 0)
println("[${this.name}] Debug: ${optimizer.optimizationsDone} statement optimizations performed")
@ -44,8 +39,8 @@ fun Module.optimizeStatements(globalNamespace: INameScope): Int {
return optimizer.optimizationsDone
}
fun Module.simplifyExpressions(namespace: INameScope) : Int {
val optimizer = SimplifyExpressions(namespace)
fun Module.simplifyExpressions(namespace: INameScope, heap: HeapValues) : Int {
val optimizer = SimplifyExpressions(namespace, heap)
this.process(optimizer)
if(optimizer.optimizationsDone > 0)
println("[${this.name}] Debug: ${optimizer.optimizationsDone} expression optimizations performed")

View File

@ -1,6 +1,7 @@
package prog8.optimizing
import prog8.ast.*
import prog8.compiler.HeapValues
import kotlin.math.abs
/*
@ -18,7 +19,7 @@ import kotlin.math.abs
*/
class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
class SimplifyExpressions(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
var optimizationsDone: Int = 0
override fun process(assignment: Assignment): IStatement {
@ -30,8 +31,8 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
override fun process(expr: BinaryExpression): IExpression {
super.process(expr)
val leftVal = expr.left.constValue(namespace)
val rightVal = expr.right.constValue(namespace)
val leftVal = expr.left.constValue(namespace, heap)
val rightVal = expr.right.constValue(namespace, heap)
val constTrue = LiteralValue.fromBoolean(true, expr.position)
val constFalse = LiteralValue.fromBoolean(false, expr.position)
@ -121,9 +122,9 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
expr.left = expr.right
expr.right = tmp
optimizationsDone++
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(namespace), leftVal)
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(namespace, heap), leftVal)
}
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(namespace))
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(namespace, heap))
}
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression {
@ -284,13 +285,13 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
}
}
if (expr.left.resultingDatatype(namespace) == DataType.BYTE) {
if (expr.left.resultingDatatype(namespace, heap) == DataType.BYTE) {
if(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) {
optimizationsDone++
return LiteralValue(DataType.BYTE, 0, position = expr.position)
}
}
else if (expr.left.resultingDatatype(namespace) == DataType.WORD) {
else if (expr.left.resultingDatatype(namespace, heap) == DataType.WORD) {
if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) {
optimizationsDone++
return LiteralValue(DataType.BYTE, 0, position = expr.position)

View File

@ -1,6 +1,7 @@
package prog8.optimizing
import prog8.ast.*
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctionNames
import prog8.functions.BuiltinFunctionsWithoutSideEffects
@ -9,6 +10,7 @@ import prog8.functions.BuiltinFunctionsWithoutSideEffects
todo remove unused blocks
todo remove unused variables
todo remove unused subroutines
todo remove unused strings and arrays from the heap
todo remove if statements with empty statement blocks
todo replace if statements with only else block
todo statement optimization: create augmented assignment from assignment that only refers to its lvalue (A=A+10, A=4*A, ...)
@ -23,7 +25,7 @@ import prog8.functions.BuiltinFunctionsWithoutSideEffects
todo inline subroutines that are "sufficiently small"
*/
class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcessor {
class StatementOptimizer(private val globalNamespace: INameScope, private val heap: HeapValues) : IAstProcessor {
var optimizationsDone: Int = 0
private set
@ -43,7 +45,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
override fun process(ifStatement: IfStatement): IStatement {
super.process(ifStatement)
val constvalue = ifStatement.condition.constValue(globalNamespace)
val constvalue = ifStatement.condition.constValue(globalNamespace, heap)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
@ -75,7 +77,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
override fun process(whileLoop: WhileLoop): IStatement {
super.process(whileLoop)
val constvalue = whileLoop.condition.constValue(globalNamespace)
val constvalue = whileLoop.condition.constValue(globalNamespace, heap)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true
@ -92,7 +94,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
override fun process(repeatLoop: RepeatLoop): IStatement {
super.process(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(globalNamespace)
val constvalue = repeatLoop.untilCondition.constValue(globalNamespace, heap)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only the statement block

View File

@ -1,13 +1,12 @@
// Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7
package prog8.parser;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.atn.*;
import org.antlr.v4.runtime.atn.ATN;
import org.antlr.v4.runtime.atn.ATNDeserializer;
import org.antlr.v4.runtime.atn.LexerATNSimulator;
import org.antlr.v4.runtime.atn.PredictionContextCache;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.misc.*;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
public class prog8Lexer extends Lexer {

View File

@ -1,13 +1,15 @@
// Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7
package prog8.parser;
import org.antlr.v4.runtime.atn.*;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.misc.*;
import org.antlr.v4.runtime.tree.*;
import org.antlr.v4.runtime.atn.ATN;
import org.antlr.v4.runtime.atn.ATNDeserializer;
import org.antlr.v4.runtime.atn.ParserATNSimulator;
import org.antlr.v4.runtime.atn.PredictionContextCache;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
public class prog8Parser extends Parser {

View File

@ -0,0 +1,328 @@
package prog8.stackvm
import prog8.ast.DataType
import prog8.compiler.HeapValues
import java.io.File
import java.io.PrintStream
import java.util.*
import java.util.regex.Pattern
class Program (val name: String,
prog: MutableList<Instruction>,
val labels: Map<String, Instruction>,
val variables: Map<String, Map<String, Value>>,
val memory: Map<Int, List<Value>>,
val heap: HeapValues)
{
companion object {
fun load(filename: String): Program {
val lines = File(filename).readLines().withIndex().iterator()
val memory = mutableMapOf<Int, List<Value>>()
val vars = mutableMapOf<String, MutableMap<String, Value>>()
var instructions = mutableListOf<Instruction>()
var labels = mapOf<String, Instruction>()
val heap = HeapValues()
while(lines.hasNext()) {
val (lineNr, line) = lines.next()
if(line.startsWith(';') || line.isEmpty())
continue
else if(line=="%memory")
loadMemory(lines, memory)
else if(line=="%heap")
loadHeap(lines, heap)
else if(line=="%variables")
loadVars(lines, vars)
else if(line=="%instructions") {
val (insResult, labelResult) = loadInstructions(lines, heap)
instructions = insResult
labels = labelResult
}
else throw VmExecutionException("syntax error at line ${lineNr + 1}")
}
return Program(filename, instructions, labels, vars, memory, heap)
}
private fun loadHeap(lines: Iterator<IndexedValue<String>>, heap: HeapValues) {
val splitpattern = Pattern.compile("\\s+")
val heapvalues = mutableListOf<Triple<Int, DataType, String>>()
while(true) {
val (lineNr, line) = lines.next()
if (line == "%end_heap")
break
val parts = line.split(splitpattern, limit=3)
val value = Triple(parts[0].toInt(), DataType.valueOf(parts[1].toUpperCase()), parts[2])
heapvalues.add(value)
}
heapvalues.sortedBy { it.first }.forEach {
when(it.second) {
DataType.STR,
DataType.STR_P,
DataType.STR_S,
DataType.STR_PS -> heap.add(it.second, unescape(it.third.substring(1, it.third.length-1)))
DataType.ARRAY,
DataType.ARRAY_W,
DataType.MATRIX -> {
val numbers = it.third.substring(1, it.third.length-1).split(',')
val intarray = numbers.map{it.trim().toInt()}.toIntArray()
heap.add(it.second, intarray)
}
else -> throw VmExecutionException("invalid heap value type $it.second")
}
}
}
private fun loadInstructions(lines: Iterator<IndexedValue<String>>, heap: HeapValues): Pair<MutableList<Instruction>, Map<String, Instruction>> {
val instructions = mutableListOf<Instruction>()
val labels = mutableMapOf<String, Instruction>()
val splitpattern = Pattern.compile("\\s+")
val nextInstructionLabels = Stack<String>() // more than one label can occur on the same line
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_instructions")
return Pair(instructions, labels)
if(!line.startsWith(' ') && line.endsWith(':')) {
nextInstructionLabels.push(line.substring(0, line.length-1))
} else if(line.startsWith(' ')) {
val parts = line.trimStart().split(splitpattern, limit = 2)
val opcodeStr = parts[0].toUpperCase()
val opcode= Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr)
val args = if(parts.size==2) parts[1] else null
val instruction = when(opcode) {
Opcode.LINE -> Instruction(opcode, null, callLabel = args)
Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS,
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC -> {
if(args!!.startsWith('$')) {
Instruction(opcode, Value(DataType.WORD, args.substring(1).toInt(16)))
} else {
Instruction(opcode, callLabel = args)
}
}
Opcode.INC_VAR, Opcode.DEC_VAR,
Opcode.SHR_VAR, Opcode.SHL_VAR, Opcode.ROL_VAR, Opcode.ROR_VAR,
Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR -> {
val withoutQuotes =
if(args!!.startsWith('"') && args.endsWith('"'))
args.substring(1, args.length-1) else args
Instruction(opcode, callLabel = withoutQuotes)
}
Opcode.SYSCALL -> {
val call = Syscall.valueOf(args!!)
Instruction(opcode, Value(DataType.BYTE, call.callNr))
}
else -> {
Instruction(opcode, getArgValue(args, heap))
}
}
instructions.add(instruction)
while(nextInstructionLabels.isNotEmpty()) {
val label = nextInstructionLabels.pop()
labels[label] = instruction
}
} else throw VmExecutionException("syntax error at line ${lineNr + 1}")
}
}
private fun getArgValue(args: String?, heap: HeapValues): Value? {
if(args==null)
return null
if(args[0]=='"' && args[args.length-1]=='"') {
throw VmExecutionException("encountered a string arg value, but all strings should already have been moved into the heap")
}
val (type, valueStr) = args.split(':')
return when(type) {
"b" -> Value(DataType.BYTE, valueStr.toShort(16))
"w" -> Value(DataType.WORD, valueStr.toInt(16))
"f" -> Value(DataType.FLOAT, valueStr.toDouble())
"heap" -> {
val heapId = valueStr.toInt()
Value(heap.get(heapId).type, heapId)
}
else -> throw VmExecutionException("invalid datatype $type")
}
}
private fun loadVars(lines: Iterator<IndexedValue<String>>,
vars: MutableMap<String, MutableMap<String, Value>>): Map<String, Map<String, Value>> {
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_variables")
return vars
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
if(valueStr[0] !='"' && !valueStr.contains(':'))
throw VmExecutionException("missing value type character")
val type = DataType.valueOf(typeStr.toUpperCase())
val value = when(type) {
DataType.BYTE -> Value(DataType.BYTE, valueStr.substring(2).toShort(16))
DataType.WORD -> Value(DataType.WORD, valueStr.substring(2).toInt(16))
DataType.FLOAT -> Value(DataType.FLOAT, valueStr.substring(2).toDouble())
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
else if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid string value, should be a heap reference")
else {
val heapId = valueStr.substring(5).toInt()
Value(type, heapId)
}
}
DataType.ARRAY,
DataType.ARRAY_W,
DataType.MATRIX -> {
if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid array/matrix value, should be a heap reference")
else {
val heapId = valueStr.substring(5).toInt()
Value(type, heapId)
}
}
}
val blockname = name.substringBefore('.')
val blockvars = vars[blockname] ?: mutableMapOf()
vars[blockname] = blockvars
blockvars[name] = value
}
}
private fun unescape(st: String): String {
val result = mutableListOf<Char>()
val iter = st.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'b' -> '\b'
'n' -> '\n'
'r' -> '\r'
't' -> '\t'
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
else -> throw VmExecutionException("invalid escape char: $ec")
})
} else {
result.add(c)
}
}
return result.joinToString("")
}
private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<Value>>): Map<Int, List<Value>> {
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_memory")
return memory
val address = line.substringBefore(' ').toInt(16)
val rest = line.substringAfter(' ').trim()
if(rest.startsWith('"')) {
TODO("memory init with char/string")
} else {
val valueStrings = rest.split(' ')
val values = mutableListOf<Value>()
valueStrings.forEach {
when(it.length) {
2 -> values.add(Value(DataType.BYTE, it.toShort(16)))
4 -> values.add(Value(DataType.WORD, it.toInt(16)))
else -> throw VmExecutionException("invalid value at line $lineNr+1")
}
}
memory[address] = values
}
}
}
}
val program: List<Instruction>
init {
prog.add(Instruction(Opcode.TERMINATE))
prog.add(Instruction(Opcode.NOP))
program = prog
connect()
}
private fun connect() {
val it1 = program.iterator()
val it2 = program.iterator()
it2.next()
while(it1.hasNext() && it2.hasNext()) {
val instr = it1.next()
val nextInstr = it2.next()
when(instr.opcode) {
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
Opcode.JUMP -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support JUMP to memory address")
} else {
// jump to label
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = target
}
}
Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support branch to memory address")
} else {
// branch to label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr
}
}
Opcode.CALL -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support CALL to memory address")
} else {
// call label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr // instruction to return to
}
}
else -> instr.next = nextInstr
}
}
}
fun print(out: PrintStream, embeddedLabels: Boolean=true) {
out.println("; stackVM program code for '$name'")
out.println("%memory")
if(memory.isNotEmpty()) {
TODO("print out initial memory load")
}
out.println("%end_memory")
out.println("%heap")
heap.allStrings().forEach {
out.println("${it.index} ${it.value.type.toString().toLowerCase()} \"${it.value.str}\"")
}
heap.allArrays().forEach {
out.println("${it.index} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}")
}
out.println("%end_heap")
out.println("%variables")
// just flatten all block vars into one global list for now...
for(variable in variables.flatMap { e->e.value.entries}) {
val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr")
}
out.println("%end_variables")
out.println("%instructions")
val labels = this.labels.entries.associateBy({it.value}) {it.key}
for(instr in this.program) {
if(!embeddedLabels) {
val label = labels[instr]
if (label != null)
out.println("$label:")
} else {
out.println(instr)
}
}
out.println("%end_instructions")
}
}

View File

@ -1,11 +1,11 @@
package prog8.stackvm
import prog8.ast.DataType
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import java.io.File
import java.io.PrintStream
import java.util.*
import java.util.regex.Pattern
import kotlin.math.*
enum class Opcode {
@ -17,7 +17,6 @@ enum class Opcode {
PUSH_MEM_F, // push float value from memory to stack
PUSH_VAR, // push a variable
DUP, // push topmost value once again
ARRAY, // create arrayvalue of number of integer values on the stack, given in argument
// popping values off the (evaluation) stack, possibly storing them in another location
DISCARD, // discard top value
@ -165,373 +164,6 @@ enum class Syscall(val callNr: Short) {
}
class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=null, val arrayvalue: IntArray?=null) {
private var byteval: Short? = null
private var wordval: Int? = null
private var floatval: Double? = null
val asBooleanValue: Boolean
init {
when(type) {
DataType.BYTE -> {
byteval = (numericvalue!!.toInt() and 255).toShort() // byte wrap around 0..255
asBooleanValue = byteval != (0.toShort())
}
DataType.WORD -> {
wordval = numericvalue!!.toInt() and 65535 // word wrap around 0..65535
asBooleanValue = wordval != 0
}
DataType.FLOAT -> {
floatval = numericvalue!!.toDouble()
asBooleanValue = floatval != 0.0
}
DataType.ARRAY, DataType.ARRAY_W -> {
asBooleanValue = arrayvalue!!.isNotEmpty()
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type")
asBooleanValue = stringvalue.isNotEmpty()
}
else -> throw VmExecutionException("invalid datatype $type")
}
}
override fun toString(): String {
return when(type) {
DataType.BYTE -> "b:%02x".format(byteval)
DataType.WORD -> "w:%04x".format(wordval)
DataType.FLOAT -> "f:$floatval"
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> "\"$stringvalue\""
DataType.ARRAY -> TODO("tostring array")
DataType.ARRAY_W -> TODO("tostring word array")
DataType.MATRIX -> TODO("tostring matrix")
}
}
fun numericValue(): Number {
return when(type) {
DataType.BYTE -> byteval!!
DataType.WORD-> wordval!!
DataType.FLOAT -> floatval!!
else -> throw VmExecutionException("invalid datatype for numeric value: $type")
}
}
fun integerValue(): Int {
return when(type) {
DataType.BYTE -> byteval!!.toInt()
DataType.WORD-> wordval!!
DataType.FLOAT -> throw VmExecutionException("float to integer loss of precision")
else -> throw VmExecutionException("invalid datatype for integer value: $type")
}
}
override fun hashCode(): Int {
val bh = byteval?.hashCode() ?: 0x10001234
val wh = wordval?.hashCode() ?: 0x01002345
val fh = floatval?.hashCode() ?: 0x00103456
val ah = arrayvalue?.hashCode() ?: 0x11119876
return bh xor wh xor fh xor ah xor type.hashCode()
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is Value)
return false
return compareTo(other)==0 // note: datatype doesn't matter
}
operator fun compareTo(other: Value): Int {
return when(type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> {
when(other.type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> {
numericValue().toDouble().compareTo(other.numericValue().toDouble())
}
else -> throw VmExecutionException("comparison can only be done between two numeric values")
}
}
else -> throw VmExecutionException("comparison can only be done between two numeric values")
}
}
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): Value {
if(result.toDouble() < 0 ) {
return when(leftDt) {
DataType.BYTE -> {
// BYTE can become WORD if right operand is WORD, or when value is too large for byte
when(rightDt) {
DataType.BYTE -> Value(DataType.BYTE, result.toInt() and 255)
DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535)
DataType.FLOAT -> throw VmExecutionException("floating point loss of precision")
else -> throw VmExecutionException("$op on non-numeric result type")
}
}
DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("$op on non-numeric type")
}
}
return when(leftDt) {
DataType.BYTE -> {
// BYTE can become WORD if right operand is WORD, or when value is too large for byte
if(result.toDouble() >= 256)
return Value(DataType.WORD, result)
when(rightDt) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> throw VmExecutionException("floating point loss of precision")
else -> throw VmExecutionException("$op on non-numeric result type")
}
}
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("$op on non-numeric type")
}
}
fun add(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return arithResult(type, result, other.type, "add")
}
fun sub(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return arithResult(type, result, other.type, "sub")
}
fun mul(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return arithResult(type, result, other.type, "mul")
}
fun div(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if(v2.toDouble()==0.0) {
if (type == DataType.BYTE)
return Value(DataType.BYTE, 255)
else if(type == DataType.WORD)
return Value(DataType.WORD, 65535)
}
val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result!
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("div on non-numeric type")
}
}
fun floordiv(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = floor(v1.toDouble() / v2.toDouble())
// NOTE: integer division returns integer result!
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("div on non-numeric type")
}
}
fun remainder(other: Value): Value? {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type,"pow")
}
fun shl(): Value {
val v = integerValue()
return Value(type, v shl 1)
}
fun shr(): Value {
val v = integerValue()
return Value(type, v ushr 1)
}
fun rol(carry: Boolean): Pair<Value, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
Pair(Value(DataType.BYTE, newval), newCarry)
}
DataType.WORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
Pair(Value(DataType.WORD, newval), newCarry)
}
else -> throw VmExecutionException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<Value, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
Pair(Value(DataType.BYTE, newval), newCarry)
}
DataType.WORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
Pair(Value(DataType.WORD, newval), newCarry)
}
else -> throw VmExecutionException("ror2 can only work on byte/word")
}
}
fun rol2(): Value {
// 8 or 16 bit rotate left
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
Value(DataType.BYTE, newval)
}
DataType.WORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
Value(DataType.WORD, newval)
}
else -> throw VmExecutionException("rol2 can only work on byte/word")
}
}
fun ror2(): Value {
// 8 or 16 bit rotate right
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
Value(DataType.BYTE, newval)
}
DataType.WORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
Value(DataType.WORD, newval)
}
else -> throw VmExecutionException("ror2 can only work on byte/word")
}
}
fun neg(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, -(byteval!!))
DataType.WORD -> Value(DataType.WORD, -(wordval!!))
DataType.FLOAT -> Value(DataType.FLOAT, -(floatval)!!)
else -> throw VmExecutionException("neg can only work on byte/word/float")
}
}
fun bitand(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 and v2
return Value(type, result)
}
fun bitor(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 or v2
return Value(type, result)
}
fun bitxor(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 xor v2
return Value(type, result)
}
fun and(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue && other.asBooleanValue) 1 else 0)
fun or(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue || other.asBooleanValue) 1 else 0)
fun xor(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue xor other.asBooleanValue) 1 else 0)
fun not() = Value(DataType.BYTE, if(this.asBooleanValue) 0 else 1)
fun inv(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv() and 255)
DataType.WORD -> Value(DataType.WORD, wordval!!.inv() and 65535)
else -> throw VmExecutionException("inv can only work on byte/word")
}
}
fun inc(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, (byteval!! + 1) and 255)
DataType.WORD -> Value(DataType.WORD, (wordval!! + 1) and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1)
else -> throw VmExecutionException("inc can only work on byte/word/float")
}
}
fun dec(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, (byteval!! - 1) and 255)
DataType.WORD -> Value(DataType.WORD, (wordval!! - 1) and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1)
else -> throw VmExecutionException("dec can only work on byte/word/float")
}
}
fun lsb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!)
DataType.WORD -> Value(DataType.BYTE, wordval!! and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
}
fun msb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, 0)
DataType.WORD -> Value(DataType.BYTE, wordval!! ushr 8 and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
}
}
open class Instruction(val opcode: Opcode,
val arg: Value? = null,
val callLabel: String? = null)
@ -547,14 +179,14 @@ open class Instruction(val opcode: Opcode,
Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR)
val result =
when {
opcode==Opcode.LINE -> "_line ${arg!!.stringvalue}"
opcode==Opcode.LINE -> "_line $callLabel"
opcode==Opcode.SYSCALL -> {
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
"syscall $syscall"
}
opcodesWithVarArgument.contains(opcode) -> {
// opcodes that manipulate a variable
"${opcode.toString().toLowerCase()} ${argStr.substring(1, argStr.length-1)}"
"${opcode.toString().toLowerCase()} $callLabel"
}
callLabel==null -> "${opcode.toString().toLowerCase()} $argStr"
else -> "${opcode.toString().toLowerCase()} $callLabel $argStr"
@ -589,269 +221,6 @@ class MyStack<T> : Stack<T>() {
}
}
class Program (val name: String,
prog: MutableList<Instruction>,
val labels: Map<String, Instruction>,
val variables: Map<String, Map<String, Value>>,
val memory: Map<Int, List<Value>>)
{
companion object {
fun load(filename: String): Program {
val lines = File(filename).readLines().withIndex().iterator()
var memory = mapOf<Int, List<Value>>()
var vars = mapOf<String, Map<String, Value>>()
var instructions = mutableListOf<Instruction>()
var labels = mapOf<String, Instruction>()
while(lines.hasNext()) {
val (lineNr, line) = lines.next()
if(line.startsWith(';') || line.isEmpty())
continue
else if(line=="%memory")
memory = loadMemory(lines)
else if(line=="%variables")
vars = loadVars(lines)
else if(line=="%instructions") {
val (insResult, labelResult) = loadInstructions(lines)
instructions = insResult
labels = labelResult
}
else throw VmExecutionException("syntax error at line ${lineNr+1}")
}
return Program(filename, instructions, labels, vars, memory)
}
private fun loadInstructions(lines: Iterator<IndexedValue<String>>): Pair<MutableList<Instruction>, Map<String, Instruction>> {
val instructions = mutableListOf<Instruction>()
val labels = mutableMapOf<String, Instruction>()
val splitpattern = Pattern.compile("\\s+")
val nextInstructionLabels = Stack<String>() // more than one label can occur on the same line
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_instructions")
return Pair(instructions, labels)
if(!line.startsWith(' ') && line.endsWith(':')) {
nextInstructionLabels.push(line.substring(0, line.length-1))
} else if(line.startsWith(' ')) {
val parts = line.trimStart().split(splitpattern, limit = 2)
val opcodeStr = parts[0].toUpperCase()
val opcode=Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr)
val args = if(parts.size==2) parts[1] else null
val instruction = when(opcode) {
Opcode.LINE -> Instruction(opcode, Value(DataType.STR, null, stringvalue = args))
Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS,
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC -> {
if(args!!.startsWith('$')) {
Instruction(opcode, Value(DataType.WORD, args.substring(1).toInt(16)))
} else {
Instruction(opcode, callLabel = args)
}
}
Opcode.INC_VAR, Opcode.DEC_VAR,
Opcode.SHR_VAR, Opcode.SHL_VAR, Opcode.ROL_VAR, Opcode.ROR_VAR,
Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR -> {
val withoutQuotes =
if(args!!.startsWith('"') && args.endsWith('"'))
args.substring(1, args.length-1) else args
Instruction(opcode, Value(DataType.STR, null, withoutQuotes))
}
Opcode.SYSCALL -> {
val call = Syscall.valueOf(args!!)
Instruction(opcode, Value(DataType.BYTE, call.callNr))
}
else -> {
// println("INSTR $opcode at $lineNr args=$args")
Instruction(opcode, getArgValue(args))
}
}
instructions.add(instruction)
while(nextInstructionLabels.isNotEmpty()) {
val label = nextInstructionLabels.pop()
labels[label] = instruction
}
} else throw VmExecutionException("syntax error at line ${lineNr+1}")
}
}
private fun getArgValue(args: String?): Value? {
if(args==null)
return null
if(args[0]=='"' && args[args.length-1]=='"') {
// it's a string.
return Value(DataType.STR, null, unescape(args.substring(1, args.length-1)))
}
val (type, valueStr) = args.split(':')
return when(type) {
"b" -> Value(DataType.BYTE, valueStr.toShort(16))
"w" -> Value(DataType.WORD, valueStr.toInt(16))
"f" -> Value(DataType.FLOAT, valueStr.toDouble())
else -> throw VmExecutionException("invalid datatype $type")
}
}
private fun loadVars(lines: Iterator<IndexedValue<String>>): Map<String, Map<String, Value>> {
val vars = mutableMapOf<String, MutableMap<String, Value>>()
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_variables")
return vars
val (name, type, valueStr) = line.split(splitpattern, limit = 3)
if(valueStr[0] !='"' && valueStr[1]!=':')
throw VmExecutionException("missing value type character")
val value = when(type) {
"byte" -> Value(DataType.BYTE, valueStr.substring(2).toShort(16))
"word" -> Value(DataType.WORD, valueStr.substring(2).toInt(16))
"float" -> Value(DataType.FLOAT, valueStr.substring(2).toDouble())
"str", "str_p", "str_s", "str_ps" -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
Value(DataType.STR, null, unescape(valueStr.substring(1, valueStr.length-1)))
else
throw VmExecutionException("str should be enclosed in quotes at line ${lineNr+1}")
}
else -> throw VmExecutionException("invalid datatype at line ${lineNr+1}")
}
val blockname = name.substringBefore('.')
val blockvars = vars[blockname] ?: mutableMapOf()
vars[blockname] = blockvars
blockvars[name] = value
}
}
private fun unescape(st: String): String {
val result = mutableListOf<Char>()
val iter = st.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'b' -> '\b'
'n' -> '\n'
'r' -> '\r'
't' -> '\t'
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
else -> throw VmExecutionException("invalid escape char: $ec")
})
} else {
result.add(c)
}
}
return result.joinToString("")
}
private fun loadMemory(lines: Iterator<IndexedValue<String>>): Map<Int, List<Value>> {
val memory = mutableMapOf<Int, List<Value>>()
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_memory")
return memory
val address = line.substringBefore(' ').toInt(16)
val rest = line.substringAfter(' ').trim()
if(rest.startsWith('"')) {
memory[address] = listOf(Value(DataType.STR, null, unescape(rest.substring(1, rest.length - 1))))
} else {
val valueStrings = rest.split(' ')
val values = mutableListOf<Value>()
valueStrings.forEach {
when(it.length) {
2 -> values.add(Value(DataType.BYTE, it.toShort(16)))
4 -> values.add(Value(DataType.WORD, it.toInt(16)))
else -> throw VmExecutionException("invalid value at line $lineNr+1")
}
}
memory[address] = values
}
}
}
}
val program: List<Instruction>
init {
prog.add(Instruction(Opcode.TERMINATE))
prog.add(Instruction(Opcode.NOP))
program = prog
connect()
}
private fun connect() {
val it1 = program.iterator()
val it2 = program.iterator()
it2.next()
while(it1.hasNext() && it2.hasNext()) {
val instr = it1.next()
val nextInstr = it2.next()
when(instr.opcode) {
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
Opcode.JUMP -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support JUMP to memory address")
} else {
// jump to label
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = target
}
}
Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support branch to memory address")
} else {
// branch to label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr
}
}
Opcode.CALL -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support CALL to memory address")
} else {
// call label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr // instruction to return to
}
}
else -> instr.next = nextInstr
}
}
}
fun print(out: PrintStream, embeddedLabels: Boolean=true) {
out.println("; stackVM program code for '$name'")
out.println("%memory")
if(memory.isNotEmpty()) {
TODO("print out initial memory load")
}
out.println("%end_memory")
out.println("%variables")
// just flatten all block vars into one global list for now...
for(variable in variables.flatMap { e->e.value.entries}) {
val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr")
}
out.println("%end_variables")
out.println("%instructions")
val labels = this.labels.entries.associateBy({it.value}) {it.key}
for(instr in this.program) {
if(!embeddedLabels) {
val label = labels[instr]
if (label != null)
out.println("$label:")
} else {
out.println(instr)
}
}
out.println("%end_instructions")
}
}
class StackVm(private var traceOutputFile: String?) {
val mem = Memory()
@ -866,6 +235,7 @@ class StackVm(private var traceOutputFile: String?) {
var callstack = MyStack<Instruction>()
private set
private var program = listOf<Instruction>()
private var heap = HeapValues()
private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null
private var canvas: BitmapScreenPanel? = null
private val rnd = Random()
@ -877,7 +247,9 @@ class StackVm(private var traceOutputFile: String?) {
fun load(program: Program, canvas: BitmapScreenPanel?) {
this.program = program.program
this.heap = program.heap
this.canvas = canvas
variables.clear()
for(variable in program.variables.flatMap { e->e.value.entries })
variables[variable.key] = variable.value
@ -955,8 +327,9 @@ class StackVm(private var traceOutputFile: String?) {
address += 5
}
DataType.STR -> {
mem.setString(address, value.stringvalue!!)
address += value.stringvalue.length+1
TODO("mem init with string")
//mem.setString(address, value.stringvalue!!)
//address += value.stringvalue.length+1
}
else -> throw VmExecutionException("invalid mem datatype ${value.type}")
}
@ -982,22 +355,6 @@ class StackVm(private var traceOutputFile: String?) {
evalstack.push(Value(DataType.FLOAT, mem.getFloat(address)))
}
Opcode.DUP -> evalstack.push(evalstack.peek())
Opcode.ARRAY -> {
val amount = ins.arg!!.integerValue()
if(amount<=0)
throw VmExecutionException("array size must be > 0")
val array = mutableListOf<Int>()
var arrayDt = DataType.ARRAY
for (i in 1..amount) {
val value = evalstack.pop()
if(value.type!=DataType.BYTE && value.type!=DataType.WORD)
throw VmExecutionException("array requires values to be all byte/word")
if(value.type==DataType.WORD)
arrayDt = DataType.ARRAY_W
array.add(0, value.integerValue())
}
evalstack.push(Value(arrayDt, null, arrayvalue = array.toIntArray()))
}
Opcode.DISCARD -> evalstack.pop()
Opcode.SWAP -> {
val (top, second) = evalstack.pop2()
@ -1120,14 +477,21 @@ class StackVm(private var traceOutputFile: String?) {
val value = evalstack.pop()
when(value.type){
DataType.BYTE, DataType.WORD, DataType.FLOAT -> print(value.numericValue())
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> print(value.stringvalue)
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> print(value.arrayvalue)
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
TODO("print stringvalue")
}
DataType.ARRAY, DataType.ARRAY_W -> {
TODO("print array value")
}
DataType.MATRIX -> {
TODO("print matrix value")
}
}
}
Syscall.INPUT_STR -> {
val maxlen = evalstack.pop().integerValue()
val input = readLine()?.substring(0, maxlen) ?: ""
evalstack.push(Value(DataType.STR, null, input))
TODO("input_str opcode should put the string in a given heap location (overwriting old string)")
}
Syscall.GFX_PIXEL -> {
// plot pixel at (x, y, color) from stack
@ -1140,23 +504,25 @@ class StackVm(private var traceOutputFile: String?) {
canvas?.clearScreen(color.integerValue())
}
Syscall.GFX_TEXT -> {
val text = evalstack.pop()
val textPtr = evalstack.pop()
val color = evalstack.pop()
val (y, x) = evalstack.pop2()
canvas?.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue())
val text = heap.get(textPtr.heapId)
canvas?.writeText(x.integerValue(), y.integerValue(), text.str!!, color.integerValue())
}
Syscall.FUNC_RND -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255))
Syscall.FUNC_RNDW -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535))
Syscall.FUNC_RNDF -> evalstack.push(Value(DataType.FLOAT, rnd.nextDouble()))
Syscall.FUNC_LEN -> {
val value = evalstack.pop()
when(value.type) {
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS ->
evalstack.push(Value(DataType.WORD, value.stringvalue!!.length))
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX ->
evalstack.push(Value(DataType.WORD, value.arrayvalue!!.size))
else -> throw VmExecutionException("cannot get length of $value")
}
TODO("func_len")
// when(value.type) {
// DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS ->
// evalstack.push(Value(DataType.WORD, value.stringvalue!!.length))
// DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX ->
// evalstack.push(Value(DataType.WORD, value.arrayvalue!!.size))
// else -> throw VmExecutionException("cannot get length of $value")
// }
}
Syscall.FUNC_SIN -> evalstack.push(Value(DataType.FLOAT, sin(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_COS -> evalstack.push(Value(DataType.FLOAT, cos(evalstack.pop().numericValue().toDouble())))
@ -1205,40 +571,46 @@ class StackVm(private var traceOutputFile: String?) {
evalstack.push(result)
}
Syscall.FUNC_MAX -> {
val array = evalstack.pop()
val iterable = evalstack.pop()
val dt =
when {
array.type==DataType.ARRAY -> DataType.BYTE
array.type==DataType.ARRAY_W -> DataType.WORD
else -> throw VmExecutionException("invalid array datatype $array")
when(iterable.type) {
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
DataType.ARRAY, DataType.MATRIX -> DataType.BYTE
DataType.ARRAY_W -> DataType.WORD
else -> throw VmExecutionException("uniterable value $iterable")
}
evalstack.push(Value(dt, array.arrayvalue!!.max()))
TODO("func_max on array/matrix/string")
}
Syscall.FUNC_MIN -> {
val array = evalstack.pop()
val iterable = evalstack.pop()
val dt =
when {
array.type==DataType.ARRAY -> DataType.BYTE
array.type==DataType.ARRAY_W -> DataType.WORD
else -> throw VmExecutionException("invalid array datatype $array")
when(iterable.type) {
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
DataType.ARRAY, DataType.MATRIX -> DataType.BYTE
DataType.ARRAY_W -> DataType.WORD
else -> throw VmExecutionException("uniterable value $iterable")
}
evalstack.push(Value(dt, array.arrayvalue!!.min()))
TODO("func_min on array/matrix/string")
}
Syscall.FUNC_AVG -> {
val array = evalstack.pop()
evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average()))
val iterable = evalstack.pop()
TODO("func_avg")
// evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average()))
}
Syscall.FUNC_SUM -> {
val array = evalstack.pop()
evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum()))
val iterable = evalstack.pop()
TODO("func_sum")
// evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum()))
}
Syscall.FUNC_ANY -> {
val array = evalstack.pop()
evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0))
val iterable = evalstack.pop()
TODO("func_any")
// evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0))
}
Syscall.FUNC_ALL -> {
val array = evalstack.pop()
evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0))
val iterable = evalstack.pop()
TODO("func_all")
// evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0))
}
else -> throw VmExecutionException("unimplemented syscall $syscall")
}
@ -1370,61 +742,51 @@ class StackVm(private var traceOutputFile: String?) {
return callstack.pop()
}
Opcode.PUSH_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
evalstack.push(variable)
}
Opcode.POP_VAR -> {
val value = evalstack.pop()
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
if(variable.type!=value.type)
throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var $varname")
variables[varname] = value
throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var ${ins.callLabel}")
variables[ins.callLabel!!] = value
}
Opcode.SHL_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.shl()
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
variables[ins.callLabel!!] = variable.shl()
}
Opcode.SHR_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.shr()
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
variables[ins.callLabel!!] = variable.shr()
}
Opcode.ROL_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val (newValue, newCarry) = variable.rol(P_carry)
variables[varname] = newValue
variables[ins.callLabel!!] = newValue
P_carry = newCarry
}
Opcode.ROR_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val (newValue, newCarry) = variable.ror(P_carry)
variables[varname] = newValue
variables[ins.callLabel!!] = newValue
P_carry = newCarry
}
Opcode.ROL2_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.rol2()
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
variables[ins.callLabel!!] = variable.rol2()
}
Opcode.ROR2_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.ror2()
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
variables[ins.callLabel!!] = variable.ror2()
}
Opcode.INC_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.inc()
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
variables[ins.callLabel!!] = variable.inc()
}
Opcode.DEC_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.dec()
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
variables[ins.callLabel!!] = variable.dec()
}
Opcode.LSB -> {
val v = evalstack.pop()
@ -1507,7 +869,7 @@ class StackVm(private var traceOutputFile: String?) {
}
}
Opcode.LINE -> {
sourceLine = ins.arg!!.stringvalue!!
sourceLine = ins.callLabel!!
}
else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}")
}

View File

@ -0,0 +1,373 @@
package prog8.stackvm
import prog8.ast.DataType
import kotlin.math.floor
import kotlin.math.pow
class Value(val type: DataType, numericvalueOrHeapId: Number) {
private var byteval: Short? = null
private var wordval: Int? = null
private var floatval: Double? = null
var heapId: Int = 0
private set
val asBooleanValue: Boolean
init {
when(type) {
DataType.BYTE -> {
byteval = (numericvalueOrHeapId.toInt() and 255).toShort() // byte wrap around 0..255
asBooleanValue = byteval != (0.toShort())
}
DataType.WORD -> {
wordval = numericvalueOrHeapId.toInt() and 65535 // word wrap around 0..65535
asBooleanValue = wordval != 0
}
DataType.FLOAT -> {
floatval = numericvalueOrHeapId.toDouble()
asBooleanValue = floatval != 0.0
}
else -> {
if(numericvalueOrHeapId !is Int)
throw VmExecutionException("for non-numeric types, the value should be an integer heapId")
heapId = numericvalueOrHeapId
asBooleanValue=true
}
}
}
override fun toString(): String {
return when(type) {
DataType.BYTE -> "b:%02x".format(byteval)
DataType.WORD -> "w:%04x".format(wordval)
DataType.FLOAT -> "f:$floatval"
else -> "heap:$heapId"
}
}
fun numericValue(): Number {
return when(type) {
DataType.BYTE -> byteval!!
DataType.WORD -> wordval!!
DataType.FLOAT -> floatval!!
else -> throw VmExecutionException("invalid datatype for numeric value: $type")
}
}
fun integerValue(): Int {
return when(type) {
DataType.BYTE -> byteval!!.toInt()
DataType.WORD -> wordval!!
DataType.FLOAT -> throw VmExecutionException("float to integer loss of precision")
else -> throw VmExecutionException("invalid datatype for integer value: $type")
}
}
override fun hashCode(): Int {
val bh = byteval?.hashCode() ?: 0x10001234
val wh = wordval?.hashCode() ?: 0x01002345
val fh = floatval?.hashCode() ?: 0x00103456
return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode()
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is Value)
return false
if(type==other.type) {
return when (type) {
DataType.STR, DataType.STR_S, DataType.STR_P, DataType.STR_PS, DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> heapId==other.heapId
DataType.BYTE, DataType.WORD, DataType.FLOAT -> compareTo(other)==0
}
}
return compareTo(other)==0 // note: datatype doesn't matter
}
operator fun compareTo(other: Value): Int {
return when(type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> {
when(other.type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> {
numericValue().toDouble().compareTo(other.numericValue().toDouble())
}
else -> throw VmExecutionException("comparison can only be done between two numeric values")
}
}
else -> throw VmExecutionException("comparison can only be done between two numeric values")
}
}
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): Value {
if(result.toDouble() < 0 ) {
return when(leftDt) {
DataType.BYTE -> {
// BYTE can become WORD if right operand is WORD, or when value is too large for byte
when(rightDt) {
DataType.BYTE -> Value(DataType.BYTE, result.toInt() and 255)
DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535)
DataType.FLOAT -> throw VmExecutionException("floating point loss of precision")
else -> throw VmExecutionException("$op on non-numeric result type")
}
}
DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("$op on non-numeric type")
}
}
return when(leftDt) {
DataType.BYTE -> {
// BYTE can become WORD if right operand is WORD, or when value is too large for byte
if(result.toDouble() >= 256)
return Value(DataType.WORD, result)
when(rightDt) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> throw VmExecutionException("floating point loss of precision")
else -> throw VmExecutionException("$op on non-numeric result type")
}
}
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("$op on non-numeric type")
}
}
fun add(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return arithResult(type, result, other.type, "add")
}
fun sub(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return arithResult(type, result, other.type, "sub")
}
fun mul(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return arithResult(type, result, other.type, "mul")
}
fun div(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if(v2.toDouble()==0.0) {
if (type == DataType.BYTE)
return Value(DataType.BYTE, 255)
else if(type == DataType.WORD)
return Value(DataType.WORD, 65535)
}
val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result!
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("div on non-numeric type")
}
}
fun floordiv(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = floor(v1.toDouble() / v2.toDouble())
// NOTE: integer division returns integer result!
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("div on non-numeric type")
}
}
fun remainder(other: Value): Value? {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type,"pow")
}
fun shl(): Value {
val v = integerValue()
return Value(type, v shl 1)
}
fun shr(): Value {
val v = integerValue()
return Value(type, v ushr 1)
}
fun rol(carry: Boolean): Pair<Value, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
Pair(Value(DataType.BYTE, newval), newCarry)
}
DataType.WORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
Pair(Value(DataType.WORD, newval), newCarry)
}
else -> throw VmExecutionException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<Value, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
Pair(Value(DataType.BYTE, newval), newCarry)
}
DataType.WORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
Pair(Value(DataType.WORD, newval), newCarry)
}
else -> throw VmExecutionException("ror2 can only work on byte/word")
}
}
fun rol2(): Value {
// 8 or 16 bit rotate left
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
Value(DataType.BYTE, newval)
}
DataType.WORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
Value(DataType.WORD, newval)
}
else -> throw VmExecutionException("rol2 can only work on byte/word")
}
}
fun ror2(): Value {
// 8 or 16 bit rotate right
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
Value(DataType.BYTE, newval)
}
DataType.WORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
Value(DataType.WORD, newval)
}
else -> throw VmExecutionException("ror2 can only work on byte/word")
}
}
fun neg(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, -(byteval!!))
DataType.WORD -> Value(DataType.WORD, -(wordval!!))
DataType.FLOAT -> Value(DataType.FLOAT, -(floatval)!!)
else -> throw VmExecutionException("neg can only work on byte/word/float")
}
}
fun bitand(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 and v2
return Value(type, result)
}
fun bitor(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 or v2
return Value(type, result)
}
fun bitxor(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 xor v2
return Value(type, result)
}
fun and(other: Value) = Value(DataType.BYTE, if (this.asBooleanValue && other.asBooleanValue) 1 else 0)
fun or(other: Value) = Value(DataType.BYTE, if (this.asBooleanValue || other.asBooleanValue) 1 else 0)
fun xor(other: Value) = Value(DataType.BYTE, if (this.asBooleanValue xor other.asBooleanValue) 1 else 0)
fun not() = Value(DataType.BYTE, if (this.asBooleanValue) 0 else 1)
fun inv(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv() and 255)
DataType.WORD -> Value(DataType.WORD, wordval!!.inv() and 65535)
else -> throw VmExecutionException("inv can only work on byte/word")
}
}
fun inc(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, (byteval!! + 1) and 255)
DataType.WORD -> Value(DataType.WORD, (wordval!! + 1) and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1)
else -> throw VmExecutionException("inc can only work on byte/word/float")
}
}
fun dec(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, (byteval!! - 1) and 255)
DataType.WORD -> Value(DataType.WORD, (wordval!! - 1) and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1)
else -> throw VmExecutionException("dec can only work on byte/word/float")
}
}
fun lsb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!)
DataType.WORD -> Value(DataType.BYTE, wordval!! and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
}
fun msb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, 0)
DataType.WORD -> Value(DataType.BYTE, wordval!! ushr 8 and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
}
}

View File

@ -2,10 +2,10 @@ package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.empty
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.DataType
import prog8.compiler.HeapValues
import prog8.stackvm.*
import kotlin.test.*
@ -49,11 +49,13 @@ class TestStackVmOpcodes {
if(vars!=null) {
for (blockvar in vars) {
val blockname = blockvar.key.substringBefore('.')
val variables = blockvars.getValue(blockname)
val variables = blockvars[blockname] ?: mutableMapOf()
blockvars[blockname] = variables
variables[blockvar.key] = blockvar.value
}
}
return Program("test", ins, labels ?: mapOf(), blockvars, mem ?: mapOf())
val heap = HeapValues()
return Program("test", ins, labels ?: mapOf(), blockvars, mem ?: mapOf(), heap)
}
@Test
@ -84,7 +86,7 @@ class TestStackVmOpcodes {
@Test
fun testLine() {
val ins = mutableListOf(Instruction(Opcode.LINE, Value(DataType.STR, null, "line 99")))
val ins = mutableListOf(Instruction(Opcode.LINE, callLabel = "line 99"))
vm.load(makeProg(ins), null)
assertEquals("", vm.sourceLine)
vm.step(1)
@ -149,7 +151,7 @@ class TestStackVmOpcodes {
@Test
fun testPushVar() {
val ins = mutableListOf(Instruction(Opcode.PUSH_VAR, Value(DataType.STR, null, "varname")))
val ins = mutableListOf(Instruction(Opcode.PUSH_VAR, callLabel = "varname"))
vm.load(makeProg(ins, mapOf("varname" to Value(DataType.FLOAT, 42.999))), null)
assertEquals(7, vm.variables.size)
assertTrue(vm.variables.containsKey("varname"))
@ -206,47 +208,6 @@ class TestStackVmOpcodes {
assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop())
}
@Test
fun testArray() {
val ins = mutableListOf(
Instruction(Opcode.PUSH, Value(DataType.WORD, 111)),
Instruction(Opcode.PUSH, Value(DataType.WORD, 222)),
Instruction(Opcode.PUSH, Value(DataType.WORD, 333)),
Instruction(Opcode.ARRAY, Value(DataType.WORD, 2)))
vm.load(makeProg(ins), null)
assertThat(vm.evalstack, empty())
vm.step(4)
assertEquals(2, vm.evalstack.size)
var array = vm.evalstack.pop()
assertEquals(DataType.ARRAY_W, array.type)
assertThat(array.arrayvalue, equalTo(intArrayOf(222, 333)))
assertEquals(Value(DataType.WORD, 111), vm.evalstack.pop())
val ins2 = mutableListOf(
Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)),
Instruction(Opcode.PUSH, Value(DataType.BYTE, 22)),
Instruction(Opcode.PUSH, Value(DataType.BYTE, 33)),
Instruction(Opcode.ARRAY, Value(DataType.WORD, 2)))
vm.load(makeProg(ins2), null)
assertThat(vm.evalstack, empty())
vm.step(4)
assertEquals(2, vm.evalstack.size)
array = vm.evalstack.pop()
assertEquals(DataType.ARRAY, array.type)
assertThat(array.arrayvalue, equalTo(intArrayOf(22, 33)))
assertEquals(Value(DataType.BYTE, 11), vm.evalstack.pop())
val ins3 = mutableListOf(
Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)),
Instruction(Opcode.PUSH, Value(DataType.WORD, 222)),
Instruction(Opcode.PUSH, Value(DataType.FLOAT, 333.33)),
Instruction(Opcode.ARRAY, Value(DataType.WORD, 2)))
vm.load(makeProg(ins3), null)
assertFailsWith<VmExecutionException> {
vm.step(4)
}
}
@Test
fun testPopMem() {
val ins = mutableListOf(
@ -275,9 +236,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.25)),
Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)),
Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)),
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1")),
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var2")),
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var3")))
Instruction(Opcode.POP_VAR, callLabel = "var1"),
Instruction(Opcode.POP_VAR, callLabel = "var2"),
Instruction(Opcode.POP_VAR, callLabel = "var3"))
val vars = mapOf(
"var1" to Value(DataType.BYTE, 0),
"var2" to Value(DataType.WORD, 0),
@ -292,7 +253,7 @@ class TestStackVmOpcodes {
val ins2 = mutableListOf(
Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)),
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1")))
Instruction(Opcode.POP_VAR, callLabel = "var1"))
val vars2 = mapOf(
"var1" to Value(DataType.BYTE, 0)
)
@ -450,12 +411,12 @@ class TestStackVmOpcodes {
@Test
fun testAnd() {
val values = listOf(
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
Value(DataType.ARRAY, 111),
Value(DataType.BYTE, 0),
Value(DataType.WORD, 0),
Value(DataType.STR, null, ""),
Value(DataType.STR, null, "hello"),
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
Value(DataType.STR, 222),
Value(DataType.STR, 333),
Value(DataType.ARRAY, 444),
Value(DataType.FLOAT, 300.33),
Value(DataType.WORD, 5000),
Value(DataType.BYTE, 200),
@ -466,7 +427,7 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 0))
@ -478,19 +439,19 @@ class TestStackVmOpcodes {
@Test
fun testOr() {
val values = listOf(
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
Value(DataType.BYTE, 0),
Value(DataType.WORD, 0),
Value(DataType.STR, null, ""),
Value(DataType.STR, null, "hello"),
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
Value(DataType.FLOAT, 300.33),
Value(DataType.WORD, 5000),
Value(DataType.STR, 222),
Value(DataType.STR, 333),
Value(DataType.ARRAY, 444),
Value(DataType.FLOAT, 0),
Value(DataType.WORD, 1),
Value(DataType.WORD, 0),
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 0))
val expected = listOf(
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1),
@ -506,12 +467,12 @@ class TestStackVmOpcodes {
@Test
fun testXor() {
val values = listOf(
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
Value(DataType.ARRAY, 111),
Value(DataType.BYTE, 1),
Value(DataType.WORD, 0),
Value(DataType.STR, null, ""),
Value(DataType.STR, null, "hello"),
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
Value(DataType.STR, 222),
Value(DataType.STR, 333),
Value(DataType.ARRAY, 444),
Value(DataType.FLOAT, 300.33),
Value(DataType.WORD, 5000),
Value(DataType.BYTE, 0),
@ -522,10 +483,10 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1))
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0))
val operator = Opcode.XOR
testBinaryOperator(values, operator, expected)
@ -534,8 +495,8 @@ class TestStackVmOpcodes {
@Test
fun testNot() {
val values = listOf(
Value(DataType.STR, null, ""),
Value(DataType.STR, null, "hello"),
Value(DataType.STR, 111),
Value(DataType.STR, 222),
Value(DataType.FLOAT, 0.0),
Value(DataType.FLOAT, 300.33),
Value(DataType.WORD, 0),
@ -550,7 +511,7 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1)
Value(DataType.BYTE, 0)
)
val operator = Opcode.NOT
@ -785,10 +746,10 @@ class TestStackVmOpcodes {
@Test
fun testIncVar() {
val ins = mutableListOf(
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")),
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2")),
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")),
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2")))
Instruction(Opcode.INC_VAR, callLabel ="var1"),
Instruction(Opcode.INC_VAR, callLabel ="var2"),
Instruction(Opcode.INC_VAR, callLabel ="var1"),
Instruction(Opcode.INC_VAR, callLabel ="var2"))
val vars = mapOf("var1" to Value(DataType.WORD, 65534),
"var2" to Value(DataType.BYTE, 254))
vm.load(makeProg(ins, vars = vars), null)
@ -803,10 +764,10 @@ class TestStackVmOpcodes {
@Test
fun testDecVar() {
val ins = mutableListOf(
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")),
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2")),
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")),
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2")))
Instruction(Opcode.DEC_VAR, callLabel = "var1"),
Instruction(Opcode.DEC_VAR, callLabel = "var2"),
Instruction(Opcode.DEC_VAR, callLabel = "var1"),
Instruction(Opcode.DEC_VAR, callLabel = "var2"))
val vars = mapOf("var1" to Value(DataType.WORD,1),
"var2" to Value(DataType.BYTE, 1))
vm.load(makeProg(ins, vars = vars), null)
@ -887,11 +848,11 @@ class TestStackVmOpcodes {
testComparisonOperator(values, expected, Opcode.LESS)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
Value(DataType.STR, 333),
Value(DataType.STR, 333)
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESS) // can't compare strings
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESS) // can't order strings
}
}
@ -917,11 +878,11 @@ class TestStackVmOpcodes {
testComparisonOperator(values, expected, Opcode.LESSEQ)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
Value(DataType.STR, 333),
Value(DataType.STR, 333)
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESSEQ) // can't compare strings
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESSEQ) // can't order strings
}
}
@ -947,11 +908,11 @@ class TestStackVmOpcodes {
testComparisonOperator(values, expected, Opcode.GREATER)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
Value(DataType.STR, 333),
Value(DataType.STR, 333)
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATER) // can't compare strings
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATER) // can't order strings
}
}
@ -977,11 +938,11 @@ class TestStackVmOpcodes {
testComparisonOperator(values, expected, Opcode.GREATEREQ)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
Value(DataType.STR, 333),
Value(DataType.STR, 333)
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATEREQ) // can't compare strings
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATEREQ) // can't order strings
}
}
@ -1007,12 +968,12 @@ class TestStackVmOpcodes {
testComparisonOperator(values, expected, Opcode.EQUAL)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
Value(DataType.STR, 111),
Value(DataType.STR, 222), // 0
Value(DataType.STR, 333),
Value(DataType.STR, 333) // 1
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.EQUAL) // can't compare strings
}
testComparisonOperator(valuesInvalid, listOf(0, 1), Opcode.EQUAL)
}
@Test
@ -1037,12 +998,12 @@ class TestStackVmOpcodes {
testComparisonOperator(values, expected, Opcode.NOTEQUAL)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
Value(DataType.STR, 111),
Value(DataType.STR, 222), // 1
Value(DataType.STR, 333),
Value(DataType.STR, 333) // 0
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.NOTEQUAL) // can't compare strings
}
testComparisonOperator(valuesInvalid, listOf(1, 0), Opcode.NOTEQUAL)
}
@Test
@ -1052,9 +1013,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BCC, callLabel = "label"),
Instruction(Opcode.CLC),
Instruction(Opcode.BCC, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
@ -1072,9 +1033,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BCS, callLabel = "label"),
Instruction(Opcode.SEC),
Instruction(Opcode.BCS, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
assertFalse(vm.P_carry)
@ -1093,9 +1054,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BZ, callLabel = "label"),
Instruction(Opcode.PUSH, Value(DataType.WORD, 0)),
Instruction(Opcode.BZ, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
@ -1113,9 +1074,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BNZ, callLabel = "label"),
Instruction(Opcode.PUSH, Value(DataType.WORD, 1)),
Instruction(Opcode.BNZ, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
@ -1135,9 +1096,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BNEG, callLabel = "label"),
Instruction(Opcode.PUSH, Value(DataType.FLOAT, -99)),
Instruction(Opcode.BNEG, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
@ -1157,9 +1118,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BPOS, callLabel = "label"),
Instruction(Opcode.PUSH, Value(DataType.WORD, 0)),
Instruction(Opcode.BPOS, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
@ -1174,9 +1135,9 @@ class TestStackVmOpcodes {
fun testJump() {
val ins = mutableListOf(
Instruction(Opcode.JUMP, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
@ -1191,7 +1152,7 @@ class TestStackVmOpcodes {
val ins = mutableListOf(
Instruction(Opcode.RETURN),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1"))
Instruction(Opcode.LINE, callLabel = "string1")
)
vm.load(makeProg(ins), null)
assertFailsWith<VmTerminationException> {
@ -1211,9 +1172,9 @@ class TestStackVmOpcodes {
// @todo this only tests call with zero parameters for now.
val ins = mutableListOf(
Instruction(Opcode.CALL, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "returned")),
Instruction(Opcode.LINE, callLabel = "returned"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "called")),
Instruction(Opcode.LINE, callLabel = "called"),
Instruction(Opcode.RETURN)
)
val labels = mapOf("label" to ins[3]) // points to the LINE instruction
@ -1560,16 +1521,16 @@ class TestStackVmOpcodes {
}
}
private fun testBinaryOperator(values: List<Value>, operator: Opcode, expected: List<Value>) {
assertEquals(values.size, expected.size+1)
private fun testBinaryOperator(valuesToPush: List<Value>, operator: Opcode, expected: List<Value>) {
assertEquals(valuesToPush.size, expected.size+1)
val ins = mutableListOf<Instruction>()
for (value in values)
for (value in valuesToPush)
ins.add(Instruction(Opcode.PUSH, value))
for (i in 1 until values.size)
for (i in 1 until valuesToPush.size)
ins.add(Instruction(operator))
vm.load(makeProg(ins), null)
vm.step(values.size)
assertEquals(values.size, vm.evalstack.size)
vm.step(valuesToPush.size)
assertEquals(valuesToPush.size, vm.evalstack.size)
for (expectedVal in expected) {
vm.step(1)
assertEquals(expectedVal, vm.evalstack.peek())
@ -1579,18 +1540,18 @@ class TestStackVmOpcodes {
}
}
private fun testUnaryOperator(values: List<Value>, operator: Opcode, expected: List<Value>) {
assertEquals(values.size, expected.size)
private fun testUnaryOperator(valuesToPush: List<Value>, operator: Opcode, expected: List<Value>) {
assertEquals(valuesToPush.size, expected.size)
val ins = mutableListOf<Instruction>()
for (value in values)
for (value in valuesToPush)
ins.add(Instruction(Opcode.PUSH, value))
for (i in 1..values.size) {
for (i in 1..valuesToPush.size) {
ins.add(Instruction(operator))
ins.add(Instruction(Opcode.DISCARD))
}
vm.load(makeProg(ins), null)
vm.step(values.size)
assertEquals(values.size, vm.evalstack.size)
vm.step(valuesToPush.size)
assertEquals(valuesToPush.size, vm.evalstack.size)
for (expectedVal in expected) {
vm.step(1)
assertEquals(expectedVal, vm.evalstack.peek())

View File

@ -77,13 +77,18 @@ class TestStackVmValue {
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.BYTE, 9)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.WORD, 9)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.0)))
}
assertFailsWith<VmExecutionException> {
assertTrue(sameValueAndType(Value(DataType.STR, null, "hello"), Value(DataType.STR, null, "hello")))
}
assertFailsWith<VmExecutionException> {
assertTrue(sameValueAndType(Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3))))
}
@Test
fun testEqualsAndNotEqualsHeapTypes()
{
assertTrue(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 999)))
assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR_P, 999)))
assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 222)))
assertTrue(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.ARRAY_W, 999)))
assertFalse(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.MATRIX, 999)))
assertFalse(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.ARRAY_W, 222)))
}
@Test