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 %output prg
%import c64lib
%import mathlib
~ main { ~ main {
sub start() -> () { sub start() -> () {
str name = "?" * 80 str name = "?" * 20
str guess = "?" * 80 str guess = "?" * 20
byte secretnumber = 0 byte secretnumber = 0
byte attempts_left = 10 byte attempts_left = 10
memory word freadstr_arg = $22 ; argument for FREADSTR
c64.init_system()
c64.VMCSB |= 2 ; activate lowercase charset
; greeting ; greeting
c64scr.print_string("Enter your name: ") _vm_write_str("Enter your name: ")
Y = c64scr.input_chars(name) ; _vm_input_str(name)
c64.CHROUT("\n") _vm_write_char($8d)
c64.CHROUT("\n") _vm_write_char($8d)
c64scr.print_string("Hello, ") _vm_write_str("Hello, ")
c64scr.print_string(name) _vm_write_str(name)
c64.CHROUT(".") _vm_write_char($2e)
c64.CHROUT("\n") _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 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 { ~ main {
sub start() -> () { 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= len("abcdef")
;A = 99/5 + lsb(12345)
_vm_write_num(A) A= len([4,5,99/X])
_vm_write_char($8d) A= max([4,5,99])
return 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() moduleAst.checkIdentifiers()
println("Optimizing...") println("Optimizing...")
moduleAst.constantFold(namespace) val heap = HeapValues()
moduleAst.checkValid(namespace, compilerOptions) // check if tree is valid moduleAst.constantFold(namespace, heap)
moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers() val allScopedSymbolDefinitions = moduleAst.checkIdentifiers()
while(true) { while(true) {
// keep optimizing expressions and statements until no more steps remain // keep optimizing expressions and statements until no more steps remain
val optsDone1 = moduleAst.simplifyExpressions(namespace) val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
val optsDone2 = moduleAst.optimizeStatements(namespace) val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
if(optsDone1 + optsDone2 == 0) if(optsDone1 + optsDone2 == 0)
break break
} }
StatementReorderer().process(moduleAst) // reorder statements to please the compiler later StatementReorderer().process(moduleAst) // reorder statements to please the compiler later
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime 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 moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
// namespace.debugPrint() // namespace.debugPrint()
// compile the syntax tree into stackvmProg form, and optimize that // compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(compilerOptions) val compiler = Compiler(compilerOptions)
val intermediate = compiler.compile(moduleAst) val intermediate = compiler.compile(moduleAst, heap)
intermediate.optimize() intermediate.optimize()
val stackVmFilename = intermediate.name + "_stackvm.txt" 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.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode import org.antlr.v4.runtime.tree.TerminalNode
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.functions.* import prog8.functions.*
import prog8.parser.prog8Parser import prog8.parser.prog8Parser
@ -677,11 +678,11 @@ data class AssignTarget(val register: Register?, val identifier: IdentifierRefer
interface IExpression: Node { interface IExpression: Node {
fun isIterable(namespace: INameScope): Boolean fun isIterable(namespace: INameScope, heap: HeapValues): Boolean
fun constValue(namespace: INameScope): LiteralValue? fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue?
fun process(processor: IAstProcessor): IExpression fun process(processor: IAstProcessor): IExpression
fun referencesIdentifier(name: String): Boolean 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) 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 process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? = expression.resultingDatatype(namespace) override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? = expression.resultingDatatype(namespace, heap)
override fun isIterable(namespace: INameScope) = false 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... // 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 constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
override fun isIterable(namespace: INameScope) = false override fun isIterable(namespace: INameScope, heap: HeapValues) = false
override fun process(processor: IAstProcessor) = processor.process(this) override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name) override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? { override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
val leftDt = left.resultingDatatype(namespace) val leftDt = left.resultingDatatype(namespace, heap)
val rightDt = right.resultingDatatype(namespace) val rightDt = right.resultingDatatype(namespace, heap)
return when(operator) { return when(operator) {
"+", "-", "*", "**", "%" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt) "+", "-", "*", "**", "%" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt)
"//" -> if(leftDt==null || rightDt==null) null else integerDivisionOpDt(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 "==", "!=" -> DataType.BYTE
"/" -> { "/" -> {
val rightNum = right.constValue(namespace)?.asNumericValue?.toDouble() val rightNum = right.constValue(namespace, heap)?.asNumericValue?.toDouble()
if(rightNum!=null) { if(rightNum!=null) {
when(leftDt) { when(leftDt) {
DataType.BYTE -> DataType.BYTE ->
@ -807,8 +808,10 @@ class LiteralValue(val type: DataType,
val floatvalue: Double? = null, val floatvalue: Double? = null,
val strvalue: String? = null, val strvalue: String? = null,
val arrayvalue: Array<IExpression>? = null, val arrayvalue: Array<IExpression>? = null,
val heapId: Int? =null,
override val position: Position) : IExpression { override val position: Position) : IExpression {
override lateinit var parent: Node override lateinit var parent: Node
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false 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 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.BYTE -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
DataType.WORD -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue") DataType.WORD -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue") DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS ->
if(strvalue==null) throw FatalAstException("literal value missing strvalue") if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
} DataType.ARRAY, DataType.ARRAY_W ->
DataType.ARRAY, DataType.ARRAY_W -> if(arrayvalue==null) throw FatalAstException("literal value missing arrayvalue") 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") 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") throw FatalAstException("literal value without actual value")
} }
@ -893,7 +896,7 @@ class LiteralValue(val type: DataType,
arrayvalue?.forEach {it.linkParents(this)} 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 process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String { override fun toString(): String {
@ -901,16 +904,25 @@ class LiteralValue(val type: DataType,
DataType.BYTE -> "byte:$bytevalue" DataType.BYTE -> "byte:$bytevalue"
DataType.WORD -> "word:$wordvalue" DataType.WORD -> "word:$wordvalue"
DataType.FLOAT -> "float:$floatvalue" DataType.FLOAT -> "float:$floatvalue"
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> "str:$strvalue" DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> {
DataType.ARRAY, DataType.ARRAY_W -> "array:$arrayvalue" if(heapId!=null) "str:#$heapId"
DataType.MATRIX -> "matrix:$arrayvalue" 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)" 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 { override fun hashCode(): Int {
val bh = bytevalue?.hashCode() ?: 0x10001234 val bh = bytevalue?.hashCode() ?: 0x10001234
@ -954,13 +966,13 @@ class RangeExpr(var from: IExpression,
step.linkParents(this) step.linkParents(this)
} }
override fun constValue(namespace: INameScope): LiteralValue? = null override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
override fun isIterable(namespace: INameScope) = true override fun isIterable(namespace: INameScope, heap: HeapValues) = true
override fun process(processor: IAstProcessor) = processor.process(this) override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name) override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? { override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
val fromDt=from.resultingDatatype(namespace) val fromDt=from.resultingDatatype(namespace, heap)
val toDt=to.resultingDatatype(namespace) val toDt=to.resultingDatatype(namespace, heap)
return when { return when {
fromDt==null || toDt==null -> null fromDt==null || toDt==null -> null
fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD 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 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 process(processor: IAstProcessor) = this
override fun referencesIdentifier(name: String): Boolean = false 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 { override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)" return "RegisterExpr(register=$register, pos=$position)"
} }
override fun resultingDatatype(namespace: INameScope): DataType? { override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
return when(register){ return when(register){
Register.A, Register.X, Register.Y -> DataType.BYTE Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD 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 this.parent = parent
} }
override fun constValue(namespace: INameScope): LiteralValue? { override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? {
val node = namespace.lookup(nameInSource, this) val node = namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this) ?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl 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) { } else if(vardecl.type!=VarDeclType.CONST) {
return null return null
} }
return vardecl.value?.constValue(namespace) return vardecl.value?.constValue(namespace, heap)
} }
override fun toString(): String { 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 process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time? 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) val targetStmt = targetStatement(namespace)
if(targetStmt is VarDecl) { if(targetStmt is VarDecl) {
return targetStmt.datatype 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) } 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 the function is a built-in function and the args are consts, should try to const-evaluate!
if(target.nameInSource.size>1) return null if(target.nameInSource.size>1) return null
try { try {
val resultValue = when (target.nameInSource[0]) { val resultValue = when (target.nameInSource[0]) {
"sin" -> builtinSin(arglist, position, namespace) "sin" -> builtinSin(arglist, position, namespace, heap)
"cos" -> builtinCos(arglist, position, namespace) "cos" -> builtinCos(arglist, position, namespace, heap)
"abs" -> builtinAbs(arglist, position, namespace) "abs" -> builtinAbs(arglist, position, namespace, heap)
"acos" -> builtinAcos(arglist, position, namespace) "acos" -> builtinAcos(arglist, position, namespace, heap)
"asin" -> builtinAsin(arglist, position, namespace) "asin" -> builtinAsin(arglist, position, namespace, heap)
"tan" -> builtinTan(arglist, position, namespace) "tan" -> builtinTan(arglist, position, namespace, heap)
"atan" -> builtinAtan(arglist, position, namespace) "atan" -> builtinAtan(arglist, position, namespace, heap)
"ln" -> builtinLn(arglist, position, namespace) "ln" -> builtinLn(arglist, position, namespace, heap)
"log2" -> builtinLog2(arglist, position, namespace) "log2" -> builtinLog2(arglist, position, namespace, heap)
"log10" -> builtinLog10(arglist, position, namespace) "log10" -> builtinLog10(arglist, position, namespace, heap)
"sqrt" -> builtinSqrt(arglist, position, namespace) "sqrt" -> builtinSqrt(arglist, position, namespace, heap)
"max" -> builtinMax(arglist, position, namespace) "max" -> builtinMax(arglist, position, namespace, heap)
"min" -> builtinMin(arglist, position, namespace) "min" -> builtinMin(arglist, position, namespace, heap)
"round" -> builtinRound(arglist, position, namespace) "round" -> builtinRound(arglist, position, namespace, heap)
"rad" -> builtinRad(arglist, position, namespace) "rad" -> builtinRad(arglist, position, namespace, heap)
"deg" -> builtinDeg(arglist, position, namespace) "deg" -> builtinDeg(arglist, position, namespace, heap)
"sum" -> builtinSum(arglist, position, namespace) "sum" -> builtinSum(arglist, position, namespace, heap)
"avg" -> builtinAvg(arglist, position, namespace) "avg" -> builtinAvg(arglist, position, namespace, heap)
"len" -> builtinLen(arglist, position, namespace) "len" -> builtinLen(arglist, position, namespace, heap)
"lsb" -> builtinLsb(arglist, position, namespace) "lsb" -> builtinLsb(arglist, position, namespace, heap)
"msb" -> builtinMsb(arglist, position, namespace) "msb" -> builtinMsb(arglist, position, namespace, heap)
"flt" -> builtinFlt(arglist, position, namespace) "flt" -> builtinFlt(arglist, position, namespace, heap)
"any" -> builtinAny(arglist, position, namespace) "any" -> builtinAny(arglist, position, namespace, heap)
"all" -> builtinAll(arglist, position, namespace) "all" -> builtinAll(arglist, position, namespace, heap)
"floor" -> builtinFloor(arglist, position, namespace) "floor" -> builtinFloor(arglist, position, namespace, heap)
"ceil" -> builtinCeil(arglist, position, namespace) "ceil" -> builtinCeil(arglist, position, namespace, heap)
"lsl", "lsr", "rol", "rol2", "ror", "ror2", "set_carry", "clear_carry", "set_irqd", "clear_irqd" -> "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) throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used in expressions because it doesn't return a value", position)
else -> null else -> null
} }
if(withDatatypeCheck) { if(withDatatypeCheck) {
val resultDt = this.resultingDatatype(namespace) val resultDt = this.resultingDatatype(namespace, heap)
if(resultValue==null || resultDt == resultValue.type) if(resultValue==null || resultDt == resultValue.type)
return resultValue return resultValue
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position") 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 process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)} override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
override fun resultingDatatype(namespace: INameScope): DataType? { override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
val constVal = constValue(namespace, false) val constVal = constValue(namespace, heap,false)
if(constVal!=null) if(constVal!=null)
return constVal.resultingDatatype(namespace) return constVal.resultingDatatype(namespace, heap)
val stmt = target.targetStatement(namespace) ?: return null val stmt = target.targetStatement(namespace) ?: return null
when (stmt) { when (stmt) {
is BuiltinFunctionStatementPlaceholder -> { is BuiltinFunctionStatementPlaceholder -> {
@ -1201,7 +1213,7 @@ class FunctionCall(override var target: IdentifierReference,
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") { target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return null // these have no return value 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 -> { is Subroutine -> {
if(stmt.returnvalues.isEmpty()) { if(stmt.returnvalues.isEmpty()) {
@ -1225,7 +1237,7 @@ class FunctionCall(override var target: IdentifierReference,
TODO("datatype of functioncall to $stmt") 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") TODO("isIterable of function call result")
} }
} }

View File

@ -1,6 +1,7 @@
package prog8.ast package prog8.ast
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctionNames import prog8.functions.BuiltinFunctionNames
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
@ -8,8 +9,8 @@ import prog8.parser.ParsingFailedError
* General checks on the Ast * General checks on the Ast
*/ */
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions) { fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) {
val checker = AstChecker(globalNamespace, compilerOptions) val checker = AstChecker(globalNamespace, compilerOptions, heap)
this.process(checker) this.process(checker)
printErrors(checker.result(), name) 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() private val checkResult: MutableList<AstException> = mutableListOf()
fun result(): List<AstException> { fun result(): List<AstException> {
@ -87,10 +90,10 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
override fun process(forLoop: ForLoop): IStatement { override fun process(forLoop: ForLoop): IStatement {
if(forLoop.body.isEmpty()) if(forLoop.body.isEmpty())
printWarning("for loop body is empty", forLoop.position) 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)) checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else { } else {
val iterableDt = forLoop.iterable.resultingDatatype(namespace) val iterableDt = forLoop.iterable.resultingDatatype(namespace, heap)
if (forLoop.loopRegister != null) { if (forLoop.loopRegister != null) {
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position) printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
// loop register // loop register
@ -281,11 +284,11 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
} }
val targetDatatype = assignment.target.determineDatatype(namespace, assignment) val targetDatatype = assignment.target.determineDatatype(namespace, assignment)
val constVal = assignment.value.constValue(namespace) val constVal = assignment.value.constValue(namespace, heap)
if(constVal!=null) { if(constVal!=null) {
checkValueTypeAndRange(targetDatatype, null, constVal) checkValueTypeAndRange(targetDatatype, null, constVal, heap)
} else { } else {
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace) val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap)
if(sourceDatatype==null) { if(sourceDatatype==null) {
if(assignment.value is FunctionCall) if(assignment.value is FunctionCall)
checkResult.add(ExpressionError("function call doesn't return a value to use in assignment", assignment.value.position)) 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 { when {
decl.value is RangeExpr -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as RangeExpr) 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 -> { else -> {
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}") err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}")
return super.process(decl) return super.process(decl)
@ -433,14 +436,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) { if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) {
checkResult.add(SyntaxError("floating point value used, but floating point is not enabled via options", literalValue.position)) 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) return super.process(literalValue)
} }
override fun process(expr: BinaryExpression): IExpression { override fun process(expr: BinaryExpression): IExpression {
when(expr.operator){ when(expr.operator){
"/", "//", "%" -> { "/", "//", "%" -> {
val numeric = expr.right.constValue(namespace)?.asNumericValue?.toDouble() val numeric = expr.right.constValue(namespace, heap)?.asNumericValue?.toDouble()
if(numeric==0.0) if(numeric==0.0)
checkResult.add(ExpressionError("division by zero", expr.right.position)) 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)) checkResult.add(SyntaxError(msg, range.position))
} }
super.process(range) super.process(range)
val from = range.from.constValue(namespace) val from = range.from.constValue(namespace, heap)
val to = range.to.constValue(namespace) val to = range.to.constValue(namespace, heap)
val stepLv = range.step.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position) val stepLv = range.step.constValue(namespace, heap) ?: LiteralValue(DataType.BYTE, 1, position = range.position)
if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) { if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) {
err("range step must be an integer != 0") err("range step must be an integer != 0")
return range 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 { private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, range: RangeExpr) : Boolean {
val from = range.from.constValue(namespace) val from = range.from.constValue(namespace, heap)
val to = range.to.constValue(namespace) val to = range.to.constValue(namespace, heap)
if(from==null || to==null) { if(from==null || to==null) {
checkResult.add(SyntaxError("range from and to values must be constants", range.position)) checkResult.add(SyntaxError("range from and to values must be constants", range.position))
return false 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 { fun err(msg: String) : Boolean {
checkResult.add(ExpressionError(msg, value.position)) checkResult.add(ExpressionError(msg, value.position))
return false 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") return err("value '$number' out of range for unsigned word")
} }
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = value.strvalue val str = value.strvalue ?: heap.get(value.heapId!!).str!!
?: return err("string value expected")
if (str.isEmpty() || str.length > 255) if (str.isEmpty() || str.length > 255)
return err("string length must be 1 to 255") return err("string length must be 1 to 255")
} }
DataType.ARRAY -> { 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) { if(value.type==DataType.ARRAY) {
val array = heap.get(value.heapId!!).array!!
if(arrayspec!=null) { if(arrayspec!=null) {
// arrayspec is not always known when checking val constX = arrayspec.x.constValue(namespace, heap)
val constX = arrayspec.x.constValue(namespace)
if(constX?.asIntegerValue==null) if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value") return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue val expectedSize = constX.asIntegerValue
if (value.arrayvalue!!.size != expectedSize) if (array.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})") return err("initializer array size mismatch (expecting $expectedSize, got ${array.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")
}
} }
} else if(value.type==DataType.ARRAY_W) { } else if(value.type==DataType.ARRAY_W) {
return err("initialization value must be an array of bytes") 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 -> { DataType.ARRAY_W -> {
// value may be either a single word, or a word array // value may be either a single word, or a word array
if(value.type==DataType.ARRAY || value.type==DataType.ARRAY_W) { if(value.type==DataType.ARRAY || value.type==DataType.ARRAY_W) {
val array = heap.get(value.heapId!!).array!!
if(arrayspec!=null) { if(arrayspec!=null) {
// arrayspec is not always known when checking // arrayspec is not always known when checking
val constX = arrayspec.x.constValue(namespace) val constX = arrayspec.x.constValue(namespace, heap)
if(constX?.asIntegerValue==null) if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value") return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue val expectedSize = constX.asIntegerValue
if (value.arrayvalue!!.size != expectedSize) if (array.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})") return err("initializer array size mismatch (expecting $expectedSize, got ${array.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")
}
} }
} else { } else {
val number = value.asIntegerValue ?: return if (value.floatvalue!=null) val number = value.asIntegerValue ?: return if (value.floatvalue!=null)
@ -708,21 +684,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
DataType.MATRIX -> { DataType.MATRIX -> {
// value can only be a single byte, or a byte array (which represents the matrix) // value can only be a single byte, or a byte array (which represents the matrix)
if(value.type==DataType.ARRAY) { if(value.type==DataType.ARRAY) {
for (av in value.arrayvalue!!) { val constX = arrayspec!!.x.constValue(namespace, heap)
val number = (av as LiteralValue).bytevalue val constY = arrayspec.y!!.constValue(namespace, heap)
?: return err("array must be all bytes") if(constX?.asIntegerValue==null || constY?.asIntegerValue==null)
val constX = arrayspec!!.x.constValue(namespace) return err("matrix size specifiers must be constant integer values")
val constY = arrayspec.y!!.constValue(namespace) val matrix = heap.get(value.heapId!!).array!!
if(constX?.asIntegerValue==null || constY?.asIntegerValue==null) val expectedSize = constX.asIntegerValue * constY.asIntegerValue
return err("matrix size specifiers must be constant integer values") if (matrix.size != expectedSize)
return err("initializer matrix size mismatch (expecting $expectedSize, got ${matrix.size} elements)")
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")
}
} else { } else {
val number = value.bytevalue val number = value.bytevalue
?: return err("unsigned byte value expected") ?: 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 instructions = mutableListOf<Instruction>()
private val variables = mutableMapOf<String, MutableMap<String, Value>>() private val variables = mutableMapOf<String, MutableMap<String, Value>>()
private val memory = mutableMapOf<Int, List<Value>>() private val memory = mutableMapOf<Int, List<Value>>()
@ -44,9 +88,19 @@ class StackVmProgram(val name: String) {
fun blockvar(scopedname: String, decl: VarDecl) { fun blockvar(scopedname: String, decl: VarDecl) {
val value = when(decl.datatype) { val value = when(decl.datatype) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue) 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.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> TODO("array/matrix variable values") 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... // 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) { 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) { fun instr(opcode: Opcode, arg: Value? = null, callLabel: String? = null) {
@ -71,7 +125,7 @@ class StackVmProgram(val name: String) {
} }
fun line(position: Position) { 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) { class Compiler(private val options: CompilationOptions) {
fun compile(module: Module) : StackVmProgram { fun compile(module: Module, heap: HeapValues) : StackVmProgram {
println("\nCreating stackVM code...") println("\nCreating stackVM code...")
val namespace = module.definingScope() 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) val varGather = VarGatherer(intermediate)
varGather.process(module) varGather.process(module)
println(" ${intermediate.numVariables} allocated variables and constants") println(" ${intermediate.numVariables} allocated variables and constants")
val translator = StatementTranslator(intermediate, namespace) val translator = StatementTranslator(intermediate, namespace, heap)
translator.process(module) translator.process(module)
println(" ${translator.stmtUniqueSequenceNr} source statements, ${intermediate.numInstructions} resulting instructions") 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 var stmtUniqueSequenceNr = 0
private set private set
@ -290,8 +346,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
} }
private fun checkForFloatPrecisionProblem(left: IExpression, right: IExpression) { private fun checkForFloatPrecisionProblem(left: IExpression, right: IExpression) {
val leftDt = left.resultingDatatype(namespace) val leftDt = left.resultingDatatype(namespace, heap)
val rightDt = right.resultingDatatype(namespace) val rightDt = right.resultingDatatype(namespace, heap)
if (leftDt == DataType.BYTE || leftDt == DataType.WORD) { if (leftDt == DataType.BYTE || leftDt == DataType.WORD) {
if(rightDt==DataType.FLOAT) 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) 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) { private fun translate(expr: IExpression) {
when(expr) { when(expr) {
is RegisterExpr -> { 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 -> { is PrefixExpression -> {
translate(expr.expression) translate(expr.expression)
@ -333,14 +389,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
is VarDecl -> { is VarDecl -> {
when(target.type) { when(target.type) {
VarDeclType.VAR -> VarDeclType.VAR ->
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname)) stackvmProg.instr(Opcode.PUSH_VAR, callLabel = target.scopedname)
VarDeclType.CONST -> VarDeclType.CONST ->
throw CompilerException("const ref should have been const-folded away") throw CompilerException("const ref should have been const-folded away")
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
when(target.datatype){ when(target.datatype){
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH_MEM, 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.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.FLOAT -> stackvmProg.instr(Opcode.PUSH_MEM_F, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!))
else -> TODO("invalid datatype for memory variable expression: $target") 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") TODO("TRANSLATE range $expr")
} }
else -> { 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) { when(lv.type) {
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue)) DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue!!))
DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue)) DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue!!))
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH, Value(DataType.FLOAT, lv.floatvalue)) 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.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
DataType.ARRAY, DataType.ARRAY_W -> { DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> stackvmProg.instr(Opcode.PUSH, Value(lv.type, lv.heapId!!))
lv.arrayvalue?.forEach { translate(it) }
stackvmProg.instr(Opcode.ARRAY, Value(DataType.WORD, lv.arrayvalue!!.size))
}
DataType.MATRIX -> TODO("matrix type")
} }
} }
} }
@ -396,7 +448,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
"flt" -> { "flt" -> {
// 1 argument, type determines the exact opcode to use // 1 argument, type determines the exact opcode to use
val arg = args.single() val arg = args.single()
when (arg.resultingDatatype(namespace)) { when (arg.resultingDatatype(namespace, heap)) {
DataType.BYTE -> stackvmProg.instr(Opcode.B2FLOAT) DataType.BYTE -> stackvmProg.instr(Opcode.B2FLOAT)
DataType.WORD -> stackvmProg.instr(Opcode.W2FLOAT) DataType.WORD -> stackvmProg.instr(Opcode.W2FLOAT)
DataType.FLOAT -> stackvmProg.instr(Opcode.NOP) DataType.FLOAT -> stackvmProg.instr(Opcode.NOP)
@ -507,14 +559,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
stackvmProg.line(stmt.position) stackvmProg.line(stmt.position)
if(stmt.target.register!=null) { if(stmt.target.register!=null) {
when(stmt.operator) { when(stmt.operator) {
"++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, stmt.target.register.toString())) "++" -> stackvmProg.instr(Opcode.INC_VAR, callLabel = stmt.target.register.toString())
"--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, stmt.target.register.toString())) "--" -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = stmt.target.register.toString())
} }
} else { } else {
val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl
when(stmt.operator) { when(stmt.operator) {
"++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, targetStatement.scopedname)) "++" -> stackvmProg.instr(Opcode.INC_VAR, callLabel = targetStatement.scopedname)
"--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, 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) { private fun translate(stmt: Assignment) {
stackvmProg.line(stmt.position) stackvmProg.line(stmt.position)
translate(stmt.value) translate(stmt.value)
val valueDt = stmt.value.resultingDatatype(namespace) val valueDt = stmt.value.resultingDatatype(namespace, heap)
val targetDt = stmt.target.determineDatatype(namespace, stmt) val targetDt = stmt.target.determineDatatype(namespace, stmt)
if(valueDt!=targetDt) { if(valueDt!=targetDt) {
// convert value to target datatype if possible // convert value to target datatype if possible
@ -552,11 +604,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
if(stmt.target.identifier!=null) { if(stmt.target.identifier!=null) {
val target = stmt.target.identifier!!.targetStatement(namespace)!! val target = stmt.target.identifier!!.targetStatement(namespace)!!
when(target) { 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 -> throw CompilerException("invalid assignment target type ${target::class}")
} }
} else if(stmt.target.register!=null) { } 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) translateAugAssignOperator(stmt.aug_op)
} }
@ -565,11 +617,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
if(stmt.target.identifier!=null) { if(stmt.target.identifier!=null) {
val target = stmt.target.identifier!!.targetStatement(namespace)!! val target = stmt.target.identifier!!.targetStatement(namespace)!!
when(target) { 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 -> throw CompilerException("invalid assignment target type ${target::class}")
} }
} else if(stmt.target.register!=null) { } 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) continueStmtLabelStack.push(continueLabel)
breakStmtLabelStack.push(breakLabel) breakStmtLabelStack.push(breakLabel)
val varValue = Value(DataType.STR, null, varname)
stackvmProg.instr(Opcode.PUSH, Value(varDt, range.first)) stackvmProg.instr(Opcode.PUSH, Value(varDt, range.first))
stackvmProg.instr(Opcode.POP_VAR, varValue) stackvmProg.instr(Opcode.POP_VAR, callLabel = varname)
stackvmProg.label(loopLabel) stackvmProg.label(loopLabel)
translate(body) translate(body)
stackvmProg.label(continueLabel) stackvmProg.label(continueLabel)
when { when {
range.step==1 -> stackvmProg.instr(Opcode.INC_VAR, varValue) range.step==1 -> stackvmProg.instr(Opcode.INC_VAR, callLabel = varname)
range.step==-1 -> stackvmProg.instr(Opcode.DEC_VAR, varValue) range.step==-1 -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = varname)
range.step>1 -> { 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.PUSH, Value(varDt, range.step))
stackvmProg.instr(Opcode.ADD) stackvmProg.instr(Opcode.ADD)
stackvmProg.instr(Opcode.POP_VAR, varValue) stackvmProg.instr(Opcode.POP_VAR, callLabel = varname)
} }
range.step<1 -> { 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.PUSH, Value(varDt, abs(range.step)))
stackvmProg.instr(Opcode.SUB) 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. // 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, 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.SUB)
stackvmProg.instr(Opcode.BNZ, callLabel = loopLabel) stackvmProg.instr(Opcode.BNZ, callLabel = loopLabel)
@ -838,9 +889,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
postIncr.linkParents(range.parent) postIncr.linkParents(range.parent)
translate(postIncr) translate(postIncr)
if(lvTarget.register!=null) 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 else
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stringvalue=targetStatement!!.scopedname)) stackvmProg.instr(Opcode.PUSH_VAR, callLabel =targetStatement!!.scopedname)
val branch = BranchStatement( val branch = BranchStatement(
BranchCondition.NZ, BranchCondition.NZ,
listOf(Jump(null, null, loopLabel, range.position)), listOf(Jump(null, null, loopLabel, range.position)),

View File

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

View File

@ -184,7 +184,7 @@ class ConstExprEvaluator {
} }
private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue { 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 { return when {
left.asIntegerValue!=null -> when { left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position) 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) right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.floatvalue, position = left.position)
else -> throw ExpressionError(error, 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) else -> throw ExpressionError(error, left.position)
} }
} }

View File

@ -1,10 +1,11 @@
package prog8.optimizing package prog8.optimizing
import prog8.ast.* import prog8.ast.*
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii 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 optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf() var errors : MutableList<AstException> = mutableListOf()
@ -94,8 +95,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
*/ */
override fun process(identifier: IdentifierReference): IExpression { override fun process(identifier: IdentifierReference): IExpression {
return try { return try {
val cval = identifier.constValue(namespace) ?: return identifier val cval = identifier.constValue(namespace, heap) ?: return identifier
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, cval.strvalue, cval.arrayvalue, identifier.position) val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, cval.strvalue, cval.arrayvalue, position=identifier.position)
copy.parent = identifier.parent copy.parent = identifier.parent
return copy return copy
} catch (ax: AstException) { } catch (ax: AstException) {
@ -107,7 +108,7 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
override fun process(functionCall: FunctionCall): IExpression { override fun process(functionCall: FunctionCall): IExpression {
return try { return try {
super.process(functionCall) super.process(functionCall)
functionCall.constValue(namespace) ?: functionCall functionCall.constValue(namespace, heap) ?: functionCall
} catch (ax: AstException) { } catch (ax: AstException) {
addError(ax) addError(ax)
functionCall functionCall
@ -183,8 +184,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
override fun process(expr: BinaryExpression): IExpression { override fun process(expr: BinaryExpression): IExpression {
return try { return try {
super.process(expr) super.process(expr)
val leftconst = expr.left.constValue(namespace) val leftconst = expr.left.constValue(namespace, heap)
val rightconst = expr.right.constValue(namespace) val rightconst = expr.right.constValue(namespace, heap)
val subExpr: BinaryExpression? = when { val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression leftconst!=null -> expr.right as? BinaryExpression
@ -192,8 +193,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
else -> null else -> null
} }
if(subExpr!=null) { if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(namespace) val subleftconst = subExpr.left.constValue(namespace, heap)
val subrightconst = subExpr.right.constValue(namespace) val subrightconst = subExpr.right.constValue(namespace, heap)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null))
// try reordering. // try reordering.
return groupTwoConstsTogether(expr, subExpr, return groupTwoConstsTogether(expr, subExpr,
@ -286,12 +287,23 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
} }
override fun process(literalValue: LiteralValue): LiteralValue { 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() val newArray = literalValue.arrayvalue.map { it.process(this) }.toTypedArray()
// determine if the values are all bytes or that we need a word array instead // determine if the values are all bytes or that we need a word array instead
var arrayDt = DataType.ARRAY var arrayDt = DataType.ARRAY
var allElementsAreConstant = true
for (expr in newArray) { 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) if(valueDt==DataType.BYTE)
continue continue
else { else {
@ -299,6 +311,37 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
break 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) val newValue = LiteralValue(arrayDt, arrayvalue = newArray, position = literalValue.position)
return super.process(newValue) return super.process(newValue)
} }

View File

@ -3,17 +3,12 @@ package prog8.optimizing
import prog8.ast.AstException import prog8.ast.AstException
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Module import prog8.ast.Module
import prog8.compiler.HeapValues
import prog8.parser.ParsingFailedError 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) { fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) {
val optimizer = ConstantFolding(globalNamespace) val optimizer = ConstantFolding(globalNamespace, heap)
try { try {
this.process(optimizer) this.process(optimizer)
} catch (ax: AstException) { } catch (ax: AstException) {
@ -35,8 +30,8 @@ fun Module.constantFold(globalNamespace: INameScope) {
} }
fun Module.optimizeStatements(globalNamespace: INameScope): Int { fun Module.optimizeStatements(globalNamespace: INameScope, heap: HeapValues): Int {
val optimizer = StatementOptimizer(globalNamespace) val optimizer = StatementOptimizer(globalNamespace, heap)
this.process(optimizer) this.process(optimizer)
if(optimizer.optimizationsDone > 0) if(optimizer.optimizationsDone > 0)
println("[${this.name}] Debug: ${optimizer.optimizationsDone} statement optimizations performed") println("[${this.name}] Debug: ${optimizer.optimizationsDone} statement optimizations performed")
@ -44,8 +39,8 @@ fun Module.optimizeStatements(globalNamespace: INameScope): Int {
return optimizer.optimizationsDone return optimizer.optimizationsDone
} }
fun Module.simplifyExpressions(namespace: INameScope) : Int { fun Module.simplifyExpressions(namespace: INameScope, heap: HeapValues) : Int {
val optimizer = SimplifyExpressions(namespace) val optimizer = SimplifyExpressions(namespace, heap)
this.process(optimizer) this.process(optimizer)
if(optimizer.optimizationsDone > 0) if(optimizer.optimizationsDone > 0)
println("[${this.name}] Debug: ${optimizer.optimizationsDone} expression optimizations performed") println("[${this.name}] Debug: ${optimizer.optimizationsDone} expression optimizations performed")

View File

@ -1,6 +1,7 @@
package prog8.optimizing package prog8.optimizing
import prog8.ast.* import prog8.ast.*
import prog8.compiler.HeapValues
import kotlin.math.abs 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 var optimizationsDone: Int = 0
override fun process(assignment: Assignment): IStatement { override fun process(assignment: Assignment): IStatement {
@ -30,8 +31,8 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
override fun process(expr: BinaryExpression): IExpression { override fun process(expr: BinaryExpression): IExpression {
super.process(expr) super.process(expr)
val leftVal = expr.left.constValue(namespace) val leftVal = expr.left.constValue(namespace, heap)
val rightVal = expr.right.constValue(namespace) val rightVal = expr.right.constValue(namespace, heap)
val constTrue = LiteralValue.fromBoolean(true, expr.position) val constTrue = LiteralValue.fromBoolean(true, expr.position)
val constFalse = LiteralValue.fromBoolean(false, 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.left = expr.right
expr.right = tmp expr.right = tmp
optimizationsDone++ 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 { 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) { if(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) {
optimizationsDone++ optimizationsDone++
return LiteralValue(DataType.BYTE, 0, position = expr.position) 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) { if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) {
optimizationsDone++ optimizationsDone++
return LiteralValue(DataType.BYTE, 0, position = expr.position) return LiteralValue(DataType.BYTE, 0, position = expr.position)

View File

@ -1,6 +1,7 @@
package prog8.optimizing package prog8.optimizing
import prog8.ast.* import prog8.ast.*
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctionNames import prog8.functions.BuiltinFunctionNames
import prog8.functions.BuiltinFunctionsWithoutSideEffects import prog8.functions.BuiltinFunctionsWithoutSideEffects
@ -9,6 +10,7 @@ import prog8.functions.BuiltinFunctionsWithoutSideEffects
todo remove unused blocks todo remove unused blocks
todo remove unused variables todo remove unused variables
todo remove unused subroutines todo remove unused subroutines
todo remove unused strings and arrays from the heap
todo remove if statements with empty statement blocks todo remove if statements with empty statement blocks
todo replace if statements with only else block 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, ...) 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" 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 var optimizationsDone: Int = 0
private set private set
@ -43,7 +45,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
override fun process(ifStatement: IfStatement): IStatement { override fun process(ifStatement: IfStatement): IStatement {
super.process(ifStatement) super.process(ifStatement)
val constvalue = ifStatement.condition.constValue(globalNamespace) val constvalue = ifStatement.condition.constValue(globalNamespace, heap)
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> keep only if-part // always true -> keep only if-part
@ -75,7 +77,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
override fun process(whileLoop: WhileLoop): IStatement { override fun process(whileLoop: WhileLoop): IStatement {
super.process(whileLoop) super.process(whileLoop)
val constvalue = whileLoop.condition.constValue(globalNamespace) val constvalue = whileLoop.condition.constValue(globalNamespace, heap)
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true // always true
@ -92,7 +94,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
override fun process(repeatLoop: RepeatLoop): IStatement { override fun process(repeatLoop: RepeatLoop): IStatement {
super.process(repeatLoop) super.process(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(globalNamespace) val constvalue = repeatLoop.untilCondition.constValue(globalNamespace, heap)
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> keep only the statement block // 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 // Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7
package prog8.parser; 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.*;
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.dfa.DFA;
import org.antlr.v4.runtime.misc.*;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
public class prog8Lexer extends Lexer { 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 // Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7
package prog8.parser; 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.*;
import org.antlr.v4.runtime.misc.*; import org.antlr.v4.runtime.atn.ATN;
import org.antlr.v4.runtime.tree.*; 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.List;
import java.util.Iterator;
import java.util.ArrayList;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
public class prog8Parser extends Parser { 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 package prog8.stackvm
import prog8.ast.DataType import prog8.ast.DataType
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
import java.util.* import java.util.*
import java.util.regex.Pattern
import kotlin.math.* import kotlin.math.*
enum class Opcode { enum class Opcode {
@ -17,7 +17,6 @@ enum class Opcode {
PUSH_MEM_F, // push float value from memory to stack PUSH_MEM_F, // push float value from memory to stack
PUSH_VAR, // push a variable PUSH_VAR, // push a variable
DUP, // push topmost value once again 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 // popping values off the (evaluation) stack, possibly storing them in another location
DISCARD, // discard top value 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, open class Instruction(val opcode: Opcode,
val arg: Value? = null, val arg: Value? = null,
val callLabel: String? = 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) Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR)
val result = val result =
when { when {
opcode==Opcode.LINE -> "_line ${arg!!.stringvalue}" opcode==Opcode.LINE -> "_line $callLabel"
opcode==Opcode.SYSCALL -> { opcode==Opcode.SYSCALL -> {
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() } val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
"syscall $syscall" "syscall $syscall"
} }
opcodesWithVarArgument.contains(opcode) -> { opcodesWithVarArgument.contains(opcode) -> {
// opcodes that manipulate a variable // opcodes that manipulate a variable
"${opcode.toString().toLowerCase()} ${argStr.substring(1, argStr.length-1)}" "${opcode.toString().toLowerCase()} $callLabel"
} }
callLabel==null -> "${opcode.toString().toLowerCase()} $argStr" callLabel==null -> "${opcode.toString().toLowerCase()} $argStr"
else -> "${opcode.toString().toLowerCase()} $callLabel $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?) { class StackVm(private var traceOutputFile: String?) {
val mem = Memory() val mem = Memory()
@ -866,6 +235,7 @@ class StackVm(private var traceOutputFile: String?) {
var callstack = MyStack<Instruction>() var callstack = MyStack<Instruction>()
private set private set
private var program = listOf<Instruction>() private var program = listOf<Instruction>()
private var heap = HeapValues()
private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null
private var canvas: BitmapScreenPanel? = null private var canvas: BitmapScreenPanel? = null
private val rnd = Random() private val rnd = Random()
@ -877,7 +247,9 @@ class StackVm(private var traceOutputFile: String?) {
fun load(program: Program, canvas: BitmapScreenPanel?) { fun load(program: Program, canvas: BitmapScreenPanel?) {
this.program = program.program this.program = program.program
this.heap = program.heap
this.canvas = canvas this.canvas = canvas
variables.clear()
for(variable in program.variables.flatMap { e->e.value.entries }) for(variable in program.variables.flatMap { e->e.value.entries })
variables[variable.key] = variable.value variables[variable.key] = variable.value
@ -955,8 +327,9 @@ class StackVm(private var traceOutputFile: String?) {
address += 5 address += 5
} }
DataType.STR -> { DataType.STR -> {
mem.setString(address, value.stringvalue!!) TODO("mem init with string")
address += value.stringvalue.length+1 //mem.setString(address, value.stringvalue!!)
//address += value.stringvalue.length+1
} }
else -> throw VmExecutionException("invalid mem datatype ${value.type}") 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))) evalstack.push(Value(DataType.FLOAT, mem.getFloat(address)))
} }
Opcode.DUP -> evalstack.push(evalstack.peek()) 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.DISCARD -> evalstack.pop()
Opcode.SWAP -> { Opcode.SWAP -> {
val (top, second) = evalstack.pop2() val (top, second) = evalstack.pop2()
@ -1120,14 +477,21 @@ class StackVm(private var traceOutputFile: String?) {
val value = evalstack.pop() val value = evalstack.pop()
when(value.type){ when(value.type){
DataType.BYTE, DataType.WORD, DataType.FLOAT -> print(value.numericValue()) DataType.BYTE, DataType.WORD, DataType.FLOAT -> print(value.numericValue())
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> print(value.stringvalue) DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> print(value.arrayvalue) TODO("print stringvalue")
}
DataType.ARRAY, DataType.ARRAY_W -> {
TODO("print array value")
}
DataType.MATRIX -> {
TODO("print matrix value")
}
} }
} }
Syscall.INPUT_STR -> { Syscall.INPUT_STR -> {
val maxlen = evalstack.pop().integerValue() val maxlen = evalstack.pop().integerValue()
val input = readLine()?.substring(0, maxlen) ?: "" 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 -> { Syscall.GFX_PIXEL -> {
// plot pixel at (x, y, color) from stack // plot pixel at (x, y, color) from stack
@ -1140,23 +504,25 @@ class StackVm(private var traceOutputFile: String?) {
canvas?.clearScreen(color.integerValue()) canvas?.clearScreen(color.integerValue())
} }
Syscall.GFX_TEXT -> { Syscall.GFX_TEXT -> {
val text = evalstack.pop() val textPtr = evalstack.pop()
val color = evalstack.pop() val color = evalstack.pop()
val (y, x) = evalstack.pop2() 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_RND -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255))
Syscall.FUNC_RNDW -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535)) Syscall.FUNC_RNDW -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535))
Syscall.FUNC_RNDF -> evalstack.push(Value(DataType.FLOAT, rnd.nextDouble())) Syscall.FUNC_RNDF -> evalstack.push(Value(DataType.FLOAT, rnd.nextDouble()))
Syscall.FUNC_LEN -> { Syscall.FUNC_LEN -> {
val value = evalstack.pop() val value = evalstack.pop()
when(value.type) { TODO("func_len")
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> // when(value.type) {
evalstack.push(Value(DataType.WORD, value.stringvalue!!.length)) // DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS ->
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> // evalstack.push(Value(DataType.WORD, value.stringvalue!!.length))
evalstack.push(Value(DataType.WORD, value.arrayvalue!!.size)) // DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX ->
else -> throw VmExecutionException("cannot get length of $value") // 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_SIN -> evalstack.push(Value(DataType.FLOAT, sin(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_COS -> evalstack.push(Value(DataType.FLOAT, cos(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) evalstack.push(result)
} }
Syscall.FUNC_MAX -> { Syscall.FUNC_MAX -> {
val array = evalstack.pop() val iterable = evalstack.pop()
val dt = val dt =
when { when(iterable.type) {
array.type==DataType.ARRAY -> DataType.BYTE DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
array.type==DataType.ARRAY_W -> DataType.WORD DataType.ARRAY, DataType.MATRIX -> DataType.BYTE
else -> throw VmExecutionException("invalid array datatype $array") 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 -> { Syscall.FUNC_MIN -> {
val array = evalstack.pop() val iterable = evalstack.pop()
val dt = val dt =
when { when(iterable.type) {
array.type==DataType.ARRAY -> DataType.BYTE DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
array.type==DataType.ARRAY_W -> DataType.WORD DataType.ARRAY, DataType.MATRIX -> DataType.BYTE
else -> throw VmExecutionException("invalid array datatype $array") 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 -> { Syscall.FUNC_AVG -> {
val array = evalstack.pop() val iterable = evalstack.pop()
evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average())) TODO("func_avg")
// evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average()))
} }
Syscall.FUNC_SUM -> { Syscall.FUNC_SUM -> {
val array = evalstack.pop() val iterable = evalstack.pop()
evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum())) TODO("func_sum")
// evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum()))
} }
Syscall.FUNC_ANY -> { Syscall.FUNC_ANY -> {
val array = evalstack.pop() val iterable = evalstack.pop()
evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0)) TODO("func_any")
// evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0))
} }
Syscall.FUNC_ALL -> { Syscall.FUNC_ALL -> {
val array = evalstack.pop() val iterable = evalstack.pop()
evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0)) TODO("func_all")
// evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0))
} }
else -> throw VmExecutionException("unimplemented syscall $syscall") else -> throw VmExecutionException("unimplemented syscall $syscall")
} }
@ -1370,61 +742,51 @@ class StackVm(private var traceOutputFile: String?) {
return callstack.pop() return callstack.pop()
} }
Opcode.PUSH_VAR -> { Opcode.PUSH_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
evalstack.push(variable) evalstack.push(variable)
} }
Opcode.POP_VAR -> { Opcode.POP_VAR -> {
val value = evalstack.pop() val value = evalstack.pop()
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
if(variable.type!=value.type) if(variable.type!=value.type)
throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var $varname") throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var ${ins.callLabel}")
variables[varname] = value variables[ins.callLabel!!] = value
} }
Opcode.SHL_VAR -> { Opcode.SHL_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") variables[ins.callLabel!!] = variable.shl()
variables[varname] = variable.shl()
} }
Opcode.SHR_VAR -> { Opcode.SHR_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") variables[ins.callLabel!!] = variable.shr()
variables[varname] = variable.shr()
} }
Opcode.ROL_VAR -> { Opcode.ROL_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val (newValue, newCarry) = variable.rol(P_carry) val (newValue, newCarry) = variable.rol(P_carry)
variables[varname] = newValue variables[ins.callLabel!!] = newValue
P_carry = newCarry P_carry = newCarry
} }
Opcode.ROR_VAR -> { Opcode.ROR_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val (newValue, newCarry) = variable.ror(P_carry) val (newValue, newCarry) = variable.ror(P_carry)
variables[varname] = newValue variables[ins.callLabel!!] = newValue
P_carry = newCarry P_carry = newCarry
} }
Opcode.ROL2_VAR -> { Opcode.ROL2_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") variables[ins.callLabel!!] = variable.rol2()
variables[varname] = variable.rol2()
} }
Opcode.ROR2_VAR -> { Opcode.ROR2_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") variables[ins.callLabel!!] = variable.ror2()
variables[varname] = variable.ror2()
} }
Opcode.INC_VAR -> { Opcode.INC_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") variables[ins.callLabel!!] = variable.inc()
variables[varname] = variable.inc()
} }
Opcode.DEC_VAR -> { Opcode.DEC_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") variables[ins.callLabel!!] = variable.dec()
variables[varname] = variable.dec()
} }
Opcode.LSB -> { Opcode.LSB -> {
val v = evalstack.pop() val v = evalstack.pop()
@ -1507,7 +869,7 @@ class StackVm(private var traceOutputFile: String?) {
} }
} }
Opcode.LINE -> { Opcode.LINE -> {
sourceLine = ins.arg!!.stringvalue!! sourceLine = ins.callLabel!!
} }
else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}") 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.MatcherAssert.assertThat
import org.hamcrest.Matchers.empty import org.hamcrest.Matchers.empty
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.DataType import prog8.ast.DataType
import prog8.compiler.HeapValues
import prog8.stackvm.* import prog8.stackvm.*
import kotlin.test.* import kotlin.test.*
@ -49,11 +49,13 @@ class TestStackVmOpcodes {
if(vars!=null) { if(vars!=null) {
for (blockvar in vars) { for (blockvar in vars) {
val blockname = blockvar.key.substringBefore('.') val blockname = blockvar.key.substringBefore('.')
val variables = blockvars.getValue(blockname) val variables = blockvars[blockname] ?: mutableMapOf()
blockvars[blockname] = variables
variables[blockvar.key] = blockvar.value 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 @Test
@ -84,7 +86,7 @@ class TestStackVmOpcodes {
@Test @Test
fun testLine() { 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) vm.load(makeProg(ins), null)
assertEquals("", vm.sourceLine) assertEquals("", vm.sourceLine)
vm.step(1) vm.step(1)
@ -149,7 +151,7 @@ class TestStackVmOpcodes {
@Test @Test
fun testPushVar() { 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) vm.load(makeProg(ins, mapOf("varname" to Value(DataType.FLOAT, 42.999))), null)
assertEquals(7, vm.variables.size) assertEquals(7, vm.variables.size)
assertTrue(vm.variables.containsKey("varname")) assertTrue(vm.variables.containsKey("varname"))
@ -206,47 +208,6 @@ class TestStackVmOpcodes {
assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop()) 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 @Test
fun testPopMem() { fun testPopMem() {
val ins = mutableListOf( val ins = mutableListOf(
@ -275,9 +236,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.25)), Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.25)),
Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)), Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)),
Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)), Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)),
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1")), Instruction(Opcode.POP_VAR, callLabel = "var1"),
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var2")), Instruction(Opcode.POP_VAR, callLabel = "var2"),
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var3"))) Instruction(Opcode.POP_VAR, callLabel = "var3"))
val vars = mapOf( val vars = mapOf(
"var1" to Value(DataType.BYTE, 0), "var1" to Value(DataType.BYTE, 0),
"var2" to Value(DataType.WORD, 0), "var2" to Value(DataType.WORD, 0),
@ -292,7 +253,7 @@ class TestStackVmOpcodes {
val ins2 = mutableListOf( val ins2 = mutableListOf(
Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)), 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( val vars2 = mapOf(
"var1" to Value(DataType.BYTE, 0) "var1" to Value(DataType.BYTE, 0)
) )
@ -450,12 +411,12 @@ class TestStackVmOpcodes {
@Test @Test
fun testAnd() { fun testAnd() {
val values = listOf( val values = listOf(
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, 111),
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
Value(DataType.WORD, 0), Value(DataType.WORD, 0),
Value(DataType.STR, null, ""), Value(DataType.STR, 222),
Value(DataType.STR, null, "hello"), Value(DataType.STR, 333),
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, 444),
Value(DataType.FLOAT, 300.33), Value(DataType.FLOAT, 300.33),
Value(DataType.WORD, 5000), Value(DataType.WORD, 5000),
Value(DataType.BYTE, 200), 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, 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), Value(DataType.BYTE, 0),
Value(DataType.BYTE, 0)) Value(DataType.BYTE, 0))
@ -478,19 +439,19 @@ class TestStackVmOpcodes {
@Test @Test
fun testOr() { fun testOr() {
val values = listOf( val values = listOf(
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
Value(DataType.WORD, 0), Value(DataType.WORD, 0),
Value(DataType.STR, null, ""), Value(DataType.STR, 222),
Value(DataType.STR, null, "hello"), Value(DataType.STR, 333),
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, 444),
Value(DataType.FLOAT, 300.33), Value(DataType.FLOAT, 0),
Value(DataType.WORD, 5000), Value(DataType.WORD, 1),
Value(DataType.WORD, 0),
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
Value(DataType.BYTE, 0)) Value(DataType.BYTE, 0))
val expected = listOf( val expected = listOf(
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1), Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1), Value(DataType.BYTE, 1),
@ -506,12 +467,12 @@ class TestStackVmOpcodes {
@Test @Test
fun testXor() { fun testXor() {
val values = listOf( val values = listOf(
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, 111),
Value(DataType.BYTE, 1), Value(DataType.BYTE, 1),
Value(DataType.WORD, 0), Value(DataType.WORD, 0),
Value(DataType.STR, null, ""), Value(DataType.STR, 222),
Value(DataType.STR, null, "hello"), Value(DataType.STR, 333),
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, 444),
Value(DataType.FLOAT, 300.33), Value(DataType.FLOAT, 300.33),
Value(DataType.WORD, 5000), Value(DataType.WORD, 5000),
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
@ -522,10 +483,10 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 1), Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1)) Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0))
val operator = Opcode.XOR val operator = Opcode.XOR
testBinaryOperator(values, operator, expected) testBinaryOperator(values, operator, expected)
@ -534,8 +495,8 @@ class TestStackVmOpcodes {
@Test @Test
fun testNot() { fun testNot() {
val values = listOf( val values = listOf(
Value(DataType.STR, null, ""), Value(DataType.STR, 111),
Value(DataType.STR, null, "hello"), Value(DataType.STR, 222),
Value(DataType.FLOAT, 0.0), Value(DataType.FLOAT, 0.0),
Value(DataType.FLOAT, 300.33), Value(DataType.FLOAT, 300.33),
Value(DataType.WORD, 0), Value(DataType.WORD, 0),
@ -550,7 +511,7 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), Value(DataType.BYTE, 1),
Value(DataType.BYTE, 0), Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1) Value(DataType.BYTE, 0)
) )
val operator = Opcode.NOT val operator = Opcode.NOT
@ -785,10 +746,10 @@ class TestStackVmOpcodes {
@Test @Test
fun testIncVar() { fun testIncVar() {
val ins = mutableListOf( val ins = mutableListOf(
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")), Instruction(Opcode.INC_VAR, callLabel ="var1"),
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2")), Instruction(Opcode.INC_VAR, callLabel ="var2"),
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")), Instruction(Opcode.INC_VAR, callLabel ="var1"),
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2"))) Instruction(Opcode.INC_VAR, callLabel ="var2"))
val vars = mapOf("var1" to Value(DataType.WORD, 65534), val vars = mapOf("var1" to Value(DataType.WORD, 65534),
"var2" to Value(DataType.BYTE, 254)) "var2" to Value(DataType.BYTE, 254))
vm.load(makeProg(ins, vars = vars), null) vm.load(makeProg(ins, vars = vars), null)
@ -803,10 +764,10 @@ class TestStackVmOpcodes {
@Test @Test
fun testDecVar() { fun testDecVar() {
val ins = mutableListOf( val ins = mutableListOf(
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")), Instruction(Opcode.DEC_VAR, callLabel = "var1"),
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2")), Instruction(Opcode.DEC_VAR, callLabel = "var2"),
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")), Instruction(Opcode.DEC_VAR, callLabel = "var1"),
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2"))) Instruction(Opcode.DEC_VAR, callLabel = "var2"))
val vars = mapOf("var1" to Value(DataType.WORD,1), val vars = mapOf("var1" to Value(DataType.WORD,1),
"var2" to Value(DataType.BYTE, 1)) "var2" to Value(DataType.BYTE, 1))
vm.load(makeProg(ins, vars = vars), null) vm.load(makeProg(ins, vars = vars), null)
@ -887,11 +848,11 @@ class TestStackVmOpcodes {
testComparisonOperator(values, expected, Opcode.LESS) testComparisonOperator(values, expected, Opcode.LESS)
val valuesInvalid = listOf( val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"), Value(DataType.STR, 333),
Value(DataType.STR, null, stringvalue = "hello") Value(DataType.STR, 333)
) )
assertFailsWith<VmExecutionException> { 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) testComparisonOperator(values, expected, Opcode.LESSEQ)
val valuesInvalid = listOf( val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"), Value(DataType.STR, 333),
Value(DataType.STR, null, stringvalue = "hello") Value(DataType.STR, 333)
) )
assertFailsWith<VmExecutionException> { 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) testComparisonOperator(values, expected, Opcode.GREATER)
val valuesInvalid = listOf( val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"), Value(DataType.STR, 333),
Value(DataType.STR, null, stringvalue = "hello") Value(DataType.STR, 333)
) )
assertFailsWith<VmExecutionException> { 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) testComparisonOperator(values, expected, Opcode.GREATEREQ)
val valuesInvalid = listOf( val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"), Value(DataType.STR, 333),
Value(DataType.STR, null, stringvalue = "hello") Value(DataType.STR, 333)
) )
assertFailsWith<VmExecutionException> { 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) testComparisonOperator(values, expected, Opcode.EQUAL)
val valuesInvalid = listOf( val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"), Value(DataType.STR, 111),
Value(DataType.STR, null, stringvalue = "hello") Value(DataType.STR, 222), // 0
Value(DataType.STR, 333),
Value(DataType.STR, 333) // 1
) )
assertFailsWith<VmExecutionException> { testComparisonOperator(valuesInvalid, listOf(0, 1), Opcode.EQUAL)
testComparisonOperator(valuesInvalid, listOf(0), Opcode.EQUAL) // can't compare strings
}
} }
@Test @Test
@ -1037,12 +998,12 @@ class TestStackVmOpcodes {
testComparisonOperator(values, expected, Opcode.NOTEQUAL) testComparisonOperator(values, expected, Opcode.NOTEQUAL)
val valuesInvalid = listOf( val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"), Value(DataType.STR, 111),
Value(DataType.STR, null, stringvalue = "hello") Value(DataType.STR, 222), // 1
Value(DataType.STR, 333),
Value(DataType.STR, 333) // 0
) )
assertFailsWith<VmExecutionException> { testComparisonOperator(valuesInvalid, listOf(1, 0), Opcode.NOTEQUAL)
testComparisonOperator(valuesInvalid, listOf(0), Opcode.NOTEQUAL) // can't compare strings
}
} }
@Test @Test
@ -1052,9 +1013,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BCC, callLabel = "label"), Instruction(Opcode.BCC, callLabel = "label"),
Instruction(Opcode.CLC), Instruction(Opcode.CLC),
Instruction(Opcode.BCC, callLabel = "label"), Instruction(Opcode.BCC, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), 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 val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
@ -1072,9 +1033,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BCS, callLabel = "label"), Instruction(Opcode.BCS, callLabel = "label"),
Instruction(Opcode.SEC), Instruction(Opcode.SEC),
Instruction(Opcode.BCS, callLabel = "label"), Instruction(Opcode.BCS, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), 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 val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
assertFalse(vm.P_carry) assertFalse(vm.P_carry)
@ -1093,9 +1054,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BZ, callLabel = "label"), Instruction(Opcode.BZ, callLabel = "label"),
Instruction(Opcode.PUSH, Value(DataType.WORD, 0)), Instruction(Opcode.PUSH, Value(DataType.WORD, 0)),
Instruction(Opcode.BZ, callLabel = "label"), Instruction(Opcode.BZ, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), 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 val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
@ -1113,9 +1074,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BNZ, callLabel = "label"), Instruction(Opcode.BNZ, callLabel = "label"),
Instruction(Opcode.PUSH, Value(DataType.WORD, 1)), Instruction(Opcode.PUSH, Value(DataType.WORD, 1)),
Instruction(Opcode.BNZ, callLabel = "label"), Instruction(Opcode.BNZ, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), 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 val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
@ -1135,9 +1096,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BNEG, callLabel = "label"), Instruction(Opcode.BNEG, callLabel = "label"),
Instruction(Opcode.PUSH, Value(DataType.FLOAT, -99)), Instruction(Opcode.PUSH, Value(DataType.FLOAT, -99)),
Instruction(Opcode.BNEG, callLabel = "label"), Instruction(Opcode.BNEG, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), 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 val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
@ -1157,9 +1118,9 @@ class TestStackVmOpcodes {
Instruction(Opcode.BPOS, callLabel = "label"), Instruction(Opcode.BPOS, callLabel = "label"),
Instruction(Opcode.PUSH, Value(DataType.WORD, 0)), Instruction(Opcode.PUSH, Value(DataType.WORD, 0)),
Instruction(Opcode.BPOS, callLabel = "label"), Instruction(Opcode.BPOS, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), 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 val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
@ -1174,9 +1135,9 @@ class TestStackVmOpcodes {
fun testJump() { fun testJump() {
val ins = mutableListOf( val ins = mutableListOf(
Instruction(Opcode.JUMP, callLabel = "label"), Instruction(Opcode.JUMP, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), 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 val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
@ -1191,7 +1152,7 @@ class TestStackVmOpcodes {
val ins = mutableListOf( val ins = mutableListOf(
Instruction(Opcode.RETURN), Instruction(Opcode.RETURN),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")) Instruction(Opcode.LINE, callLabel = "string1")
) )
vm.load(makeProg(ins), null) vm.load(makeProg(ins), null)
assertFailsWith<VmTerminationException> { assertFailsWith<VmTerminationException> {
@ -1211,9 +1172,9 @@ class TestStackVmOpcodes {
// @todo this only tests call with zero parameters for now. // @todo this only tests call with zero parameters for now.
val ins = mutableListOf( val ins = mutableListOf(
Instruction(Opcode.CALL, callLabel = "label"), Instruction(Opcode.CALL, callLabel = "label"),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "returned")), Instruction(Opcode.LINE, callLabel = "returned"),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "called")), Instruction(Opcode.LINE, callLabel = "called"),
Instruction(Opcode.RETURN) Instruction(Opcode.RETURN)
) )
val labels = mapOf("label" to ins[3]) // points to the LINE instruction 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>) { private fun testBinaryOperator(valuesToPush: List<Value>, operator: Opcode, expected: List<Value>) {
assertEquals(values.size, expected.size+1) assertEquals(valuesToPush.size, expected.size+1)
val ins = mutableListOf<Instruction>() val ins = mutableListOf<Instruction>()
for (value in values) for (value in valuesToPush)
ins.add(Instruction(Opcode.PUSH, value)) ins.add(Instruction(Opcode.PUSH, value))
for (i in 1 until values.size) for (i in 1 until valuesToPush.size)
ins.add(Instruction(operator)) ins.add(Instruction(operator))
vm.load(makeProg(ins), null) vm.load(makeProg(ins), null)
vm.step(values.size) vm.step(valuesToPush.size)
assertEquals(values.size, vm.evalstack.size) assertEquals(valuesToPush.size, vm.evalstack.size)
for (expectedVal in expected) { for (expectedVal in expected) {
vm.step(1) vm.step(1)
assertEquals(expectedVal, vm.evalstack.peek()) assertEquals(expectedVal, vm.evalstack.peek())
@ -1579,18 +1540,18 @@ class TestStackVmOpcodes {
} }
} }
private fun testUnaryOperator(values: List<Value>, operator: Opcode, expected: List<Value>) { private fun testUnaryOperator(valuesToPush: List<Value>, operator: Opcode, expected: List<Value>) {
assertEquals(values.size, expected.size) assertEquals(valuesToPush.size, expected.size)
val ins = mutableListOf<Instruction>() val ins = mutableListOf<Instruction>()
for (value in values) for (value in valuesToPush)
ins.add(Instruction(Opcode.PUSH, value)) 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(operator))
ins.add(Instruction(Opcode.DISCARD)) ins.add(Instruction(Opcode.DISCARD))
} }
vm.load(makeProg(ins), null) vm.load(makeProg(ins), null)
vm.step(values.size) vm.step(valuesToPush.size)
assertEquals(values.size, vm.evalstack.size) assertEquals(valuesToPush.size, vm.evalstack.size)
for (expectedVal in expected) { for (expectedVal in expected) {
vm.step(1) vm.step(1)
assertEquals(expectedVal, vm.evalstack.peek()) 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.BYTE, 9)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.WORD, 9))) assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.WORD, 9)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.0))) assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.0)))
}
assertFailsWith<VmExecutionException> { @Test
assertTrue(sameValueAndType(Value(DataType.STR, null, "hello"), Value(DataType.STR, null, "hello"))) fun testEqualsAndNotEqualsHeapTypes()
} {
assertFailsWith<VmExecutionException> { assertTrue(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 999)))
assertTrue(sameValueAndType(Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)))) 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 @Test