Merge branch 'v7.1'

This commit is contained in:
Irmen de Jong 2021-10-19 23:21:44 +02:00
commit f0cff661df
24 changed files with 243 additions and 183 deletions

View File

@ -9,6 +9,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.ICompilationTarget
@ -39,7 +40,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& compTarget.isInRegularRAM(assignment.target, program)) {
&& assignment.target.isInRegularRAMof(compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {

View File

@ -215,12 +215,12 @@ fun parseImports(filepath: Path,
}
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule
val outputDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
val launcherDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
val toplevelModule = program.toplevelModule
val outputDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
val launcherDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
val outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
val zpoption: String? = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.uppercase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }
.flatMap { (it as Directive).args }.toSet()
@ -242,7 +242,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
val zpReserved = toplevelModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
@ -280,6 +280,8 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
programAst.checkIdentifiers(errors, compilerOptions)
errors.report()
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
// NOTE: we will then lose the opportunity to do constant-folding on any expression containing a char literal, but how often will those occur?
// Also they might be optimized away eventually in codegen or by the assembler even
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)

View File

@ -35,13 +35,17 @@ class ModuleImporter(private val program: Program,
file = filePath.normalize().toFile(),
reason = "searched in $searchIn"))
1 -> candidates.first()
else -> candidates.first() // TODO: report error if more than 1 candidate?
else -> candidates.first() // when more candiates, pick the one from the first location
}
val logMsg = "importing '${filePath.nameWithoutExtension}' (from file $srcPath)"
println(logMsg)
return Ok(importModule(SourceCode.File(srcPath)))
val module = importModule(SourceCode.File(srcPath))
return if(module==null)
Err(NoSuchFileException(srcPath.toFile()))
else
Ok(module)
}
fun importLibraryModule(name: String): Module? {
@ -51,20 +55,24 @@ class ModuleImporter(private val program: Program,
return executeImportDirective(import, null)
}
//private fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
private fun importModule(src: SourceCode) : Module {
private fun importModule(src: SourceCode) : Module? {
val moduleAst = Prog8Parser.parseModule(src)
program.addModule(moduleAst)
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
try {
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> i to it }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(it.second as Directive, moduleAst) }
moduleAst.statements = lines
return moduleAst
moduleAst.statements = lines
return moduleAst
} catch (x: Exception) {
// in case of error, make sure the module we're importing is no longer in the Ast
program.removeModule(moduleAst)
throw x
}
}
private fun executeImportDirective(import: Directive, importingModule: Module?): Module? {
@ -104,7 +112,8 @@ class ModuleImporter(private val program: Program,
}
)
removeDirectivesFromImportedModule(importedModule)
if(importedModule!=null)
removeDirectivesFromImportedModule(importedModule)
return importedModule
}
@ -133,7 +142,7 @@ class ModuleImporter(private val program: Program,
} else {
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
sourcePaths.drop(dropCurDir) +
// FIXME: won't work until Prog8Parser is fixed s.t. it fully initialzes the modules it returns
// TODO: won't work until Prog8Parser is fixed s.t. it fully initializes the modules it returns. // hm, what won't work?)
listOf(Path(importingModule.position.file).parent ?: Path("")) +
listOf(Path(".", "prog8lib"))
}

View File

@ -13,7 +13,6 @@ import prog8.compiler.ZeropageType
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.ICompilationTarget
import prog8.parser.SourceCode
import java.io.CharConversionException
import java.io.File
import java.util.*
@ -46,7 +45,7 @@ internal class AstChecker(private val program: Program,
if(compilerOptions.floats) {
if (compilerOptions.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
errors.err("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'", program.mainModule.position)
errors.err("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'", program.toplevelModule.position)
}
super.visit(program)

View File

@ -3,11 +3,9 @@ package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RangeExpr
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Directive
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@ -16,6 +14,7 @@ import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.IStringEncoding
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.IMachineDefinition
import kotlin.math.abs
@ -127,26 +126,6 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
lit2decl.visit(this)
lit2decl.applyModifications()
}
// Check if each module has a unique name.
// If not report those that haven't.
// TODO: move check for unique module names to earlier stage and/or to unit tests
val namesToModules = mapOf<String, MutableList<prog8.ast.Module>>().toMutableMap()
for (m in modules) {
val others = namesToModules[m.name]
if (others == null) {
namesToModules[m.name] = listOf(m).toMutableList()
} else {
others.add(m)
}
}
val nonUniqueNames = namesToModules.keys
.map { Pair(it, namesToModules[it]!!.size) }
.filter { it.second > 1 }
.map { "\"${it.first}\" (x${it.second})"}
if (nonUniqueNames.isNotEmpty()) {
throw FatalAstException("modules must have unique names; of the ttl ${modules.size} these have not: $nonUniqueNames")
}
}
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
@ -186,3 +165,47 @@ internal fun Program.moveMainAndStartToFirst() {
modules[0].statements.add(0, directive)
}
}
internal fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
val memAddr = memoryAddress
val arrayIdx = arrayindexed
val ident = identifier
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val program = definingModule.program
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val program = definingModule.program
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val program = definingModule.program
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
}

