mirror of
https://github.com/irmen/prog8.git
synced 2025-01-25 12:30:09 +00:00
added compiler option to choose string literal deduplication yes/no -- default changed to NO
This commit is contained in:
parent
74f918d911
commit
25d80f4df1
@ -42,6 +42,7 @@ private fun compileMain(args: Array<String>) {
|
|||||||
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 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 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 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 {
|
try {
|
||||||
cli.parse(args)
|
cli.parse(args)
|
||||||
@ -69,7 +70,7 @@ private fun compileMain(args: Array<String>) {
|
|||||||
val results = mutableListOf<CompilationResult>()
|
val results = mutableListOf<CompilationResult>()
|
||||||
for(filepathRaw in moduleFiles) {
|
for(filepathRaw in moduleFiles) {
|
||||||
val filepath = pathFrom(filepathRaw).normalize()
|
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)
|
results.add(compilationResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ private fun compileMain(args: Array<String>) {
|
|||||||
val filepath = pathFrom(filepathRaw).normalize()
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
val compilationResult: CompilationResult
|
val compilationResult: CompilationResult
|
||||||
try {
|
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)
|
if(!compilationResult.success)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
} catch (x: ParsingFailedError) {
|
} catch (x: ParsingFailedError) {
|
||||||
|
@ -50,6 +50,7 @@ data class CompilationOptions(val output: OutputType,
|
|||||||
val zpReserved: List<IntRange>,
|
val zpReserved: List<IntRange>,
|
||||||
val floats: Boolean,
|
val floats: Boolean,
|
||||||
val noSysInit: Boolean,
|
val noSysInit: Boolean,
|
||||||
|
val stringDedup: Boolean,
|
||||||
val compTarget: ICompilationTarget) {
|
val compTarget: ICompilationTarget) {
|
||||||
var slowCodegenWarnings = false
|
var slowCodegenWarnings = false
|
||||||
var optimize = false
|
var optimize = false
|
||||||
@ -69,6 +70,7 @@ fun compileProgram(filepath: Path,
|
|||||||
optimize: Boolean,
|
optimize: Boolean,
|
||||||
writeAssembly: Boolean,
|
writeAssembly: Boolean,
|
||||||
slowCodegenWarnings: Boolean,
|
slowCodegenWarnings: Boolean,
|
||||||
|
stringDedup: Boolean,
|
||||||
compilationTarget: String,
|
compilationTarget: String,
|
||||||
libdirs: List<String>,
|
libdirs: List<String>,
|
||||||
outputDir: Path): CompilationResult {
|
outputDir: Path): CompilationResult {
|
||||||
@ -90,7 +92,7 @@ fun compileProgram(filepath: Path,
|
|||||||
try {
|
try {
|
||||||
val totalTime = measureTimeMillis {
|
val totalTime = measureTimeMillis {
|
||||||
// import main module and everything it needs
|
// 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.slowCodegenWarnings = slowCodegenWarnings
|
||||||
compilationOptions.optimize = optimize
|
compilationOptions.optimize = optimize
|
||||||
programAst = ast
|
programAst = ast
|
||||||
@ -166,7 +168,11 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
|||||||
builtinFunctionReturnType(name, args, program)
|
builtinFunctionReturnType(name, args, program)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget, libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
private fun parseImports(filepath: Path,
|
||||||
|
errors: IErrorReporter,
|
||||||
|
compTarget: ICompilationTarget,
|
||||||
|
stringDedup: Boolean,
|
||||||
|
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||||
val compilationTargetName = compTarget.name
|
val compilationTargetName = compTarget.name
|
||||||
println("Compiler target: $compilationTargetName. Parsing...")
|
println("Compiler target: $compilationTargetName. Parsing...")
|
||||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||||
@ -178,7 +184,7 @@ private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICo
|
|||||||
errors.report()
|
errors.report()
|
||||||
|
|
||||||
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
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)
|
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type 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)
|
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 mainModule = program.mainModule
|
||||||
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
||||||
as? Directive)?.args?.single()?.name?.uppercase()
|
as? Directive)?.args?.single()?.name?.uppercase()
|
||||||
@ -240,7 +246,7 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati
|
|||||||
return CompilationOptions(
|
return CompilationOptions(
|
||||||
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
||||||
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
||||||
zpType, zpReserved, floatsEnabled, noSysInit,
|
zpType, zpReserved, floatsEnabled, noSysInit, stringDedup,
|
||||||
compTarget
|
compTarget
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -248,7 +254,7 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati
|
|||||||
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
// perform initial syntax checks and processings
|
// perform initial syntax checks and processings
|
||||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||||
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
programAst.checkIdentifiers(errors, compilerOptions)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.constantFold(errors, compilerOptions.compTarget)
|
programAst.constantFold(errors, compilerOptions.compTarget)
|
||||||
errors.report()
|
errors.report()
|
||||||
@ -260,7 +266,7 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
|
|||||||
errors.report()
|
errors.report()
|
||||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
programAst.checkIdentifiers(errors, compilerOptions)
|
||||||
errors.report()
|
errors.report()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,16 +44,16 @@ internal fun Program.verifyFunctionArgTypes() {
|
|||||||
fixer.visit(this)
|
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)
|
checker2.visit(this)
|
||||||
|
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
val transforms = AstVariousTransforms(this)
|
val transforms = AstVariousTransforms(this)
|
||||||
transforms.visit(this)
|
transforms.visit(this)
|
||||||
transforms.applyModifications()
|
transforms.applyModifications()
|
||||||
val lit2decl = LiteralsToAutoVars(this)
|
val lit2decl = LiteralsToAutoVars(this, options)
|
||||||
lit2decl.visit(this)
|
lit2decl.visit(this)
|
||||||
lit2decl.applyModifications()
|
lit2decl.applyModifications()
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,15 @@ import prog8.ast.statements.VarDecl
|
|||||||
import prog8.ast.statements.WhenChoice
|
import prog8.ast.statements.WhenChoice
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
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<IAstModification> {
|
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||||
// replace the literal string by a identifier reference to the interned string
|
// 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)
|
val identifier = IdentifierReference(scopedName, string.position)
|
||||||
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
||||||
}
|
}
|
||||||
|
@ -381,18 +381,14 @@ internal class AsmGen(private val program: Program,
|
|||||||
out("\n; non-zeropage variables")
|
out("\n; non-zeropage variables")
|
||||||
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||||
|
|
||||||
// special treatment for string types: merge strings that are identical
|
|
||||||
val encodedstringVars = vars
|
val encodedstringVars = vars
|
||||||
.filter {it.datatype == DataType.STR }
|
.filter {it.datatype == DataType.STR }
|
||||||
.map {
|
.map {
|
||||||
val str = it.value as StringLiteralValue
|
val str = it.value as StringLiteralValue
|
||||||
it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
|
it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
|
||||||
}
|
}
|
||||||
.groupBy({it.second}, {it.first})
|
for((decl, variables) in encodedstringVars) {
|
||||||
for((encoded, variables) in encodedstringVars) {
|
outputStringvar(decl, variables)
|
||||||
variables.dropLast(1).forEach { out(it.name) }
|
|
||||||
val lastvar = variables.last()
|
|
||||||
outputStringvar(lastvar, encoded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// non-string variables
|
// non-string variables
|
||||||
@ -402,11 +398,11 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
|
private fun outputStringvar(strdecl: VarDecl, bytes: List<Short>) {
|
||||||
val sv = lastvar.value as StringLiteralValue
|
val sv = strdecl.value as StringLiteralValue
|
||||||
val altEncoding = if(sv.altEncoding) "@" else ""
|
val altEncoding = if(sv.altEncoding) "@" else ""
|
||||||
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
|
out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
|
||||||
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
|
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
|
||||||
for (chunk in outputBytes.chunked(16))
|
for (chunk in outputBytes.chunked(16))
|
||||||
out(" .byte " + chunk.joinToString())
|
out(" .byte " + chunk.joinToString())
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ class TestC64Zeropage {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNames() {
|
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)
|
||||||
zp.allocate("", DataType.UBYTE, null, errors)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
@ -147,37 +147,37 @@ class TestC64Zeropage {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testZpFloatEnable() {
|
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<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
zp.allocate("", DataType.FLOAT, null, errors)
|
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<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
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)
|
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testZpModesWithFloats() {
|
fun testZpModesWithFloats() {
|
||||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, 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, 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, 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, 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, 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, C64Target))
|
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, true, C64Target))
|
||||||
assertFailsWith<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
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<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
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
|
@Test
|
||||||
fun testZpDontuse() {
|
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)
|
println(zp.free)
|
||||||
assertEquals(0, zp.available())
|
assertEquals(0, zp.available())
|
||||||
assertFailsWith<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
@ -187,19 +187,19 @@ class TestC64Zeropage {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFreeSpaces() {
|
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())
|
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())
|
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())
|
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())
|
assertEquals(238, zp4.available())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testReservedSpace() {
|
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())
|
assertEquals(238, zp1.available())
|
||||||
assertTrue(50 in zp1.free)
|
assertTrue(50 in zp1.free)
|
||||||
assertTrue(100 in zp1.free)
|
assertTrue(100 in zp1.free)
|
||||||
@ -208,7 +208,7 @@ class TestC64Zeropage {
|
|||||||
assertTrue(200 in zp1.free)
|
assertTrue(200 in zp1.free)
|
||||||
assertTrue(255 in zp1.free)
|
assertTrue(255 in zp1.free)
|
||||||
assertTrue(199 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())
|
assertEquals(139, zp2.available())
|
||||||
assertFalse(50 in zp2.free)
|
assertFalse(50 in zp2.free)
|
||||||
assertFalse(100 in zp2.free)
|
assertFalse(100 in zp2.free)
|
||||||
@ -221,7 +221,7 @@ class TestC64Zeropage {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBasicsafeAllocation() {
|
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())
|
assertEquals(18, zp.available())
|
||||||
|
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
assertFailsWith<ZeropageDepletedError> {
|
||||||
@ -244,7 +244,7 @@ class TestC64Zeropage {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFullAllocation() {
|
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())
|
assertEquals(238, zp.available())
|
||||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||||
assertTrue(loc > 3)
|
assertTrue(loc > 3)
|
||||||
@ -274,7 +274,7 @@ class TestC64Zeropage {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testEfficientAllocation() {
|
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(18, zp.available())
|
||||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||||
@ -293,7 +293,7 @@ class TestC64Zeropage {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testReservedLocations() {
|
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")
|
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 {
|
class TestCx16Zeropage {
|
||||||
@Test
|
@Test
|
||||||
fun testReservedLocations() {
|
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")
|
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFreeSpaces() {
|
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())
|
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())
|
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())
|
assertEquals(216, zp4.available())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testReservedSpace() {
|
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())
|
assertEquals(216, zp1.available())
|
||||||
assertTrue(0x22 in zp1.free)
|
assertTrue(0x22 in zp1.free)
|
||||||
assertTrue(0x80 in zp1.free)
|
assertTrue(0x80 in zp1.free)
|
||||||
|
@ -7,7 +7,6 @@ import prog8.ast.base.VarDeclType
|
|||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
|
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
|
||||||
|
@ -256,7 +256,7 @@ class Program(val name: String,
|
|||||||
get() = mainModule.loadAddress
|
get() = mainModule.loadAddress
|
||||||
|
|
||||||
var actualLoadAddress: Int = 0
|
var actualLoadAddress: Int = 0
|
||||||
private val internedStrings = mutableMapOf<Pair<String, Boolean>, List<String>>()
|
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
|
||||||
val internedStringsModuleName = "prog8_interned_strings"
|
val internedStringsModuleName = "prog8_interned_strings"
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -280,22 +280,40 @@ class Program(val name: String,
|
|||||||
return mainBlocks[0].subScope("start") as Subroutine
|
return mainBlocks[0].subScope("start") as Subroutine
|
||||||
}
|
}
|
||||||
|
|
||||||
fun internString(string: StringLiteralValue): List<String> {
|
fun internString(string: StringLiteralValue, dedup: Boolean): List<String> {
|
||||||
val key = Pair(string.value, string.altEncoding)
|
|
||||||
val existing = internedStrings[key]
|
|
||||||
if(existing!=null)
|
|
||||||
return existing
|
|
||||||
|
|
||||||
val decl = VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, "string_${internedStrings.size}", string,
|
fun getScopedName(string: StringLiteralValue): List<String> {
|
||||||
isArray = false, autogeneratedDontRemove = true, position = string.position)
|
val internedStringsBlock = modules
|
||||||
val internedStringsBlock = modules.first { it.name==internedStringsModuleName }.statements.first { it is Block && it.name == internedStringsModuleName}
|
.first { it.name == internedStringsModuleName }.statements
|
||||||
(internedStringsBlock as Block).statements.add(decl)
|
.first { it is Block && it.name == internedStringsModuleName } as Block
|
||||||
decl.linkParents(internedStringsBlock)
|
val varName = "string_${internedStringsBlock.statements.size}"
|
||||||
val scopedName = listOf(internedStringsModuleName, decl.name)
|
val decl = VarDecl(
|
||||||
internedStrings[key] = scopedName
|
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
|
||||||
return scopedName
|
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<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
||||||
|
|
||||||
override val position: Position = Position.DUMMY
|
override val position: Position = Position.DUMMY
|
||||||
|
@ -13,7 +13,6 @@ import prog8.parser.prog8Parser
|
|||||||
import java.io.CharConversionException
|
import java.io.CharConversionException
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
/***************** Antlr Extension methods to create AST ****************/
|
/***************** Antlr Extension methods to create AST ****************/
|
||||||
|
@ -6,7 +6,7 @@ import prog8.ast.base.*
|
|||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import java.util.Objects
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
|
@ -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?
|
- 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???
|
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?
|
- 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
|
- test all examples before release of the new version
|
||||||
|
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
%import textio ; txt.*
|
%import textio ; txt.*
|
||||||
main {
|
%zeropage basicsafe
|
||||||
sub start() {
|
main {
|
||||||
; ATTENTION: uncomment only one problematic line at a time!
|
sub start() {
|
||||||
|
txt.print("a")
|
||||||
; Normal string literals, i.e. PETSCII encoding
|
txt.print("a")
|
||||||
; ---------------------------------------------
|
txt.print("bb")
|
||||||
txt.print("\"") ; fine
|
txt.print("bb")
|
||||||
txt.print("\n") ; fine
|
txt.print("\n")
|
||||||
txt.print("\r") ; fine
|
txt.print("\n\n")
|
||||||
txt.print("\\") ; yields CharConversionException
|
; txt.print("hello\n")
|
||||||
txt.print("xyz\\") ; yields prog8.compiler.AssemblyError
|
; txt.print("hello\n")
|
||||||
|
; txt.print("hello\n")
|
||||||
; @-strings, i.e. translated into
|
; txt.print("hello\n")
|
||||||
; the alternate character encoding (Screencodes/pokes)
|
; txt.print("hello22\n")
|
||||||
; ----------------------------------------------------
|
; txt.print("hello22\n")
|
||||||
txt.print(@"\"") ; fine
|
; txt.print("hello22\n")
|
||||||
txt.print(@"\n") ; yields CharConversionException
|
; txt.print("hello22\n")
|
||||||
txt.print(@"xyz\n") ; yields prog8.compiler.AssemblyError
|
; txt.print("hello666\n")
|
||||||
txt.print(@"\r") ; yields CharConversionException
|
; txt.print("hello666\n")
|
||||||
txt.print(@"xyz\r") ; yields prog8.compiler.AssemblyError
|
; txt.print("hello666\n")
|
||||||
txt.print(@"\\") ; yields CharConversionException
|
; txt.print("hello666\n")
|
||||||
txt.print(@"\\") ; yields prog8.compiler.AssemblyError
|
}
|
||||||
|
}
|
||||||
; there may be more...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -29,7 +29,7 @@ class RequestParser : Take {
|
|||||||
val form = RqFormBase(request)
|
val form = RqFormBase(request)
|
||||||
val names = form.names()
|
val names = form.names()
|
||||||
val a = form.param("a").single()
|
val a = form.param("a").single()
|
||||||
val compilationResult = compileProgram(Path.of(a), true, true, true, "c64", emptyList<String>(), Path.of("."))
|
val compilationResult = compileProgram(Path.of(a), true, true, true, false, "c64", emptyList<String>(), Path.of("."))
|
||||||
return RsJson(Jsonding())
|
return RsJson(Jsonding())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user