allow using ubyte[] as subroutine parameter type (because it is equivalent to uword pointer var)

This commit is contained in:
Irmen de Jong 2021-12-07 23:21:49 +01:00
parent c812b5ee09
commit 98315de723
6 changed files with 71 additions and 76 deletions

View File

@ -387,13 +387,13 @@ internal class AstChecker(private val program: Program,
if(statusFlagsNoCarry.isNotEmpty()) if(statusFlagsNoCarry.isNotEmpty())
err("can only use Carry as status flag parameter") err("can only use Carry as status flag parameter")
} else { }
// Non-string Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD). // Non-string and non-ubytearray Pass-by-reference datatypes can not occur as parameters to a subroutine directly
for(p in subroutine.parameters) { // Instead, their reference (address) should be passed (as an UWORD).
if(p.type in PassByReferenceDatatypes && p.type != DataType.STR) { for(p in subroutine.parameters) {
err("Non-string pass-by-reference types cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.") if(p.type in PassByReferenceDatatypes && p.type !in listOf(DataType.STR, DataType.ARRAY_UB)) {
} errors.err("this pass-by-reference type can't be used as a parameter type. Instead, use an uword to receive the address, or access the variable from the outer scope directly.", p.position)
} }
} }
} }

View File

@ -119,23 +119,28 @@ internal class StatementReorderer(val program: Program,
} }
} }
val modifications = mutableListOf<IAstModification>()
val subs = subroutine.statements.filterIsInstance<Subroutine>() val subs = subroutine.statements.filterIsInstance<Subroutine>()
if(subs.isNotEmpty()) { if(subs.isNotEmpty()) {
// all subroutines defined within this subroutine are moved to the end // all subroutines defined within this subroutine are moved to the end
return subs.map { IAstModification.Remove(it, subroutine) } + // NOTE: this doesn't check if this has already been done!!!
subs.map { IAstModification.InsertLast(it, subroutine) } modifications +=
subs.map { IAstModification.Remove(it, subroutine) } +
subs.map { IAstModification.InsertLast(it, subroutine) }
} }
if(!subroutine.isAsmSubroutine) { // change 'str' and 'ubyte[]' parameters into 'uword' (just treat it as an address)
// change 'str' parameters into 'uword' (just treat it as an address) val stringParams = subroutine.parameters.filter { it.type==DataType.STR || it.type==DataType.ARRAY_UB }
val stringParams = subroutine.parameters.filter { it.type==DataType.STR } val parameterChanges = stringParams.map {
val parameterChanges = stringParams.map { val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position) IAstModification.ReplaceNode(it, uwordParam, subroutine)
IAstModification.ReplaceNode(it, uwordParam, subroutine) }
}
val varsChanges = mutableListOf<IAstModification>()
if(!subroutine.isAsmSubroutine) {
val stringParamsByNames = stringParams.associateBy { it.name } val stringParamsByNames = stringParams.associateBy { it.name }
val varsChanges = varsChanges +=
if(stringParamsByNames.isNotEmpty()) { if(stringParamsByNames.isNotEmpty()) {
subroutine.statements subroutine.statements
.filterIsInstance<VarDecl>() .filterIsInstance<VarDecl>()
@ -146,11 +151,9 @@ internal class StatementReorderer(val program: Program,
} }
} }
else emptyList() else emptyList()
return parameterChanges + varsChanges
} }
return noModifications return modifications + parameterChanges + varsChanges
} }
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
@ -396,7 +399,8 @@ internal class StatementReorderer(val program: Program,
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") } val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
if(argDt in ArrayDatatypes) { if(argDt in ArrayDatatypes) {
// pass the address of the array instead // pass the address of the array instead
argumentValue = AddressOf(argumentValue as IdentifierReference, argumentValue.position) if(argumentValue is IdentifierReference)
argumentValue = AddressOf(argumentValue, argumentValue.position)
} }
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position) Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position)
} }

View File