View File

@ -24,49 +24,6 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
// TODO: rename param target, and also AST node AssignTarget - *different meaning of "target"!*
// TODO: remove param program - can be obtained from AST node
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
val memAddr = target.memoryAddress
val arrayIdx = target.arrayindexed
val ident = target.identifier
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
}
}

View File

@ -9,6 +9,7 @@ import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.ICompilationTarget
@ -54,7 +55,7 @@ X = BinExpr X = LeftExpr
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program)) {
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications
@ -77,9 +78,9 @@ X = BinExpr X = LeftExpr
return noModifications
}
private fun isSimpleTarget(target: AssignTarget, program: Program) =
private fun isSimpleTarget(target: AssignTarget) =
if (target.identifier!=null || target.memoryAddress!=null)
compTarget.isInRegularRAM(target, program)
target.isInRegularRAMof(compTarget.machine)
else
false

View File

@ -10,6 +10,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.ICompilationTarget
@ -122,7 +123,7 @@ internal class UnusedCodeRemover(private val program: Program,
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && compTarget.isInRegularRAM(assign1.target, program)) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAMof(compTarget.machine)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
// only remove the second assignment if its value is a simple expression!
when(assign2.value) {

View File

@ -71,7 +71,7 @@ locallabel:
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
.addModule(module)
module.linkParents(program.namespace)
module.linkIntoProgram(program)
return program
}

View File

@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.Disabled
import prog8.ast.Program
import prog8.ast.internedStringsModuleName
import prog8.compiler.IErrorReporter
import prog8.compiler.ModuleImporter
import prog8.parser.ParseError
@ -207,7 +208,6 @@ class TestModuleImporter {
}
@Test
@Disabled("TODO: module that imports faulty module should not be kept in Program.modules")
fun testImportingFileWithSyntaxError_twice() {
doTestImportingFileWithSyntaxError(2)
}
@ -227,7 +227,8 @@ class TestModuleImporter {
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
}
assertThat(program.modules.size, equalTo(2))
assertThat("imported module with error in it should not be present", program.modules.size, equalTo(1))
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
}
}
}
@ -258,7 +259,7 @@ class TestModuleImporter {
val result2 = importer.importLibraryModule(filenameWithExt)
assertThat(count[n] + " call / with .p8 extension", result2, Is(nullValue()))
assertFalse(importer.errors.noErrors(), count[n] + " call / with .p8 extension")
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist.p8") // TODO don't add a p8 extension in the import logic...
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist.p8")
errors.report()
assertThat(program.modules.size, equalTo(1))
}
@ -303,7 +304,8 @@ class TestModuleImporter {
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
}
assertThat(program.modules.size, equalTo(2))
assertThat("imported module with error in it should not be present", program.modules.size, equalTo(1))
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
importer.errors.report()
}
}
@ -314,7 +316,6 @@ class TestModuleImporter {
}
@Test
@Disabled("TODO: module that imports faulty module should not be kept in Program.modules")
fun testImportingFileWithSyntaxError_twice() {
doTestImportingFileWithSyntaxError(2)
}

View File

