From 1a60119fde39516316125f81c5d03af7b56c8e87 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 6 Sep 2018 01:02:36 +0200 Subject: [PATCH] check assignment targets --- il65/examples/test.ill | 9 +++- il65/src/il65/ast/AST.kt | 44 +++++++++++------- il65/src/il65/ast/AstChecker.kt | 80 ++++++++++++++++++++++++--------- 3 files changed, 95 insertions(+), 38 deletions(-) diff --git a/il65/examples/test.ill b/il65/examples/test.ill index 1f9e1aef3..3199244b2 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -80,7 +80,6 @@ equalQQ = foo(33) equalQQ = main.foo(33) XY = hopla*2+hopla1 - A = "derp" * %000100 byte equalWW = 4==4 const byte equalWW2 = (4+hopla)>0 @@ -123,6 +122,14 @@ main.foo(1,2,3) + sub start () -> () { + word dinges = 0 + dinges=round(blerp1) + A=round(blerp1) + return + } + + mega: X += 1 cool: diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index 80866c40c..a7f9a0721 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -17,6 +17,8 @@ enum class DataType { STR_P, STR_S, STR_PS + + // TODO arrays (of byte, word) and matrix (of byte) should have their own datatype as well? } enum class Register { @@ -25,12 +27,7 @@ enum class Register { Y, AX, AY, - XY, - PC, - PI, - PZ, - PN, - PV + XY } enum class Statusflag { @@ -168,6 +165,12 @@ interface IAstProcessor { fun process(literalValue: LiteralValue): LiteralValue { return literalValue } + + fun process(assignment: Assignment): IStatement { + assignment.target = assignment.target.process(this) + assignment.value = assignment.value.process(this) + return assignment + } } @@ -362,7 +365,7 @@ private class GlobalNamespace(override val name: String, override var statements: MutableList, override val position: Position?) : INameScope { - private val scopedNamesUsed: MutableSet = mutableSetOf("main") // main is always used + private val scopedNamesUsed: MutableSet = mutableSetOf("main", "main.start") // main and main.start are always used override fun usedNames(): Set = scopedNamesUsed @@ -515,7 +518,7 @@ class VarDecl(val type: VarDeclType, override fun process(processor: IAstProcessor) = processor.process(this) - val isScalar = arrayspec==null + 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) } @@ -543,11 +546,7 @@ class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExp value.linkParents(this) } - override fun process(processor: IAstProcessor): IStatement { - target = target.process(processor) - value = value.process(processor) - return this - } + override fun process(processor: IAstProcessor) = processor.process(this) } data class AssignTarget(val register: Register?, val identifier: IdentifierReference?) : Node { @@ -560,6 +559,18 @@ data class AssignTarget(val register: Register?, val identifier: IdentifierRefer } fun process(processor: IAstProcessor) = this + + fun determineDatatype(namespace: INameScope, stmt: IStatement): DataType { + if(register!=null) + return when(register){ + Register.A, Register.X, Register.Y -> DataType.BYTE + Register.AX, Register.AY, Register.XY -> DataType.WORD + } + + val symbol = namespace.lookup(identifier!!.nameInSource, stmt) + if(symbol is VarDecl) return symbol.datatype + throw FatalAstException("cannot determine datatype of assignment target $this") + } } @@ -597,9 +608,8 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I right.linkParents(this) } - override fun constValue(namespace: INameScope): LiteralValue? { - throw FatalAstException("binary expression should have been optimized away into a single value, before const value was requested (this error is often caused by another) pos=$position") - } + // 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 process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name) @@ -813,7 +823,7 @@ class FunctionCallStatement(override var target: IdentifierReference, override v override fun process(processor: IAstProcessor) = processor.process(this) override fun toString(): String { - return "FunctionCall(target=$target, targetStmt=$targetStatement, pos=$position)" + return "FunctionCall(target=$target, pos=$position)" } } diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index 5d278b1cf..818b7a2d6 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -41,6 +41,13 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C entry.value.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) } } } + + // there must be a 'main' block with a 'start' subroutine for the program entry point. + val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } as? Block? + val startSub = mainBlock?.subScopes()?.get("start") + if(startSub==null) { + checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position)) + } } override fun process(jump: Jump): IStatement { @@ -107,6 +114,31 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C return subroutine } + /** + * Assignment target must be register, or a variable name + * for constant-value assignments, check the datatype as well + */ + override fun process(assignment: Assignment): IStatement { + if(assignment.target.identifier!=null) { + val targetSymbol = globalNamespace.lookup(assignment.target.identifier!!.nameInSource, assignment) + if(targetSymbol !is VarDecl) { + checkResult.add(SyntaxError("assignment LHS must be register or variable", assignment.position)) + return super.process(assignment) + } else if(targetSymbol.type==VarDeclType.CONST) { + checkResult.add(SyntaxError("cannot assign new value to a constant", assignment.position)) + return super.process(assignment) + } + } + + 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) + } + } + return super.process(assignment) + } + /** * Check the variable declarations (values within range etc) */ @@ -399,28 +431,36 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C return false } when { - vardecl.isScalar -> when (vardecl.datatype) { - DataType.FLOAT -> { - if (value.floatvalue == null) - return err("floating point value expected") - } - 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") - } - } - vardecl.isArray -> if(value.arrayvalue==null) - return err("array value expected") + vardecl.isScalar -> checkValueType(vardecl.datatype, value, position) + vardecl.isArray -> if(value.arrayvalue==null) return err("array value expected") vardecl.isMatrix -> TODO() } return true } + + 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") + } + 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") + } + } + return true + } }