@ -151,10 +151,12 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val requiredType = pair.first.type val requiredType = pair.first.type
if (requiredType != argtype) { if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) { if (argtype isAssignableTo requiredType) {
modifications += IAstModification.ReplaceNode( // don't need a cast for pass-by-reference types that are assigned to UWORD
call.args[index], if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
TypecastExpression(pair.second, requiredType, true, pair.second.position), modifications += IAstModification.ReplaceNode(
call as Node) call.args[index],
TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) { } else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// We allow STR/ARRAY values in place of UWORD parameters. // We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine // Take their address instead, UNLESS it's a str parameter in the containing subroutine

View File

@ -33,7 +33,9 @@ internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
// there are some exceptions that are considered compatible, such as STR <> UWORD // there are some exceptions that are considered compatible, such as STR <> UWORD
if(argDt==DataType.STR && paramDt==DataType.UWORD || if(argDt==DataType.STR && paramDt==DataType.UWORD ||
argDt==DataType.UWORD && paramDt==DataType.STR) argDt==DataType.UWORD && paramDt==DataType.STR ||
argDt==DataType.UWORD && paramDt==DataType.ARRAY_UB ||
argDt==DataType.STR && paramDt==DataType.ARRAY_UB)
return true return true
return false return false

View File

@ -48,10 +48,10 @@ class TestSubroutines: FunSpec({
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"} val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"} val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true asmfunc.isAsmSubroutine shouldBe true
asmfunc.parameters.single().type shouldBe DataType.STR
asmfunc.statements.isEmpty() shouldBe true asmfunc.statements.isEmpty() shouldBe true
func.isAsmSubroutine shouldBe false func.isAsmSubroutine shouldBe false
withClue("str param for normal subroutine should be changed into UWORD") { withClue("str param for subroutines should be changed into UWORD") {
asmfunc.parameters.single().type shouldBe DataType.UWORD
func.parameters.single().type shouldBe DataType.UWORD func.parameters.single().type shouldBe DataType.UWORD
func.statements.size shouldBe 4 func.statements.size shouldBe 4
val paramvar = func.statements[0] as VarDecl val paramvar = func.statements[0] as VarDecl
@ -100,10 +100,10 @@ class TestSubroutines: FunSpec({
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"} val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"} val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true asmfunc.isAsmSubroutine shouldBe true
asmfunc.parameters.single().type shouldBe DataType.STR
asmfunc.statements.single() shouldBe instanceOf<Return>() asmfunc.statements.single() shouldBe instanceOf<Return>()
func.isAsmSubroutine shouldBe false func.isAsmSubroutine shouldBe false
withClue("asmgen should have changed str to uword type") { withClue("str param should have been changed to uword") {
asmfunc.parameters.single().type shouldBe DataType.UWORD
func.parameters.single().type shouldBe DataType.UWORD func.parameters.single().type shouldBe DataType.UWORD
} }
asmfunc.statements.last() shouldBe instanceOf<Return>() asmfunc.statements.last() shouldBe instanceOf<Return>()
@ -129,49 +129,24 @@ class TestSubroutines: FunSpec({
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing" (call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
} }
test("array param not yet allowd (but should perhaps be?)") { test("ubyte[] array parameters") {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
main {
sub start() {
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors, false).assertFailure("currently array dt in signature is invalid") // TODO should not be invalid?
errors.warnings.size shouldBe 0
errors.errors.single() shouldContain ".p8:9:17) Non-string pass-by-reference types cannot occur as a parameter type directly"
}
// TODO allow this?
xtest("arrayParameter") {
val text = """ val text = """
main { main {
sub start() { sub start() {
ubyte[] array = [1,2,3] ubyte[] array = [1,2,3]
asmfunc(array) asmfunc(array)
asmfunc([4,5,6])
asmfunc($2000) asmfunc($2000)
asmfunc(12.345) asmfunc("zzzz")
func(array) func(array)
func([4,5,6])
func($2000) func($2000)
func(12.345) func("zzzz")
} }
asmsub asmfunc(ubyte[] thing @AY) { asmsub asmfunc(ubyte[] thing @AY) {
} }
sub func(ubyte[22] thing) { sub func(ubyte[] thing) {
} }
} }
""" """
@ -181,12 +156,31 @@ class TestSubroutines: FunSpec({
val mainBlock = module.statements.single() as Block val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"} val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"} val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true withClue("ubyte array param should have been replaced by UWORD pointer") {
asmfunc.parameters.single().type shouldBe DataType.ARRAY_UB asmfunc.parameters.single().type shouldBe DataType.UWORD
asmfunc.statements.isEmpty() shouldBe true func.parameters.single().type shouldBe DataType.UWORD
func.isAsmSubroutine shouldBe false }
func.parameters.single().type shouldBe DataType.ARRAY_UB }
func.statements.isEmpty() shouldBe true
test("not ubyte[] array parameters not allowed") {
val text = """
main {
sub start() {
}
asmsub func1(uword[] thing @AY) {
}
sub func(byte[] thing) {
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "pass-by-reference type can't be used"
errors.errors[1] shouldContain "pass-by-reference type can't be used"
} }
test("uword param and normal varindexed as array work as DirectMemoryRead") { test("uword param and normal varindexed as array work as DirectMemoryRead") {

View File

@ -3,17 +3,10 @@
main { main {
sub start() { sub start() {
str name = "irmen"
txt.print(name)
}
uword array = $8000 sub func(uword[22] thing) {
array[0] = 10
array[1] = 20
array[2] = 30
txt.print_ub(@($8000))
txt.spc()
txt.print_ub(@($8001))
txt.spc()
txt.print_ub(@($8002))
txt.spc()
} }
} }