@ -42,7 +42,7 @@ class TestCompilerOnCharLit {
"char literal should have been replaced by ubyte literal")
val arg = funCall.args[0] as NumericLiteralValue
assertEquals(DataType.UBYTE, arg.type)
assertEquals(platform.encodeString("\n", false)[0], arg.number.toShort()) // TODO: short/int/UBYTE - which should it be?
assertEquals(platform.encodeString("\n", false)[0], arg.number.toShort())
}
@Test
@ -77,7 +77,7 @@ class TestCompilerOnCharLit {
"char literal should have been replaced by ubyte literal")
val initializerValue = decl.value as NumericLiteralValue
assertEquals(DataType.UBYTE, initializerValue.type)
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort()) // TODO: short/int/UBYTE - which should it be?
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort())
}
@Test
@ -105,12 +105,12 @@ class TestCompilerOnCharLit {
assertEquals(DataType.UBYTE, decl.datatype)
assertEquals(
platform.encodeString("\n", false)[0],
(decl.value as NumericLiteralValue).number.toShort()) // TODO: short/int/UBYTE - which should it be?
(decl.value as NumericLiteralValue).number.toShort())
}
is NumericLiteralValue -> {
assertEquals(
platform.encodeString("\n", false)[0],
arg.number.toShort()) // TODO: short/int/UBYTE - which should it be?
arg.number.toShort())
}
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
}

View File

