mirror of
https://github.com/irmen/prog8.git
synced 2024-09-10 09:54:32 +00:00
check assignment targets
This commit is contained in:
parent
f23808eaae
commit
1a60119fde
@ -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:
|
||||
|
@ -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<IStatement>,
|
||||
override val position: Position?) : INameScope {
|
||||
|
||||
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main") // main is always used
|
||||
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main", "main.start") // main and main.start are always used
|
||||
|
||||
override fun usedNames(): Set<String> = 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<String> 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)"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user