mirror of
https://github.com/irmen/prog8.git
synced 2024-12-27 05:29:38 +00:00
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:
parent
d4232721fc
commit
6b89bb7be5
76
compiler/examples/numbergame-c64.p8
Normal file
76
compiler/examples/numbergame-c64.p8
Normal 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
|
||||
}
|
||||
}
|
@ -1,76 +1,72 @@
|
||||
%output prg
|
||||
%import c64lib
|
||||
%import mathlib
|
||||
|
||||
~ main {
|
||||
sub start() -> () {
|
||||
str name = "?" * 80
|
||||
str guess = "?" * 80
|
||||
str name = "?" * 20
|
||||
str guess = "?" * 20
|
||||
byte secretnumber = 0
|
||||
byte attempts_left = 10
|
||||
memory word freadstr_arg = $22 ; argument for FREADSTR
|
||||
|
||||
c64.init_system()
|
||||
c64.VMCSB |= 2 ; activate lowercase charset
|
||||
|
||||
; greeting
|
||||
c64scr.print_string("Enter your name: ")
|
||||
Y = c64scr.input_chars(name)
|
||||
c64.CHROUT("\n")
|
||||
c64.CHROUT("\n")
|
||||
c64scr.print_string("Hello, ")
|
||||
c64scr.print_string(name)
|
||||
c64.CHROUT(".")
|
||||
c64.CHROUT("\n")
|
||||
_vm_write_str("Enter your name: ")
|
||||
; _vm_input_str(name)
|
||||
_vm_write_char($8d)
|
||||
_vm_write_char($8d)
|
||||
_vm_write_str("Hello, ")
|
||||
_vm_write_str(name)
|
||||
_vm_write_char($2e)
|
||||
_vm_write_char($8d)
|
||||
|
||||
; create a secret random number from 1-100
|
||||
c64.RNDA(0) ; fac = rnd(0)
|
||||
c64.MUL10() ; fac *= 10
|
||||
c64.MUL10() ; .. and now *100
|
||||
c64.FADDH() ; add 0.5..
|
||||
c64.FADDH() ; and again, so +1 total
|
||||
AY = c64flt.GETADRAY()
|
||||
secretnumber = A
|
||||
;A=math.randbyte()
|
||||
;A+=c64.RASTER
|
||||
;A-=c64.TIME_LO
|
||||
;X,secretnumber=math.divmod_bytes(A, 99)
|
||||
|
||||
c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n")
|
||||
|
||||
ask_guess:
|
||||
c64scr.print_string("\nYou have ")
|
||||
c64scr.print_byte_decimal(attempts_left)
|
||||
c64scr.print_string(" guess")
|
||||
if(attempts_left>0) c64scr.print_string("es")
|
||||
|
||||
c64scr.print_string(" left.\nWhat is your next guess? ")
|
||||
Y = c64scr.input_chars(guess)
|
||||
c64.CHROUT("\n")
|
||||
freadstr_arg = guess
|
||||
c64.FREADSTR(A)
|
||||
AY = c64flt.GETADRAY()
|
||||
if(A==secretnumber) {
|
||||
c64scr.print_string("\nThat's my number, impressive!\n")
|
||||
goto goodbye
|
||||
}
|
||||
c64scr.print_string("That is too ")
|
||||
if(A > secretnumber)
|
||||
c64scr.print_string("low!\n")
|
||||
else
|
||||
c64scr.print_string("high!\n")
|
||||
|
||||
attempts_left--
|
||||
if(attempts_left>0) goto ask_guess
|
||||
; more efficient: if_nz goto ask_guess
|
||||
|
||||
; game over.
|
||||
c64scr.print_string("\nToo bad! It was: ")
|
||||
c64scr.print_byte_decimal(secretnumber)
|
||||
c64.CHROUT("\n")
|
||||
|
||||
goodbye:
|
||||
c64scr.print_string("\nThanks for playing. Bye!\n")
|
||||
return
|
||||
|
||||
; ; create a secret random number from 1-100
|
||||
; c64.RNDA(0) ; fac = rnd(0)
|
||||
; c64.MUL10() ; fac *= 10
|
||||
; c64.MUL10() ; .. and now *100
|
||||
; c64.FADDH() ; add 0.5..
|
||||
; c64.FADDH() ; and again, so +1 total
|
||||
; AY = c64flt.GETADRAY()
|
||||
; secretnumber = A
|
||||
; ;A=math.randbyte()
|
||||
; ;A+=c64.RASTER
|
||||
; ;A-=c64.TIME_LO
|
||||
; ;X,secretnumber=math.divmod_bytes(A, 99)
|
||||
;
|
||||
; c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n")
|
||||
;
|
||||
;ask_guess:
|
||||
; c64scr.print_string("\nYou have ")
|
||||
; c64scr.print_byte_decimal(attempts_left)
|
||||
; c64scr.print_string(" guess")
|
||||
; if(attempts_left>0) c64scr.print_string("es")
|
||||
;
|
||||
; c64scr.print_string(" left.\nWhat is your next guess? ")
|
||||
; Y = c64scr.input_chars(guess)
|
||||
; c64.CHROUT("\n")
|
||||
; freadstr_arg = guess
|
||||
; c64.FREADSTR(A)
|
||||
; AY = c64flt.GETADRAY()
|
||||
; if(A==secretnumber) {
|
||||
; c64scr.print_string("\nThat's my number, impressive!\n")
|
||||
; goto goodbye
|
||||
; }
|
||||
; c64scr.print_string("That is too ")
|
||||
; if(A > secretnumber)
|
||||
; c64scr.print_string("low!\n")
|
||||
; else
|
||||
; c64scr.print_string("high!\n")
|
||||
;
|
||||
; attempts_left--
|
||||
; if(attempts_left>0) goto ask_guess
|
||||
; ; more efficient: if_nz goto ask_guess
|
||||
;
|
||||
; ; game over.
|
||||
; c64scr.print_string("\nToo bad! It was: ")
|
||||
; c64scr.print_byte_decimal(secretnumber)
|
||||
; c64.CHROUT("\n")
|
||||
;
|
||||
;goodbye:
|
||||
; c64scr.print_string("\nThanks for playing. Bye!\n")
|
||||
; return
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -2,17 +2,45 @@
|
||||
|
||||
~ main {
|
||||
sub start() -> () {
|
||||
str msg1 = "abc"
|
||||
str msg2 = "abc"
|
||||
str msg3 = "abc123"
|
||||
str_p msg4 = "abc"
|
||||
byte[5] array1 = 0
|
||||
byte[5] array2 = 222
|
||||
byte[6] array3 = [1,2,3,4,5,66]
|
||||
word[5] array4 = 333
|
||||
word[5] array5 = [1,2,3,4,59999]
|
||||
word[5] array6 = [1,2,3,4,5]
|
||||
word[5] array7 = [1,2,3,4,999+22/33]
|
||||
byte[2,3] matrix1 = [1,2, 3,4, 5,6]
|
||||
byte[2,3] matrix2 = [1,2, 3,4, 5,6]
|
||||
byte[2,3] matrix3 = [11,22, 33,44, 55,66]
|
||||
str message = "Calculating Mandelbrot Fractal..."
|
||||
_vm_gfx_text(5, 5, 7, message)
|
||||
_vm_gfx_text(5, 5, 7, "Calculating Mandelbrot Fractal...")
|
||||
|
||||
A = calcIt(12345, 99)
|
||||
;A = 99/5 + lsb(12345)
|
||||
_vm_write_num(A)
|
||||
_vm_write_char($8d)
|
||||
return
|
||||
A= len("abcdef")
|
||||
|
||||
A= len([4,5,99/X])
|
||||
A= max([4,5,99])
|
||||
A= min([4,5,99])
|
||||
A= avg([4,5,99])
|
||||
A= sum([4,5,99])
|
||||
A= any([4,5,99])
|
||||
A= all([4,5,99])
|
||||
|
||||
A= len(msg3)
|
||||
|
||||
float xx
|
||||
|
||||
A= len(array3)
|
||||
A= max(array3)
|
||||
A= min(array3)
|
||||
xx= avg(array3)
|
||||
A= sum(array3)
|
||||
A= any(array3)
|
||||
A= all(array3)
|
||||
|
||||
}
|
||||
|
||||
sub calcIt(length: XY, control: A) -> (Y) {
|
||||
|
||||
return control/5 +lsb(length)
|
||||
}
|
||||
}
|
||||
|
@ -55,27 +55,28 @@ fun main(args: Array<String>) {
|
||||
moduleAst.checkIdentifiers()
|
||||
|
||||
println("Optimizing...")
|
||||
moduleAst.constantFold(namespace)
|
||||
moduleAst.checkValid(namespace, compilerOptions) // check if tree is valid
|
||||
val heap = HeapValues()
|
||||
moduleAst.constantFold(namespace, heap)
|
||||
moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid
|
||||
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers()
|
||||
while(true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = moduleAst.simplifyExpressions(namespace)
|
||||
val optsDone2 = moduleAst.optimizeStatements(namespace)
|
||||
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
|
||||
val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
|
||||
if(optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
StatementReorderer().process(moduleAst) // reorder statements to please the compiler later
|
||||
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime
|
||||
moduleAst.checkValid(namespace, compilerOptions) // check if final tree is valid
|
||||
moduleAst.checkValid(namespace, compilerOptions, heap) // check if final tree is valid
|
||||
moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
|
||||
|
||||
// namespace.debugPrint()
|
||||
|
||||
// compile the syntax tree into stackvmProg form, and optimize that
|
||||
val compiler = Compiler(compilerOptions)
|
||||
val intermediate = compiler.compile(moduleAst)
|
||||
val intermediate = compiler.compile(moduleAst, heap)
|
||||
intermediate.optimize()
|
||||
|
||||
val stackVmFilename = intermediate.name + "_stackvm.txt"
|
||||
|
@ -2,6 +2,7 @@ package prog8.ast
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.functions.*
|
||||
import prog8.parser.prog8Parser
|
||||
@ -677,11 +678,11 @@ data class AssignTarget(val register: Register?, val identifier: IdentifierRefer
|
||||
|
||||
|
||||
interface IExpression: Node {
|
||||
fun isIterable(namespace: INameScope): Boolean
|
||||
fun constValue(namespace: INameScope): LiteralValue?
|
||||
fun isIterable(namespace: INameScope, heap: HeapValues): Boolean
|
||||
fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue?
|
||||
fun process(processor: IAstProcessor): IExpression
|
||||
fun referencesIdentifier(name: String): Boolean
|
||||
fun resultingDatatype(namespace: INameScope): DataType?
|
||||
fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType?
|
||||
}
|
||||
|
||||
|
||||
@ -695,11 +696,11 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri
|
||||
expression.linkParents(this)
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? = null
|
||||
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
|
||||
override fun resultingDatatype(namespace: INameScope): DataType? = expression.resultingDatatype(namespace)
|
||||
override fun isIterable(namespace: INameScope) = false
|
||||
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? = expression.resultingDatatype(namespace, heap)
|
||||
override fun isIterable(namespace: INameScope, heap: HeapValues) = false
|
||||
}
|
||||
|
||||
|
||||
@ -713,13 +714,13 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
|
||||
}
|
||||
|
||||
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
||||
override fun constValue(namespace: INameScope): LiteralValue? = null
|
||||
override fun isIterable(namespace: INameScope) = false
|
||||
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
|
||||
override fun isIterable(namespace: INameScope, heap: HeapValues) = false
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
|
||||
override fun resultingDatatype(namespace: INameScope): DataType? {
|
||||
val leftDt = left.resultingDatatype(namespace)
|
||||
val rightDt = right.resultingDatatype(namespace)
|
||||
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
|
||||
val leftDt = left.resultingDatatype(namespace, heap)
|
||||
val rightDt = right.resultingDatatype(namespace, heap)
|
||||
return when(operator) {
|
||||
"+", "-", "*", "**", "%" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt)
|
||||
"//" -> if(leftDt==null || rightDt==null) null else integerDivisionOpDt(leftDt, rightDt)
|
||||
@ -731,7 +732,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
|
||||
"<=", ">=",
|
||||
"==", "!=" -> DataType.BYTE
|
||||
"/" -> {
|
||||
val rightNum = right.constValue(namespace)?.asNumericValue?.toDouble()
|
||||
val rightNum = right.constValue(namespace, heap)?.asNumericValue?.toDouble()
|
||||
if(rightNum!=null) {
|
||||
when(leftDt) {
|
||||
DataType.BYTE ->
|
||||
@ -807,8 +808,10 @@ class LiteralValue(val type: DataType,
|
||||
val floatvalue: Double? = null,
|
||||
val strvalue: String? = null,
|
||||
val arrayvalue: Array<IExpression>? = null,
|
||||
val heapId: Int? =null,
|
||||
override val position: Position) : IExpression {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false
|
||||
|
||||
val isString = type==DataType.STR || type==DataType.STR_P || type==DataType.STR_S || type==DataType.STR_PS
|
||||
@ -858,13 +861,13 @@ class LiteralValue(val type: DataType,
|
||||
DataType.BYTE -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
|
||||
DataType.WORD -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
|
||||
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
if(strvalue==null) throw FatalAstException("literal value missing strvalue")
|
||||
}
|
||||
DataType.ARRAY, DataType.ARRAY_W -> if(arrayvalue==null) throw FatalAstException("literal value missing arrayvalue")
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS ->
|
||||
if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
|
||||
DataType.ARRAY, DataType.ARRAY_W ->
|
||||
if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
|
||||
DataType.MATRIX -> TODO("matrix literalvalue? for now, arrays are good enough for this")
|
||||
}
|
||||
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null)
|
||||
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null)
|
||||
throw FatalAstException("literal value without actual value")
|
||||
}
|
||||
|
||||
@ -893,7 +896,7 @@ class LiteralValue(val type: DataType,
|
||||
arrayvalue?.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? = this
|
||||
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = this
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
|
||||
override fun toString(): String {
|
||||
@ -901,16 +904,25 @@ class LiteralValue(val type: DataType,
|
||||
DataType.BYTE -> "byte:$bytevalue"
|
||||
DataType.WORD -> "word:$wordvalue"
|
||||
DataType.FLOAT -> "float:$floatvalue"
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> "str:$strvalue"
|
||||
DataType.ARRAY, DataType.ARRAY_W -> "array:$arrayvalue"
|
||||
DataType.MATRIX -> "matrix:$arrayvalue"
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> {
|
||||
if(heapId!=null) "str:#$heapId"
|
||||
else "str:$strvalue"
|
||||
}
|
||||
DataType.ARRAY, DataType.ARRAY_W -> {
|
||||
if(heapId!=null) "array:#$heapId"
|
||||
else "array:$arrayvalue"
|
||||
}
|
||||
DataType.MATRIX -> {
|
||||
if(heapId!=null) "matrix:#$heapId"
|
||||
else "matrix:$arrayvalue"
|
||||
}
|
||||
}
|
||||
return "LiteralValue($vstr)"
|
||||
}
|
||||
|
||||
override fun resultingDatatype(namespace: INameScope) = type
|
||||
override fun resultingDatatype(namespace: INameScope, heap: HeapValues) = type
|
||||
|
||||
override fun isIterable(namespace: INameScope): Boolean = IterableDatatypes.contains(type)
|
||||
override fun isIterable(namespace: INameScope, heap: HeapValues): Boolean = IterableDatatypes.contains(type)
|
||||
|
||||
override fun hashCode(): Int {
|
||||
val bh = bytevalue?.hashCode() ?: 0x10001234
|
||||
@ -954,13 +966,13 @@ class RangeExpr(var from: IExpression,
|
||||
step.linkParents(this)
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? = null
|
||||
override fun isIterable(namespace: INameScope) = true
|
||||
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
|
||||
override fun isIterable(namespace: INameScope, heap: HeapValues) = true
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name)
|
||||
override fun resultingDatatype(namespace: INameScope): DataType? {
|
||||
val fromDt=from.resultingDatatype(namespace)
|
||||
val toDt=to.resultingDatatype(namespace)
|
||||
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
|
||||
val fromDt=from.resultingDatatype(namespace, heap)
|
||||
val toDt=to.resultingDatatype(namespace, heap)
|
||||
return when {
|
||||
fromDt==null || toDt==null -> null
|
||||
fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD
|
||||
@ -1023,15 +1035,15 @@ class RegisterExpr(val register: Register, override val position: Position) : IE
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? = null
|
||||
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
|
||||
override fun process(processor: IAstProcessor) = this
|
||||
override fun referencesIdentifier(name: String): Boolean = false
|
||||
override fun isIterable(namespace: INameScope) = false
|
||||
override fun isIterable(namespace: INameScope, heap: HeapValues) = false
|
||||
override fun toString(): String {
|
||||
return "RegisterExpr(register=$register, pos=$position)"
|
||||
}
|
||||
|
||||
override fun resultingDatatype(namespace: INameScope): DataType? {
|
||||
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
|
||||
return when(register){
|
||||
Register.A, Register.X, Register.Y -> DataType.BYTE
|
||||
Register.AX, Register.AY, Register.XY -> DataType.WORD
|
||||
@ -1053,7 +1065,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope): LiteralValue? {
|
||||
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? {
|
||||
val node = namespace.lookup(nameInSource, this)
|
||||
?: throw UndefinedSymbolError(this)
|
||||
val vardecl = node as? VarDecl
|
||||
@ -1062,7 +1074,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
} else if(vardecl.type!=VarDeclType.CONST) {
|
||||
return null
|
||||
}
|
||||
return vardecl.value?.constValue(namespace)
|
||||
return vardecl.value?.constValue(namespace, heap)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -1072,7 +1084,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time?
|
||||
|
||||
override fun resultingDatatype(namespace: INameScope): DataType? {
|
||||
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
|
||||
val targetStmt = targetStatement(namespace)
|
||||
if(targetStmt is VarDecl) {
|
||||
return targetStmt.datatype
|
||||
@ -1081,7 +1093,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIterable(namespace: INameScope): Boolean = IterableDatatypes.contains(resultingDatatype(namespace))
|
||||
override fun isIterable(namespace: INameScope, heap: HeapValues): Boolean = IterableDatatypes.contains(resultingDatatype(namespace, heap))
|
||||
}
|
||||
|
||||
|
||||
@ -1131,45 +1143,45 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
arglist.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun constValue(namespace: INameScope) = constValue(namespace, true)
|
||||
override fun constValue(namespace: INameScope, heap: HeapValues) = constValue(namespace, heap, true)
|
||||
|
||||
private fun constValue(namespace: INameScope, withDatatypeCheck: Boolean): LiteralValue? {
|
||||
private fun constValue(namespace: INameScope, heap: HeapValues, withDatatypeCheck: Boolean): LiteralValue? {
|
||||
// if the function is a built-in function and the args are consts, should try to const-evaluate!
|
||||
if(target.nameInSource.size>1) return null
|
||||
try {
|
||||
val resultValue = when (target.nameInSource[0]) {
|
||||
"sin" -> builtinSin(arglist, position, namespace)
|
||||
"cos" -> builtinCos(arglist, position, namespace)
|
||||
"abs" -> builtinAbs(arglist, position, namespace)
|
||||
"acos" -> builtinAcos(arglist, position, namespace)
|
||||
"asin" -> builtinAsin(arglist, position, namespace)
|
||||
"tan" -> builtinTan(arglist, position, namespace)
|
||||
"atan" -> builtinAtan(arglist, position, namespace)
|
||||
"ln" -> builtinLn(arglist, position, namespace)
|
||||
"log2" -> builtinLog2(arglist, position, namespace)
|
||||
"log10" -> builtinLog10(arglist, position, namespace)
|
||||
"sqrt" -> builtinSqrt(arglist, position, namespace)
|
||||
"max" -> builtinMax(arglist, position, namespace)
|
||||
"min" -> builtinMin(arglist, position, namespace)
|
||||
"round" -> builtinRound(arglist, position, namespace)
|
||||
"rad" -> builtinRad(arglist, position, namespace)
|
||||
"deg" -> builtinDeg(arglist, position, namespace)
|
||||
"sum" -> builtinSum(arglist, position, namespace)
|
||||
"avg" -> builtinAvg(arglist, position, namespace)
|
||||
"len" -> builtinLen(arglist, position, namespace)
|
||||
"lsb" -> builtinLsb(arglist, position, namespace)
|
||||
"msb" -> builtinMsb(arglist, position, namespace)
|
||||
"flt" -> builtinFlt(arglist, position, namespace)
|
||||
"any" -> builtinAny(arglist, position, namespace)
|
||||
"all" -> builtinAll(arglist, position, namespace)
|
||||
"floor" -> builtinFloor(arglist, position, namespace)
|
||||
"ceil" -> builtinCeil(arglist, position, namespace)
|
||||
"sin" -> builtinSin(arglist, position, namespace, heap)
|
||||
"cos" -> builtinCos(arglist, position, namespace, heap)
|
||||
"abs" -> builtinAbs(arglist, position, namespace, heap)
|
||||
"acos" -> builtinAcos(arglist, position, namespace, heap)
|
||||
"asin" -> builtinAsin(arglist, position, namespace, heap)
|
||||
"tan" -> builtinTan(arglist, position, namespace, heap)
|
||||
"atan" -> builtinAtan(arglist, position, namespace, heap)
|
||||
"ln" -> builtinLn(arglist, position, namespace, heap)
|
||||
"log2" -> builtinLog2(arglist, position, namespace, heap)
|
||||
"log10" -> builtinLog10(arglist, position, namespace, heap)
|
||||
"sqrt" -> builtinSqrt(arglist, position, namespace, heap)
|
||||
"max" -> builtinMax(arglist, position, namespace, heap)
|
||||
"min" -> builtinMin(arglist, position, namespace, heap)
|
||||
"round" -> builtinRound(arglist, position, namespace, heap)
|
||||
"rad" -> builtinRad(arglist, position, namespace, heap)
|
||||
"deg" -> builtinDeg(arglist, position, namespace, heap)
|
||||
"sum" -> builtinSum(arglist, position, namespace, heap)
|
||||
"avg" -> builtinAvg(arglist, position, namespace, heap)
|
||||
"len" -> builtinLen(arglist, position, namespace, heap)
|
||||
"lsb" -> builtinLsb(arglist, position, namespace, heap)
|
||||
"msb" -> builtinMsb(arglist, position, namespace, heap)
|
||||
"flt" -> builtinFlt(arglist, position, namespace, heap)
|
||||
"any" -> builtinAny(arglist, position, namespace, heap)
|
||||
"all" -> builtinAll(arglist, position, namespace, heap)
|
||||
"floor" -> builtinFloor(arglist, position, namespace, heap)
|
||||
"ceil" -> builtinCeil(arglist, position, namespace, heap)
|
||||
"lsl", "lsr", "rol", "rol2", "ror", "ror2", "set_carry", "clear_carry", "set_irqd", "clear_irqd" ->
|
||||
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used in expressions because it doesn't return a value", position)
|
||||
else -> null
|
||||
}
|
||||
if(withDatatypeCheck) {
|
||||
val resultDt = this.resultingDatatype(namespace)
|
||||
val resultDt = this.resultingDatatype(namespace, heap)
|
||||
if(resultValue==null || resultDt == resultValue.type)
|
||||
return resultValue
|
||||
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
|
||||
@ -1190,10 +1202,10 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
|
||||
|
||||
override fun resultingDatatype(namespace: INameScope): DataType? {
|
||||
val constVal = constValue(namespace, false)
|
||||
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
|
||||
val constVal = constValue(namespace, heap,false)
|
||||
if(constVal!=null)
|
||||
return constVal.resultingDatatype(namespace)
|
||||
return constVal.resultingDatatype(namespace, heap)
|
||||
val stmt = target.targetStatement(namespace) ?: return null
|
||||
when (stmt) {
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
@ -1201,7 +1213,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
|
||||
return null // these have no return value
|
||||
}
|
||||
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace)
|
||||
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace, heap)
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(stmt.returnvalues.isEmpty()) {
|
||||
@ -1225,7 +1237,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
TODO("datatype of functioncall to $stmt")
|
||||
}
|
||||
|
||||
override fun isIterable(namespace: INameScope) : Boolean {
|
||||
override fun isIterable(namespace: INameScope, heap: HeapValues) : Boolean {
|
||||
TODO("isIterable of function call result")
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.functions.BuiltinFunctionNames
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
@ -8,8 +9,8 @@ import prog8.parser.ParsingFailedError
|
||||
* General checks on the Ast
|
||||
*/
|
||||
|
||||
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions) {
|
||||
val checker = AstChecker(globalNamespace, compilerOptions)
|
||||
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) {
|
||||
val checker = AstChecker(globalNamespace, compilerOptions, heap)
|
||||
this.process(checker)
|
||||
printErrors(checker.result(), name)
|
||||
}
|
||||
@ -38,7 +39,9 @@ fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
|
||||
}
|
||||
|
||||
|
||||
class AstChecker(private val namespace: INameScope, private val compilerOptions: CompilationOptions) : IAstProcessor {
|
||||
class AstChecker(private val namespace: INameScope,
|
||||
private val compilerOptions: CompilationOptions,
|
||||
private val heap: HeapValues) : IAstProcessor {
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
|
||||
fun result(): List<AstException> {
|
||||
@ -87,10 +90,10 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
override fun process(forLoop: ForLoop): IStatement {
|
||||
if(forLoop.body.isEmpty())
|
||||
printWarning("for loop body is empty", forLoop.position)
|
||||
if(!forLoop.iterable.isIterable(namespace)) {
|
||||
if(!forLoop.iterable.isIterable(namespace, heap)) {
|
||||
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
|
||||
} else {
|
||||
val iterableDt = forLoop.iterable.resultingDatatype(namespace)
|
||||
val iterableDt = forLoop.iterable.resultingDatatype(namespace, heap)
|
||||
if (forLoop.loopRegister != null) {
|
||||
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
|
||||
// loop register
|
||||
@ -281,11 +284,11 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
}
|
||||
|
||||
val targetDatatype = assignment.target.determineDatatype(namespace, assignment)
|
||||
val constVal = assignment.value.constValue(namespace)
|
||||
val constVal = assignment.value.constValue(namespace, heap)
|
||||
if(constVal!=null) {
|
||||
checkValueTypeAndRange(targetDatatype, null, constVal)
|
||||
checkValueTypeAndRange(targetDatatype, null, constVal, heap)
|
||||
} else {
|
||||
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace)
|
||||
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap)
|
||||
if(sourceDatatype==null) {
|
||||
if(assignment.value is FunctionCall)
|
||||
checkResult.add(ExpressionError("function call doesn't return a value to use in assignment", assignment.value.position))
|
||||
@ -340,7 +343,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
}
|
||||
when {
|
||||
decl.value is RangeExpr -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as RangeExpr)
|
||||
decl.value is LiteralValue -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as LiteralValue)
|
||||
decl.value is LiteralValue -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as LiteralValue, heap)
|
||||
else -> {
|
||||
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}")
|
||||
return super.process(decl)
|
||||
@ -433,14 +436,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) {
|
||||
checkResult.add(SyntaxError("floating point value used, but floating point is not enabled via options", literalValue.position))
|
||||
}
|
||||
checkValueTypeAndRange(literalValue.type, null, literalValue)
|
||||
checkValueTypeAndRange(literalValue.type, null, literalValue, heap)
|
||||
return super.process(literalValue)
|
||||
}
|
||||
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
when(expr.operator){
|
||||
"/", "//", "%" -> {
|
||||
val numeric = expr.right.constValue(namespace)?.asNumericValue?.toDouble()
|
||||
val numeric = expr.right.constValue(namespace, heap)?.asNumericValue?.toDouble()
|
||||
if(numeric==0.0)
|
||||
checkResult.add(ExpressionError("division by zero", expr.right.position))
|
||||
}
|
||||
@ -453,9 +456,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
checkResult.add(SyntaxError(msg, range.position))
|
||||
}
|
||||
super.process(range)
|
||||
val from = range.from.constValue(namespace)
|
||||
val to = range.to.constValue(namespace)
|
||||
val stepLv = range.step.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position)
|
||||
val from = range.from.constValue(namespace, heap)
|
||||
val to = range.to.constValue(namespace, heap)
|
||||
val stepLv = range.step.constValue(namespace, heap) ?: LiteralValue(DataType.BYTE, 1, position = range.position)
|
||||
if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) {
|
||||
err("range step must be an integer != 0")
|
||||
return range
|
||||
@ -555,8 +558,8 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
|
||||
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, range: RangeExpr) : Boolean {
|
||||
val from = range.from.constValue(namespace)
|
||||
val to = range.to.constValue(namespace)
|
||||
val from = range.from.constValue(namespace, heap)
|
||||
val to = range.to.constValue(namespace, heap)
|
||||
if(from==null || to==null) {
|
||||
checkResult.add(SyntaxError("range from and to values must be constants", range.position))
|
||||
return false
|
||||
@ -596,7 +599,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, value: LiteralValue) : Boolean {
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, value: LiteralValue, heap: HeapValues) : Boolean {
|
||||
fun err(msg: String) : Boolean {
|
||||
checkResult.add(ExpressionError(msg, value.position))
|
||||
return false
|
||||
@ -629,37 +632,21 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
return err("value '$number' out of range for unsigned word")
|
||||
}
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
val str = value.strvalue
|
||||
?: return err("string value expected")
|
||||
val str = value.strvalue ?: heap.get(value.heapId!!).str!!
|
||||
if (str.isEmpty() || str.length > 255)
|
||||
return err("string length must be 1 to 255")
|
||||
}
|
||||
DataType.ARRAY -> {
|
||||
// value may be either a single byte, or a byte array
|
||||
// value may be either a single byte, or a byte array (of all constant values)
|
||||
if(value.type==DataType.ARRAY) {
|
||||
val array = heap.get(value.heapId!!).array!!
|
||||
if(arrayspec!=null) {
|
||||
// arrayspec is not always known when checking
|
||||
val constX = arrayspec.x.constValue(namespace)
|
||||
val constX = arrayspec.x.constValue(namespace, heap)
|
||||
if(constX?.asIntegerValue==null)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX.asIntegerValue
|
||||
if (value.arrayvalue!!.size != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
|
||||
}
|
||||
for (av in value.arrayvalue!!) {
|
||||
if(av is LiteralValue) {
|
||||
val number = av.bytevalue
|
||||
?: return err("array must be all bytes")
|
||||
if (number < 0 || number > 255)
|
||||
return err("value '$number' in array is out of range for unsigned byte")
|
||||
} else if(av is RegisterExpr) {
|
||||
if(av.register!=Register.A && av.register!=Register.X && av.register!=Register.Y)
|
||||
return err("register '$av' in byte array is not a single register")
|
||||
} else {
|
||||
val avDt = av.resultingDatatype(namespace)
|
||||
if(avDt!=DataType.BYTE)
|
||||
return err("array must be all bytes")
|
||||
}
|
||||
if (array.size != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got ${array.size})")
|
||||
}
|
||||
} else if(value.type==DataType.ARRAY_W) {
|
||||
return err("initialization value must be an array of bytes")
|
||||
@ -675,26 +662,15 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
DataType.ARRAY_W -> {
|
||||
// value may be either a single word, or a word array
|
||||
if(value.type==DataType.ARRAY || value.type==DataType.ARRAY_W) {
|
||||
val array = heap.get(value.heapId!!).array!!
|
||||
if(arrayspec!=null) {
|
||||
// arrayspec is not always known when checking
|
||||
val constX = arrayspec.x.constValue(namespace)
|
||||
val constX = arrayspec.x.constValue(namespace, heap)
|
||||
if(constX?.asIntegerValue==null)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX.asIntegerValue
|
||||
if (value.arrayvalue!!.size != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
|
||||
}
|
||||
for (av in value.arrayvalue!!) {
|
||||
if(av is LiteralValue) {
|
||||
val number = av.asIntegerValue
|
||||
?: return err("array must be all integers")
|
||||
if (number < 0 || number > 65535)
|
||||
return err("value '$number' in array is out of range for unsigned word")
|
||||
} else {
|
||||
val avDt = av.resultingDatatype(namespace)
|
||||
if(avDt!=DataType.BYTE && avDt!=DataType.WORD)
|
||||
return err("array must be all integers")
|
||||
}
|
||||
if (array.size != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got ${array.size})")
|
||||
}
|
||||
} else {
|
||||
val number = value.asIntegerValue ?: return if (value.floatvalue!=null)
|
||||
@ -708,21 +684,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
DataType.MATRIX -> {
|
||||
// value can only be a single byte, or a byte array (which represents the matrix)
|
||||
if(value.type==DataType.ARRAY) {
|
||||
for (av in value.arrayvalue!!) {
|
||||
val number = (av as LiteralValue).bytevalue
|
||||
?: return err("array must be all bytes")
|
||||
val constX = arrayspec!!.x.constValue(namespace)
|
||||
val constY = arrayspec.y!!.constValue(namespace)
|
||||
if(constX?.asIntegerValue==null || constY?.asIntegerValue==null)
|
||||
return err("matrix size specifiers must be constant integer values")
|
||||
|
||||
val expectedSize = constX.asIntegerValue * constY.asIntegerValue
|
||||
if (value.arrayvalue.size != expectedSize)
|
||||
return err("initializer matrix size mismatch (expecting $expectedSize, got ${value.arrayvalue.size} elements)")
|
||||
|
||||
if (number < 0 || number > 255)
|
||||
return err("value '$number' in byte array is out of range for unsigned byte")
|
||||
}
|
||||
val constX = arrayspec!!.x.constValue(namespace, heap)
|
||||
val constY = arrayspec.y!!.constValue(namespace, heap)
|
||||
if(constX?.asIntegerValue==null || constY?.asIntegerValue==null)
|
||||
return err("matrix size specifiers must be constant integer values")
|
||||
val matrix = heap.get(value.heapId!!).array!!
|
||||
val expectedSize = constX.asIntegerValue * constY.asIntegerValue
|
||||
if (matrix.size != expectedSize)
|
||||
return err("initializer matrix size mismatch (expecting $expectedSize, got ${matrix.size} elements)")
|
||||
} else {
|
||||
val number = value.bytevalue
|
||||
?: return err("unsigned byte value expected")
|
||||
|
@ -24,8 +24,52 @@ fun Number.toHex(): String {
|
||||
}
|
||||
|
||||
|
||||
class HeapValues {
|
||||
class HeapValue(val type: DataType, val str: String?, val array: IntArray?) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
other as HeapValue
|
||||
return type==other.type && str==other.str && Arrays.equals(array, other.array)
|
||||
}
|
||||
|
||||
class StackVmProgram(val name: String) {
|
||||
override fun hashCode(): Int {
|
||||
var result = type.hashCode()
|
||||
result = 31 * result + (str?.hashCode() ?: 0)
|
||||
result = 31 * result + (array?.let { Arrays.hashCode(it) } ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private val heap = mutableListOf<HeapValue>()
|
||||
|
||||
fun add(type: DataType, str: String): Int {
|
||||
if (str.isEmpty() || str.length > 255)
|
||||
throw IllegalArgumentException("string length must be 1-255")
|
||||
|
||||
// strings are 'interned' and shared if they're the same
|
||||
val value = HeapValue(type, str, null)
|
||||
val existing = heap.indexOf(value)
|
||||
if(existing>=0)
|
||||
return existing
|
||||
heap.add(value)
|
||||
return heap.size-1
|
||||
}
|
||||
|
||||
fun add(type: DataType, array: IntArray): Int {
|
||||
// arrays are never shared
|
||||
heap.add(HeapValue(type, null, array))
|
||||
return heap.size-1
|
||||
}
|
||||
|
||||
fun get(heapId: Int): HeapValue = heap[heapId]
|
||||
|
||||
fun allStrings() = heap.asSequence().withIndex().filter { it.value.str!=null }.toList()
|
||||
fun allArrays() = heap.asSequence().withIndex().filter { it.value.array!=null }.toList()
|
||||
}
|
||||
|
||||
|
||||
class StackVmProgram(val name: String, val heap: HeapValues) {
|
||||
private val instructions = mutableListOf<Instruction>()
|
||||
private val variables = mutableMapOf<String, MutableMap<String, Value>>()
|
||||
private val memory = mutableMapOf<Int, List<Value>>()
|
||||
@ -44,9 +88,19 @@ class StackVmProgram(val name: String) {
|
||||
|
||||
fun blockvar(scopedname: String, decl: VarDecl) {
|
||||
val value = when(decl.datatype) {
|
||||
DataType.BYTE, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue)
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> Value(decl.datatype, null, (decl.value as LiteralValue).strvalue)
|
||||
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> TODO("array/matrix variable values")
|
||||
DataType.BYTE, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
val litval = (decl.value as LiteralValue)
|
||||
if(litval.heapId==null)
|
||||
throw CompilerException("string should already be in the heap")
|
||||
Value(decl.datatype, litval.heapId)
|
||||
}
|
||||
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> {
|
||||
val litval = (decl.value as LiteralValue)
|
||||
if(litval.heapId==null)
|
||||
throw CompilerException("array/matrix should already be in the heap")
|
||||
Value(decl.datatype, litval.heapId)
|
||||
}
|
||||
}
|
||||
|
||||
// We keep the block structure intact: vars are stored per block. This is needed eventually for the actual 6502 code generation later...
|
||||
@ -57,7 +111,7 @@ class StackVmProgram(val name: String) {
|
||||
}
|
||||
|
||||
fun writeAsText(out: PrintStream) {
|
||||
Program(name, instructions, labels, variables, memory).print(out)
|
||||
Program(name, instructions, labels, variables, memory, heap).print(out)
|
||||
}
|
||||
|
||||
fun instr(opcode: Opcode, arg: Value? = null, callLabel: String? = null) {
|
||||
@ -71,7 +125,7 @@ class StackVmProgram(val name: String) {
|
||||
}
|
||||
|
||||
fun line(position: Position) {
|
||||
instructions.add(Instruction(Opcode.LINE, Value(DataType.STR, null, "${position.line} ${position.file}")))
|
||||
instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,18 +153,18 @@ data class CompilationOptions(val output: OutputType,
|
||||
|
||||
|
||||
class Compiler(private val options: CompilationOptions) {
|
||||
fun compile(module: Module) : StackVmProgram {
|
||||
fun compile(module: Module, heap: HeapValues) : StackVmProgram {
|
||||
println("\nCreating stackVM code...")
|
||||
|
||||
val namespace = module.definingScope()
|
||||
val intermediate = StackVmProgram(module.name)
|
||||
val intermediate = StackVmProgram(module.name, heap)
|
||||
|
||||
// create the pool of all variables used in all blocks and scopes
|
||||
// create the heap of all variables used in all blocks and scopes
|
||||
val varGather = VarGatherer(intermediate)
|
||||
varGather.process(module)
|
||||
println(" ${intermediate.numVariables} allocated variables and constants")
|
||||
|
||||
val translator = StatementTranslator(intermediate, namespace)
|
||||
val translator = StatementTranslator(intermediate, namespace, heap)
|
||||
translator.process(module)
|
||||
println(" ${translator.stmtUniqueSequenceNr} source statements, ${intermediate.numInstructions} resulting instructions")
|
||||
|
||||
@ -130,7 +184,9 @@ class Compiler(private val options: CompilationOptions) {
|
||||
|
||||
}
|
||||
|
||||
private class StatementTranslator(private val stackvmProg: StackVmProgram, private val namespace: INameScope): IAstProcessor {
|
||||
private class StatementTranslator(private val stackvmProg: StackVmProgram,
|
||||
private val namespace: INameScope,
|
||||
private val heap: HeapValues): IAstProcessor {
|
||||
var stmtUniqueSequenceNr = 0
|
||||
private set
|
||||
|
||||
@ -290,8 +346,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
}
|
||||
|
||||
private fun checkForFloatPrecisionProblem(left: IExpression, right: IExpression) {
|
||||
val leftDt = left.resultingDatatype(namespace)
|
||||
val rightDt = right.resultingDatatype(namespace)
|
||||
val leftDt = left.resultingDatatype(namespace, heap)
|
||||
val rightDt = right.resultingDatatype(namespace, heap)
|
||||
if (leftDt == DataType.BYTE || leftDt == DataType.WORD) {
|
||||
if(rightDt==DataType.FLOAT)
|
||||
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit flt() conversion or revert to byte/word arithmetic", left.position)
|
||||
@ -301,7 +357,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
private fun translate(expr: IExpression) {
|
||||
when(expr) {
|
||||
is RegisterExpr -> {
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, expr.register.toString()))
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = expr.register.toString())
|
||||
}
|
||||
is PrefixExpression -> {
|
||||
translate(expr.expression)
|
||||
@ -333,14 +389,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
is VarDecl -> {
|
||||
when(target.type) {
|
||||
VarDeclType.VAR ->
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname))
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = target.scopedname)
|
||||
VarDeclType.CONST ->
|
||||
throw CompilerException("const ref should have been const-folded away")
|
||||
VarDeclType.MEMORY -> {
|
||||
when(target.datatype){
|
||||
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH_MEM, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue))
|
||||
DataType.WORD -> stackvmProg.instr(Opcode.PUSH_MEM_W, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue))
|
||||
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH_MEM_F, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue))
|
||||
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH_MEM, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.WORD -> stackvmProg.instr(Opcode.PUSH_MEM_W, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH_MEM_F, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
else -> TODO("invalid datatype for memory variable expression: $target")
|
||||
}
|
||||
}
|
||||
@ -354,17 +410,13 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
TODO("TRANSLATE range $expr")
|
||||
}
|
||||
else -> {
|
||||
val lv = expr.constValue(namespace) ?: throw CompilerException("constant expression required, not $expr")
|
||||
val lv = expr.constValue(namespace, heap) ?: throw CompilerException("constant expression required, not $expr")
|
||||
when(lv.type) {
|
||||
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue))
|
||||
DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue))
|
||||
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH, Value(DataType.FLOAT, lv.floatvalue))
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> stackvmProg.instr(Opcode.PUSH, Value(DataType.STR,null, lv.strvalue))
|
||||
DataType.ARRAY, DataType.ARRAY_W -> {
|
||||
lv.arrayvalue?.forEach { translate(it) }
|
||||
stackvmProg.instr(Opcode.ARRAY, Value(DataType.WORD, lv.arrayvalue!!.size))
|
||||
}
|
||||
DataType.MATRIX -> TODO("matrix type")
|
||||
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue!!))
|
||||
DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue!!))
|
||||
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH, Value(DataType.FLOAT, lv.floatvalue!!))
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
|
||||
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> stackvmProg.instr(Opcode.PUSH, Value(lv.type, lv.heapId!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -396,7 +448,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
"flt" -> {
|
||||
// 1 argument, type determines the exact opcode to use
|
||||
val arg = args.single()
|
||||
when (arg.resultingDatatype(namespace)) {
|
||||
when (arg.resultingDatatype(namespace, heap)) {
|
||||
DataType.BYTE -> stackvmProg.instr(Opcode.B2FLOAT)
|
||||
DataType.WORD -> stackvmProg.instr(Opcode.W2FLOAT)
|
||||
DataType.FLOAT -> stackvmProg.instr(Opcode.NOP)
|
||||
@ -507,14 +559,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
stackvmProg.line(stmt.position)
|
||||
if(stmt.target.register!=null) {
|
||||
when(stmt.operator) {
|
||||
"++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
|
||||
"--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
|
||||
"++" -> stackvmProg.instr(Opcode.INC_VAR, callLabel = stmt.target.register.toString())
|
||||
"--" -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = stmt.target.register.toString())
|
||||
}
|
||||
} else {
|
||||
val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl
|
||||
when(stmt.operator) {
|
||||
"++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, targetStatement.scopedname))
|
||||
"--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, targetStatement.scopedname))
|
||||
"++" -> stackvmProg.instr(Opcode.INC_VAR, callLabel = targetStatement.scopedname)
|
||||
"--" -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = targetStatement.scopedname)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -522,7 +574,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
private fun translate(stmt: Assignment) {
|
||||
stackvmProg.line(stmt.position)
|
||||
translate(stmt.value)
|
||||
val valueDt = stmt.value.resultingDatatype(namespace)
|
||||
val valueDt = stmt.value.resultingDatatype(namespace, heap)
|
||||
val targetDt = stmt.target.determineDatatype(namespace, stmt)
|
||||
if(valueDt!=targetDt) {
|
||||
// convert value to target datatype if possible
|
||||
@ -552,11 +604,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
if(stmt.target.identifier!=null) {
|
||||
val target = stmt.target.identifier!!.targetStatement(namespace)!!
|
||||
when(target) {
|
||||
is VarDecl -> stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname))
|
||||
is VarDecl -> stackvmProg.instr(Opcode.PUSH_VAR, callLabel = target.scopedname)
|
||||
else -> throw CompilerException("invalid assignment target type ${target::class}")
|
||||
}
|
||||
} else if(stmt.target.register!=null) {
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = stmt.target.register.toString())
|
||||
}
|
||||
translateAugAssignOperator(stmt.aug_op)
|
||||
}
|
||||
@ -565,11 +617,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
if(stmt.target.identifier!=null) {
|
||||
val target = stmt.target.identifier!!.targetStatement(namespace)!!
|
||||
when(target) {
|
||||
is VarDecl -> stackvmProg.instr(Opcode.POP_VAR, Value(DataType.STR, null, target.scopedname))
|
||||
is VarDecl -> stackvmProg.instr(Opcode.POP_VAR, callLabel = target.scopedname)
|
||||
else -> throw CompilerException("invalid assignment target type ${target::class}")
|
||||
}
|
||||
} else if(stmt.target.register!=null) {
|
||||
stackvmProg.instr(Opcode.POP_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
|
||||
stackvmProg.instr(Opcode.POP_VAR, callLabel = stmt.target.register.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -704,32 +756,31 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
continueStmtLabelStack.push(continueLabel)
|
||||
breakStmtLabelStack.push(breakLabel)
|
||||
|
||||
val varValue = Value(DataType.STR, null, varname)
|
||||
stackvmProg.instr(Opcode.PUSH, Value(varDt, range.first))
|
||||
stackvmProg.instr(Opcode.POP_VAR, varValue)
|
||||
stackvmProg.instr(Opcode.POP_VAR, callLabel = varname)
|
||||
stackvmProg.label(loopLabel)
|
||||
translate(body)
|
||||
stackvmProg.label(continueLabel)
|
||||
when {
|
||||
range.step==1 -> stackvmProg.instr(Opcode.INC_VAR, varValue)
|
||||
range.step==-1 -> stackvmProg.instr(Opcode.DEC_VAR, varValue)
|
||||
range.step==1 -> stackvmProg.instr(Opcode.INC_VAR, callLabel = varname)
|
||||
range.step==-1 -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = varname)
|
||||
range.step>1 -> {
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, varValue)
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname)
|
||||
stackvmProg.instr(Opcode.PUSH, Value(varDt, range.step))
|
||||
stackvmProg.instr(Opcode.ADD)
|
||||
stackvmProg.instr(Opcode.POP_VAR, varValue)
|
||||
stackvmProg.instr(Opcode.POP_VAR, callLabel = varname)
|
||||
}
|
||||
range.step<1 -> {
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, varValue)
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname)
|
||||
stackvmProg.instr(Opcode.PUSH, Value(varDt, abs(range.step)))
|
||||
stackvmProg.instr(Opcode.SUB)
|
||||
stackvmProg.instr(Opcode.POP_VAR, varValue)
|
||||
stackvmProg.instr(Opcode.POP_VAR, callLabel = varname)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: optimize edge cases if last value = 255 or 0 (for bytes) etc. to avoid PUSH / SUB opcodes and make use of the wrapping around of the value.
|
||||
stackvmProg.instr(Opcode.PUSH, Value(varDt, range.last+range.step))
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, varValue)
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname)
|
||||
stackvmProg.instr(Opcode.SUB)
|
||||
stackvmProg.instr(Opcode.BNZ, callLabel = loopLabel)
|
||||
|
||||
@ -838,9 +889,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
postIncr.linkParents(range.parent)
|
||||
translate(postIncr)
|
||||
if(lvTarget.register!=null)
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stringvalue=lvTarget.register.toString()))
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel =lvTarget.register.toString())
|
||||
else
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stringvalue=targetStatement!!.scopedname))
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel =targetStatement!!.scopedname)
|
||||
val branch = BranchStatement(
|
||||
BranchCondition.NZ,
|
||||
listOf(Jump(null, null, loopLabel, range.position)),
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.functions
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.HeapValues
|
||||
import kotlin.math.log2
|
||||
|
||||
|
||||
@ -19,9 +20,9 @@ val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf(
|
||||
"_vm_write_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text")
|
||||
|
||||
|
||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope): DataType? {
|
||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope, heap: HeapValues): DataType? {
|
||||
fun integerDatatypeFromArg(arg: IExpression): DataType {
|
||||
val dt = arg.resultingDatatype(namespace)
|
||||
val dt = arg.resultingDatatype(namespace, heap)
|
||||
return when(dt) {
|
||||
DataType.BYTE -> DataType.BYTE
|
||||
DataType.WORD -> DataType.WORD
|
||||
@ -33,7 +34,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
|
||||
fun datatypeFromListArg(arglist: IExpression): DataType {
|
||||
if(arglist is LiteralValue) {
|
||||
if(arglist.type==DataType.ARRAY || arglist.type==DataType.ARRAY_W || arglist.type==DataType.MATRIX) {
|
||||
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace)}
|
||||
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)}
|
||||
if(dt.any { it!=DataType.BYTE && it!=DataType.WORD && it!=DataType.FLOAT}) {
|
||||
throw FatalAstException("fuction $function only accepts array of numeric values")
|
||||
}
|
||||
@ -51,7 +52,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
|
||||
"lsb", "msb", "any", "all", "rnd" -> DataType.BYTE
|
||||
"rndw" -> DataType.WORD
|
||||
"rol", "rol2", "ror", "ror2", "lsl", "lsr", "set_carry", "clear_carry", "set_irqd", "clear_irqd" -> null // no return value so no datatype
|
||||
"abs" -> args.single().resultingDatatype(namespace)
|
||||
"abs" -> args.single().resultingDatatype(namespace, heap)
|
||||
"max", "min" -> datatypeFromListArg(args.single())
|
||||
"round", "floor", "ceil" -> integerDatatypeFromArg(args.single())
|
||||
"sum" -> {
|
||||
@ -96,10 +97,10 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
|
||||
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
||||
|
||||
|
||||
private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
|
||||
private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
|
||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
||||
if(constval.type!=DataType.FLOAT)
|
||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||
|
||||
@ -107,10 +108,10 @@ private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:
|
||||
return numericLiteral(function(float), args[0].position)
|
||||
}
|
||||
|
||||
private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
|
||||
private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
|
||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
||||
val float: Double = when(constval.type) {
|
||||
DataType.BYTE, DataType.WORD, DataType.FLOAT -> constval.asNumericValue!!.toDouble()
|
||||
else -> throw SyntaxError("built-in function requires one floating point argument", position)
|
||||
@ -118,10 +119,10 @@ private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position, n
|
||||
return numericLiteral(function(float).toInt(), args[0].position)
|
||||
}
|
||||
|
||||
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, function: (arg: Int)->Number): LiteralValue {
|
||||
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Int)->Number): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("built-in function requires one integer argument", position)
|
||||
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
|
||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
||||
if(constval.type!=DataType.BYTE && constval.type!=DataType.WORD)
|
||||
throw SyntaxError("built-in function requires one integer argument", position)
|
||||
|
||||
@ -129,95 +130,117 @@ private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, name
|
||||
return numericLiteral(function(integer).toInt(), args[0].position)
|
||||
}
|
||||
|
||||
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position, namespace:INameScope,
|
||||
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
|
||||
namespace:INameScope, heap: HeapValues,
|
||||
function: (arg: Collection<Double>)->Number): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||
val iterable = args[0].constValue(namespace)
|
||||
if(iterable?.arrayvalue == null)
|
||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
|
||||
if(constants.contains(null))
|
||||
throw NotConstArgumentException()
|
||||
val result = function(constants.map { it!!.toDouble() }).toDouble()
|
||||
var iterable = args[0].constValue(namespace, heap)
|
||||
if(iterable==null) {
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("function over weird argument ${args[0]}", position)
|
||||
iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap)
|
||||
?: throw SyntaxError("function over weird argument ${args[0]}", position)
|
||||
}
|
||||
|
||||
val result = if(iterable.arrayvalue != null) {
|
||||
val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue }
|
||||
if(constants.contains(null))
|
||||
throw NotConstArgumentException()
|
||||
function(constants.map { it!!.toDouble() }).toDouble()
|
||||
} else {
|
||||
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array/matrix argument", position)
|
||||
function(array.map { it.toDouble() })
|
||||
}
|
||||
return numericLiteral(result, args[0].position)
|
||||
}
|
||||
|
||||
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position, namespace:INameScope,
|
||||
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
|
||||
namespace:INameScope, heap: HeapValues,
|
||||
function: (arg: Collection<Double>)->Boolean): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||
val iterable = args[0].constValue(namespace)
|
||||
if(iterable?.arrayvalue == null)
|
||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
|
||||
if(constants.contains(null))
|
||||
throw NotConstArgumentException()
|
||||
val result = function(constants.map { it?.toDouble()!! })
|
||||
var iterable = args[0].constValue(namespace, heap)
|
||||
if(iterable==null) {
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("function over weird argument ${args[0]}", position)
|
||||
iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap)
|
||||
?: throw SyntaxError("function over weird argument ${args[0]}", position)
|
||||
}
|
||||
|
||||
val result = if(iterable.arrayvalue != null) {
|
||||
val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue }
|
||||
if(constants.contains(null))
|
||||
throw NotConstArgumentException()
|
||||
function(constants.map { it!!.toDouble() })
|
||||
} else {
|
||||
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array/matrix argument", position)
|
||||
function(array.map { it.toDouble() })
|
||||
}
|
||||
return LiteralValue.fromBoolean(result, position)
|
||||
}
|
||||
|
||||
fun builtinRound(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArgOutputInt(args, position, namespace, Math::round)
|
||||
fun builtinRound(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArgOutputInt(args, position, namespace, heap, Math::round)
|
||||
|
||||
fun builtinFloor(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArgOutputInt(args, position, namespace, Math::floor)
|
||||
fun builtinFloor(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArgOutputInt(args, position, namespace, heap, Math::floor)
|
||||
|
||||
fun builtinCeil(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArgOutputInt(args, position, namespace, Math::ceil)
|
||||
fun builtinCeil(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArgOutputInt(args, position, namespace, heap, Math::ceil)
|
||||
|
||||
fun builtinSin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::sin)
|
||||
fun builtinSin(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::sin)
|
||||
|
||||
fun builtinCos(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::cos)
|
||||
fun builtinCos(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::cos)
|
||||
|
||||
fun builtinAcos(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::acos)
|
||||
fun builtinAcos(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::acos)
|
||||
|
||||
fun builtinAsin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::asin)
|
||||
fun builtinAsin(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::asin)
|
||||
|
||||
fun builtinTan(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::tan)
|
||||
fun builtinTan(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::tan)
|
||||
|
||||
fun builtinAtan(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::atan)
|
||||
fun builtinAtan(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::atan)
|
||||
|
||||
fun builtinLn(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::log)
|
||||
fun builtinLn(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::log)
|
||||
|
||||
fun builtinLog2(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, ::log2)
|
||||
fun builtinLog2(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, ::log2)
|
||||
|
||||
fun builtinLog10(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::log10)
|
||||
fun builtinLog10(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::log10)
|
||||
|
||||
fun builtinSqrt(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::sqrt)
|
||||
fun builtinSqrt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::sqrt)
|
||||
|
||||
fun builtinRad(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::toRadians)
|
||||
fun builtinRad(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::toRadians)
|
||||
|
||||
fun builtinDeg(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, Math::toDegrees)
|
||||
fun builtinDeg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneDoubleArg(args, position, namespace, heap, Math::toDegrees)
|
||||
|
||||
fun builtinFlt(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
|
||||
fun builtinFlt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
||||
// 1 numeric arg, convert to float
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("flt requires one numeric argument", position)
|
||||
|
||||
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
|
||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
||||
val number = constval.asNumericValue ?: throw SyntaxError("flt requires one numeric argument", position)
|
||||
return LiteralValue(DataType.FLOAT, floatvalue = number.toDouble(), position = position)
|
||||
}
|
||||
|
||||
fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
|
||||
fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
||||
// 1 arg, type = float or int, result type= same as argument type
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("abs requires one numeric argument", position)
|
||||
|
||||
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
|
||||
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
||||
val number = constval.asNumericValue
|
||||
return when (number) {
|
||||
is Int, is Byte, is Short -> numericLiteral(Math.abs(number.toInt()), args[0].position)
|
||||
@ -227,50 +250,73 @@ fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope
|
||||
}
|
||||
|
||||
|
||||
fun builtinLsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 }
|
||||
fun builtinLsb(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneIntArgOutputInt(args, position, namespace, heap) { x: Int -> x and 255 }
|
||||
|
||||
fun builtinMsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 8 and 255}
|
||||
fun builtinMsb(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= oneIntArgOutputInt(args, position, namespace, heap) { x: Int -> x ushr 8 and 255}
|
||||
|
||||
fun builtinMin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= collectionArgOutputNumber(args, position, namespace) { it.min()!! }
|
||||
fun builtinMin(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= collectionArgOutputNumber(args, position, namespace, heap) { it.min()!! }
|
||||
|
||||
fun builtinMax(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= collectionArgOutputNumber(args, position, namespace) { it.max()!! }
|
||||
fun builtinMax(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= collectionArgOutputNumber(args, position, namespace, heap) { it.max()!! }
|
||||
|
||||
fun builtinSum(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= collectionArgOutputNumber(args, position, namespace) { it.sum() }
|
||||
fun builtinSum(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= collectionArgOutputNumber(args, position, namespace, heap) { it.sum() }
|
||||
|
||||
fun builtinAvg(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
|
||||
fun builtinAvg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("avg requires one non-scalar argument", position)
|
||||
val iterable = args[0].constValue(namespace)
|
||||
if(iterable?.arrayvalue == null)
|
||||
throw SyntaxError("avg requires one non-scalar argument", position)
|
||||
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
|
||||
if(constants.contains(null))
|
||||
throw NotConstArgumentException()
|
||||
val result = (constants.map { it!!.toDouble() }).average()
|
||||
throw SyntaxError("avg requires array/matrix argument", position)
|
||||
var iterable = args[0].constValue(namespace, heap)
|
||||
if(iterable==null) {
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("avg over weird argument ${args[0]}", position)
|
||||
iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap)
|
||||
?: throw SyntaxError("avg over weird argument ${args[0]}", position)
|
||||
}
|
||||
|
||||
val result = if(iterable.arrayvalue!=null) {
|
||||
val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue }
|
||||
if (constants.contains(null))
|
||||
throw NotConstArgumentException()
|
||||
(constants.map { it!!.toDouble() }).average()
|
||||
}
|
||||
else {
|
||||
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("avg requires array/matrix argument", position)
|
||||
array.average()
|
||||
}
|
||||
return numericLiteral(result, args[0].position)
|
||||
}
|
||||
|
||||
fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue {
|
||||
fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("len requires one argument", position)
|
||||
val argument = args[0].constValue(namespace) ?: throw NotConstArgumentException()
|
||||
var argument = args[0].constValue(namespace, heap)
|
||||
if(argument==null) {
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len over weird argument ${args[0]}", position)
|
||||
argument = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap)
|
||||
?: throw SyntaxError("len over weird argument ${args[0]}", position)
|
||||
}
|
||||
return when(argument.type) {
|
||||
DataType.ARRAY, DataType.ARRAY_W -> numericLiteral(argument.arrayvalue!!.size, args[0].position)
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> numericLiteral(argument.strvalue!!.length, args[0].position)
|
||||
DataType.ARRAY, DataType.ARRAY_W -> {
|
||||
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).array!!.size
|
||||
numericLiteral(arraySize, args[0].position)
|
||||
}
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
val str = argument.strvalue ?: heap.get(argument.heapId!!).str!!
|
||||
numericLiteral(str.length, args[0].position)
|
||||
}
|
||||
else -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
}
|
||||
}
|
||||
|
||||
fun builtinAny(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= collectionArgOutputBoolean(args, position, namespace) { it.any { v -> v != 0.0} }
|
||||
fun builtinAny(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= collectionArgOutputBoolean(args, position, namespace, heap) { it.any { v -> v != 0.0} }
|
||||
|
||||
fun builtinAll(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
|
||||
= collectionArgOutputBoolean(args, position, namespace) { it.all { v -> v != 0.0} }
|
||||
fun builtinAll(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue
|
||||
= collectionArgOutputBoolean(args, position, namespace, heap) { it.all { v -> v != 0.0} }
|
||||
|
||||
|
||||
private fun numericLiteral(value: Number, position: Position): LiteralValue {
|
||||
|
@ -184,7 +184,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
val error = "cannot multiply $left and $right"
|
||||
val error = "cannot multiply ${left.type} and ${right.type}"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position)
|
||||
@ -200,13 +200,6 @@ class ConstExprEvaluator {
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.floatvalue, position = left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
left.strvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> {
|
||||
if(left.strvalue.length * right.asIntegerValue > 255) throw ExpressionError("string too long", left.position)
|
||||
LiteralValue(DataType.STR, strvalue = left.strvalue.repeat(right.asIntegerValue), position = left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
|
||||
|
||||
class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
|
||||
class ConstantFolding(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
var errors : MutableList<AstException> = mutableListOf()
|
||||
|
||||
@ -94,8 +95,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
|
||||
*/
|
||||
override fun process(identifier: IdentifierReference): IExpression {
|
||||
return try {
|
||||
val cval = identifier.constValue(namespace) ?: return identifier
|
||||
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, cval.strvalue, cval.arrayvalue, identifier.position)
|
||||
val cval = identifier.constValue(namespace, heap) ?: return identifier
|
||||
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, cval.strvalue, cval.arrayvalue, position=identifier.position)
|
||||
copy.parent = identifier.parent
|
||||
return copy
|
||||
} catch (ax: AstException) {
|
||||
@ -107,7 +108,7 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
return try {
|
||||
super.process(functionCall)
|
||||
functionCall.constValue(namespace) ?: functionCall
|
||||
functionCall.constValue(namespace, heap) ?: functionCall
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
functionCall
|
||||
@ -183,8 +184,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
return try {
|
||||
super.process(expr)
|
||||
val leftconst = expr.left.constValue(namespace)
|
||||
val rightconst = expr.right.constValue(namespace)
|
||||
val leftconst = expr.left.constValue(namespace, heap)
|
||||
val rightconst = expr.right.constValue(namespace, heap)
|
||||
|
||||
val subExpr: BinaryExpression? = when {
|
||||
leftconst!=null -> expr.right as? BinaryExpression
|
||||
@ -192,8 +193,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
|
||||
else -> null
|
||||
}
|
||||
if(subExpr!=null) {
|
||||
val subleftconst = subExpr.left.constValue(namespace)
|
||||
val subrightconst = subExpr.right.constValue(namespace)
|
||||
val subleftconst = subExpr.left.constValue(namespace, heap)
|
||||
val subrightconst = subExpr.right.constValue(namespace, heap)
|
||||
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null))
|
||||
// try reordering.
|
||||
return groupTwoConstsTogether(expr, subExpr,
|
||||
@ -286,12 +287,23 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
|
||||
}
|
||||
|
||||
override fun process(literalValue: LiteralValue): LiteralValue {
|
||||
if(literalValue.arrayvalue!=null) {
|
||||
if(literalValue.strvalue!=null) {
|
||||
// intern the string; move it into the heap
|
||||
if(literalValue.strvalue.length !in 1..255)
|
||||
addError(ExpressionError("string literal length must be between 1 and 255", literalValue.position))
|
||||
else {
|
||||
val heapId = heap.add(literalValue.type, literalValue.strvalue) // TODO: we don't know the actual string type yet, STR != STR_P etc...
|
||||
val newValue = LiteralValue(literalValue.type, heapId = heapId, position = literalValue.position)
|
||||
return super.process(newValue)
|
||||
}
|
||||
} else if(literalValue.arrayvalue!=null) {
|
||||
val newArray = literalValue.arrayvalue.map { it.process(this) }.toTypedArray()
|
||||
// determine if the values are all bytes or that we need a word array instead
|
||||
var arrayDt = DataType.ARRAY
|
||||
var allElementsAreConstant = true
|
||||
for (expr in newArray) {
|
||||
val valueDt = expr.resultingDatatype(namespace)
|
||||
allElementsAreConstant = allElementsAreConstant and (expr is LiteralValue)
|
||||
val valueDt = expr.resultingDatatype(namespace, heap)
|
||||
if(valueDt==DataType.BYTE)
|
||||
continue
|
||||
else {
|
||||
@ -299,6 +311,37 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if the values are all constants, the array is moved to the heap
|
||||
if(allElementsAreConstant) {
|
||||
val array = newArray.map {
|
||||
val litval = it as? LiteralValue
|
||||
if(litval==null) {
|
||||
addError(ExpressionError("array elements must all be constants", literalValue.position))
|
||||
return super.process(literalValue)
|
||||
}
|
||||
if(litval.bytevalue==null && litval.wordvalue==null) {
|
||||
if(arrayDt==DataType.ARRAY)
|
||||
addError(ExpressionError("byte array elements must all be integers 0..255", literalValue.position))
|
||||
else
|
||||
addError(ExpressionError("word array elements must all be integers 0..65535", literalValue.position))
|
||||
return super.process(literalValue)
|
||||
}
|
||||
val integer = litval.asIntegerValue!!
|
||||
if(arrayDt==DataType.ARRAY && integer !in 0..255) {
|
||||
addError(ExpressionError("byte array elements must all be integers 0..255", literalValue.position))
|
||||
return super.process(literalValue)
|
||||
} else if(arrayDt==DataType.ARRAY_W && integer !in 0..65535) {
|
||||
addError(ExpressionError("word array elements must all be integers 0..65535", literalValue.position))
|
||||
return super.process(literalValue)
|
||||
}
|
||||
integer
|
||||
}.toIntArray()
|
||||
val heapId = heap.add(arrayDt, array)
|
||||
val newValue = LiteralValue(arrayDt, heapId=heapId, position = literalValue.position)
|
||||
return super.process(newValue)
|
||||
}
|
||||
|
||||
val newValue = LiteralValue(arrayDt, arrayvalue = newArray, position = literalValue.position)
|
||||
return super.process(newValue)
|
||||
}
|
||||
|
@ -3,17 +3,12 @@ package prog8.optimizing
|
||||
import prog8.ast.AstException
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
/**
|
||||
* TODO: array, matrix, string and float constants should be put into a constant-pool,
|
||||
* so that they're only stored once instead of replicated everywhere.
|
||||
* Note that initial constant folding of them is fine: it's needed to be able to
|
||||
* optimize the expressions. But as a final step, they should be consolidated again
|
||||
*/
|
||||
|
||||
fun Module.constantFold(globalNamespace: INameScope) {
|
||||
val optimizer = ConstantFolding(globalNamespace)
|
||||
fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) {
|
||||
val optimizer = ConstantFolding(globalNamespace, heap)
|
||||
try {
|
||||
this.process(optimizer)
|
||||
} catch (ax: AstException) {
|
||||
@ -35,8 +30,8 @@ fun Module.constantFold(globalNamespace: INameScope) {
|
||||
}
|
||||
|
||||
|
||||
fun Module.optimizeStatements(globalNamespace: INameScope): Int {
|
||||
val optimizer = StatementOptimizer(globalNamespace)
|
||||
fun Module.optimizeStatements(globalNamespace: INameScope, heap: HeapValues): Int {
|
||||
val optimizer = StatementOptimizer(globalNamespace, heap)
|
||||
this.process(optimizer)
|
||||
if(optimizer.optimizationsDone > 0)
|
||||
println("[${this.name}] Debug: ${optimizer.optimizationsDone} statement optimizations performed")
|
||||
@ -44,8 +39,8 @@ fun Module.optimizeStatements(globalNamespace: INameScope): Int {
|
||||
return optimizer.optimizationsDone
|
||||
}
|
||||
|
||||
fun Module.simplifyExpressions(namespace: INameScope) : Int {
|
||||
val optimizer = SimplifyExpressions(namespace)
|
||||
fun Module.simplifyExpressions(namespace: INameScope, heap: HeapValues) : Int {
|
||||
val optimizer = SimplifyExpressions(namespace, heap)
|
||||
this.process(optimizer)
|
||||
if(optimizer.optimizationsDone > 0)
|
||||
println("[${this.name}] Debug: ${optimizer.optimizationsDone} expression optimizations performed")
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.HeapValues
|
||||
import kotlin.math.abs
|
||||
|
||||
/*
|
||||
@ -18,7 +19,7 @@ import kotlin.math.abs
|
||||
|
||||
*/
|
||||
|
||||
class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
|
||||
class SimplifyExpressions(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun process(assignment: Assignment): IStatement {
|
||||
@ -30,8 +31,8 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
|
||||
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
super.process(expr)
|
||||
val leftVal = expr.left.constValue(namespace)
|
||||
val rightVal = expr.right.constValue(namespace)
|
||||
val leftVal = expr.left.constValue(namespace, heap)
|
||||
val rightVal = expr.right.constValue(namespace, heap)
|
||||
val constTrue = LiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = LiteralValue.fromBoolean(false, expr.position)
|
||||
|
||||
@ -121,9 +122,9 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
|
||||
expr.left = expr.right
|
||||
expr.right = tmp
|
||||
optimizationsDone++
|
||||
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(namespace), leftVal)
|
||||
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(namespace, heap), leftVal)
|
||||
}
|
||||
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(namespace))
|
||||
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(namespace, heap))
|
||||
}
|
||||
|
||||
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression {
|
||||
@ -284,13 +285,13 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
if (expr.left.resultingDatatype(namespace) == DataType.BYTE) {
|
||||
if (expr.left.resultingDatatype(namespace, heap) == DataType.BYTE) {
|
||||
if(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) {
|
||||
optimizationsDone++
|
||||
return LiteralValue(DataType.BYTE, 0, position = expr.position)
|
||||
}
|
||||
}
|
||||
else if (expr.left.resultingDatatype(namespace) == DataType.WORD) {
|
||||
else if (expr.left.resultingDatatype(namespace, heap) == DataType.WORD) {
|
||||
if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) {
|
||||
optimizationsDone++
|
||||
return LiteralValue(DataType.BYTE, 0, position = expr.position)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.functions.BuiltinFunctionNames
|
||||
import prog8.functions.BuiltinFunctionsWithoutSideEffects
|
||||
|
||||
@ -9,6 +10,7 @@ import prog8.functions.BuiltinFunctionsWithoutSideEffects
|
||||
todo remove unused blocks
|
||||
todo remove unused variables
|
||||
todo remove unused subroutines
|
||||
todo remove unused strings and arrays from the heap
|
||||
todo remove if statements with empty statement blocks
|
||||
todo replace if statements with only else block
|
||||
todo statement optimization: create augmented assignment from assignment that only refers to its lvalue (A=A+10, A=4*A, ...)
|
||||
@ -23,7 +25,7 @@ import prog8.functions.BuiltinFunctionsWithoutSideEffects
|
||||
todo inline subroutines that are "sufficiently small"
|
||||
*/
|
||||
|
||||
class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcessor {
|
||||
class StatementOptimizer(private val globalNamespace: INameScope, private val heap: HeapValues) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
private set
|
||||
|
||||
@ -43,7 +45,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
|
||||
override fun process(ifStatement: IfStatement): IStatement {
|
||||
super.process(ifStatement)
|
||||
val constvalue = ifStatement.condition.constValue(globalNamespace)
|
||||
val constvalue = ifStatement.condition.constValue(globalNamespace, heap)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
@ -75,7 +77,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
|
||||
override fun process(whileLoop: WhileLoop): IStatement {
|
||||
super.process(whileLoop)
|
||||
val constvalue = whileLoop.condition.constValue(globalNamespace)
|
||||
val constvalue = whileLoop.condition.constValue(globalNamespace, heap)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true
|
||||
@ -92,7 +94,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
|
||||
override fun process(repeatLoop: RepeatLoop): IStatement {
|
||||
super.process(repeatLoop)
|
||||
val constvalue = repeatLoop.untilCondition.constValue(globalNamespace)
|
||||
val constvalue = repeatLoop.untilCondition.constValue(globalNamespace, heap)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only the statement block
|
||||
|
@ -1,13 +1,12 @@
|
||||
// Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7
|
||||
package prog8.parser;
|
||||
import org.antlr.v4.runtime.Lexer;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenStream;
|
||||
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.atn.*;
|
||||
import org.antlr.v4.runtime.atn.ATN;
|
||||
import org.antlr.v4.runtime.atn.ATNDeserializer;
|
||||
import org.antlr.v4.runtime.atn.LexerATNSimulator;
|
||||
import org.antlr.v4.runtime.atn.PredictionContextCache;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.misc.*;
|
||||
|
||||
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
|
||||
public class prog8Lexer extends Lexer {
|
||||
|
@ -1,13 +1,15 @@
|
||||
// Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7
|
||||
package prog8.parser;
|
||||
import org.antlr.v4.runtime.atn.*;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.misc.*;
|
||||
import org.antlr.v4.runtime.tree.*;
|
||||
import org.antlr.v4.runtime.atn.ATN;
|
||||
import org.antlr.v4.runtime.atn.ATNDeserializer;
|
||||
import org.antlr.v4.runtime.atn.ParserATNSimulator;
|
||||
import org.antlr.v4.runtime.atn.PredictionContextCache;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
|
||||
public class prog8Parser extends Parser {
|
||||
|
328
compiler/src/prog8/stackvm/Program.kt
Normal file
328
compiler/src/prog8/stackvm/Program.kt
Normal 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")
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package prog8.stackvm
|
||||
|
||||
import prog8.ast.DataType
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.*
|
||||
|
||||
enum class Opcode {
|
||||
@ -17,7 +17,6 @@ enum class Opcode {
|
||||
PUSH_MEM_F, // push float value from memory to stack
|
||||
PUSH_VAR, // push a variable
|
||||
DUP, // push topmost value once again
|
||||
ARRAY, // create arrayvalue of number of integer values on the stack, given in argument
|
||||
|
||||
// popping values off the (evaluation) stack, possibly storing them in another location
|
||||
DISCARD, // discard top value
|
||||
@ -165,373 +164,6 @@ enum class Syscall(val callNr: Short) {
|
||||
}
|
||||
|
||||
|
||||
class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=null, val arrayvalue: IntArray?=null) {
|
||||
private var byteval: Short? = null
|
||||
private var wordval: Int? = null
|
||||
private var floatval: Double? = null
|
||||
val asBooleanValue: Boolean
|
||||
|
||||
init {
|
||||
when(type) {
|
||||
DataType.BYTE -> {
|
||||
byteval = (numericvalue!!.toInt() and 255).toShort() // byte wrap around 0..255
|
||||
asBooleanValue = byteval != (0.toShort())
|
||||
}
|
||||
DataType.WORD -> {
|
||||
wordval = numericvalue!!.toInt() and 65535 // word wrap around 0..65535
|
||||
asBooleanValue = wordval != 0
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
floatval = numericvalue!!.toDouble()
|
||||
asBooleanValue = floatval != 0.0
|
||||
}
|
||||
DataType.ARRAY, DataType.ARRAY_W -> {
|
||||
asBooleanValue = arrayvalue!!.isNotEmpty()
|
||||
}
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type")
|
||||
asBooleanValue = stringvalue.isNotEmpty()
|
||||
}
|
||||
else -> throw VmExecutionException("invalid datatype $type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return when(type) {
|
||||
DataType.BYTE -> "b:%02x".format(byteval)
|
||||
DataType.WORD -> "w:%04x".format(wordval)
|
||||
DataType.FLOAT -> "f:$floatval"
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> "\"$stringvalue\""
|
||||
DataType.ARRAY -> TODO("tostring array")
|
||||
DataType.ARRAY_W -> TODO("tostring word array")
|
||||
DataType.MATRIX -> TODO("tostring matrix")
|
||||
}
|
||||
}
|
||||
|
||||
fun numericValue(): Number {
|
||||
return when(type) {
|
||||
DataType.BYTE -> byteval!!
|
||||
DataType.WORD-> wordval!!
|
||||
DataType.FLOAT -> floatval!!
|
||||
else -> throw VmExecutionException("invalid datatype for numeric value: $type")
|
||||
}
|
||||
}
|
||||
|
||||
fun integerValue(): Int {
|
||||
return when(type) {
|
||||
DataType.BYTE -> byteval!!.toInt()
|
||||
DataType.WORD-> wordval!!
|
||||
DataType.FLOAT -> throw VmExecutionException("float to integer loss of precision")
|
||||
else -> throw VmExecutionException("invalid datatype for integer value: $type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
val bh = byteval?.hashCode() ?: 0x10001234
|
||||
val wh = wordval?.hashCode() ?: 0x01002345
|
||||
val fh = floatval?.hashCode() ?: 0x00103456
|
||||
val ah = arrayvalue?.hashCode() ?: 0x11119876
|
||||
return bh xor wh xor fh xor ah xor type.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is Value)
|
||||
return false
|
||||
return compareTo(other)==0 // note: datatype doesn't matter
|
||||
}
|
||||
|
||||
operator fun compareTo(other: Value): Int {
|
||||
return when(type) {
|
||||
DataType.BYTE, DataType.WORD, DataType.FLOAT -> {
|
||||
when(other.type) {
|
||||
DataType.BYTE, DataType.WORD, DataType.FLOAT -> {
|
||||
numericValue().toDouble().compareTo(other.numericValue().toDouble())
|
||||
}
|
||||
else -> throw VmExecutionException("comparison can only be done between two numeric values")
|
||||
}
|
||||
}
|
||||
else -> throw VmExecutionException("comparison can only be done between two numeric values")
|
||||
}
|
||||
}
|
||||
|
||||
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): Value {
|
||||
if(result.toDouble() < 0 ) {
|
||||
return when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// BYTE can become WORD if right operand is WORD, or when value is too large for byte
|
||||
when(rightDt) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, result.toInt() and 255)
|
||||
DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535)
|
||||
DataType.FLOAT -> throw VmExecutionException("floating point loss of precision")
|
||||
else -> throw VmExecutionException("$op on non-numeric result type")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535)
|
||||
DataType.FLOAT -> Value(DataType.FLOAT, result)
|
||||
else -> throw VmExecutionException("$op on non-numeric type")
|
||||
}
|
||||
}
|
||||
|
||||
return when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// BYTE can become WORD if right operand is WORD, or when value is too large for byte
|
||||
if(result.toDouble() >= 256)
|
||||
return Value(DataType.WORD, result)
|
||||
when(rightDt) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, result)
|
||||
DataType.WORD -> Value(DataType.WORD, result)
|
||||
DataType.FLOAT -> throw VmExecutionException("floating point loss of precision")
|
||||
else -> throw VmExecutionException("$op on non-numeric result type")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> Value(DataType.WORD, result)
|
||||
DataType.FLOAT -> Value(DataType.FLOAT, result)
|
||||
else -> throw VmExecutionException("$op on non-numeric type")
|
||||
}
|
||||
}
|
||||
|
||||
fun add(other: Value): Value {
|
||||
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
|
||||
throw VmExecutionException("floating point loss of precision on type $type")
|
||||
val v1 = numericValue()
|
||||
val v2 = other.numericValue()
|
||||
val result = v1.toDouble() + v2.toDouble()
|
||||
return arithResult(type, result, other.type, "add")
|
||||
}
|
||||
|
||||
fun sub(other: Value): Value {
|
||||
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
|
||||
throw VmExecutionException("floating point loss of precision on type $type")
|
||||
val v1 = numericValue()
|
||||
val v2 = other.numericValue()
|
||||
val result = v1.toDouble() - v2.toDouble()
|
||||
return arithResult(type, result, other.type, "sub")
|
||||
}
|
||||
|
||||
fun mul(other: Value): Value {
|
||||
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
|
||||
throw VmExecutionException("floating point loss of precision on type $type")
|
||||
val v1 = numericValue()
|
||||
val v2 = other.numericValue()
|
||||
val result = v1.toDouble() * v2.toDouble()
|
||||
return arithResult(type, result, other.type, "mul")
|
||||
}
|
||||
|
||||
fun div(other: Value): Value {
|
||||
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
|
||||
throw VmExecutionException("floating point loss of precision on type $type")
|
||||
val v1 = numericValue()
|
||||
val v2 = other.numericValue()
|
||||
if(v2.toDouble()==0.0) {
|
||||
if (type == DataType.BYTE)
|
||||
return Value(DataType.BYTE, 255)
|
||||
else if(type == DataType.WORD)
|
||||
return Value(DataType.WORD, 65535)
|
||||
}
|
||||
val result = v1.toDouble() / v2.toDouble()
|
||||
// NOTE: integer division returns integer result!
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, result)
|
||||
DataType.WORD -> Value(DataType.WORD, result)
|
||||
DataType.FLOAT -> Value(DataType.FLOAT, result)
|
||||
else -> throw VmExecutionException("div on non-numeric type")
|
||||
}
|
||||
}
|
||||
|
||||
fun floordiv(other: Value): Value {
|
||||
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
|
||||
throw VmExecutionException("floating point loss of precision on type $type")
|
||||
val v1 = numericValue()
|
||||
val v2 = other.numericValue()
|
||||
val result = floor(v1.toDouble() / v2.toDouble())
|
||||
// NOTE: integer division returns integer result!
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, result)
|
||||
DataType.WORD -> Value(DataType.WORD, result)
|
||||
DataType.FLOAT -> Value(DataType.FLOAT, result)
|
||||
else -> throw VmExecutionException("div on non-numeric type")
|
||||
}
|
||||
}
|
||||
|
||||
fun remainder(other: Value): Value? {
|
||||
val v1 = numericValue()
|
||||
val v2 = other.numericValue()
|
||||
val result = v1.toDouble() % v2.toDouble()
|
||||
return arithResult(type, result, other.type, "remainder")
|
||||
}
|
||||
|
||||
fun pow(other: Value): Value {
|
||||
val v1 = numericValue()
|
||||
val v2 = other.numericValue()
|
||||
val result = v1.toDouble().pow(v2.toDouble())
|
||||
return arithResult(type, result, other.type,"pow")
|
||||
}
|
||||
|
||||
fun shl(): Value {
|
||||
val v = integerValue()
|
||||
return Value(type, v shl 1)
|
||||
}
|
||||
|
||||
fun shr(): Value {
|
||||
val v = integerValue()
|
||||
return Value(type, v ushr 1)
|
||||
}
|
||||
|
||||
fun rol(carry: Boolean): Pair<Value, Boolean> {
|
||||
// 9 or 17 bit rotate left (with carry))
|
||||
return when(type) {
|
||||
DataType.BYTE -> {
|
||||
val v = byteval!!.toInt()
|
||||
val newCarry = (v and 0x80) != 0
|
||||
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
|
||||
Pair(Value(DataType.BYTE, newval), newCarry)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
val v = wordval!!
|
||||
val newCarry = (v and 0x8000) != 0
|
||||
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
|
||||
Pair(Value(DataType.WORD, newval), newCarry)
|
||||
}
|
||||
else -> throw VmExecutionException("rol can only work on byte/word")
|
||||
}
|
||||
}
|
||||
|
||||
fun ror(carry: Boolean): Pair<Value, Boolean> {
|
||||
// 9 or 17 bit rotate right (with carry)
|
||||
return when(type) {
|
||||
DataType.BYTE -> {
|
||||
val v = byteval!!.toInt()
|
||||
val newCarry = v and 1 != 0
|
||||
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
|
||||
Pair(Value(DataType.BYTE, newval), newCarry)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
val v = wordval!!
|
||||
val newCarry = v and 1 != 0
|
||||
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
|
||||
Pair(Value(DataType.WORD, newval), newCarry)
|
||||
}
|
||||
else -> throw VmExecutionException("ror2 can only work on byte/word")
|
||||
}
|
||||
}
|
||||
|
||||
fun rol2(): Value {
|
||||
// 8 or 16 bit rotate left
|
||||
return when(type) {
|
||||
DataType.BYTE -> {
|
||||
val v = byteval!!.toInt()
|
||||
val carry = (v and 0x80) ushr 7
|
||||
val newval = (v and 0x7f shl 1) or carry
|
||||
Value(DataType.BYTE, newval)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
val v = wordval!!
|
||||
val carry = (v and 0x8000) ushr 15
|
||||
val newval = (v and 0x7fff shl 1) or carry
|
||||
Value(DataType.WORD, newval)
|
||||
}
|
||||
else -> throw VmExecutionException("rol2 can only work on byte/word")
|
||||
}
|
||||
}
|
||||
|
||||
fun ror2(): Value {
|
||||
// 8 or 16 bit rotate right
|
||||
return when(type) {
|
||||
DataType.BYTE -> {
|
||||
val v = byteval!!.toInt()
|
||||
val carry = v and 1 shl 7
|
||||
val newval = (v ushr 1) or carry
|
||||
Value(DataType.BYTE, newval)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
val v = wordval!!
|
||||
val carry = v and 1 shl 15
|
||||
val newval = (v ushr 1) or carry
|
||||
Value(DataType.WORD, newval)
|
||||
}
|
||||
else -> throw VmExecutionException("ror2 can only work on byte/word")
|
||||
}
|
||||
}
|
||||
|
||||
fun neg(): Value {
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, -(byteval!!))
|
||||
DataType.WORD -> Value(DataType.WORD, -(wordval!!))
|
||||
DataType.FLOAT -> Value(DataType.FLOAT, -(floatval)!!)
|
||||
else -> throw VmExecutionException("neg can only work on byte/word/float")
|
||||
}
|
||||
}
|
||||
|
||||
fun bitand(other: Value): Value {
|
||||
val v1 = integerValue()
|
||||
val v2 = other.integerValue()
|
||||
val result = v1 and v2
|
||||
return Value(type, result)
|
||||
}
|
||||
|
||||
fun bitor(other: Value): Value {
|
||||
val v1 = integerValue()
|
||||
val v2 = other.integerValue()
|
||||
val result = v1 or v2
|
||||
return Value(type, result)
|
||||
}
|
||||
|
||||
fun bitxor(other: Value): Value {
|
||||
val v1 = integerValue()
|
||||
val v2 = other.integerValue()
|
||||
val result = v1 xor v2
|
||||
return Value(type, result)
|
||||
}
|
||||
|
||||
fun and(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue && other.asBooleanValue) 1 else 0)
|
||||
fun or(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue || other.asBooleanValue) 1 else 0)
|
||||
fun xor(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue xor other.asBooleanValue) 1 else 0)
|
||||
fun not() = Value(DataType.BYTE, if(this.asBooleanValue) 0 else 1)
|
||||
|
||||
fun inv(): Value {
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv() and 255)
|
||||
DataType.WORD -> Value(DataType.WORD, wordval!!.inv() and 65535)
|
||||
else -> throw VmExecutionException("inv can only work on byte/word")
|
||||
}
|
||||
}
|
||||
|
||||
fun inc(): Value {
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, (byteval!! + 1) and 255)
|
||||
DataType.WORD -> Value(DataType.WORD, (wordval!! + 1) and 65535)
|
||||
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1)
|
||||
else -> throw VmExecutionException("inc can only work on byte/word/float")
|
||||
}
|
||||
}
|
||||
|
||||
fun dec(): Value {
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, (byteval!! - 1) and 255)
|
||||
DataType.WORD -> Value(DataType.WORD, (wordval!! - 1) and 65535)
|
||||
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1)
|
||||
else -> throw VmExecutionException("dec can only work on byte/word/float")
|
||||
}
|
||||
}
|
||||
|
||||
fun lsb(): Value {
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, byteval!!)
|
||||
DataType.WORD -> Value(DataType.BYTE, wordval!! and 255)
|
||||
else -> throw VmExecutionException("not can only work on byte/word")
|
||||
}
|
||||
}
|
||||
|
||||
fun msb(): Value {
|
||||
return when(type) {
|
||||
DataType.BYTE -> Value(DataType.BYTE, 0)
|
||||
DataType.WORD -> Value(DataType.BYTE, wordval!! ushr 8 and 255)
|
||||
else -> throw VmExecutionException("not can only work on byte/word")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class Instruction(val opcode: Opcode,
|
||||
val arg: Value? = null,
|
||||
val callLabel: String? = null)
|
||||
@ -547,14 +179,14 @@ open class Instruction(val opcode: Opcode,
|
||||
Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR)
|
||||
val result =
|
||||
when {
|
||||
opcode==Opcode.LINE -> "_line ${arg!!.stringvalue}"
|
||||
opcode==Opcode.LINE -> "_line $callLabel"
|
||||
opcode==Opcode.SYSCALL -> {
|
||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
||||
"syscall $syscall"
|
||||
}
|
||||
opcodesWithVarArgument.contains(opcode) -> {
|
||||
// opcodes that manipulate a variable
|
||||
"${opcode.toString().toLowerCase()} ${argStr.substring(1, argStr.length-1)}"
|
||||
"${opcode.toString().toLowerCase()} $callLabel"
|
||||
}
|
||||
callLabel==null -> "${opcode.toString().toLowerCase()} $argStr"
|
||||
else -> "${opcode.toString().toLowerCase()} $callLabel $argStr"
|
||||
@ -589,269 +221,6 @@ class MyStack<T> : Stack<T>() {
|
||||
}
|
||||
}
|
||||
|
||||
class Program (val name: String,
|
||||
prog: MutableList<Instruction>,
|
||||
val labels: Map<String, Instruction>,
|
||||
val variables: Map<String, Map<String, Value>>,
|
||||
val memory: Map<Int, List<Value>>)
|
||||
{
|
||||
companion object {
|
||||
fun load(filename: String): Program {
|
||||
val lines = File(filename).readLines().withIndex().iterator()
|
||||
var memory = mapOf<Int, List<Value>>()
|
||||
var vars = mapOf<String, Map<String, Value>>()
|
||||
var instructions = mutableListOf<Instruction>()
|
||||
var labels = mapOf<String, Instruction>()
|
||||
while(lines.hasNext()) {
|
||||
val (lineNr, line) = lines.next()
|
||||
if(line.startsWith(';') || line.isEmpty())
|
||||
continue
|
||||
else if(line=="%memory")
|
||||
memory = loadMemory(lines)
|
||||
else if(line=="%variables")
|
||||
vars = loadVars(lines)
|
||||
else if(line=="%instructions") {
|
||||
val (insResult, labelResult) = loadInstructions(lines)
|
||||
instructions = insResult
|
||||
labels = labelResult
|
||||
}
|
||||
else throw VmExecutionException("syntax error at line ${lineNr+1}")
|
||||
}
|
||||
return Program(filename, instructions, labels, vars, memory)
|
||||
}
|
||||
|
||||
private fun loadInstructions(lines: Iterator<IndexedValue<String>>): Pair<MutableList<Instruction>, Map<String, Instruction>> {
|
||||
val instructions = mutableListOf<Instruction>()
|
||||
val labels = mutableMapOf<String, Instruction>()
|
||||
val splitpattern = Pattern.compile("\\s+")
|
||||
val nextInstructionLabels = Stack<String>() // more than one label can occur on the same line
|
||||
|
||||
while(true) {
|
||||
val (lineNr, line) = lines.next()
|
||||
if(line=="%end_instructions")
|
||||
return Pair(instructions, labels)
|
||||
if(!line.startsWith(' ') && line.endsWith(':')) {
|
||||
nextInstructionLabels.push(line.substring(0, line.length-1))
|
||||
} else if(line.startsWith(' ')) {
|
||||
val parts = line.trimStart().split(splitpattern, limit = 2)
|
||||
val opcodeStr = parts[0].toUpperCase()
|
||||
val opcode=Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr)
|
||||
val args = if(parts.size==2) parts[1] else null
|
||||
val instruction = when(opcode) {
|
||||
Opcode.LINE -> Instruction(opcode, Value(DataType.STR, null, stringvalue = args))
|
||||
Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS,
|
||||
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC -> {
|
||||
if(args!!.startsWith('$')) {
|
||||
Instruction(opcode, Value(DataType.WORD, args.substring(1).toInt(16)))
|
||||
} else {
|
||||
Instruction(opcode, callLabel = args)
|
||||
}
|
||||
}
|
||||
Opcode.INC_VAR, Opcode.DEC_VAR,
|
||||
Opcode.SHR_VAR, Opcode.SHL_VAR, Opcode.ROL_VAR, Opcode.ROR_VAR,
|
||||
Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR -> {
|
||||
val withoutQuotes =
|
||||
if(args!!.startsWith('"') && args.endsWith('"'))
|
||||
args.substring(1, args.length-1) else args
|
||||
Instruction(opcode, Value(DataType.STR, null, withoutQuotes))
|
||||
}
|
||||
Opcode.SYSCALL -> {
|
||||
val call = Syscall.valueOf(args!!)
|
||||
Instruction(opcode, Value(DataType.BYTE, call.callNr))
|
||||
}
|
||||
else -> {
|
||||
// println("INSTR $opcode at $lineNr args=$args")
|
||||
Instruction(opcode, getArgValue(args))
|
||||
}
|
||||
}
|
||||
instructions.add(instruction)
|
||||
while(nextInstructionLabels.isNotEmpty()) {
|
||||
val label = nextInstructionLabels.pop()
|
||||
labels[label] = instruction
|
||||
}
|
||||
} else throw VmExecutionException("syntax error at line ${lineNr+1}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgValue(args: String?): Value? {
|
||||
if(args==null)
|
||||
return null
|
||||
if(args[0]=='"' && args[args.length-1]=='"') {
|
||||
// it's a string.
|
||||
return Value(DataType.STR, null, unescape(args.substring(1, args.length-1)))
|
||||
}
|
||||
val (type, valueStr) = args.split(':')
|
||||
return when(type) {
|
||||
"b" -> Value(DataType.BYTE, valueStr.toShort(16))
|
||||
"w" -> Value(DataType.WORD, valueStr.toInt(16))
|
||||
"f" -> Value(DataType.FLOAT, valueStr.toDouble())
|
||||
else -> throw VmExecutionException("invalid datatype $type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVars(lines: Iterator<IndexedValue<String>>): Map<String, Map<String, Value>> {
|
||||
val vars = mutableMapOf<String, MutableMap<String, Value>>()
|
||||
val splitpattern = Pattern.compile("\\s+")
|
||||
while(true) {
|
||||
val (lineNr, line) = lines.next()
|
||||
if(line=="%end_variables")
|
||||
return vars
|
||||
val (name, type, valueStr) = line.split(splitpattern, limit = 3)
|
||||
if(valueStr[0] !='"' && valueStr[1]!=':')
|
||||
throw VmExecutionException("missing value type character")
|
||||
val value = when(type) {
|
||||
"byte" -> Value(DataType.BYTE, valueStr.substring(2).toShort(16))
|
||||
"word" -> Value(DataType.WORD, valueStr.substring(2).toInt(16))
|
||||
"float" -> Value(DataType.FLOAT, valueStr.substring(2).toDouble())
|
||||
"str", "str_p", "str_s", "str_ps" -> {
|
||||
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
|
||||
Value(DataType.STR, null, unescape(valueStr.substring(1, valueStr.length-1)))
|
||||
else
|
||||
throw VmExecutionException("str should be enclosed in quotes at line ${lineNr+1}")
|
||||
}
|
||||
else -> throw VmExecutionException("invalid datatype at line ${lineNr+1}")
|
||||
}
|
||||
val blockname = name.substringBefore('.')
|
||||
val blockvars = vars[blockname] ?: mutableMapOf()
|
||||
vars[blockname] = blockvars
|
||||
blockvars[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun unescape(st: String): String {
|
||||
val result = mutableListOf<Char>()
|
||||
val iter = st.iterator()
|
||||
while(iter.hasNext()) {
|
||||
val c = iter.nextChar()
|
||||
if(c=='\\') {
|
||||
val ec = iter.nextChar()
|
||||
result.add(when(ec) {
|
||||
'\\' -> '\\'
|
||||
'b' -> '\b'
|
||||
'n' -> '\n'
|
||||
'r' -> '\r'
|
||||
't' -> '\t'
|
||||
'u' -> {
|
||||
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||
}
|
||||
else -> throw VmExecutionException("invalid escape char: $ec")
|
||||
})
|
||||
} else {
|
||||
result.add(c)
|
||||
}
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
||||
|
||||
private fun loadMemory(lines: Iterator<IndexedValue<String>>): Map<Int, List<Value>> {
|
||||
val memory = mutableMapOf<Int, List<Value>>()
|
||||
while(true) {
|
||||
val (lineNr, line) = lines.next()
|
||||
if(line=="%end_memory")
|
||||
return memory
|
||||
val address = line.substringBefore(' ').toInt(16)
|
||||
val rest = line.substringAfter(' ').trim()
|
||||
if(rest.startsWith('"')) {
|
||||
memory[address] = listOf(Value(DataType.STR, null, unescape(rest.substring(1, rest.length - 1))))
|
||||
} else {
|
||||
val valueStrings = rest.split(' ')
|
||||
val values = mutableListOf<Value>()
|
||||
valueStrings.forEach {
|
||||
when(it.length) {
|
||||
2 -> values.add(Value(DataType.BYTE, it.toShort(16)))
|
||||
4 -> values.add(Value(DataType.WORD, it.toInt(16)))
|
||||
else -> throw VmExecutionException("invalid value at line $lineNr+1")
|
||||
}
|
||||
}
|
||||
memory[address] = values
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val program: List<Instruction>
|
||||
|
||||
init {
|
||||
prog.add(Instruction(Opcode.TERMINATE))
|
||||
prog.add(Instruction(Opcode.NOP))
|
||||
program = prog
|
||||
connect()
|
||||
}
|
||||
|
||||
private fun connect() {
|
||||
val it1 = program.iterator()
|
||||
val it2 = program.iterator()
|
||||
it2.next()
|
||||
|
||||
while(it1.hasNext() && it2.hasNext()) {
|
||||
val instr = it1.next()
|
||||
val nextInstr = it2.next()
|
||||
when(instr.opcode) {
|
||||
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
|
||||
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
|
||||
Opcode.JUMP -> {
|
||||
if(instr.callLabel==null) {
|
||||
throw VmExecutionException("stackVm doesn't support JUMP to memory address")
|
||||
} else {
|
||||
// jump to label
|
||||
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = target
|
||||
}
|
||||
}
|
||||
Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS -> {
|
||||
if(instr.callLabel==null) {
|
||||
throw VmExecutionException("stackVm doesn't support branch to memory address")
|
||||
} else {
|
||||
// branch to label
|
||||
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = jumpInstr
|
||||
instr.nextAlt = nextInstr
|
||||
}
|
||||
}
|
||||
Opcode.CALL -> {
|
||||
if(instr.callLabel==null) {
|
||||
throw VmExecutionException("stackVm doesn't support CALL to memory address")
|
||||
} else {
|
||||
// call label
|
||||
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = jumpInstr
|
||||
instr.nextAlt = nextInstr // instruction to return to
|
||||
}
|
||||
}
|
||||
else -> instr.next = nextInstr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun print(out: PrintStream, embeddedLabels: Boolean=true) {
|
||||
out.println("; stackVM program code for '$name'")
|
||||
out.println("%memory")
|
||||
if(memory.isNotEmpty()) {
|
||||
TODO("print out initial memory load")
|
||||
}
|
||||
out.println("%end_memory")
|
||||
out.println("%variables")
|
||||
// just flatten all block vars into one global list for now...
|
||||
for(variable in variables.flatMap { e->e.value.entries}) {
|
||||
val valuestr = variable.value.toString()
|
||||
out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr")
|
||||
}
|
||||
out.println("%end_variables")
|
||||
out.println("%instructions")
|
||||
val labels = this.labels.entries.associateBy({it.value}) {it.key}
|
||||
for(instr in this.program) {
|
||||
if(!embeddedLabels) {
|
||||
val label = labels[instr]
|
||||
if (label != null)
|
||||
out.println("$label:")
|
||||
} else {
|
||||
out.println(instr)
|
||||
}
|
||||
}
|
||||
out.println("%end_instructions")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StackVm(private var traceOutputFile: String?) {
|
||||
val mem = Memory()
|
||||
@ -866,6 +235,7 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
var callstack = MyStack<Instruction>()
|
||||
private set
|
||||
private var program = listOf<Instruction>()
|
||||
private var heap = HeapValues()
|
||||
private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null
|
||||
private var canvas: BitmapScreenPanel? = null
|
||||
private val rnd = Random()
|
||||
@ -877,7 +247,9 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
|
||||
fun load(program: Program, canvas: BitmapScreenPanel?) {
|
||||
this.program = program.program
|
||||
this.heap = program.heap
|
||||
this.canvas = canvas
|
||||
variables.clear()
|
||||
for(variable in program.variables.flatMap { e->e.value.entries })
|
||||
variables[variable.key] = variable.value
|
||||
|
||||
@ -955,8 +327,9 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
address += 5
|
||||
}
|
||||
DataType.STR -> {
|
||||
mem.setString(address, value.stringvalue!!)
|
||||
address += value.stringvalue.length+1
|
||||
TODO("mem init with string")
|
||||
//mem.setString(address, value.stringvalue!!)
|
||||
//address += value.stringvalue.length+1
|
||||
}
|
||||
else -> throw VmExecutionException("invalid mem datatype ${value.type}")
|
||||
}
|
||||
@ -982,22 +355,6 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
evalstack.push(Value(DataType.FLOAT, mem.getFloat(address)))
|
||||
}
|
||||
Opcode.DUP -> evalstack.push(evalstack.peek())
|
||||
Opcode.ARRAY -> {
|
||||
val amount = ins.arg!!.integerValue()
|
||||
if(amount<=0)
|
||||
throw VmExecutionException("array size must be > 0")
|
||||
val array = mutableListOf<Int>()
|
||||
var arrayDt = DataType.ARRAY
|
||||
for (i in 1..amount) {
|
||||
val value = evalstack.pop()
|
||||
if(value.type!=DataType.BYTE && value.type!=DataType.WORD)
|
||||
throw VmExecutionException("array requires values to be all byte/word")
|
||||
if(value.type==DataType.WORD)
|
||||
arrayDt = DataType.ARRAY_W
|
||||
array.add(0, value.integerValue())
|
||||
}
|
||||
evalstack.push(Value(arrayDt, null, arrayvalue = array.toIntArray()))
|
||||
}
|
||||
Opcode.DISCARD -> evalstack.pop()
|
||||
Opcode.SWAP -> {
|
||||
val (top, second) = evalstack.pop2()
|
||||
@ -1120,14 +477,21 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
val value = evalstack.pop()
|
||||
when(value.type){
|
||||
DataType.BYTE, DataType.WORD, DataType.FLOAT -> print(value.numericValue())
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> print(value.stringvalue)
|
||||
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> print(value.arrayvalue)
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
TODO("print stringvalue")
|
||||
}
|
||||
DataType.ARRAY, DataType.ARRAY_W -> {
|
||||
TODO("print array value")
|
||||
}
|
||||
DataType.MATRIX -> {
|
||||
TODO("print matrix value")
|
||||
}
|
||||
}
|
||||
}
|
||||
Syscall.INPUT_STR -> {
|
||||
val maxlen = evalstack.pop().integerValue()
|
||||
val input = readLine()?.substring(0, maxlen) ?: ""
|
||||
evalstack.push(Value(DataType.STR, null, input))
|
||||
TODO("input_str opcode should put the string in a given heap location (overwriting old string)")
|
||||
}
|
||||
Syscall.GFX_PIXEL -> {
|
||||
// plot pixel at (x, y, color) from stack
|
||||
@ -1140,23 +504,25 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
canvas?.clearScreen(color.integerValue())
|
||||
}
|
||||
Syscall.GFX_TEXT -> {
|
||||
val text = evalstack.pop()
|
||||
val textPtr = evalstack.pop()
|
||||
val color = evalstack.pop()
|
||||
val (y, x) = evalstack.pop2()
|
||||
canvas?.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue())
|
||||
val text = heap.get(textPtr.heapId)
|
||||
canvas?.writeText(x.integerValue(), y.integerValue(), text.str!!, color.integerValue())
|
||||
}
|
||||
Syscall.FUNC_RND -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255))
|
||||
Syscall.FUNC_RNDW -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535))
|
||||
Syscall.FUNC_RNDF -> evalstack.push(Value(DataType.FLOAT, rnd.nextDouble()))
|
||||
Syscall.FUNC_LEN -> {
|
||||
val value = evalstack.pop()
|
||||
when(value.type) {
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS ->
|
||||
evalstack.push(Value(DataType.WORD, value.stringvalue!!.length))
|
||||
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX ->
|
||||
evalstack.push(Value(DataType.WORD, value.arrayvalue!!.size))
|
||||
else -> throw VmExecutionException("cannot get length of $value")
|
||||
}
|
||||
TODO("func_len")
|
||||
// when(value.type) {
|
||||
// DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS ->
|
||||
// evalstack.push(Value(DataType.WORD, value.stringvalue!!.length))
|
||||
// DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX ->
|
||||
// evalstack.push(Value(DataType.WORD, value.arrayvalue!!.size))
|
||||
// else -> throw VmExecutionException("cannot get length of $value")
|
||||
// }
|
||||
}
|
||||
Syscall.FUNC_SIN -> evalstack.push(Value(DataType.FLOAT, sin(evalstack.pop().numericValue().toDouble())))
|
||||
Syscall.FUNC_COS -> evalstack.push(Value(DataType.FLOAT, cos(evalstack.pop().numericValue().toDouble())))
|
||||
@ -1205,40 +571,46 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
evalstack.push(result)
|
||||
}
|
||||
Syscall.FUNC_MAX -> {
|
||||
val array = evalstack.pop()
|
||||
val iterable = evalstack.pop()
|
||||
val dt =
|
||||
when {
|
||||
array.type==DataType.ARRAY -> DataType.BYTE
|
||||
array.type==DataType.ARRAY_W -> DataType.WORD
|
||||
else -> throw VmExecutionException("invalid array datatype $array")
|
||||
when(iterable.type) {
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
|
||||
DataType.ARRAY, DataType.MATRIX -> DataType.BYTE
|
||||
DataType.ARRAY_W -> DataType.WORD
|
||||
else -> throw VmExecutionException("uniterable value $iterable")
|
||||
}
|
||||
evalstack.push(Value(dt, array.arrayvalue!!.max()))
|
||||
TODO("func_max on array/matrix/string")
|
||||
}
|
||||
Syscall.FUNC_MIN -> {
|
||||
val array = evalstack.pop()
|
||||
val iterable = evalstack.pop()
|
||||
val dt =
|
||||
when {
|
||||
array.type==DataType.ARRAY -> DataType.BYTE
|
||||
array.type==DataType.ARRAY_W -> DataType.WORD
|
||||
else -> throw VmExecutionException("invalid array datatype $array")
|
||||
when(iterable.type) {
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
|
||||
DataType.ARRAY, DataType.MATRIX -> DataType.BYTE
|
||||
DataType.ARRAY_W -> DataType.WORD
|
||||
else -> throw VmExecutionException("uniterable value $iterable")
|
||||
}
|
||||
evalstack.push(Value(dt, array.arrayvalue!!.min()))
|
||||
TODO("func_min on array/matrix/string")
|
||||
}
|
||||
Syscall.FUNC_AVG -> {
|
||||
val array = evalstack.pop()
|
||||
evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average()))
|
||||
val iterable = evalstack.pop()
|
||||
TODO("func_avg")
|
||||
// evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average()))
|
||||
}
|
||||
Syscall.FUNC_SUM -> {
|
||||
val array = evalstack.pop()
|
||||
evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum()))
|
||||
val iterable = evalstack.pop()
|
||||
TODO("func_sum")
|
||||
// evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum()))
|
||||
}
|
||||
Syscall.FUNC_ANY -> {
|
||||
val array = evalstack.pop()
|
||||
evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0))
|
||||
val iterable = evalstack.pop()
|
||||
TODO("func_any")
|
||||
// evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0))
|
||||
}
|
||||
Syscall.FUNC_ALL -> {
|
||||
val array = evalstack.pop()
|
||||
evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0))
|
||||
val iterable = evalstack.pop()
|
||||
TODO("func_all")
|
||||
// evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0))
|
||||
}
|
||||
else -> throw VmExecutionException("unimplemented syscall $syscall")
|
||||
}
|
||||
@ -1370,61 +742,51 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
return callstack.pop()
|
||||
}
|
||||
Opcode.PUSH_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
evalstack.push(variable)
|
||||
}
|
||||
Opcode.POP_VAR -> {
|
||||
val value = evalstack.pop()
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
if(variable.type!=value.type)
|
||||
throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var $varname")
|
||||
variables[varname] = value
|
||||
throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var ${ins.callLabel}")
|
||||
variables[ins.callLabel!!] = value
|
||||
}
|
||||
Opcode.SHL_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
variables[varname] = variable.shl()
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
variables[ins.callLabel!!] = variable.shl()
|
||||
}
|
||||
Opcode.SHR_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
variables[varname] = variable.shr()
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
variables[ins.callLabel!!] = variable.shr()
|
||||
}
|
||||
Opcode.ROL_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
val (newValue, newCarry) = variable.rol(P_carry)
|
||||
variables[varname] = newValue
|
||||
variables[ins.callLabel!!] = newValue
|
||||
P_carry = newCarry
|
||||
}
|
||||
Opcode.ROR_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
val (newValue, newCarry) = variable.ror(P_carry)
|
||||
variables[varname] = newValue
|
||||
variables[ins.callLabel!!] = newValue
|
||||
P_carry = newCarry
|
||||
}
|
||||
Opcode.ROL2_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
variables[varname] = variable.rol2()
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
variables[ins.callLabel!!] = variable.rol2()
|
||||
}
|
||||
Opcode.ROR2_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
variables[varname] = variable.ror2()
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
variables[ins.callLabel!!] = variable.ror2()
|
||||
}
|
||||
Opcode.INC_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
variables[varname] = variable.inc()
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
variables[ins.callLabel!!] = variable.inc()
|
||||
}
|
||||
Opcode.DEC_VAR -> {
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
variables[varname] = variable.dec()
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
variables[ins.callLabel!!] = variable.dec()
|
||||
}
|
||||
Opcode.LSB -> {
|
||||
val v = evalstack.pop()
|
||||
@ -1507,7 +869,7 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
}
|
||||
}
|
||||
Opcode.LINE -> {
|
||||
sourceLine = ins.arg!!.stringvalue!!
|
||||
sourceLine = ins.callLabel!!
|
||||
}
|
||||
else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}")
|
||||
}
|
||||
|
373
compiler/src/prog8/stackvm/Value.kt
Normal file
373
compiler/src/prog8/stackvm/Value.kt
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,10 @@ package prog8tests
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.empty
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.DataType
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.stackvm.*
|
||||
import kotlin.test.*
|
||||
|
||||
@ -49,11 +49,13 @@ class TestStackVmOpcodes {
|
||||
if(vars!=null) {
|
||||
for (blockvar in vars) {
|
||||
val blockname = blockvar.key.substringBefore('.')
|
||||
val variables = blockvars.getValue(blockname)
|
||||
val variables = blockvars[blockname] ?: mutableMapOf()
|
||||
blockvars[blockname] = variables
|
||||
variables[blockvar.key] = blockvar.value
|
||||
}
|
||||
}
|
||||
return Program("test", ins, labels ?: mapOf(), blockvars, mem ?: mapOf())
|
||||
val heap = HeapValues()
|
||||
return Program("test", ins, labels ?: mapOf(), blockvars, mem ?: mapOf(), heap)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -84,7 +86,7 @@ class TestStackVmOpcodes {
|
||||
|
||||
@Test
|
||||
fun testLine() {
|
||||
val ins = mutableListOf(Instruction(Opcode.LINE, Value(DataType.STR, null, "line 99")))
|
||||
val ins = mutableListOf(Instruction(Opcode.LINE, callLabel = "line 99"))
|
||||
vm.load(makeProg(ins), null)
|
||||
assertEquals("", vm.sourceLine)
|
||||
vm.step(1)
|
||||
@ -149,7 +151,7 @@ class TestStackVmOpcodes {
|
||||
|
||||
@Test
|
||||
fun testPushVar() {
|
||||
val ins = mutableListOf(Instruction(Opcode.PUSH_VAR, Value(DataType.STR, null, "varname")))
|
||||
val ins = mutableListOf(Instruction(Opcode.PUSH_VAR, callLabel = "varname"))
|
||||
vm.load(makeProg(ins, mapOf("varname" to Value(DataType.FLOAT, 42.999))), null)
|
||||
assertEquals(7, vm.variables.size)
|
||||
assertTrue(vm.variables.containsKey("varname"))
|
||||
@ -206,47 +208,6 @@ class TestStackVmOpcodes {
|
||||
assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArray() {
|
||||
val ins = mutableListOf(
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 111)),
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 222)),
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 333)),
|
||||
Instruction(Opcode.ARRAY, Value(DataType.WORD, 2)))
|
||||
vm.load(makeProg(ins), null)
|
||||
assertThat(vm.evalstack, empty())
|
||||
vm.step(4)
|
||||
assertEquals(2, vm.evalstack.size)
|
||||
var array = vm.evalstack.pop()
|
||||
assertEquals(DataType.ARRAY_W, array.type)
|
||||
assertThat(array.arrayvalue, equalTo(intArrayOf(222, 333)))
|
||||
assertEquals(Value(DataType.WORD, 111), vm.evalstack.pop())
|
||||
|
||||
val ins2 = mutableListOf(
|
||||
Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)),
|
||||
Instruction(Opcode.PUSH, Value(DataType.BYTE, 22)),
|
||||
Instruction(Opcode.PUSH, Value(DataType.BYTE, 33)),
|
||||
Instruction(Opcode.ARRAY, Value(DataType.WORD, 2)))
|
||||
vm.load(makeProg(ins2), null)
|
||||
assertThat(vm.evalstack, empty())
|
||||
vm.step(4)
|
||||
assertEquals(2, vm.evalstack.size)
|
||||
array = vm.evalstack.pop()
|
||||
assertEquals(DataType.ARRAY, array.type)
|
||||
assertThat(array.arrayvalue, equalTo(intArrayOf(22, 33)))
|
||||
assertEquals(Value(DataType.BYTE, 11), vm.evalstack.pop())
|
||||
|
||||
val ins3 = mutableListOf(
|
||||
Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)),
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 222)),
|
||||
Instruction(Opcode.PUSH, Value(DataType.FLOAT, 333.33)),
|
||||
Instruction(Opcode.ARRAY, Value(DataType.WORD, 2)))
|
||||
vm.load(makeProg(ins3), null)
|
||||
assertFailsWith<VmExecutionException> {
|
||||
vm.step(4)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopMem() {
|
||||
val ins = mutableListOf(
|
||||
@ -275,9 +236,9 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.25)),
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)),
|
||||
Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)),
|
||||
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1")),
|
||||
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var2")),
|
||||
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var3")))
|
||||
Instruction(Opcode.POP_VAR, callLabel = "var1"),
|
||||
Instruction(Opcode.POP_VAR, callLabel = "var2"),
|
||||
Instruction(Opcode.POP_VAR, callLabel = "var3"))
|
||||
val vars = mapOf(
|
||||
"var1" to Value(DataType.BYTE, 0),
|
||||
"var2" to Value(DataType.WORD, 0),
|
||||
@ -292,7 +253,7 @@ class TestStackVmOpcodes {
|
||||
|
||||
val ins2 = mutableListOf(
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)),
|
||||
Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1")))
|
||||
Instruction(Opcode.POP_VAR, callLabel = "var1"))
|
||||
val vars2 = mapOf(
|
||||
"var1" to Value(DataType.BYTE, 0)
|
||||
)
|
||||
@ -450,12 +411,12 @@ class TestStackVmOpcodes {
|
||||
@Test
|
||||
fun testAnd() {
|
||||
val values = listOf(
|
||||
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
|
||||
Value(DataType.ARRAY, 111),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.WORD, 0),
|
||||
Value(DataType.STR, null, ""),
|
||||
Value(DataType.STR, null, "hello"),
|
||||
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
|
||||
Value(DataType.STR, 222),
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.ARRAY, 444),
|
||||
Value(DataType.FLOAT, 300.33),
|
||||
Value(DataType.WORD, 5000),
|
||||
Value(DataType.BYTE, 200),
|
||||
@ -466,7 +427,7 @@ class TestStackVmOpcodes {
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 0))
|
||||
@ -478,19 +439,19 @@ class TestStackVmOpcodes {
|
||||
@Test
|
||||
fun testOr() {
|
||||
val values = listOf(
|
||||
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.WORD, 0),
|
||||
Value(DataType.STR, null, ""),
|
||||
Value(DataType.STR, null, "hello"),
|
||||
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
|
||||
Value(DataType.FLOAT, 300.33),
|
||||
Value(DataType.WORD, 5000),
|
||||
Value(DataType.STR, 222),
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.ARRAY, 444),
|
||||
Value(DataType.FLOAT, 0),
|
||||
Value(DataType.WORD, 1),
|
||||
Value(DataType.WORD, 0),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 0))
|
||||
val expected = listOf(
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 1),
|
||||
@ -506,12 +467,12 @@ class TestStackVmOpcodes {
|
||||
@Test
|
||||
fun testXor() {
|
||||
val values = listOf(
|
||||
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
|
||||
Value(DataType.ARRAY, 111),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.WORD, 0),
|
||||
Value(DataType.STR, null, ""),
|
||||
Value(DataType.STR, null, "hello"),
|
||||
Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)),
|
||||
Value(DataType.STR, 222),
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.ARRAY, 444),
|
||||
Value(DataType.FLOAT, 300.33),
|
||||
Value(DataType.WORD, 5000),
|
||||
Value(DataType.BYTE, 0),
|
||||
@ -522,10 +483,10 @@ class TestStackVmOpcodes {
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 1))
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 0))
|
||||
val operator = Opcode.XOR
|
||||
|
||||
testBinaryOperator(values, operator, expected)
|
||||
@ -534,8 +495,8 @@ class TestStackVmOpcodes {
|
||||
@Test
|
||||
fun testNot() {
|
||||
val values = listOf(
|
||||
Value(DataType.STR, null, ""),
|
||||
Value(DataType.STR, null, "hello"),
|
||||
Value(DataType.STR, 111),
|
||||
Value(DataType.STR, 222),
|
||||
Value(DataType.FLOAT, 0.0),
|
||||
Value(DataType.FLOAT, 300.33),
|
||||
Value(DataType.WORD, 0),
|
||||
@ -550,7 +511,7 @@ class TestStackVmOpcodes {
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 1),
|
||||
Value(DataType.BYTE, 0),
|
||||
Value(DataType.BYTE, 1)
|
||||
Value(DataType.BYTE, 0)
|
||||
)
|
||||
val operator = Opcode.NOT
|
||||
|
||||
@ -785,10 +746,10 @@ class TestStackVmOpcodes {
|
||||
@Test
|
||||
fun testIncVar() {
|
||||
val ins = mutableListOf(
|
||||
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")),
|
||||
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2")),
|
||||
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")),
|
||||
Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2")))
|
||||
Instruction(Opcode.INC_VAR, callLabel ="var1"),
|
||||
Instruction(Opcode.INC_VAR, callLabel ="var2"),
|
||||
Instruction(Opcode.INC_VAR, callLabel ="var1"),
|
||||
Instruction(Opcode.INC_VAR, callLabel ="var2"))
|
||||
val vars = mapOf("var1" to Value(DataType.WORD, 65534),
|
||||
"var2" to Value(DataType.BYTE, 254))
|
||||
vm.load(makeProg(ins, vars = vars), null)
|
||||
@ -803,10 +764,10 @@ class TestStackVmOpcodes {
|
||||
@Test
|
||||
fun testDecVar() {
|
||||
val ins = mutableListOf(
|
||||
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")),
|
||||
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2")),
|
||||
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")),
|
||||
Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2")))
|
||||
Instruction(Opcode.DEC_VAR, callLabel = "var1"),
|
||||
Instruction(Opcode.DEC_VAR, callLabel = "var2"),
|
||||
Instruction(Opcode.DEC_VAR, callLabel = "var1"),
|
||||
Instruction(Opcode.DEC_VAR, callLabel = "var2"))
|
||||
val vars = mapOf("var1" to Value(DataType.WORD,1),
|
||||
"var2" to Value(DataType.BYTE, 1))
|
||||
vm.load(makeProg(ins, vars = vars), null)
|
||||
@ -887,11 +848,11 @@ class TestStackVmOpcodes {
|
||||
testComparisonOperator(values, expected, Opcode.LESS)
|
||||
|
||||
val valuesInvalid = listOf(
|
||||
Value(DataType.STR, null, stringvalue = "hello"),
|
||||
Value(DataType.STR, null, stringvalue = "hello")
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.STR, 333)
|
||||
)
|
||||
assertFailsWith<VmExecutionException> {
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESS) // can't compare strings
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESS) // can't order strings
|
||||
}
|
||||
}
|
||||
|
||||
@ -917,11 +878,11 @@ class TestStackVmOpcodes {
|
||||
testComparisonOperator(values, expected, Opcode.LESSEQ)
|
||||
|
||||
val valuesInvalid = listOf(
|
||||
Value(DataType.STR, null, stringvalue = "hello"),
|
||||
Value(DataType.STR, null, stringvalue = "hello")
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.STR, 333)
|
||||
)
|
||||
assertFailsWith<VmExecutionException> {
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESSEQ) // can't compare strings
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESSEQ) // can't order strings
|
||||
}
|
||||
}
|
||||
|
||||
@ -947,11 +908,11 @@ class TestStackVmOpcodes {
|
||||
testComparisonOperator(values, expected, Opcode.GREATER)
|
||||
|
||||
val valuesInvalid = listOf(
|
||||
Value(DataType.STR, null, stringvalue = "hello"),
|
||||
Value(DataType.STR, null, stringvalue = "hello")
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.STR, 333)
|
||||
)
|
||||
assertFailsWith<VmExecutionException> {
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATER) // can't compare strings
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATER) // can't order strings
|
||||
}
|
||||
}
|
||||
|
||||
@ -977,11 +938,11 @@ class TestStackVmOpcodes {
|
||||
testComparisonOperator(values, expected, Opcode.GREATEREQ)
|
||||
|
||||
val valuesInvalid = listOf(
|
||||
Value(DataType.STR, null, stringvalue = "hello"),
|
||||
Value(DataType.STR, null, stringvalue = "hello")
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.STR, 333)
|
||||
)
|
||||
assertFailsWith<VmExecutionException> {
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATEREQ) // can't compare strings
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATEREQ) // can't order strings
|
||||
}
|
||||
}
|
||||
|
||||
@ -1007,12 +968,12 @@ class TestStackVmOpcodes {
|
||||
testComparisonOperator(values, expected, Opcode.EQUAL)
|
||||
|
||||
val valuesInvalid = listOf(
|
||||
Value(DataType.STR, null, stringvalue = "hello"),
|
||||
Value(DataType.STR, null, stringvalue = "hello")
|
||||
Value(DataType.STR, 111),
|
||||
Value(DataType.STR, 222), // 0
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.STR, 333) // 1
|
||||
)
|
||||
assertFailsWith<VmExecutionException> {
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.EQUAL) // can't compare strings
|
||||
}
|
||||
testComparisonOperator(valuesInvalid, listOf(0, 1), Opcode.EQUAL)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1037,12 +998,12 @@ class TestStackVmOpcodes {
|
||||
testComparisonOperator(values, expected, Opcode.NOTEQUAL)
|
||||
|
||||
val valuesInvalid = listOf(
|
||||
Value(DataType.STR, null, stringvalue = "hello"),
|
||||
Value(DataType.STR, null, stringvalue = "hello")
|
||||
Value(DataType.STR, 111),
|
||||
Value(DataType.STR, 222), // 1
|
||||
Value(DataType.STR, 333),
|
||||
Value(DataType.STR, 333) // 0
|
||||
)
|
||||
assertFailsWith<VmExecutionException> {
|
||||
testComparisonOperator(valuesInvalid, listOf(0), Opcode.NOTEQUAL) // can't compare strings
|
||||
}
|
||||
testComparisonOperator(valuesInvalid, listOf(1, 0), Opcode.NOTEQUAL)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1052,9 +1013,9 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.BCC, callLabel = "label"),
|
||||
Instruction(Opcode.CLC),
|
||||
Instruction(Opcode.BCC, callLabel = "label"),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
|
||||
Instruction(Opcode.LINE, callLabel = "string2"))
|
||||
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
|
||||
vm.load(makeProg(ins, labels=labels), null)
|
||||
vm.step(2)
|
||||
@ -1072,9 +1033,9 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.BCS, callLabel = "label"),
|
||||
Instruction(Opcode.SEC),
|
||||
Instruction(Opcode.BCS, callLabel = "label"),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
|
||||
Instruction(Opcode.LINE, callLabel = "string2"))
|
||||
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
|
||||
vm.load(makeProg(ins, labels=labels), null)
|
||||
assertFalse(vm.P_carry)
|
||||
@ -1093,9 +1054,9 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.BZ, callLabel = "label"),
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 0)),
|
||||
Instruction(Opcode.BZ, callLabel = "label"),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
|
||||
Instruction(Opcode.LINE, callLabel = "string2"))
|
||||
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
|
||||
vm.load(makeProg(ins, labels=labels), null)
|
||||
vm.step(2)
|
||||
@ -1113,9 +1074,9 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.BNZ, callLabel = "label"),
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 1)),
|
||||
Instruction(Opcode.BNZ, callLabel = "label"),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
|
||||
Instruction(Opcode.LINE, callLabel = "string2"))
|
||||
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
|
||||
vm.load(makeProg(ins, labels=labels), null)
|
||||
vm.step(2)
|
||||
@ -1135,9 +1096,9 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.BNEG, callLabel = "label"),
|
||||
Instruction(Opcode.PUSH, Value(DataType.FLOAT, -99)),
|
||||
Instruction(Opcode.BNEG, callLabel = "label"),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
|
||||
Instruction(Opcode.LINE, callLabel = "string2"))
|
||||
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
|
||||
vm.load(makeProg(ins, labels=labels), null)
|
||||
vm.step(2)
|
||||
@ -1157,9 +1118,9 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.BPOS, callLabel = "label"),
|
||||
Instruction(Opcode.PUSH, Value(DataType.WORD, 0)),
|
||||
Instruction(Opcode.BPOS, callLabel = "label"),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
|
||||
Instruction(Opcode.LINE, callLabel = "string2"))
|
||||
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
|
||||
vm.load(makeProg(ins, labels=labels), null)
|
||||
vm.step(2)
|
||||
@ -1174,9 +1135,9 @@ class TestStackVmOpcodes {
|
||||
fun testJump() {
|
||||
val ins = mutableListOf(
|
||||
Instruction(Opcode.JUMP, callLabel = "label"),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")),
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2")))
|
||||
Instruction(Opcode.LINE, callLabel = "string2"))
|
||||
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
|
||||
vm.load(makeProg(ins, labels=labels), null)
|
||||
vm.step(2)
|
||||
@ -1191,7 +1152,7 @@ class TestStackVmOpcodes {
|
||||
val ins = mutableListOf(
|
||||
Instruction(Opcode.RETURN),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1"))
|
||||
Instruction(Opcode.LINE, callLabel = "string1")
|
||||
)
|
||||
vm.load(makeProg(ins), null)
|
||||
assertFailsWith<VmTerminationException> {
|
||||
@ -1211,9 +1172,9 @@ class TestStackVmOpcodes {
|
||||
// @todo this only tests call with zero parameters for now.
|
||||
val ins = mutableListOf(
|
||||
Instruction(Opcode.CALL, callLabel = "label"),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "returned")),
|
||||
Instruction(Opcode.LINE, callLabel = "returned"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "called")),
|
||||
Instruction(Opcode.LINE, callLabel = "called"),
|
||||
Instruction(Opcode.RETURN)
|
||||
)
|
||||
val labels = mapOf("label" to ins[3]) // points to the LINE instruction
|
||||
@ -1560,16 +1521,16 @@ class TestStackVmOpcodes {
|
||||
}
|
||||
}
|
||||
|
||||
private fun testBinaryOperator(values: List<Value>, operator: Opcode, expected: List<Value>) {
|
||||
assertEquals(values.size, expected.size+1)
|
||||
private fun testBinaryOperator(valuesToPush: List<Value>, operator: Opcode, expected: List<Value>) {
|
||||
assertEquals(valuesToPush.size, expected.size+1)
|
||||
val ins = mutableListOf<Instruction>()
|
||||
for (value in values)
|
||||
for (value in valuesToPush)
|
||||
ins.add(Instruction(Opcode.PUSH, value))
|
||||
for (i in 1 until values.size)
|
||||
for (i in 1 until valuesToPush.size)
|
||||
ins.add(Instruction(operator))
|
||||
vm.load(makeProg(ins), null)
|
||||
vm.step(values.size)
|
||||
assertEquals(values.size, vm.evalstack.size)
|
||||
vm.step(valuesToPush.size)
|
||||
assertEquals(valuesToPush.size, vm.evalstack.size)
|
||||
for (expectedVal in expected) {
|
||||
vm.step(1)
|
||||
assertEquals(expectedVal, vm.evalstack.peek())
|
||||
@ -1579,18 +1540,18 @@ class TestStackVmOpcodes {
|
||||
}
|
||||
}
|
||||
|
||||
private fun testUnaryOperator(values: List<Value>, operator: Opcode, expected: List<Value>) {
|
||||
assertEquals(values.size, expected.size)
|
||||
private fun testUnaryOperator(valuesToPush: List<Value>, operator: Opcode, expected: List<Value>) {
|
||||
assertEquals(valuesToPush.size, expected.size)
|
||||
val ins = mutableListOf<Instruction>()
|
||||
for (value in values)
|
||||
for (value in valuesToPush)
|
||||
ins.add(Instruction(Opcode.PUSH, value))
|
||||
for (i in 1..values.size) {
|
||||
for (i in 1..valuesToPush.size) {
|
||||
ins.add(Instruction(operator))
|
||||
ins.add(Instruction(Opcode.DISCARD))
|
||||
}
|
||||
vm.load(makeProg(ins), null)
|
||||
vm.step(values.size)
|
||||
assertEquals(values.size, vm.evalstack.size)
|
||||
vm.step(valuesToPush.size)
|
||||
assertEquals(valuesToPush.size, vm.evalstack.size)
|
||||
for (expectedVal in expected) {
|
||||
vm.step(1)
|
||||
assertEquals(expectedVal, vm.evalstack.peek())
|
||||
|
@ -77,13 +77,18 @@ class TestStackVmValue {
|
||||
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.BYTE, 9)))
|
||||
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.WORD, 9)))
|
||||
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.0)))
|
||||
}
|
||||
|
||||
assertFailsWith<VmExecutionException> {
|
||||
assertTrue(sameValueAndType(Value(DataType.STR, null, "hello"), Value(DataType.STR, null, "hello")))
|
||||
}
|
||||
assertFailsWith<VmExecutionException> {
|
||||
assertTrue(sameValueAndType(Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3))))
|
||||
}
|
||||
@Test
|
||||
fun testEqualsAndNotEqualsHeapTypes()
|
||||
{
|
||||
assertTrue(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 999)))
|
||||
assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR_P, 999)))
|
||||
assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 222)))
|
||||
|
||||
assertTrue(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.ARRAY_W, 999)))
|
||||
assertFalse(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.MATRIX, 999)))
|
||||
assertFalse(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.ARRAY_W, 222)))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
Reference in New Issue
Block a user