@ -19,7 +19,7 @@ import kotlin.io.path.exists
* They are not really unit tests, but rather tests of the whole process,
* from source file loading all the way through to running 64tass.
*/
//@Disabled("to save some time")
// @Disabled("disable to save some time")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompilerOnExamples {
private val examplesDir = assumeDirectory(workingDir, "../examples")
@ -49,7 +49,7 @@ class TestCompilerOnExamples {
}
@TestFactory
// @Disabled
// @Disabled("disable to save some time")
fun bothCx16AndC64() = mapCombinations(
dim1 = listOf(
"animals",
@ -80,7 +80,7 @@ class TestCompilerOnExamples {
)
@TestFactory
// @Disabled
// @Disabled("disable to save some time")
fun onlyC64() = mapCombinations(
dim1 = listOf(
"balloonflight",
@ -99,7 +99,7 @@ class TestCompilerOnExamples {
)
@TestFactory
// @Disabled
// @Disabled("disable to save some time")
fun onlyCx16() = mapCombinations(
dim1 = listOf(
"vtui/testvtui",

View File

@ -19,7 +19,7 @@ import kotlin.test.assertTrue
class TestImportedModulesOrderAndOptions {
@Test
fun testImportedModuleOrderCorrect() {
fun testImportedModuleOrderAndMainModuleCorrect() {
val result = compileText(C64Target, false, """
%import textio
%import floats
@ -30,7 +30,7 @@ main {
}
}
""").assertSuccess()
assertTrue(result.programAst.mainModule.name.startsWith("on_the_fly_test"))
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
val moduleNames = result.programAst.modules.map { it.name }
assertTrue(moduleNames[0].startsWith("on_the_fly_test"), "main module must be first")
@ -43,6 +43,8 @@ main {
"math",
"prog8_lib"
), moduleNames.drop(1), "module order in parse tree")
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
}
@Test
@ -59,7 +61,7 @@ main {
}
}
""").assertSuccess()
assertTrue(result.programAst.mainModule.name.startsWith("on_the_fly_test"))
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
val options = determineCompilationOptions(result.programAst, C64Target)
assertTrue(options.floats)
assertEquals(ZeropageType.DONTUSE, options.zeropage)
@ -86,7 +88,7 @@ main {
filepath.toFile().writeText(sourceText)
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
assertEquals(filenameBase, program.mainModule.name)
assertEquals(filenameBase, program.toplevelModule.name)
assertEquals(1, importedfiles.size, "all imports other than the test source must have been internal resources library files")
assertEquals(listOf(
internedStringsModuleName,

View File

@ -5,7 +5,6 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.ArrayIndexedExpression
@ -13,6 +12,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.*
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.C64Target
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
@ -29,24 +29,23 @@ class TestMemory {
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer)
assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAMof(C64Target.machine))
}
@Test
@ -54,20 +53,19 @@ class TestMemory {
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer)
assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAMof(C64Target.machine))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAMof(C64Target.machine))
}
@Test
@ -75,15 +73,15 @@ class TestMemory {
val program = Program("test", DummyFunctions, DummyMemsizer)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAMof(C64Target.machine))
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAMof(C64Target.machine))
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAMof(C64Target.machine))
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAMof(C64Target.machine))
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAMof(C64Target.machine))
}
private fun createTestProgramForMemoryRefViaVar(program: Program, address: Int, vartype: VarDeclType): AssignTarget {
@ -93,7 +91,7 @@ class TestMemory {
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
module.linkParents(program.namespace)
module.linkIntoProgram(program)
return target
}
@ -101,8 +99,7 @@ class TestMemory {
fun testInValidRamC64_memory_expression() {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer)
assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAMof(C64Target.machine))
}
@Test
@ -114,8 +111,8 @@ class TestMemory {
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
.addModule(module)
module.linkParents(program.namespace)
assertTrue(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
}
@Test
@ -128,8 +125,8 @@ class TestMemory {
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
.addModule(module)
module.linkParents(program.namespace)
assertTrue(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
}
@Test
@ -142,8 +139,8 @@ class TestMemory {
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
.addModule(module)
module.linkParents(program.namespace)
assertFalse(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine))
}
@Test
@ -156,8 +153,8 @@ class TestMemory {
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
.addModule(module)
module.linkParents(program.namespace)
assertTrue(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
}
@Test
@ -171,8 +168,8 @@ class TestMemory {
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
.addModule(module)
module.linkParents(program.namespace)
assertTrue(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
}
@Test
@ -186,7 +183,7 @@ class TestMemory {
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
.addModule(module)
module.linkParents(program.namespace)
assertFalse(C64Target.isInRegularRAM(target, program))
module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine))
}
}

View File

@ -2,7 +2,7 @@
main {
str myBar = "main.bar"
;foo_bar:
; %asminclude "foo_bar.asm" ; FIXME: should be accessible from inside start() but give assembler error. See github issue #62
; %asminclude "foo_bar.asm" ; TODO: should be accessible from inside start() but give assembler error. See github issue #62
sub start() {
txt.print(myBar)
txt.print(&foo_bar)

View File

@ -259,11 +259,12 @@ class Program(val name: String,
require(null == _modules.firstOrNull { it.name == module.name })
{ "module '${module.name}' already present" }
_modules.add(module)
module.linkParents(namespace)
module.program = this
module.linkIntoProgram(this)
return this
}
fun removeModule(module: Module) = _modules.remove(module)
fun moveModuleToFront(module: Module): Program {
require(_modules.contains(module))
{ "Not a module of this program: '${module.name}'"}
@ -285,11 +286,11 @@ class Program(val name: String,
}
}
val mainModule: Module // TODO: rename Program.mainModule - it's NOT necessarily the one containing the main *block*!
val toplevelModule: Module
get() = modules.first { it.name!=internedStringsModuleName }
val definedLoadAddress: Int
get() = mainModule.loadAddress
get() = toplevelModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
@ -342,9 +343,8 @@ class Program(val name: String,
require(node is Module && replacement is Module)
val idx = _modules.indexOfFirst { it===node }
_modules[idx] = replacement
replacement.parent = this // TODO: why not replacement.program = this; replacement.linkParents(namespace)?!
replacement.linkIntoProgram(this)
}
}
open class Module(final override var statements: MutableList<Statement>,
@ -370,6 +370,11 @@ open class Module(final override var statements: MutableList<Statement>,
statements.forEach {it.linkParents(this)}
}
fun linkIntoProgram(program: Program) {
this.program = program
linkParents(program.namespace)
}
override val definingScope: INameScope
get() = program.namespace
override fun replaceChildNode(node: Node, replacement: Node) {

View File

@ -301,7 +301,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun inferType(program: Program) = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null
val cast = cv.cast(type)
@ -334,7 +334,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifier(vararg scopedName: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
}
@ -359,7 +359,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null
override fun toString(): String {
@ -422,7 +422,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun inferType(program: Program) = InferredTypes.knownFor(type)
override fun hashCode(): Int = Objects.hash(type, number)
@ -518,7 +518,7 @@ class CharLiteral(val value: Char,
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String = "'${escape(value.toString())}'"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UNDEFINED) // FIXME: CharLiteral.inferType
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE)
operator fun compareTo(other: CharLiteral): Int = value.compareTo(other.value)
override fun hashCode(): Int = Objects.hash(value, altEncoding)
override fun equals(other: Any?): Boolean {
@ -550,7 +550,7 @@ class StringLiteralValue(val value: String,
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "'${escape(value)}'"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.STR)
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
override fun hashCode(): Int = Objects.hash(value, altEncoding)
override fun equals(other: Any?): Boolean {

View File

@ -31,12 +31,9 @@ object Prog8Parser {
parser.addErrorListener(antlrErrorListener)
val parseTree = parser.module()
val module = ParsedModule(src)
// .linkParents called in ParsedModule.add
parseTree.directive().forEach { module.add(it.toAst()) }
// TODO: remove Encoding
parseTree.block().forEach { module.add(it.toAst(module.isLibrary)) }
return module
@ -95,10 +92,13 @@ object Prog8Parser {
private class AntlrErrorListener(val src: SourceCode): BaseErrorListener() {
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
if (e == null) {
TODO("no RecognitionException - create your own ParseError")
//throw ParseError()
throw ParseError(msg, Position(src.origin, line, charPositionInLine, charPositionInLine), RuntimeException("parse error"))
} else {
throw ParseError(msg, e.getPosition(src.origin), e)
if(e.offendingToken==null) {
throw ParseError(msg, Position(src.origin, line, charPositionInLine, charPositionInLine), e)
} else {
throw ParseError(msg, e.getPosition(src.origin), e)
}
}
}
}
@ -107,7 +107,7 @@ object Prog8Parser {
val offending = this.offendingToken
val line = offending.line
val beginCol = offending.charPositionInLine
val endCol = beginCol + offending.stopIndex - offending.startIndex // TODO: point to col *after* token?
val endCol = beginCol + offending.stopIndex - offending.startIndex // TODO: point to col *after* token? / why, what's wrong with endCol being inclusive
return Position(file, line, beginCol, endCol)
}

View File

@ -4,6 +4,7 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.IFunctionCall
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.base.Position
import prog8.ast.expressions.CharLiteral
@ -17,12 +18,11 @@ import prog8.parser.SourceCode
import prog8tests.helpers.assumeNotExists
import prog8tests.helpers.assumeReadableFile
import prog8tests.helpers.fixturesDir
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.name
import kotlin.io.path.nameWithoutExtension
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -219,7 +219,7 @@ class TestProg8Parser {
val module = parseModule(SourceCode.Text(srcText))
// Note: assertContains has *actual* as first param
assertContains(module.name, Regex("^<String@[0-9a-f]+>$"))
assertContains(module.name, Regex("^<String@[0-9a-f\\-]+>$"))
}
@Test
@ -289,7 +289,7 @@ class TestProg8Parser {
try {
parseModule(SourceCode.Text(srcText))
} catch (e: ParseError) {
assertPosition(e.position, Regex("^<String@[0-9a-f]+>$"), 1, 4, 4)
assertPosition(e.position, Regex("^<String@[0-9a-f\\-]+>$"), 1, 4, 4)
}
}
@ -312,7 +312,7 @@ class TestProg8Parser {
}
""".trimIndent()
val module = parseModule(SourceCode.Text(srcText))
assertPositionOf(module, Regex("^<String@[0-9a-f]+>$"), 1, 0) // TODO: endCol wrong
assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0) // TODO: endCol wrong
}
@Test
@ -330,9 +330,9 @@ class TestProg8Parser {
val mpf = module.position.file
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0) // TODO: endCol wrong
val mainBlock = module.statements.filterIsInstance<Block>()[0]
assertPositionOf(mainBlock, mpf, 1, 0) // TODO: endCol wrong!
assertPositionOf(mainBlock, mpf, 2, 0) // TODO: endCol wrong!
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
assertPositionOf(startSub, mpf, 2, 4) // TODO: endCol wrong!
assertPositionOf(startSub, mpf, 3, 4) // TODO: endCol wrong!
}
@ -378,6 +378,59 @@ class TestProg8Parser {
}
}
@Nested
inner class PositionFile {
@Test
fun `isn't absolute for filesystem paths`() {
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
val module = parseModule(SourceCode.File(path))
assertSomethingForAllNodes(module) {
assertFalse(Path(it.position.file).isAbsolute)
assertTrue(Path(it.position.file).isRegularFile())
}
}
@Test
fun `is mangled string id for string sources`()
{
val srcText="""
%zeropage basicsafe
main {
sub start() {
ubyte aa=99
aa++
}
}
""".trimIndent()
val module = parseModule(SourceCode.Text(srcText))
assertSomethingForAllNodes(module) {
assertTrue(it.position.file.startsWith(SourceCode.stringSourcePrefix))
}
}
@Test
fun `is library prefixed path for resources`()
{
val resource = SourceCode.Resource("prog8lib/math.p8")
val module = parseModule(resource)
assertSomethingForAllNodes(module) {
assertTrue(it.position.file.startsWith(SourceCode.libraryFilePrefix))
}
}
private fun assertSomethingForAllNodes(module: Module, asserter: (Node) -> Unit) {
asserter(module)
module.statements.forEach(asserter)
module.statements.filterIsInstance<Block>().forEach { b ->
asserter(b)
b.statements.forEach(asserter)
b.statements.filterIsInstance<Subroutine>().forEach { s ->
asserter(s)
s.statements.forEach(asserter)
}
}
} }
@Nested
inner class CharLiterals {

View File

@ -1,30 +1,32 @@
package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.StringStartsWith
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.parser.SourceCode
import prog8.parser.SourceCode.Companion.libraryFilePrefix
import prog8tests.helpers.*
import kotlin.io.path.Path
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSourceCode {
@Test
fun testFactoryMethod_Of() {
fun testFromString() {
val text = """
main { }
""".trimIndent()
"""
val src = SourceCode.Text(text)
val actualText = src.getCharStream().toString()
assertContains(src.origin, Regex("^<String@[0-9a-f]+>$"))
assertContains(src.origin, Regex("^<String@[0-9a-f\\-]+>$"))
assertEquals(text, actualText)
assertFalse(src.isFromResources)
assertFalse(src.isFromFilesystem)
assertThat(src.toString(), StringStartsWith("prog8.parser.SourceCode"))
}
@Test
@ -54,6 +56,8 @@ class TestSourceCode {
val expectedOrigin = SourceCode.relative(path).toString()
assertEquals(expectedOrigin, src.origin)
assertEquals(path.toFile().readText(), src.asString())
assertFalse(src.isFromResources)
assertTrue(src.isFromFilesystem)
}
@Test
@ -75,6 +79,8 @@ class TestSourceCode {
assertEquals("$libraryFilePrefix/$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString())
assertTrue(src.isFromResources)
assertFalse(src.isFromFilesystem)
}
@Test

View File

@ -50,12 +50,12 @@ class ProgramTests {
assertSame(program, m1.program)
assertSame(program.namespace, m1.parent)
assertFailsWith<IllegalArgumentException> { program.addModule(m1) }
.let { assertThat(it.message, containsString(m1.name)) }
assertThat("module may not occur multiple times",
assertFailsWith<IllegalArgumentException> { program.addModule(m1) }.message, containsString(m1.name))
val m2 = Module(mutableListOf(), m1.position, m1.source)
assertFailsWith<IllegalArgumentException> { program.addModule(m2) }
.let { assertThat(it.message, containsString(m2.name)) }
assertThat("other module but with same name may not occur multiple times",
assertFailsWith<IllegalArgumentException> { program.addModule(m2) }.message, containsString(m2.name))
}
}

View File

@ -1,4 +1,7 @@
%zeropage basicsafe
main {
sub start() {
ubyte aa=99
aa++
}
}

View File

@ -14,7 +14,7 @@ Blocked by Commander-x16 v39 release
Future
^^^^^^
- get rid of all TODO's and FIXME's in the code
- get rid of all TODO's in the code
- improve testability further, add more tests, address more questions/issues from the testability discussions.
- replace certain uses of inferredType.getOr(DataType.UNDEFINED) by i.getOrElse({ errorhandler })
- see if we can remove more "[InferredType].getOr(DataType.UNDEFINED)"

View File

@ -22,7 +22,7 @@ main {
; Not yet implemented in ROM: cx16.FB_set_palette(&colors, 0, len(colors)*3)
palette.set_rgb(&colors, len(colors))
cx16.screen_set_mode(128) ; low-res bitmap 256 colors
void cx16.screen_set_mode(128) ; low-res bitmap 256 colors
cx16.FB_init()
cx16.VERA_DC_VSCALE = 0 ; display trick spoiler.......: stretch display all the way to the bottom
cx16.set_rasterirq(&irq.irqhandler, 0)