diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 4f3751838..59cbdecdb 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -42,6 +42,7 @@ private fun compileMain(args: Array) { val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name) val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator) + val stringDedup by cli.option(ArgType.Boolean, fullName="strdedup", description = "deduplicate identical string literals").default(false) try { cli.parse(args) @@ -69,7 +70,7 @@ private fun compileMain(args: Array) { val results = mutableListOf() for(filepathRaw in moduleFiles) { val filepath = pathFrom(filepathRaw).normalize() - val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) + val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, stringDedup, compilationTarget, libdirs, outputPath) results.add(compilationResult) } @@ -106,7 +107,7 @@ private fun compileMain(args: Array) { val filepath = pathFrom(filepathRaw).normalize() val compilationResult: CompilationResult try { - compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) + compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, stringDedup, compilationTarget, libdirs, outputPath) if(!compilationResult.success) exitProcess(1) } catch (x: ParsingFailedError) { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index f03010f59..997d544c8 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -50,6 +50,7 @@ data class CompilationOptions(val output: OutputType, val zpReserved: List, val floats: Boolean, val noSysInit: Boolean, + val stringDedup: Boolean, val compTarget: ICompilationTarget) { var slowCodegenWarnings = false var optimize = false @@ -69,6 +70,7 @@ fun compileProgram(filepath: Path, optimize: Boolean, writeAssembly: Boolean, slowCodegenWarnings: Boolean, + stringDedup: Boolean, compilationTarget: String, libdirs: List, outputDir: Path): CompilationResult { @@ -90,7 +92,7 @@ fun compileProgram(filepath: Path, try { val totalTime = measureTimeMillis { // import main module and everything it needs - val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, libdirs) + val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, stringDedup, libdirs) compilationOptions.slowCodegenWarnings = slowCodegenWarnings compilationOptions.optimize = optimize programAst = ast @@ -166,7 +168,11 @@ private class BuiltinFunctionsFacade(functions: Map): IBuilt builtinFunctionReturnType(name, args, program) } -private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget, libdirs: List): Triple> { +private fun parseImports(filepath: Path, + errors: IErrorReporter, + compTarget: ICompilationTarget, + stringDedup: Boolean, + libdirs: List): Triple> { val compilationTargetName = compTarget.name println("Compiler target: $compilationTargetName. Parsing...") val bf = BuiltinFunctionsFacade(BuiltinFunctions) @@ -178,7 +184,7 @@ private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICo errors.report() val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source } - val compilerOptions = determineCompilationOptions(programAst, compTarget) + val compilerOptions = determineCompilationOptions(programAst, stringDedup, compTarget) if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG) throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.") @@ -193,7 +199,7 @@ private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICo return Triple(programAst, compilerOptions, importedFiles) } -private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions { +private fun determineCompilationOptions(program: Program, stringDedup: Boolean, compTarget: ICompilationTarget): CompilationOptions { val mainModule = program.mainModule val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)?.args?.single()?.name?.uppercase() @@ -240,7 +246,7 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati return CompilationOptions( if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType), if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType), - zpType, zpReserved, floatsEnabled, noSysInit, + zpType, zpReserved, floatsEnabled, noSysInit, stringDedup, compTarget ) } @@ -248,7 +254,7 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { // perform initial syntax checks and processings println("Processing for target ${compilerOptions.compTarget.name}...") - programAst.checkIdentifiers(errors, compilerOptions.compTarget) + programAst.checkIdentifiers(errors, compilerOptions) errors.report() programAst.constantFold(errors, compilerOptions.compTarget) errors.report() @@ -260,7 +266,7 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti errors.report() programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) errors.report() - programAst.checkIdentifiers(errors, compilerOptions.compTarget) + programAst.checkIdentifiers(errors, compilerOptions) errors.report() } diff --git a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt index 624e409e1..fc4469098 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt @@ -44,16 +44,16 @@ internal fun Program.verifyFunctionArgTypes() { fixer.visit(this) } -internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) { +internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) { - val checker2 = AstIdentifiersChecker(this, errors, compTarget) + val checker2 = AstIdentifiersChecker(this, errors, options.compTarget) checker2.visit(this) if(errors.noErrors()) { val transforms = AstVariousTransforms(this) transforms.visit(this) transforms.applyModifications() - val lit2decl = LiteralsToAutoVars(this) + val lit2decl = LiteralsToAutoVars(this, options) lit2decl.visit(this) lit2decl.applyModifications() } diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index 705939bcf..9d8788719 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -10,14 +10,15 @@ import prog8.ast.statements.VarDecl import prog8.ast.statements.WhenChoice import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification +import prog8.compiler.CompilationOptions -internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { +internal class LiteralsToAutoVars(private val program: Program, private val options: CompilationOptions) : AstWalker() { override fun after(string: StringLiteralValue, parent: Node): Iterable { if(string.parent !is VarDecl && string.parent !is WhenChoice) { // replace the literal string by a identifier reference to the interned string - val scopedName = program.internString(string) + val scopedName = program.internString(string, options.stringDedup) val identifier = IdentifierReference(scopedName, string.position) return listOf(IAstModification.ReplaceNode(string, identifier, parent)) } diff --git a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt index 9229f96df..2427c085c 100644 --- a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt @@ -381,18 +381,14 @@ internal class AsmGen(private val program: Program, out("\n; non-zeropage variables") val vars = statements.filterIsInstance().filter { it.type==VarDeclType.VAR } - // special treatment for string types: merge strings that are identical val encodedstringVars = vars .filter {it.datatype == DataType.STR } .map { val str = it.value as StringLiteralValue it to compTarget.encodeString(str.value, str.altEncoding).plus(0) } - .groupBy({it.second}, {it.first}) - for((encoded, variables) in encodedstringVars) { - variables.dropLast(1).forEach { out(it.name) } - val lastvar = variables.last() - outputStringvar(lastvar, encoded) + for((decl, variables) in encodedstringVars) { + outputStringvar(decl, variables) } // non-string variables @@ -402,11 +398,11 @@ internal class AsmGen(private val program: Program, } } - private fun outputStringvar(lastvar: VarDecl, encoded: List) { - val sv = lastvar.value as StringLiteralValue + private fun outputStringvar(strdecl: VarDecl, bytes: List) { + val sv = strdecl.value as StringLiteralValue val altEncoding = if(sv.altEncoding) "@" else "" - out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "")}\"") - val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') } + out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "")}\"") + val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') } for (chunk in outputBytes.chunked(16)) out(" .byte " + chunk.joinToString()) } diff --git a/compiler/test/UnitTests.kt b/compiler/test/UnitTests.kt index 735842dbd..685839910 100644 --- a/compiler/test/UnitTests.kt +++ b/compiler/test/UnitTests.kt @@ -134,7 +134,7 @@ class TestC64Zeropage { @Test fun testNames() { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target)) + val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, true, C64Target)) zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors) @@ -147,37 +147,37 @@ class TestC64Zeropage { @Test fun testZpFloatEnable() { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) + val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target)) assertFailsWith { zp.allocate("", DataType.FLOAT, null, errors) } - val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target)) + val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, true, C64Target)) assertFailsWith { zp2.allocate("", DataType.FLOAT, null, errors) } - val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target)) + val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, true, C64Target)) zp3.allocate("", DataType.FLOAT, null, errors) } @Test fun testZpModesWithFloats() { - C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) - C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target)) - C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target)) - C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target)) - C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) - C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target)) + C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target)) + C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, true, C64Target)) + C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, true, C64Target)) + C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, true, C64Target)) + C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, C64Target)) + C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, true, C64Target)) assertFailsWith { - C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target)) + C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, true, C64Target)) } assertFailsWith { - C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target)) + C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, true, C64Target)) } } @Test fun testZpDontuse() { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target)) + val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, true, C64Target)) println(zp.free) assertEquals(0, zp.available()) assertFailsWith { @@ -187,19 +187,19 @@ class TestC64Zeropage { @Test fun testFreeSpaces() { - val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) + val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, C64Target)) assertEquals(18, zp1.available()) - val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target)) + val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, true, C64Target)) assertEquals(85, zp2.available()) - val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target)) + val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, true, C64Target)) assertEquals(125, zp3.available()) - val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) + val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target)) assertEquals(238, zp4.available()) } @Test fun testReservedSpace() { - val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) + val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target)) assertEquals(238, zp1.available()) assertTrue(50 in zp1.free) assertTrue(100 in zp1.free) @@ -208,7 +208,7 @@ class TestC64Zeropage { assertTrue(200 in zp1.free) assertTrue(255 in zp1.free) assertTrue(199 in zp1.free) - val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target)) + val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, true, C64Target)) assertEquals(139, zp2.available()) assertFalse(50 in zp2.free) assertFalse(100 in zp2.free) @@ -221,7 +221,7 @@ class TestC64Zeropage { @Test fun testBasicsafeAllocation() { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) + val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, C64Target)) assertEquals(18, zp.available()) assertFailsWith { @@ -244,7 +244,7 @@ class TestC64Zeropage { @Test fun testFullAllocation() { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) + val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, C64Target)) assertEquals(238, zp.available()) val loc = zp.allocate("", DataType.UWORD, null, errors) assertTrue(loc > 3) @@ -274,7 +274,7 @@ class TestC64Zeropage { @Test fun testEfficientAllocation() { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) + val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, C64Target)) assertEquals(18, zp.available()) assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors)) assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors)) @@ -293,7 +293,7 @@ class TestC64Zeropage { @Test fun testReservedLocations() { - val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target)) + val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, true, C64Target)) assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word") } } @@ -303,23 +303,23 @@ class TestC64Zeropage { class TestCx16Zeropage { @Test fun testReservedLocations() { - val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target)) + val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, true, Cx16Target)) assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word") } @Test fun testFreeSpaces() { - val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target)) + val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, true, Cx16Target)) assertEquals(88, zp1.available()) - val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target)) + val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, true, Cx16Target)) assertEquals(175, zp3.available()) - val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) + val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, Cx16Target)) assertEquals(216, zp4.available()) } @Test fun testReservedSpace() { - val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) + val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, true, Cx16Target)) assertEquals(216, zp1.available()) assertTrue(0x22 in zp1.free) assertTrue(0x80 in zp1.free) diff --git a/compilerAst/src/prog8/ast/AstToSourceCode.kt b/compilerAst/src/prog8/ast/AstToSourceCode.kt index 56dfa52d9..ca4dae435 100644 --- a/compilerAst/src/prog8/ast/AstToSourceCode.kt +++ b/compilerAst/src/prog8/ast/AstToSourceCode.kt @@ -7,7 +7,6 @@ import prog8.ast.base.VarDeclType import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.ast.walk.IAstVisitor -import java.util.* class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor { diff --git a/compilerAst/src/prog8/ast/AstToplevel.kt b/compilerAst/src/prog8/ast/AstToplevel.kt index 1fa17a95b..2ac43b654 100644 --- a/compilerAst/src/prog8/ast/AstToplevel.kt +++ b/compilerAst/src/prog8/ast/AstToplevel.kt @@ -256,7 +256,7 @@ class Program(val name: String, get() = mainModule.loadAddress var actualLoadAddress: Int = 0 - private val internedStrings = mutableMapOf, List>() + private val internedStringsUnique = mutableMapOf, List>() val internedStringsModuleName = "prog8_interned_strings" init { @@ -280,22 +280,40 @@ class Program(val name: String, return mainBlocks[0].subScope("start") as Subroutine } - fun internString(string: StringLiteralValue): List { - val key = Pair(string.value, string.altEncoding) - val existing = internedStrings[key] - if(existing!=null) - return existing + fun internString(string: StringLiteralValue, dedup: Boolean): List { - val decl = VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, "string_${internedStrings.size}", string, - isArray = false, autogeneratedDontRemove = true, position = string.position) - val internedStringsBlock = modules.first { it.name==internedStringsModuleName }.statements.first { it is Block && it.name == internedStringsModuleName} - (internedStringsBlock as Block).statements.add(decl) - decl.linkParents(internedStringsBlock) - val scopedName = listOf(internedStringsModuleName, decl.name) - internedStrings[key] = scopedName - return scopedName + fun getScopedName(string: StringLiteralValue): List { + val internedStringsBlock = modules + .first { it.name == internedStringsModuleName }.statements + .first { it is Block && it.name == internedStringsModuleName } as Block + val varName = "string_${internedStringsBlock.statements.size}" + val decl = VarDecl( + VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string, + isArray = false, autogeneratedDontRemove = true, position = string.position + ) + internedStringsBlock.statements.add(decl) + decl.linkParents(internedStringsBlock) + val scopedName = listOf(internedStringsModuleName, decl.name) + return scopedName + } + + val key = Pair(string.value, string.altEncoding) + if(dedup) { + val existing = internedStringsUnique[key] + if (existing != null) + return existing + + val scopedName = getScopedName(string) + internedStringsUnique[key] = scopedName + return scopedName + } else { + // don't deduplicate string literals + return getScopedName(string) + } } + + fun allBlocks(): List = modules.flatMap { it.statements.filterIsInstance() } override val position: Position = Position.DUMMY diff --git a/compilerAst/src/prog8/ast/antlr/Antr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antr2Kotlin.kt index 30c64e6a3..4d2d5d70e 100644 --- a/compilerAst/src/prog8/ast/antlr/Antr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antr2Kotlin.kt @@ -13,7 +13,6 @@ import prog8.parser.prog8Parser import java.io.CharConversionException import java.io.File import java.nio.file.Path -import java.util.* /***************** Antlr Extension methods to create AST ****************/ diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 75f6114c3..4e6558a01 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -6,7 +6,7 @@ import prog8.ast.base.* import prog8.ast.statements.* import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstVisitor -import java.util.Objects +import java.util.* import kotlin.math.abs diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 922e4d584..b46914cfd 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -5,6 +5,7 @@ TODO - possible idea: option to mark vardecls 'shared' to indicate they should not be optimized away because they're shared with assembly code? However: who even needs variables declared in prog8 code that are only used by assembly??? - github issue: make strings no longer immutable? Deduplication selectable via command line switch? + IMPROVE DOCUMENTATION ABOUT STRINGS AND DEDUP. - test all examples before release of the new version diff --git a/examples/test.p8 b/examples/test.p8 index 220bb3873..5380dba20 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,27 +1,24 @@ - %import textio ; txt.* - main { - sub start() { - ; ATTENTION: uncomment only one problematic line at a time! - - ; Normal string literals, i.e. PETSCII encoding - ; --------------------------------------------- - txt.print("\"") ; fine - txt.print("\n") ; fine - txt.print("\r") ; fine - txt.print("\\") ; yields CharConversionException - txt.print("xyz\\") ; yields prog8.compiler.AssemblyError - - ; @-strings, i.e. translated into - ; the alternate character encoding (Screencodes/pokes) - ; ---------------------------------------------------- - txt.print(@"\"") ; fine - txt.print(@"\n") ; yields CharConversionException - txt.print(@"xyz\n") ; yields prog8.compiler.AssemblyError - txt.print(@"\r") ; yields CharConversionException - txt.print(@"xyz\r") ; yields prog8.compiler.AssemblyError - txt.print(@"\\") ; yields CharConversionException - txt.print(@"\\") ; yields prog8.compiler.AssemblyError - - ; there may be more... - } - } +%import textio ; txt.* +%zeropage basicsafe +main { + sub start() { + txt.print("a") + txt.print("a") + txt.print("bb") + txt.print("bb") + txt.print("\n") + txt.print("\n\n") +; txt.print("hello\n") +; txt.print("hello\n") +; txt.print("hello\n") +; txt.print("hello\n") +; txt.print("hello22\n") +; txt.print("hello22\n") +; txt.print("hello22\n") +; txt.print("hello22\n") +; txt.print("hello666\n") +; txt.print("hello666\n") +; txt.print("hello666\n") +; txt.print("hello666\n") + } +} diff --git a/httpCompilerService/src/prog8/http/TestHttp.kt b/httpCompilerService/src/prog8/http/TestHttp.kt index 8820fde7f..6736978cf 100644 --- a/httpCompilerService/src/prog8/http/TestHttp.kt +++ b/httpCompilerService/src/prog8/http/TestHttp.kt @@ -29,7 +29,7 @@ class RequestParser : Take { val form = RqFormBase(request) val names = form.names() val a = form.param("a").single() - val compilationResult = compileProgram(Path.of(a), true, true, true, "c64", emptyList(), Path.of(".")) + val compilationResult = compileProgram(Path.of(a), true, true, true, false, "c64", emptyList(), Path.of(".")) return RsJson(Jsonding()) } }