mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
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:
parent
c05cd72d23
commit
3426593a06
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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}")
|
||||
|
@ -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
|
||||
-----------
|
||||
|
Loading…
x
Reference in New Issue
Block a user