fixed a load of type checks regarding arrays and matrixes and strings

added more builtin functions to the compiler to access the syscalls in the stackVm
This commit is contained in:
Irmen de Jong 2018-09-15 23:56:56 +02:00
parent c05cd72d23
commit 3426593a06
10 changed files with 272 additions and 106 deletions

View File

@ -24,6 +24,16 @@
~ main $c003 {
const byte [2,3] cmatrix1 = [1,2,3,4,5,255]
const word[1000] constarray1000 = 99.w
const byte[4] constarray1 = 99
byte[20] array2_20 = 199
byte[3] array3_3 = [1,2,3]
word zzzzz=len(cmatrix1)
word qqqqq=len(constarray1000)
byte Qzzzzz=len(cmatrix1)
byte Qqqqqq=len(constarray1)
word lsb1 = lsb($ea31)
word msb1 = msb($ea31)
byte lsb2 = lsb($ea31)
@ -56,6 +66,8 @@
const word all3 = all([wa1, wa2, ws1, all1])
const word max1 = max([-1,-2,3.33,99+22])
const str greeting = "Hello world!"
str greeting2 = "Hello world 2!"
const word min1 = min([1,2,3,99+22])
word dinges = round(not_main.len1)
@ -64,6 +76,21 @@
wa3 = rndw()
wa3 = rndf(22)
_vm_write_memchr($a000)
_vm_write_memstr($a000)
_vm_write_num(wa3)
_vm_write_char(wa2c)
_vm_write_str(greeting)
_vm_write_str(greeting2)
_vm_write_str(greeting2)
_vm_write_str(wa3)
; greeting2 = _vm_input_var(len(greeting2))
_vm_gfx_clearscr(6)
_vm_gfx_pixel(10, 20, 1)
_vm_gfx_pixel(11, 21, 2)
_vm_gfx_pixel(12, 22, 3)
_vm_gfx_text(50,100,7,"Ahoy!")
A += 8
A += rnd()
A =A+ rnd()
@ -96,6 +123,8 @@
const float blerp2 = zwop / 2.22
const byte equal = 4==4
const byte equal2 = (4+hopla)>0
byte[10] array10 = 22
byte[1000] array1000 = 33
; goto 64738
@ -116,7 +145,7 @@
byte equalQQ = 4==4
const byte equalQQ2 = (4+hopla)>0
const str string1 = "hallo"
str string2 = "doei"
str string2 = "doei dat was het weer"+"sdfdsf"*5
equalQQ++
AX++
@ -127,7 +156,10 @@
equalQQ= len("abcdef")
equalQQ= len([1,2,3])
equalQQ= len(string1)
equalQQ= lsb(len(string2))
equalQQ= len(string2)
;equalQQ = len(array10) ; @todo muust work byte
; equalQQ = len(array1000) ; @todo must fail word
P_carry(1)
P_irqd(0)

View File

@ -491,6 +491,20 @@ class ArraySpec(var x: IExpression, var y: IExpression?, override val position:
x = x.process(processor)
y = y?.process(processor)
}
override fun toString(): String {
return("ArraySpec(x: $x, y: $y, pos=$position)")
}
fun size() : Int? {
if(y==null) {
return (x as? LiteralValue)?.asIntegerValue
} else {
val sizeX = (x as? LiteralValue)?.asIntegerValue ?: return null
val sizeY = (y as? LiteralValue)?.asIntegerValue ?: return null
return sizeX * sizeY
}
}
}
@ -696,13 +710,13 @@ private data class ByteOrWordLiteral(val intvalue: Int, val datatype: DataType)
fun asByte() = ByteOrWordLiteral(intvalue, DataType.BYTE)
}
data class LiteralValue(val type: DataType,
val bytevalue: Short? = null,
val wordvalue: Int? = null,
val floatvalue: Double? = null,
val strvalue: String? = null,
val arrayvalue: MutableList<IExpression>? = null,
override val position: Position) : IExpression {
class LiteralValue(val type: DataType,
val bytevalue: Short? = null,
val wordvalue: Int? = null,
val floatvalue: Double? = null,
val strvalue: String? = null,
val arrayvalue: Array<IExpression>? = null,
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false
@ -720,7 +734,7 @@ data class LiteralValue(val type: DataType,
// note: we cheat a little here and allow negative integers during expression evaluations
in -128..255 -> LiteralValue(DataType.BYTE, bytevalue = floatval.toShort(), position = position)
in -32768..65535 -> LiteralValue(DataType.WORD, wordvalue = floatval.toInt(), position = position)
else -> throw FatalAstException("integer overflow: $floatval")
else -> LiteralValue(DataType.FLOAT, floatvalue = floatval, position = position)
}
} else {
LiteralValue(DataType.FLOAT, floatvalue = floatval, position = position)
@ -733,7 +747,9 @@ data 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.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.MATRIX -> TODO("matrix literalvalue? for now, arrays are good enough for this")
}
@ -982,7 +998,7 @@ class FunctionCall(override var target: IdentifierReference,
val constVal = constValue(namespace, false)
if(constVal!=null)
return constVal.resultingDatatype(namespace)
val stmt = target.targetStatement(namespace)
val stmt = target.targetStatement(namespace) ?: return null
if(stmt is BuiltinFunctionStatementPlaceholder) {
if(target.nameInSource[0] == "P_carry" || target.nameInSource[0]=="P_irqd") {
return null // these have no return value
@ -1448,8 +1464,8 @@ private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
}
private fun prog8Parser.ArrayliteralContext.toAst() =
expression().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.ArrayliteralContext.toAst() : Array<IExpression> =
expression().map { it.toAst() }.toTypedArray()
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {

View File

@ -385,12 +385,12 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
}
}
private fun checkValueTypeAndRange(datatype: DataType, arrayspec: ArraySpec?, value: LiteralValue) : Boolean {
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, value: LiteralValue) : Boolean {
fun err(msg: String) : Boolean {
checkResult.add(ExpressionError(msg, value.position))
return false
}
when (datatype) {
when (targetDt) {
DataType.FLOAT -> {
val number = value.floatvalue
?: return err("floating point value expected")
@ -416,32 +416,33 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val str = value.strvalue
?: return err("string value expected")
if (str.isEmpty() || str.length > 65535)
return err("string length must be 1..65535")
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
if(value.type==DataType.ARRAY) {
if(arrayspec!=null) {
// arrayspec is not always known when checking
val constX = arrayspec.x.constValue(namespace)
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(arrayspec!=null) {
// arrayspec is not always known when checking
val expectedSize = arrayspec.x.constValue(namespace)?.asIntegerValue
if (value.arrayvalue.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
}
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 byte array is out of range for unsigned byte")
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 {
TODO("check array value $av")
}
}
} else {
val number = value.bytevalue ?: return if (value.floatvalue!=null)
@ -455,26 +456,30 @@ 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) {
if(arrayspec!=null) {
// arrayspec is not always known when checking
val constX = arrayspec.x.constValue(namespace)
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!!) {
val number = (av as LiteralValue).asIntegerValue // both byte and word are acceptable
?: return err("array must be all words")
if(arrayspec!=null) {
// arrayspec is not always known when checking
val expectedSize = arrayspec.x.constValue(namespace)?.asIntegerValue
if (value.arrayvalue.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
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 {
TODO("check array value $av")
}
if (number < 0 || number > 65535)
return err("value '$number' in word array is out of range for unsigned word")
}
} else {
val number = value.asIntegerValue // both byte and word are acceptable
?: return if (value.floatvalue!=null)
err("unsigned word integer value expected instead of float; possible loss of precision")
else
err("unsigned word integer value expected")
val number = value.asIntegerValue ?: return if (value.floatvalue!=null)
err("unsigned byte or word integer value expected instead of float; possible loss of precision")
else
err("unsigned byte or word integer value expected")
if (number < 0 || number > 65535)
return err("value '$number' out of range for unsigned word")
}
@ -485,12 +490,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
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 expectedSizeX = arrayspec!!.x.constValue(namespace)?.asIntegerValue!!
val expectedSizeY = arrayspec.y!!.constValue(namespace)?.asIntegerValue!!
val expectedSize = expectedSizeX * expectedSizeY
val expectedSize = constX.asIntegerValue * constY.asIntegerValue
if (value.arrayvalue.size != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
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")

View File

@ -257,7 +257,8 @@ class Compiler(private val options: CompilationOptions) {
val target = expr.target.targetStatement(namespace)
if(target is BuiltinFunctionStatementPlaceholder) {
// call to a builtin function
stackvmProg.instruction("syscall FUNC_${expr.target.nameInSource[0].toUpperCase()}") // call builtin function
val funcname = expr.target.nameInSource[0].toUpperCase()
createFunctionCall(funcname) // call builtin function
} else {
when(target) {
is Subroutine -> {
@ -279,8 +280,8 @@ class Compiler(private val options: CompilationOptions) {
else -> {
val lv = expr.constValue(namespace) ?: throw CompilerException("constant expression required, not $expr")
when(lv.type) {
DataType.BYTE -> stackvmProg.instruction("push b:${lv.bytevalue!!.toString(16)}")
DataType.WORD -> stackvmProg.instruction("push w:${lv.wordvalue!!.toString(16)}")
DataType.BYTE -> stackvmProg.instruction("push b:%02x".format(lv.bytevalue!!))
DataType.WORD -> stackvmProg.instruction("push w:%04x".format(lv.wordvalue!!))
DataType.FLOAT -> stackvmProg.instruction("push f:${lv.floatvalue}")
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> stackvmProg.instruction("push \"${lv.strvalue}\"")
DataType.ARRAY, DataType.ARRAY_W -> {
@ -322,7 +323,8 @@ class Compiler(private val options: CompilationOptions) {
val targetStmt = stmt.target.targetStatement(namespace)!!
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
stmt.arglist.forEach { translate(it) }
stackvmProg.instruction("syscall FUNC_${stmt.target.nameInSource[0].toUpperCase()}") // call builtin function
val funcname = stmt.target.nameInSource[0].toUpperCase()
createFunctionCall(funcname) // call builtin function
return
}
@ -335,6 +337,13 @@ class Compiler(private val options: CompilationOptions) {
stackvmProg.instruction("call $targetname")
}
private fun createFunctionCall(funcname: String) {
if (funcname.startsWith("_VM_"))
stackvmProg.instruction("syscall ${funcname.substring(4)}") // call builtin function
else
stackvmProg.instruction("syscall FUNC_$funcname")
}
private fun translate(stmt: Jump) {
val instr =
if(stmt.address!=null) {

View File

@ -9,9 +9,15 @@ val BuiltinFunctionNames = setOf(
"P_carry", "P_irqd", "rol", "ror", "rol2", "ror2", "lsl", "lsr",
"sin", "cos", "abs", "acos", "asin", "tan", "atan", "rnd", "rndw", "rndf",
"log", "log10", "sqrt", "rad", "deg", "round", "floor", "ceil",
"max", "min", "avg", "sum", "len", "any", "all", "lsb", "msb")
"max", "min", "avg", "sum", "len", "any", "all", "lsb", "msb",
"_vm_write_memchr", "_vm_write_memstr", "_vm_write_num", "_vm_write_char",
"_vm_write_str", "_vm_input_var", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text"
)
val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf("P_carry", "P_irqd")
val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf("P_carry", "P_irqd",
"_vm_write_memchr", "_vm_write_memstr", "_vm_write_num", "_vm_write_char",
"_vm_write_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text")
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope): DataType? {
fun integerDatatypeFromArg(arg: IExpression): DataType {
@ -41,12 +47,36 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
return when (function) {
"sin", "cos", "tan", "asin", "acos", "atan", "log", "log10", "sqrt", "rad", "deg", "avg", "rndf" -> DataType.FLOAT
"len", "lsb", "msb", "any", "all", "rnd" -> DataType.BYTE
"lsb", "msb", "any", "all", "rnd" -> DataType.BYTE
"rndw" -> DataType.WORD
"rol", "rol2", "ror", "ror2", "P_carry", "P_irqd" -> null // no return value so no datatype
"abs" -> args.single().resultingDatatype(namespace)
"max", "min", "sum" -> datatypeFromListArg(args.single())
"round", "floor", "ceil", "lsl", "lsr" -> integerDatatypeFromArg(args.single())
"len" -> {
// len of a str is always 1..255 so always a byte,
// len of other things is assumed to need a word (even though the actual length could be less than 256)
val arg = args.single()
when(arg) {
is IdentifierReference -> {
val stmt = arg.targetStatement(namespace)
when(stmt) {
is VarDecl -> {
val value = stmt.value
if(value is LiteralValue) {
if(value.isString) return DataType.BYTE // strings are 1..255
}
}
}
DataType.WORD // assume other lengths are words for now.
}
is LiteralValue -> throw FatalAstException("len of literalvalue should have been const-folded away already")
else -> DataType.WORD
}
}
"_vm_write_memchr", "_vm_write_memstr", "_vm_write_num", "_vm_write_char",
"_vm_write_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text" -> null // no return value for these
"_vm_input_var" -> DataType.STR
else -> throw FatalAstException("invalid builtin function $function")
}
}
@ -214,7 +244,7 @@ fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope
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)
else -> throw FatalAstException("len of weird argument ${args[0]}")
else -> throw SyntaxError("len of weird argument ${args[0]}", position)
}
}

View File

@ -217,6 +217,14 @@ 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.strvalue!=null -> {
val newStr = left.strvalue + right.strvalue
if(newStr.length > 255) throw ExpressionError("string too long", left.position)
LiteralValue(DataType.STR, strvalue = newStr, position = left.position)
}
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
}
}
@ -245,7 +253,7 @@ class ConstExprEvaluator {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue * right.floatvalue, position = left.position)
right.strvalue!=null -> {
if(right.strvalue.length * left.asIntegerValue > 65535) throw ExpressionError("string too large", left.position)
if(right.strvalue.length * left.asIntegerValue > 255) throw ExpressionError("string too long", left.position)
LiteralValue(DataType.STR, strvalue = right.strvalue.repeat(left.asIntegerValue), position = left.position)
}
else -> throw ExpressionError(error, left.position)
@ -257,7 +265,7 @@ class ConstExprEvaluator {
}
left.strvalue!=null -> when {
right.asIntegerValue!=null -> {
if(left.strvalue.length * right.asIntegerValue > 65535) throw ExpressionError("string too large", left.position)
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)

View File

@ -30,8 +30,8 @@ class ConstantFolding(private val globalNamespace: INameScope) : IAstProcessor {
val result = super.process(decl)
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
when {
decl.datatype == DataType.FLOAT -> {
when(decl.datatype) {
DataType.FLOAT -> {
// vardecl: for float vars, promote constant integer initialization values to floats
val literal = decl.value as? LiteralValue
if (literal != null && (literal.type == DataType.BYTE || literal.type==DataType.WORD)) {
@ -39,7 +39,7 @@ class ConstantFolding(private val globalNamespace: INameScope) : IAstProcessor {
decl.value = newValue
}
}
decl.datatype == DataType.BYTE || decl.datatype == DataType.WORD -> {
DataType.BYTE, DataType.WORD -> {
// vardecl: for byte/word vars, convert char/string of length 1 initialization values to integer
val literal = decl.value as? LiteralValue
if (literal != null && literal.isString && literal.strvalue?.length == 1) {
@ -48,6 +48,42 @@ class ConstantFolding(private val globalNamespace: INameScope) : IAstProcessor {
decl.value = newValue
}
}
DataType.MATRIX -> {
(decl.value as LiteralValue).let {
val intvalue = it.asIntegerValue
if(intvalue!=null) {
// replace the single int value by a properly sized array to fill the matrix.
val size = decl.arrayspec!!.size()
if(size!=null) {
val newArray = Array<IExpression>(size) { _ -> LiteralValue(DataType.BYTE, bytevalue = intvalue.toShort(), position = it.position) }
decl.value = LiteralValue(DataType.ARRAY, arrayvalue = newArray, position = it.position)
} else {
addError(SyntaxError("matrix size spec should be constant integer values", it.position))
}
}
}
}
DataType.ARRAY, DataType.ARRAY_W -> {
(decl.value as LiteralValue).let {
val intvalue = it.asIntegerValue
if(intvalue!=null) {
// replace the single int value by a properly sized array to fill the array with.
val size = decl.arrayspec!!.size()
if(size!=null) {
val newArray = Array<IExpression>(size) { _ ->
if (decl.datatype == DataType.ARRAY)
LiteralValue(DataType.BYTE, bytevalue = intvalue.toShort(), position = it.position)
else
LiteralValue(DataType.WORD, wordvalue = intvalue, position = it.position)
}
decl.value = LiteralValue(decl.datatype, arrayvalue = newArray, position=it.position)
} else {
addError(SyntaxError("array size should be a constant integer value", it.position))
}
}
}
}
else -> return result
}
}
return result
@ -170,7 +206,7 @@ class ConstantFolding(private val globalNamespace: INameScope) : IAstProcessor {
val v = LiteralValue(DataType.WORD, wordvalue = it, position = range.position)
v.parent = range.parent
v
}.toMutableList(), position = from.position)
}.toTypedArray(), position = from.position)
}
from.type==DataType.BYTE && to.type==DataType.BYTE -> {
// range on byte value boundaries
@ -182,7 +218,7 @@ class ConstantFolding(private val globalNamespace: INameScope) : IAstProcessor {
val v = LiteralValue(DataType.BYTE, bytevalue = it.toShort(), position = range.position)
v.parent = range.parent
v
}.toMutableList(), position = from.position)
}.toTypedArray(), position = from.position)
}
from.strvalue != null && to.strvalue != null -> {
// char range
@ -206,9 +242,9 @@ class ConstantFolding(private val globalNamespace: INameScope) : IAstProcessor {
override fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.arrayvalue!=null) {
val newArray = literalValue.arrayvalue.map { it.process(this) }
literalValue.arrayvalue.clear()
literalValue.arrayvalue.addAll(newArray)
val newArray = literalValue.arrayvalue.map { it.process(this) }.toTypedArray()
val newValue = LiteralValue(literalValue.type, arrayvalue = newArray, position = literalValue.position)
return super.process(newValue)
}
return super.process(literalValue)
}

View File

@ -6,6 +6,13 @@ import prog8.ast.IStatement
import prog8.ast.Module
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)
try {

View File

@ -119,15 +119,15 @@ enum class Opcode {
NOP,
BREAK, // breakpoint
TERMINATE, // end the program
_LINE // record source file line number
LINE // record source file line number
}
enum class Syscall(val callNr: Short) {
WRITE_MEMCHR(10), // print a single char from the memory
WRITE_MEMSTR(11), // print a 0-terminated petscii string from the memory
WRITE_MEMCHR(10), // print a single char from the memory address popped from stack
WRITE_MEMSTR(11), // print a 0-terminated petscii string from the memory address popped from stack
WRITE_NUM(12), // pop from the evaluation stack and print it as a number
WRITE_CHAR(13), // pop from the evaluation stack and print it as a single petscii character
WRITE_VAR(14), // print the number or string from the given variable
WRITE_STR(14), // pop from the evaluation stack and print it as a string
INPUT_VAR(15), // user input a string into a variable
GFX_PIXEL(16), // plot a pixel at (x,y,color) pushed on stack in that order
GFX_CLEARSCR(17), // clear the screen with color pushed on stack
@ -488,24 +488,16 @@ class Value(val type: DataType, private val numericvalue: Number?, val stringval
data class Instruction(val opcode: Opcode,
val arg: Value? = null,
val callArgs: List<Value>? = emptyList(),
val callArgsAllocations: List<String> = emptyList(),
val callLabel: String? = null)
{
lateinit var next: Instruction
var nextAlt: Instruction? = null
init {
if(callLabel!=null) {
if(callArgs!!.size != callArgsAllocations.size)
throw VmExecutionException("for $opcode the callArgsAllocations size is not the same as the callArgs size")
}
}
override fun toString(): String {
return if(callLabel==null)
"$opcode $arg"
else
"$opcode $callLabel $callArgs $callArgsAllocations"
"$opcode $callLabel $arg"
}
}
@ -571,10 +563,11 @@ class Program (prog: MutableList<Instruction>,
nextInstructionLabelname = line.substring(0, line.length-1)
} else if(line.startsWith(' ')) {
val parts = line.trimStart().split(splitpattern, limit = 2)
val opcode=Opcode.valueOf(parts[0].toUpperCase())
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.LINE -> Instruction(opcode, Value(DataType.STR, null, stringvalue = args))
Opcode.JUMP, Opcode.CALL, Opcode.BMI, Opcode.BPL,
Opcode.BEQ, Opcode.BNE, Opcode.BCS, Opcode.BCC -> {
if(args!!.startsWith('$')) {
@ -592,11 +585,8 @@ class Program (prog: MutableList<Instruction>,
Instruction(opcode, Value(DataType.STR, null, withoutQuotes))
}
Opcode.SYSCALL -> {
val syscallparts = args!!.split(' ')
val call = Syscall.valueOf(syscallparts[0])
val callValue = if(syscallparts.size==2) getArgValue(syscallparts[1]) else null
val callValues = if(callValue==null) emptyList() else listOf(callValue)
Instruction(opcode, Value(DataType.BYTE, call.callNr), callValues)
val call = Syscall.valueOf(args!!)
Instruction(opcode, Value(DataType.BYTE, call.callNr))
}
else -> {
// println("INSTR $opcode at $lineNr args=$args")
@ -815,6 +805,9 @@ class StackVm(val traceOutputFile: String?) {
} catch (es: EmptyStackException) {
System.err.println("stack error! source line: $sourceLine")
throw es
} catch (x: RuntimeException) {
System.err.println("runtime error! source line: $sourceLine")
throw x
}
}
val time = System.currentTimeMillis()-start
@ -975,21 +968,31 @@ class StackVm(val traceOutputFile: String?) {
val callId = ins.arg!!.integerValue().toShort()
val syscall = Syscall.values().first { it.callNr == callId }
when (syscall) {
Syscall.WRITE_MEMCHR -> print(Petscii.decodePetscii(listOf(mem.getByte(ins.callArgs!![0].integerValue())), true))
Syscall.WRITE_MEMSTR -> print(mem.getString(ins.callArgs!![0].integerValue()))
Syscall.WRITE_NUM -> print(evalstack.pop().numericValue())
Syscall.WRITE_CHAR -> print(Petscii.decodePetscii(listOf(evalstack.pop().integerValue().toShort()), true))
Syscall.WRITE_VAR -> {
val varname = ins.callArgs!![0].stringvalue ?: throw VmExecutionException("$syscall expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
when(variable.type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> print(variable.numericValue())
DataType.STR -> print(variable.stringvalue)
else -> throw VmExecutionException("invalid datatype")
Syscall.WRITE_MEMCHR -> {
val address = evalstack.pop().integerValue()
print(Petscii.decodePetscii(listOf(mem.getByte(address)), true))
}
Syscall.WRITE_MEMSTR -> {
val address = evalstack.pop().integerValue()
print(mem.getString(address))
}
Syscall.WRITE_NUM -> {
print(evalstack.pop().numericValue())
}
Syscall.WRITE_CHAR -> {
print(Petscii.decodePetscii(listOf(evalstack.pop().integerValue().toShort()), true))
}
Syscall.WRITE_STR -> {
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)
}
}
Syscall.INPUT_VAR -> {
val varname = ins.callArgs!![0].stringvalue ?: throw VmExecutionException("$syscall expects string argument (the variable name)")
// TODO: replace with regular INPUT_STR that simply puts a str on the stack (1 argument: the max. length of the input)
val varname = ins.callLabel ?: throw VmExecutionException("$syscall expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val input = readLine() ?: throw VmExecutionException("expected user input")
val value = when(variable.type) {
@ -1020,6 +1023,17 @@ class StackVm(val traceOutputFile: String?) {
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: implement remaining functions
else -> throw VmExecutionException("unimplemented syscall $syscall")
}
}
@ -1271,7 +1285,7 @@ class StackVm(val traceOutputFile: String?) {
throw VmExecutionException("attempt to make a float from a non-word value $byte")
}
}
Opcode._LINE -> {
Opcode.LINE -> {
sourceLine = ins.arg!!.stringvalue!!
}
else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}")

View File

@ -165,7 +165,7 @@ Values will usually be part of an expression or assignment statement::
byte counter = 42 ; variable of size 8 bits, with initial value 42
Array and Matrix (2-dimensional array) types are also supported in a limited way::
Array and Matrix (2-dimensional array) types are also supported like this::
byte[4] array = [1, 2, 3, 4] ; initialize the array
byte[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...]
@ -229,14 +229,20 @@ in hexadecimal and in binary notation.
Strings
^^^^^^^
Strings are a sequence of characters enclosed in ``"`` quotes.
Strings are a sequence of characters enclosed in ``"`` quotes. The length is limited to 255 characters.
They're stored and treated much the same as a byte array,
but they have some special properties because they are considered to be *text*.
Strings in your source code files will be encoded (translated from ASCII/UTF-8) into either CBM PETSCII or C-64 screencodes.
PETSCII is the default choice. If you need screencodes (also called 'poke' codes) instead,
you have to use the ``str_s`` variants of the string type identifier.
If you assign a string literal of length 1 to a non-string variable, it is treated as a *byte* value instead
with has the PETSCII value of that single character,
with has the PETSCII value of that single character.
.. caution::
It's probably best that you don't change strings after they're created.
This is because if your program exits and is restarted (without loading it again),
it will then operate on the changed strings instead of the original ones.
The same is true for arrays and matrixes by the way.
Floating point numbers
@ -255,7 +261,7 @@ Initial values across multiple runs of the program
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The initial values of your variables will be restored automatically when the program is (re)started,
*except for string variables*. It is assumed these are left unchanged by the program.
*except for string variables, arrays and matrices*. It is assumed these are left unchanged by the program.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
@ -292,11 +298,6 @@ You can also create loops by using the ``goto`` statement, but this should usual
Conditional Execution
---------------------
.. todo::
eventually allow local variable definitions inside the sub blocks but for now,
they have to use the same variables as the block the ``if`` statement itself is in.
Conditional execution means that the flow of execution changes based on certiain conditions,
rather than having fixed gotos or subroutine calls::
@ -324,6 +325,12 @@ The eight branching instructions of the CPU each have an if-equivalent:
``if_cs``, ``if_cc``, ``if_eq``, ``if_ne``, ``if_pl``, ``if_mi``, ``if_vs`` and ``if_vc``.
So ``if_cc goto target`` will directly translate into the single CPU instruction ``BCC target``.
.. note::
For now, the symbols used or declared in the statement block(s) are shared with
the same scope the if statement itself is in.
Maybe in the future this will be a separate nested scope, but for now, that is
only possible when defining a subroutine.
Assignments
-----------