From e928997193b2ab91cec590d04a1caabf10431612 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 6 Sep 2018 22:28:37 +0200 Subject: [PATCH] arrays are now a datatype as well fixed array initializer checks --- docs/source/syntaxreference.rst | 1 + il65/examples/numbergame.ill | 11 +-- il65/src/il65/ast/AST.kt | 27 ++++-- il65/src/il65/ast/AstChecker.kt | 138 ++++++++++------------------- il65/src/il65/ast/StmtReorderer.kt | 62 ++++++------- il65/src/il65/stackvm/StackVm.kt | 4 +- 6 files changed, 108 insertions(+), 135 deletions(-) diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 43749a2c3..2bb97484f 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -211,6 +211,7 @@ Various examples:: str name = "my name is Irmen" word address = #counter byte[5] values = [11, 22, 33, 44, 55] + byte[5] values = 255 ; initialize with five 255 bytes byte[5][6] empty_matrix diff --git a/il65/examples/numbergame.ill b/il65/examples/numbergame.ill index c06b9064d..a1eb6bbb4 100644 --- a/il65/examples/numbergame.ill +++ b/il65/examples/numbergame.ill @@ -11,7 +11,7 @@ memory word freadstr_arg = $22 ; argument for FREADSTR c64.init_system() - c64.VMCSB |= 2 ; lowercase charset + c64.VMCSB |= 2 ; activate lowercase charset ; greeting c64scr.print_string("Enter your name: ") @@ -30,7 +30,7 @@ c64.FADDH() ; add 0.5.. c64.FADDH() ; and again, so +1 total AY = c64flt.GETADRAY() - secretnumber=A + secretnumber = A ;A=math.randbyte() ;A+=c64.RASTER ;A-=c64.TIME_LO @@ -51,8 +51,8 @@ ask_guess: c64.FREADSTR(A) AY = c64flt.GETADRAY() if(A==secretnumber) { - c64scr.print_string("\nThat's my number, impressive!\n") - goto goodbye + c64scr.print_string("\nThat's my number, impressive!\n") + goto goodbye } c64scr.print_string("That is too ") if(A > secretnumber) @@ -62,8 +62,9 @@ ask_guess: attempts_left-- if(attempts_left>0) goto ask_guess + ; more efficient: if_nz goto ask_guess -game_over: + ; game over. c64scr.print_string("\nToo bad! It was: ") c64scr.print_byte_decimal(secretnumber) c64.CHROUT("\n") diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index a7583d751..795202827 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -16,9 +16,10 @@ enum class DataType { STR, STR_P, STR_S, - STR_PS - - // TODO arrays (of byte, word) and matrix (of byte) should have their own datatype as well? + STR_PS, + ARRAY, + ARRAY_W, + MATRIX } enum class Register { @@ -74,7 +75,7 @@ open class ExpressionException(message: String, val position: Position?) : AstEx } } -class UndefinedSymbolException(val symbol: IdentifierReference) +class UndefinedSymbolException(symbol: IdentifierReference) : ExpressionException("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) @@ -503,13 +504,26 @@ enum class VarDeclType { } class VarDecl(val type: VarDeclType, - val datatype: DataType, + declaredDatatype: DataType, val arrayspec: ArraySpec?, val name: String, var value: IExpression?) : IStatement { override var position: Position? = null override lateinit var parent: Node + val datatype: DataType + init { + datatype = when { + arrayspec!=null -> // it's not a scalar, adjust the datatype + when(declaredDatatype) { + DataType.BYTE -> DataType.ARRAY + DataType.WORD -> DataType.ARRAY_W + DataType.MATRIX -> TODO() + else -> throw FatalAstException("invalid vardecl array datatype $declaredDatatype at $position") + } + else -> declaredDatatype + } + } override fun linkParents(parent: Node) { this.parent = parent arrayspec?.linkParents(this) @@ -518,9 +532,6 @@ class VarDecl(val type: VarDeclType, override fun process(processor: IAstProcessor) = processor.process(this) - val isScalar = arrayspec==null // TODO replace with actual array/matrix datatype itself? - val isArray = arrayspec!=null && arrayspec.y==null - val isMatrix = arrayspec?.y != null val scopedname: List by lazy { makeScopedName(name) } fun arraySizeX(namespace: INameScope) : Int? { diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index e15750d91..a68d4c01c 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -25,7 +25,7 @@ fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationO * todo check subroutine return values against the call's result assignments */ -class AstChecker(private val globalNamespace: INameScope, val compilerOptions: CompilationOptions) : IAstProcessor { +class AstChecker(private val globalNamespace: INameScope, private val compilerOptions: CompilationOptions) : IAstProcessor { private val checkResult: MutableList = mutableListOf() fun result(): List { @@ -132,9 +132,7 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C if(assignment.value is LiteralValue) { val targetDatatype = assignment.target.determineDatatype(globalNamespace, assignment) - if(checkValueType(targetDatatype, assignment.value as LiteralValue, assignment.position)) { - checkValueRange(targetDatatype, assignment.value as LiteralValue, assignment.position) - } + checkValueTypeAndRange(targetDatatype, null, assignment.value as LiteralValue, assignment.position) } return super.process(assignment) } @@ -160,20 +158,17 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C when(decl.type) { VarDeclType.VAR, VarDeclType.CONST -> { - when { - decl.value == null -> + when(decl.value) { + null -> { err("var/const declaration needs a compile-time constant initializer value") - decl.value !is LiteralValue -> - err("var/const declaration needs a compile-time constant initializer value, found: ${decl.value!!::class.simpleName}") - decl.isScalar -> { - if(checkValueType(decl, decl.value as LiteralValue, decl.position)) { - checkValueRange(decl.datatype, decl.value as LiteralValue, decl.position) - } + return super.process(decl) } - decl.isArray || decl.isMatrix -> { - checkConstInitializerValueArray(decl) + !is LiteralValue -> { + err("var/const declaration needs a compile-time constant initializer value, found: ${decl.value!!::class.simpleName}") + return super.process(decl) } } + checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as LiteralValue, decl.position) } VarDeclType.MEMORY -> { if(decl.value !is LiteralValue) { @@ -277,45 +272,6 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C return super.process(literalValue) } - private fun checkConstInitializerValueArray(decl: VarDecl) { - val value = decl.value as LiteralValue - // init value should either be a scalar or an array with the same dimensions as the arrayspec. - - if(decl.isArray) { - if(value.arrayvalue==null) { - checkValueRange(decl.datatype, value.constValue(globalNamespace)!!, value.position) - } - else { - val expected = decl.arraySizeX(globalNamespace) - if (value.arrayvalue.size != expected) - checkResult.add(SyntaxError("initializer array size mismatch (expecting $expected, got ${value.arrayvalue.size})", decl.position)) - else { - for(v in value.arrayvalue) { - if(!checkValueRange(decl.datatype, v.constValue(globalNamespace)!!, v.position)) - break - } - } - } - } - - if(decl.isMatrix) { - if(value.arrayvalue==null) { - checkValueRange(decl.datatype, value.constValue(globalNamespace)!!, value.position) - } - else { - val expected = decl.arraySizeX(globalNamespace)!! * decl.arraySizeY(globalNamespace)!! - if (value.arrayvalue.size != expected) - checkResult.add(SyntaxError("initializer array size mismatch (expecting $expected, got ${value.arrayvalue.size})", decl.position)) - else { - for(v in value.arrayvalue) { - if(!checkValueRange(decl.datatype, v.constValue(globalNamespace)!!, v.position)) - break - } - } - } - } - } - override fun process(range: RangeExpr): IExpression { fun err(msg: String) { checkResult.add(SyntaxError(msg, range.position)) @@ -391,7 +347,7 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C } } - private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) : Boolean { + private fun checkValueTypeAndRange(datatype: DataType, arrayspec: ArraySpec?, value: LiteralValue, position: Position?) : Boolean { fun err(msg: String) : Boolean { checkResult.add(SyntaxError(msg, position)) return false @@ -421,45 +377,49 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C if (str.isEmpty() || str.length > 65535) return err("string length must be 1..65535") } - } - return true - } + DataType.ARRAY -> { + // value may be either a single byte, or a byte array + if(value.isArray) { + for (av in value.arrayvalue!!) { + val number = (av as LiteralValue).intvalue + ?: return err("array must be all bytes") - private fun checkValueType(vardecl: VarDecl, value: LiteralValue, position: Position?) : Boolean { - fun err(msg: String) : Boolean { - checkResult.add(SyntaxError(msg, position)) - return false - } - when { - vardecl.isScalar -> checkValueType(vardecl.datatype, value, position) - vardecl.isArray -> if(value.arrayvalue==null) return err("array value expected") - vardecl.isMatrix -> TODO() - } - return true - } + val expectedSize = arrayspec?.x?.constValue(globalNamespace)?.intvalue + if (value.arrayvalue.size != expectedSize) + return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})") - private fun checkValueType(targetType: DataType, value: LiteralValue, position: Position?) : Boolean { - fun err(msg: String) : Boolean { - checkResult.add(SyntaxError(msg, position)) - return false - } - when(targetType) { - DataType.FLOAT -> { - if (value.floatvalue == null) - return err("floating point value expected") + if (number < 0 || number > 255) + return err("value '$number' in byte array is out of range for unsigned byte") + } + } else { + val number = value.intvalue + ?: return err("byte integer value expected") + if (number < 0 || number > 255) + return err("value '$number' out of range for unsigned byte") + } } - DataType.BYTE -> { - if (value.intvalue == null) - return err("byte integer value expected") - } - DataType.WORD -> { - if (value.intvalue == null) - return err("word integer value expected") - } - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { - if (value.strvalue == null) - return err("string value expected") + DataType.ARRAY_W -> { + // value may be either a single word, or a word array + if(value.isArray) { + for (av in value.arrayvalue!!) { + val number = (av as LiteralValue).intvalue + ?: return err("array must be all words") + + val expectedSize = arrayspec?.x?.constValue(globalNamespace)?.intvalue + if (value.arrayvalue.size != expectedSize) + return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})") + + if (number < 0 || number > 65535) + return err("value '$number' in word array is out of range for unsigned word") + } + } else { + val number = value.intvalue + ?: return err("word integer value expected") + if (number < 0 || number > 65535) + return err("value '$number' out of range for unsigned word") + } } + DataType.MATRIX -> TODO() } return true } diff --git a/il65/src/il65/ast/StmtReorderer.kt b/il65/src/il65/ast/StmtReorderer.kt index 63035a714..8def7d2da 100644 --- a/il65/src/il65/ast/StmtReorderer.kt +++ b/il65/src/il65/ast/StmtReorderer.kt @@ -9,43 +9,43 @@ class StatementReorderer: IAstProcessor { // -- the remaining statements then follow in their original order. // - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives. - val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%address", "%option") + private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%address", "%option") - override fun process(node: Module) { - val mainBlock = node.statements.single { it is Block && it.name=="main" } - node.statements.remove(mainBlock) - node.statements.add(0, mainBlock) - val varDecls = node.statements.filter { it is VarDecl } - node.statements.removeAll(varDecls) - node.statements.addAll(0, varDecls) - val directives = node.statements.filter {it is Directive && directivesToMove.contains(it.directive)} - node.statements.removeAll(directives) - node.statements.addAll(0, directives) - super.process(node) + override fun process(module: Module) { + val mainBlock = module.statements.single { it is Block && it.name=="main" } + module.statements.remove(mainBlock) + module.statements.add(0, mainBlock) + val varDecls = module.statements.filter { it is VarDecl } + module.statements.removeAll(varDecls) + module.statements.addAll(0, varDecls) + val directives = module.statements.filter {it is Directive && directivesToMove.contains(it.directive)} + module.statements.removeAll(directives) + module.statements.addAll(0, directives) + super.process(module) } - override fun process(node: Block): IStatement { - val startSub = node.statements.singleOrNull {it is Subroutine && it.name=="start"} + override fun process(block: Block): IStatement { + val startSub = block.statements.singleOrNull {it is Subroutine && it.name=="start"} if(startSub!=null) { - node.statements.remove(startSub) - node.statements.add(0, startSub) + block.statements.remove(startSub) + block.statements.add(0, startSub) } - val varDecls = node.statements.filter { it is VarDecl } - node.statements.removeAll(varDecls) - node.statements.addAll(0, varDecls) - val directives = node.statements.filter {it is Directive && directivesToMove.contains(it.directive)} - node.statements.removeAll(directives) - node.statements.addAll(0, directives) - return super.process(node) + val varDecls = block.statements.filter { it is VarDecl } + block.statements.removeAll(varDecls) + block.statements.addAll(0, varDecls) + val directives = block.statements.filter {it is Directive && directivesToMove.contains(it.directive)} + block.statements.removeAll(directives) + block.statements.addAll(0, directives) + return super.process(block) } - override fun process(node: Subroutine): IStatement { - val varDecls = node.statements.filter { it is VarDecl } - node.statements.removeAll(varDecls) - node.statements.addAll(0, varDecls) - val directives = node.statements.filter {it is Directive && directivesToMove.contains(it.directive)} - node.statements.removeAll(directives) - node.statements.addAll(0, directives) - return super.process(node) + override fun process(subroutine: Subroutine): IStatement { + val varDecls = subroutine.statements.filter { it is VarDecl } + subroutine.statements.removeAll(varDecls) + subroutine.statements.addAll(0, varDecls) + val directives = subroutine.statements.filter {it is Directive && directivesToMove.contains(it.directive)} + subroutine.statements.removeAll(directives) + subroutine.statements.addAll(0, directives) + return super.process(subroutine) } } diff --git a/il65/src/il65/stackvm/StackVm.kt b/il65/src/il65/stackvm/StackVm.kt index 27d019255..4275daa4b 100644 --- a/il65/src/il65/stackvm/StackVm.kt +++ b/il65/src/il65/stackvm/StackVm.kt @@ -488,7 +488,7 @@ class Program (prog: MutableList, Opcode.SYSCALL -> { val syscallparts = args!!.split(' ') val call = Syscall.valueOf(syscallparts[0]) - val callValue = if(parts.size==2) getArgValue(syscallparts[1]) else null + 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) } @@ -690,7 +690,7 @@ class StackVm(val traceOutputFile: String?) { } } - fun dispatch(ins: Instruction) : Instruction { + private fun dispatch(ins: Instruction) : Instruction { traceOutput?.println("\n$ins") when (ins.opcode) { Opcode.NOP -> {}