merge now also allows monkeypatching if signature is 100% identical

This commit is contained in:
Irmen de Jong 2024-10-30 00:37:45 +01:00
parent 6f727aff88
commit eeeb8d81f4
7 changed files with 92 additions and 26 deletions

View File

@ -105,7 +105,7 @@ internal fun Program.verifyFunctionArgTypes(errors: IErrorReporter, options: Com
}
internal fun Program.preprocessAst(errors: IErrorReporter, options: CompilationOptions) {
val mergeBlocks = BlockMerger()
val mergeBlocks = BlockMerger(errors)
mergeBlocks.visit(this)
if(errors.noErrors()) {
val transforms = AstPreprocessor(this, errors, options)

View File

@ -3,8 +3,10 @@ package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.ast.statements.Block
import prog8.ast.statements.Directive
import prog8.ast.statements.Subroutine
import prog8.code.core.IErrorReporter
class BlockMerger {
class BlockMerger(val errors: IErrorReporter) {
// All blocks having a 'merge' option,
// will be joined into a block with the same name, coming from a library.
// (or a normal block if no library block with that name was found)
@ -29,9 +31,21 @@ class BlockMerger {
}
private fun merge(block: Block, target: Block) {
val named = target.statements.filterIsInstance<Subroutine>().associateBy { it.name }
for(stmt in block.statements.filter { it !is Directive }) {
if(stmt is Subroutine && stmt.name in named) {
val existing = named.getValue(stmt.name)
if(stmt.returntypes==existing.returntypes) {
if(stmt.parameters == existing.parameters) {
// overwrite the target subroutine, everything is identical!
existing.definingScope.remove(existing)
errors.info("monkeypatched subroutine '${existing.scopedName.joinToString(".")}' in ${existing.definingModule.name}", stmt.position)
}
}
}
target.statements.add(stmt)
stmt.linkParents(target)
stmt.parent = target
}
block.statements.clear()
block.definingScope.remove(block)

View File

@ -14,10 +14,8 @@ import prog8.code.core.Position
import prog8.code.core.SourceCode
import prog8.code.core.internedStringsModuleName
import prog8.code.target.C64Target
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.compileText
import prog8.code.target.VMTarget
import prog8tests.helpers.*
class TestProgram: FunSpec({
@ -147,5 +145,54 @@ blah {
}"""
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
test("merge override existing subroutine") {
val src="""
%import textio
main {
sub start() {
txt.print("sdfdsf")
}
}
txt {
%option merge
sub print(str text) {
cx16.r0++
; just some dummy implementation to replace existing print
}
}"""
val result = compileText(VMTarget(), optimize=false, src, writeAssembly=false)
result shouldNotBe null
}
test("merge doesn't override existing subroutine if signature differs") {
val src="""
%import textio
main {
sub start() {
txt.print("sdfdsf")
}
}
txt {
%option merge
sub print(str anotherparamname) {
cx16.r0++
; just some dummy implementation to replace existing print
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), optimize=false, src, writeAssembly=false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "name conflict"
}
}
})

View File

@ -6,6 +6,7 @@ import prog8.ast.expressions.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import java.util.*
interface INamedStatement {
@ -861,6 +862,14 @@ open class SubroutineParameter(val name: String,
override fun copy() = SubroutineParameter(name, type, zp, position)
override fun toString() = "Param($type:$name)"
override fun referencesIdentifier(nameInSource: List<String>): Boolean = nameInSource.size==1 && name==nameInSource[0]
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is SubroutineParameter) return false
return name == other.name && type == other.type && zp == other.zp
}
override fun hashCode(): Int = Objects.hash(name, type, zp)
}
class IfElse(var condition: Expression,

View File

@ -199,7 +199,9 @@ Directives
program was launched.
- ``force_output`` (in a block) will force the block to be outputted in the final program.
Can be useful to make sure some data is generated that would otherwise be discarded because the compiler thinks it's not referenced (such as sprite data)
- ``merge`` (in a block) will merge this block's contents into an already existing block with the same name. Can be used to add subroutines to an existing library block, for instance.
- ``merge`` (in a block) will merge this block's contents into an already existing block with the same name.
Can be used to add or override subroutines to an existing library block, for instance.
Overriding (monkeypatching) happens only if the signature of the subroutine exactly matches the original subroutine, including the exact names and types of the parameters.
- ``splitarrays`` (block or module) makes all word-arrays in this scope lsb/msb split arrays (as if they all have the @split tag). See Arrays.
- ``no_symbol_prefixing`` (block or module) makes the compiler *not* use symbol-prefixing when translating prog8 code into assembly.
Only use this if you know what you're doing because it could result in invalid assembly code being generated.

View File

@ -1,6 +1,8 @@
TODO
====
Allow %merge to overwrite existing subs if the signature is identical?
Improve register load order in subroutine call args assignments:
in certain situations, the "wrong" order of evaluation of function call arguments is done which results
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)

View File

@ -5,7 +5,7 @@
main {
sub start() {
blah.test()
txt.print("sdfdsf")
}
}
@ -13,22 +13,14 @@ txt {
; merges this block into the txt block coming from the textio library
%option merge
sub schrijf(str arg) {
print(arg)
}
}
blah {
; merges this block into the other 'blah' one
%option merge
sub test() {
printit("test merge")
}
}
blah {
sub printit(str arg) {
txt.schrijf(arg)
sub print(str text) {
repeat 4 chrout('@')
repeat {
cx16.r0L = @(text)
if_z break
chrout(cx16.r0L)
text++
}
repeat 4 chrout('@')
}
}