mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
46
Commits
v7.1-beta
...
version7.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f8fe75ab2 | ||
|
|
44143f481a | ||
|
|
440abf4998 | ||
|
|
3c10427e04 | ||
|
|
85956b5828 | ||
|
|
41e40cad03 | ||
|
|
df2d5c6585 | ||
|
|
f696fce187 | ||
|
|
82d3d81bb2 | ||
|
|
f0cff661df | ||
|
|
82d20dea39 | ||
|
|
804bb06859 | ||
|
|
5afa7e53f8 | ||
|
|
7f15b7b716 | ||
|
|
552e0c2248 | ||
|
|
e5b9e1f5e7 | ||
|
|
502bf90007 | ||
|
|
40bf117497 | ||
|
|
4011dce31b | ||
|
|
9e881e32f8 | ||
|
|
637a8899c5 | ||
|
|
cf0e395921 | ||
|
|
6ef438ce50 | ||
|
|
46e4b977a4 | ||
|
|
9626c5dead | ||
|
|
aea364e43d | ||
|
|
06defd0cb0 | ||
|
|
0f80897c50 | ||
|
|
57bb1c2c0d | ||
|
|
7b35b414e8 | ||
|
|
761aac7a23 | ||
|
|
15a02d7664 | ||
|
|
16ed68c1ec | ||
|
|
e63cf660c6 | ||
|
|
aaff484306 | ||
|
|
3281d9a215 | ||
|
|
0fcd61e00f | ||
|
|
c4523ea470 | ||
|
|
0447b3e4cc | ||
|
|
4d27c2901b | ||
|
|
855e18b31c | ||
|
|
d790878af6 | ||
|
|
85b244df2f | ||
|
|
6070afa6b6 | ||
|
|
975594703d | ||
|
|
6b8c3ef614 |
No files matched your search
@@ -1 +1 @@
|
||||
7.1-beta
|
||||
7.1
|
||||
@@ -39,7 +39,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||
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 libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||
|
||||
try {
|
||||
@@ -61,9 +61,9 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
val libdirs = libDirs.toMutableList()
|
||||
if(libdirs.firstOrNull()!=".")
|
||||
libdirs.add(0, ".")
|
||||
val srcdirs = sourceDirs.toMutableList()
|
||||
if(srcdirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
@@ -74,7 +74,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
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, compilationTarget, srcdirs, outputPath)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
for (importedFile in allImportedFiles) {
|
||||
print(" ")
|
||||
println(importedFile)
|
||||
val watchDir = importedFile.parent ?: Path.of(".")
|
||||
val watchDir = importedFile.parent ?: Path.of("")
|
||||
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||
}
|
||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||
@@ -111,7 +111,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
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, compilationTarget, srcdirs, outputPath)
|
||||
if(!compilationResult.success)
|
||||
return false
|
||||
} catch (x: ParsingFailedError) {
|
||||
|
||||
@@ -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) {
|
||||
@@ -119,13 +120,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if (subroutine.asmAddress == null
|
||||
&& !subroutine.inline
|
||||
&& subroutine.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm() == 0
|
||||
if (subroutine.asmAddress == null && !subroutine.inline) {
|
||||
if(subroutine.statements.isEmpty() ||
|
||||
(subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
&& subroutine.statements.last() !is Subroutine)) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||
@@ -341,7 +342,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val register = if(dt.istype(DataType.UBYTE) || dt.istype(DataType.BYTE)) "r9L" else "r9"
|
||||
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
|
||||
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||
|
||||
@@ -17,7 +17,9 @@ import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.target.asmGeneratorFor
|
||||
import prog8.optimizer.*
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.SourceCode
|
||||
import prog8.parser.SourceCode.Companion.libraryFilePrefix
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
@@ -70,7 +72,7 @@ fun compileProgram(filepath: Path,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
compilationTarget: String,
|
||||
libdirs: List<String>,
|
||||
sourceDirs: List<String>,
|
||||
outputDir: Path): CompilationResult {
|
||||
var programName = ""
|
||||
lateinit var programAst: Program
|
||||
@@ -87,21 +89,27 @@ 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, sourceDirs)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
compilationOptions.optimize = optimize
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
if (compilationOptions.optimize)
|
||||
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compilationOptions)
|
||||
optimizeAst(
|
||||
programAst,
|
||||
errors,
|
||||
BuiltinFunctionsFacade(BuiltinFunctions),
|
||||
compTarget,
|
||||
compilationOptions
|
||||
)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
if(writeAssembly) {
|
||||
if (writeAssembly) {
|
||||
val result = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
||||
when(result) {
|
||||
when (result) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
System.err.println(result.error)
|
||||
@@ -114,10 +122,13 @@ fun compileProgram(filepath: Path,
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
|
||||
|
||||
} catch (px: ParsingFailedError) {
|
||||
} catch (px: ParseError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.println(px.message)
|
||||
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (pfx: ParsingFailedError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.println(pfx.message)
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (ax: AstException) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
@@ -172,23 +183,22 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
}
|
||||
|
||||
fun parseImports(filepath: Path,
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Compiler target: ${compTarget.name}. Parsing...")
|
||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
|
||||
bf.program = programAst
|
||||
|
||||
val importer = ModuleImporter(programAst, compTarget.name, errors, libdirs)
|
||||
val importer = ModuleImporter(programAst, compTarget.name, errors, sourceDirs)
|
||||
val importedModuleResult = importer.importModule(filepath)
|
||||
importedModuleResult.onFailure { throw it }
|
||||
errors.report()
|
||||
|
||||
val importedFiles = programAst.modules
|
||||
.mapNotNull { it.source }
|
||||
.filter { !it.isFromResources } // TODO: parseImports/importedFiles - maybe rather `source.isFromFilesystem`?
|
||||
.map { Path(it.pathString()) }
|
||||
val importedFiles = programAst.modules.map { it.source }
|
||||
.filter { it.isFromFilesystem }
|
||||
.map { Path(it.origin) }
|
||||
val compilerOptions = determineCompilationOptions(programAst, compTarget)
|
||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||
@@ -205,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()
|
||||
@@ -232,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 }
|
||||
@@ -270,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)
|
||||
@@ -365,7 +377,7 @@ fun printAst(programAst: Program) {
|
||||
println()
|
||||
}
|
||||
|
||||
internal fun loadAsmIncludeFile(filename: String, sourcePath: Path): Result<String, NoSuchFileException> {
|
||||
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
||||
return if (filename.startsWith(libraryFilePrefix)) {
|
||||
return runCatching {
|
||||
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode
|
||||
@@ -373,7 +385,7 @@ internal fun loadAsmIncludeFile(filename: String, sourcePath: Path): Result<Stri
|
||||
}.mapError { NoSuchFileException(File(filename)) }
|
||||
} else {
|
||||
// first try in the isSameAs folder as where the containing file was imported from
|
||||
val sib = sourcePath.resolveSibling(filename)
|
||||
val sib = Path(source.origin).resolveSibling(filename)
|
||||
|
||||
if (sib.toFile().isFile)
|
||||
Ok(sib.toFile().readText())
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package prog8.compiler
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
@@ -11,6 +9,7 @@ import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.DirectiveArg
|
||||
import prog8.parser.Prog8Parser
|
||||
import prog8.parser.SourceCode
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
@@ -18,13 +17,13 @@ import kotlin.io.path.*
|
||||
class ModuleImporter(private val program: Program,
|
||||
private val compilationTargetName: String,
|
||||
val errors: IErrorReporter,
|
||||
libdirs: List<String>) {
|
||||
sourceDirs: List<String>) {
|
||||
|
||||
private val libpaths: List<Path> = libdirs.map { Path(it) }
|
||||
private val sourcePaths: List<Path> = sourceDirs.map { Path(it) }
|
||||
|
||||
fun importModule(filePath: Path): Result<Module, NoSuchFileException> {
|
||||
val currentDir = Path("").absolute()
|
||||
val searchIn = listOf(currentDir) + libpaths
|
||||
val searchIn = listOf(currentDir) + sourcePaths
|
||||
val candidates = searchIn
|
||||
.map { it.absolute().div(filePath).normalize().absolute() }
|
||||
.filter { it.exists() }
|
||||
@@ -36,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.fromPath(srcPath)))
|
||||
val module = importModule(SourceCode.File(srcPath))
|
||||
return if(module==null)
|
||||
Err(NoSuchFileException(srcPath.toFile()))
|
||||
else
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
@@ -52,94 +55,105 @@ 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? {
|
||||
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
||||
if(import.directive!="%import" || import.args.size!=1)
|
||||
throw SyntaxError("invalid import directive", import.position)
|
||||
if(!import.args[0].str.isNullOrEmpty() || import.args[0].name==null)
|
||||
throw SyntaxError("%import requires unquoted module name", import.position)
|
||||
val moduleName = import.args[0].name!!
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if (existing!=null)
|
||||
return null // TODO: why return null instead of Module instance?
|
||||
return existing
|
||||
|
||||
var srcCode = tryGetModuleFromResource("$moduleName.p8", compilationTargetName)
|
||||
// try internal library first
|
||||
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName)
|
||||
val importedModule =
|
||||
if (srcCode != null) {
|
||||
println("importing '$moduleName' (from internal ${srcCode.origin})")
|
||||
importModule(srcCode)
|
||||
} else {
|
||||
srcCode = tryGetModuleFromFile(moduleName, importingModule)
|
||||
if (srcCode == null) {
|
||||
errors.err("imported file not found: $moduleName.p8", import.position)
|
||||
return null
|
||||
//throw NoSuchFileException(File("$moduleName.p8"))
|
||||
moduleResourceSrc.fold(
|
||||
success = {
|
||||
println("importing '$moduleName' (from internal ${it.origin})")
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
// try filesystem next
|
||||
val moduleSrc = getModuleFromFile(moduleName, importingModule)
|
||||
moduleSrc.fold(
|
||||
success = {
|
||||
println("importing '$moduleName' (from file ${it.origin})")
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
errors.err("no module found with name $moduleName", import.position)
|
||||
return null
|
||||
}
|
||||
)
|
||||
}
|
||||
importModule(srcCode)
|
||||
}
|
||||
)
|
||||
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
if(importedModule!=null)
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun removeDirectivesFromImportedModule(importedModule: Module) {
|
||||
// Most global directives don't apply for imported modules, so remove them
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%target")
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
var directives = importedModule.statements.filterIsInstance<Directive>()
|
||||
importedModule.statements.removeAll(directives)
|
||||
directives = directives.filter{ it.directive !in moduleLevelDirectives }
|
||||
importedModule.statements.addAll(0, directives)
|
||||
}
|
||||
|
||||
private fun tryGetModuleFromResource(name: String, compilationTargetName: String): SourceCode? {
|
||||
// try target speficic first
|
||||
try {
|
||||
return SourceCode.fromResources("/prog8lib/$compilationTargetName/$name")
|
||||
} catch (e: FileSystemException) {
|
||||
}
|
||||
try {
|
||||
return SourceCode.fromResources("/prog8lib/$name")
|
||||
} catch (e: FileSystemException) {
|
||||
}
|
||||
return null
|
||||
private fun getModuleFromResource(name: String, compilationTargetName: String): Result<SourceCode, NoSuchFileException> {
|
||||
val result =
|
||||
runCatching { SourceCode.Resource("/prog8lib/$compilationTargetName/$name") }
|
||||
.orElse { runCatching { SourceCode.Resource("/prog8lib/$name") } }
|
||||
|
||||
return result.mapError { NoSuchFileException(File(name)) }
|
||||
}
|
||||
|
||||
private fun tryGetModuleFromFile(name: String, importingModule: Module?): SourceCode? {
|
||||
private fun getModuleFromFile(name: String, importingModule: Module?): Result<SourceCode, NoSuchFileException> {
|
||||
val fileName = "$name.p8"
|
||||
val locations =
|
||||
if (importingModule == null) { // <=> imported from library module
|
||||
libpaths
|
||||
sourcePaths
|
||||
} else {
|
||||
libpaths.drop(1) + // TODO: why drop the first?
|
||||
// FIXME: won't work until Prog8Parser is fixed s.t. it fully initialzes the modules it returns
|
||||
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
|
||||
sourcePaths.drop(dropCurDir) +
|
||||
// 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"))
|
||||
}
|
||||
|
||||
locations.forEach {
|
||||
try {
|
||||
return SourceCode.fromPath(it.resolve(fileName))
|
||||
return Ok(SourceCode.File(it.resolve(fileName)))
|
||||
} catch (e: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
//throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: embedded libs and $locations)")
|
||||
return null
|
||||
return Err(NoSuchFileException(File("name")))
|
||||
}
|
||||
}
|
||||
@@ -44,8 +44,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
if(compilerOptions.floats) {
|
||||
if (compilerOptions.zeropage !in setOf(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)
|
||||
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.toplevelModule.position)
|
||||
}
|
||||
|
||||
super.visit(program)
|
||||
@@ -152,11 +152,11 @@ internal class AstChecker(private val program: Program,
|
||||
val to = range.to as? NumericLiteralValue
|
||||
if(from != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, from)
|
||||
else if(!range.from.inferType(program).istype(loopvar.datatype))
|
||||
else if(range.from.inferType(program) isnot loopvar.datatype)
|
||||
errors.err("range start value is incompatible with loop variable type", range.position)
|
||||
if(to != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, to)
|
||||
else if(!range.to.inferType(program).istype(loopvar.datatype))
|
||||
else if(range.to.inferType(program) isnot loopvar.datatype)
|
||||
errors.err("range end value is incompatible with loop variable type", range.position)
|
||||
}
|
||||
}
|
||||
@@ -283,11 +283,11 @@ internal class AstChecker(private val program: Program,
|
||||
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
|
||||
err("number of return registers is not the isSameAs as number of return values")
|
||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||
if(param.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if(param.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE)
|
||||
err("parameter '${param.first.name}' should be (u)byte")
|
||||
}
|
||||
else if(param.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
||||
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
|
||||
err("parameter '${param.first.name}' should be (u)word/address")
|
||||
@@ -298,11 +298,11 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
||||
if(pair.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if(pair.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
|
||||
err("return value #${index + 1} should be (u)byte")
|
||||
}
|
||||
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
else if(pair.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
||||
err("return value #${index + 1} should be (u)word/address")
|
||||
@@ -428,12 +428,12 @@ internal class AstChecker(private val program: Program,
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
if(targetDt.isIterable)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else if(!(valueDt.istype(DataType.STR) && targetDt.istype(DataType.UWORD)))
|
||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
|
||||
errors.err("type of value doesn't match target", assignment.value.position)
|
||||
}
|
||||
|
||||
if(assignment.value is TypecastExpression) {
|
||||
if(assignment.isAugmentable && targetDt.istype(DataType.FLOAT))
|
||||
if(assignment.isAugmentable && targetDt istype DataType.FLOAT)
|
||||
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
|
||||
}
|
||||
|
||||
@@ -518,7 +518,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
// FLOATS enabled?
|
||||
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
|
||||
if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
|
||||
err("floating point used, but that is not enabled via options")
|
||||
|
||||
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
|
||||
@@ -596,7 +596,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||
if (!declValue.inferType(program).istype(decl.datatype)) {
|
||||
if (declValue.inferType(program) isnot decl.datatype) {
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
}
|
||||
}
|
||||
@@ -714,7 +714,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
|
||||
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
}
|
||||
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
|
||||
@@ -726,25 +726,21 @@ internal class AstChecker(private val program: Program,
|
||||
if (File(filename).isFile)
|
||||
return
|
||||
|
||||
var definingModule = directive.parent // TODO: why not just use directive.definingModule() here?
|
||||
while (definingModule !is Module)
|
||||
definingModule = definingModule.parent
|
||||
if (definingModule.isLibrary)
|
||||
val definingModule = directive.definingModule
|
||||
if (definingModule.isLibrary || !definingModule.source.isFromFilesystem)
|
||||
return
|
||||
|
||||
val s = definingModule.source?.pathString()
|
||||
if (s != null) {
|
||||
val sourceFileCandidate = Path(s).resolveSibling(filename).toFile()
|
||||
if (sourceFileCandidate.isFile)
|
||||
return
|
||||
}
|
||||
|
||||
errors.err("included file not found: $filename", directive.position)
|
||||
val s = definingModule.source.origin
|
||||
val sourceFileCandidate = Path(s).resolveSibling(filename).toFile()
|
||||
if (sourceFileCandidate.isFile)
|
||||
return
|
||||
else
|
||||
errors.err("included file not found: $filename", directive.position)
|
||||
}
|
||||
|
||||
override fun visit(array: ArrayLiteralValue) {
|
||||
if(array.type.isKnown) {
|
||||
if (!compilerOptions.floats && array.type.getOr(DataType.UNDEFINED) in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
if (!compilerOptions.floats && array.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.err("floating point used, but that is not enabled via options", array.position)
|
||||
}
|
||||
val arrayspec = ArrayIndex.forArray(array)
|
||||
@@ -919,7 +915,7 @@ internal class AstChecker(private val program: Program,
|
||||
// warn about sgn(unsigned) this is likely a mistake
|
||||
if(functionCall.target.nameInSource.last()=="sgn") {
|
||||
val sgnArgType = functionCall.args.first().inferType(program)
|
||||
if(sgnArgType.istype(DataType.UBYTE) || sgnArgType.istype(DataType.UWORD))
|
||||
if(sgnArgType istype DataType.UBYTE || sgnArgType istype DataType.UWORD)
|
||||
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
|
||||
}
|
||||
|
||||
@@ -988,12 +984,12 @@ internal class AstChecker(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
||||
// sort is not supported on float arrays
|
||||
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
|
||||
if(idref!=null && idref.inferType(program).istype(DataType.ARRAY_F)) {
|
||||
if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
|
||||
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
if(functionCallStatement.target.nameInSource.last() in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
@@ -1122,7 +1118,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
// check index value 0..255
|
||||
val dtxNum = arrayIndexedExpression.indexer.indexExpr.inferType(program)
|
||||
if(!dtxNum.istype(DataType.UBYTE) && !dtxNum.istype(DataType.BYTE))
|
||||
if(dtxNum isnot DataType.UBYTE && dtxNum isnot DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
|
||||
super.visit(arrayIndexedExpression)
|
||||
@@ -1160,7 +1156,7 @@ internal class AstChecker(private val program: Program,
|
||||
when {
|
||||
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
|
||||
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
|
||||
constvalue.type != conditionType.getOr(DataType.UNDEFINED) -> errors.err("choice value datatype differs from condition value", whenChoice.position)
|
||||
conditionType isnot constvalue.type -> errors.err("choice value datatype differs from condition value", whenChoice.position)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1204,7 +1200,7 @@ internal class AstChecker(private val program: Program,
|
||||
DataType.STR -> return err("string value expected")
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
// value may be either a single byte, or a byte arraysize (of all constant values), or a range
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(value.type istype targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
@@ -1223,7 +1219,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
// value may be either a single word, or a word arraysize, or a range
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(value.type istype targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
@@ -1242,7 +1238,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
// value may be either a single float, or a float arraysize
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(value.type istype targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySize = value.value.size
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
if(arrayDt isnot vardecl.datatype) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast !== array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
|
||||
@@ -111,7 +111,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
}
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(!leftDt.istype(parent.datatype)) {
|
||||
if(leftDt isnot parent.datatype) {
|
||||
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val declValue = decl.value
|
||||
if(decl.type==VarDeclType.VAR && declValue!=null) {
|
||||
val valueDt = declValue.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
if(valueDt isnot decl.datatype) {
|
||||
|
||||
// don't add a typecast on an array initializer value
|
||||
if(valueDt.isInteger && decl.datatype in ArrayDatatypes)
|
||||
@@ -182,7 +182,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// warn about any implicit type casts to Float, because that may not be intended
|
||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
if(typecast.implicit && typecast.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||
}
|
||||
return noModifications
|
||||
@@ -217,7 +217,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val subroutine = returnStmt.definingSubroutine!!
|
||||
if(subroutine.returntypes.size==1) {
|
||||
val subReturnType = subroutine.returntypes.first()
|
||||
if (returnValue.inferType(program).istype(subReturnType))
|
||||
if (returnValue.inferType(program) istype subReturnType)
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteralValue) {
|
||||
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||
|
||||
@@ -71,7 +71,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
}
|
||||
|
||||
val sourceDt = typecast.expression.inferType(program)
|
||||
if(sourceDt.istype(typecast.type))
|
||||
if(sourceDt istype typecast.type)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
return noModifications
|
||||
|
||||
@@ -9,7 +9,7 @@ import prog8.compiler.CompilerException
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
|
||||
|
||||
|
||||
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
|
||||
@@ -90,10 +90,10 @@ class FSignature(val name: String,
|
||||
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
|
||||
private val functionSignatures: List<FSignature> = listOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||
@@ -103,46 +103,46 @@ private val functionSignatures: List<FSignature> = listOf(
|
||||
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
|
||||
FSignature("sizeof" , true, listOf(FParam("object", DataType.values())), DataType.UBYTE, ::builtinSizeof),
|
||||
// normal functions follow:
|
||||
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("sin" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
FSignature("sin8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
FSignature("sin8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
FSignature("sin16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
FSignature("sin16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
FSignature("cos" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
FSignature("cos8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
FSignature("cos8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
FSignature("cos16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
FSignature("cos16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
FSignature("tan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
FSignature("atan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
FSignature("ln" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
FSignature("log2" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
FSignature("sqrt" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
FSignature("rad" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
FSignature("deg" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
FSignature("round" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
FSignature("floor" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
FSignature("ceil" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
|
||||
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
|
||||
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
FSignature("peek" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE),
|
||||
FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("poke" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null),
|
||||
FSignature("pokew" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null),
|
||||
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
|
||||
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
|
||||
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
|
||||
FSignature("memory" , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||
FSignature("callfar" , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null),
|
||||
FSignature("callrom" , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null),
|
||||
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
||||
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
||||
|
||||
)
|
||||
|
||||
@@ -305,7 +305,7 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
|
||||
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
|
||||
numericLiteral(memsizer.memorySize(elementDt) * length, position)
|
||||
}
|
||||
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@@ -23,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,8 +33,8 @@ internal object C64Target: ICompilationTarget {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
{ throw it },
|
||||
{ it }
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
@@ -103,8 +61,8 @@ internal object Cx16Target: ICompilationTarget {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
{ throw it },
|
||||
{ it}
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
|
||||
@@ -86,7 +86,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -14,6 +14,7 @@ import prog8.compiler.target.cbm.AssemblyProgram
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
|
||||
import prog8.optimizer.CallGraph
|
||||
import prog8.parser.SourceCode
|
||||
import java.nio.file.Path
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@@ -154,7 +155,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
out("""
|
||||
; zeropage is clobbered so we need to reset the machine at exit
|
||||
lda #>sys.reset_system
|
||||
@@ -1325,8 +1326,9 @@ $repeatLabel lda $counterVar
|
||||
"%asminclude" -> {
|
||||
// TODO: handle %asminclude with SourceCode
|
||||
val includedName = stmt.args[0].str!!
|
||||
val sourcePath = Path(stmt.definingModule.source!!.pathString()) // FIXME: %asminclude inside non-library, non-filesystem module
|
||||
loadAsmIncludeFile(includedName, sourcePath).fold(
|
||||
if(stmt.definingModule.source is SourceCode.Generated)
|
||||
TODO("%asminclude inside non-library, non-filesystem module")
|
||||
loadAsmIncludeFile(includedName, stmt.definingModule.source).fold(
|
||||
success = { assemblyLines.add(it.trimEnd().trimStart('\n')) },
|
||||
failure = { errors.err(it.toString(), stmt.position) }
|
||||
)
|
||||
@@ -1335,11 +1337,13 @@ $repeatLabel lda $counterVar
|
||||
val includedName = stmt.args[0].str!!
|
||||
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
|
||||
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
|
||||
val sourcePath = Path(stmt.definingModule.source!!.pathString()) // FIXME: %asmbinary inside non-library, non-filesystem module
|
||||
if(stmt.definingModule.source is SourceCode.Generated)
|
||||
TODO("%asmbinary inside non-library, non-filesystem module")
|
||||
val sourcePath = Path(stmt.definingModule.source.origin)
|
||||
val includedPath = sourcePath.resolveSibling(includedName)
|
||||
val pathForAssembler = outputDir // #54: 64tass needs the path *relative to the .asm file*
|
||||
.absolute() // avoid IllegalArgumentExc due to non-absolute path .relativize(absolute path)
|
||||
.relativize(includedPath)
|
||||
.toAbsolutePath()
|
||||
.relativize(includedPath.toAbsolutePath())
|
||||
.normalize() // avoid assembler warnings (-Wportable; only some, not all)
|
||||
.toString().replace('\\', '/')
|
||||
out(" .binary \"$pathForAssembler\" $offset $length")
|
||||
@@ -1459,11 +1463,11 @@ $label nop""")
|
||||
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
|
||||
val leftDt = pointerOffsetExpr.left.inferType(program)
|
||||
val rightDt = pointerOffsetExpr.left.inferType(program)
|
||||
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UBYTE))
|
||||
if(leftDt istype DataType.UWORD && rightDt istype DataType.UBYTE)
|
||||
return Pair(pointerOffsetExpr.left, pointerOffsetExpr.right)
|
||||
if(leftDt.istype(DataType.UBYTE) && rightDt.istype(DataType.UWORD))
|
||||
if(leftDt istype DataType.UBYTE && rightDt istype DataType.UWORD)
|
||||
return Pair(pointerOffsetExpr.right, pointerOffsetExpr.left)
|
||||
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UWORD)) {
|
||||
if(leftDt istype DataType.UWORD && rightDt istype DataType.UWORD) {
|
||||
// could be that the index was a constant numeric byte but converted to word, check that
|
||||
val constIdx = pointerOffsetExpr.right.constValue(program)
|
||||
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
|
||||
@@ -1471,10 +1475,10 @@ $label nop""")
|
||||
}
|
||||
// could be that the index was typecasted into uword, check that
|
||||
val rightTc = pointerOffsetExpr.right as? TypecastExpression
|
||||
if(rightTc!=null && rightTc.expression.inferType(program).istype(DataType.UBYTE))
|
||||
if(rightTc!=null && rightTc.expression.inferType(program) istype DataType.UBYTE)
|
||||
return Pair(pointerOffsetExpr.left, rightTc.expression)
|
||||
val leftTc = pointerOffsetExpr.left as? TypecastExpression
|
||||
if(leftTc!=null && leftTc.expression.inferType(program).istype(DataType.UBYTE))
|
||||
if(leftTc!=null && leftTc.expression.inferType(program) istype DataType.UBYTE)
|
||||
return Pair(pointerOffsetExpr.right, leftTc.expression)
|
||||
}
|
||||
|
||||
@@ -1487,7 +1491,7 @@ $label nop""")
|
||||
|
||||
fun evalBytevalueWillClobberA(expr: Expression): Boolean {
|
||||
val dt = expr.inferType(program)
|
||||
if(!dt.istype(DataType.UBYTE) && !dt.istype(DataType.BYTE))
|
||||
if(dt isnot DataType.UBYTE && dt isnot DataType.BYTE)
|
||||
return true
|
||||
return when(expr) {
|
||||
is IdentifierReference -> false
|
||||
|
||||
@@ -715,11 +715,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val firstName = asmgen.asmVariableName(first)
|
||||
val secondName = asmgen.asmVariableName(second)
|
||||
val dt = first.inferType(program)
|
||||
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
|
||||
if(dt istype DataType.BYTE || dt istype DataType.UBYTE) {
|
||||
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
|
||||
if(dt istype DataType.WORD || dt istype DataType.UWORD) {
|
||||
asmgen.out("""
|
||||
ldy $firstName
|
||||
lda $secondName
|
||||
@@ -732,7 +732,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
""")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.FLOAT)) {
|
||||
if(dt istype DataType.FLOAT) {
|
||||
asmgen.out("""
|
||||
lda #<$firstName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
|
||||
@@ -157,11 +157,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
argForCarry = argi
|
||||
}
|
||||
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
|
||||
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||
require(argForXregister==null)
|
||||
argForXregister = argi
|
||||
}
|
||||
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
|
||||
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
|
||||
require(argForAregister == null)
|
||||
argForAregister = argi
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
||||
val position: Position) {
|
||||
|
||||
init {
|
||||
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
|
||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||
"source storage size must be less or equal to target datatype storage size"
|
||||
|
||||
@@ -97,7 +97,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.inferType(program).istype(DataType.FLOAT)) {
|
||||
if(expr.inferType(program) istype DataType.FLOAT) {
|
||||
val subExpr: BinaryExpression? = when {
|
||||
leftconst != null -> expr.right as? BinaryExpression
|
||||
rightconst != null -> expr.left as? BinaryExpression
|
||||
@@ -277,7 +277,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
val numval = decl.value as? NumericLiteralValue
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
if(valueDt isnot decl.datatype) {
|
||||
val cast = numval.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
|
||||
@@ -21,7 +21,7 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
|
||||
try {
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& !declConstValue.inferType(program).istype(decl.datatype)) {
|
||||
&& declConstValue.inferType(program) isnot decl.datatype) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
|
||||
@@ -45,7 +45,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||
}
|
||||
} else {
|
||||
if (typecast.expression.inferType(program).istype(typecast.type)) {
|
||||
if (typecast.expression.inferType(program) istype typecast.type) {
|
||||
// remove duplicate cast
|
||||
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
|
||||
}
|
||||
@@ -289,13 +289,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val arg = functionCall.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
|
||||
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
|
||||
// useless lsb() of byte value that was typecasted to word
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
|
||||
}
|
||||
} else {
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
|
||||
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
|
||||
// useless lsb() of byte value
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
|
||||
}
|
||||
@@ -305,7 +305,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val arg = functionCall.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
|
||||
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
|
||||
// useless msb() of byte value that was typecasted to word, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
@@ -314,7 +314,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
} else {
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
|
||||
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
|
||||
// useless msb() of byte value, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -80,20 +81,20 @@ internal class UnusedCodeRemover(private val program: Program,
|
||||
val forceOutput = "force_output" in subroutine.definingBlock.options()
|
||||
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
|
||||
if(callgraph.unused(subroutine)) {
|
||||
if(subroutine.containsNoCodeNorVars) {
|
||||
if(!subroutine.definingModule.isLibrary)
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope))
|
||||
callgraph.calledBy[subroutine]?.let {
|
||||
for(node in it)
|
||||
removals.add(IAstModification.Remove(node, node.definingScope))
|
||||
}
|
||||
return removals
|
||||
}
|
||||
if(!subroutine.definingModule.isLibrary)
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope))
|
||||
}
|
||||
if(subroutine.containsNoCodeNorVars) {
|
||||
if(!subroutine.definingModule.isLibrary)
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope))
|
||||
callgraph.calledBy[subroutine]?.let {
|
||||
for(node in it)
|
||||
removals.add(IAstModification.Remove(node, node.definingScope))
|
||||
}
|
||||
return removals
|
||||
}
|
||||
}
|
||||
|
||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
||||
@@ -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) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import prog8.compiler.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import java.nio.file.Path
|
||||
@@ -67,10 +68,10 @@ locallabel:
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
|
||||
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
|
||||
|
||||
val module = Module("test", mutableListOf(block), Position.DUMMY, null)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -78,7 +79,7 @@ locallabel:
|
||||
val errors = ErrorReporter()
|
||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
|
||||
val zp = C64MachineDefinition.C64Zeropage(options)
|
||||
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of("."))
|
||||
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of(""))
|
||||
return asmgen
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ 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
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
@@ -191,7 +193,7 @@ class TestModuleImporter {
|
||||
|
||||
repeat(2) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(srcPath.absolutePathString()))
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
@@ -206,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)
|
||||
}
|
||||
@@ -221,12 +222,13 @@ class TestModuleImporter {
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(imported.absolutePathString()))
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,14 +252,14 @@ class TestModuleImporter {
|
||||
val result = importer.importLibraryModule(filenameNoExt)
|
||||
assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue()))
|
||||
assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension")
|
||||
assertEquals(errors.errors.single(), "imported file not found: i_do_not_exist.p8")
|
||||
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist")
|
||||
errors.report()
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
|
||||
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(), "imported file not found: i_do_not_exist.p8.p8") // TODO don't duplicate the 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))
|
||||
}
|
||||
@@ -277,7 +279,7 @@ class TestModuleImporter {
|
||||
repeat(2) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call")
|
||||
{ importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
assertThat(it.position.file, equalTo(srcPath.absolutePathString()))
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
@@ -297,12 +299,14 @@ class TestModuleImporter {
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(imported.normalize().absolutePathString()))
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.optimizer.CallGraph
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCallgraph {
|
||||
@Test
|
||||
fun testGraphForEmptySubs() {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
sub empty() {
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.programAst)
|
||||
|
||||
assertEquals(1, graph.imports.size)
|
||||
assertEquals(1, graph.importedBy.size)
|
||||
val toplevelModule = result.programAst.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
assertEquals("string", importedModule.name)
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
|
||||
|
||||
assertFalse(graph.unused(toplevelModule))
|
||||
assertFalse(graph.unused(importedModule))
|
||||
|
||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||
for(stmt in mainBlock.statements) {
|
||||
val sub = stmt as Subroutine
|
||||
assertFalse(sub in graph.calls)
|
||||
assertFalse(sub in graph.calledBy)
|
||||
|
||||
if(sub === result.programAst.entrypoint)
|
||||
assertFalse(graph.unused(sub), "start() should always be marked as used to avoid having it removed")
|
||||
else
|
||||
assertTrue(graph.unused(sub))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGraphForEmptyButReferencedSub() {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
sub start() {
|
||||
uword xx = &empty
|
||||
xx++
|
||||
}
|
||||
sub empty() {
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.programAst)
|
||||
|
||||
assertEquals(1, graph.imports.size)
|
||||
assertEquals(1, graph.importedBy.size)
|
||||
val toplevelModule = result.programAst.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
assertEquals("string", importedModule.name)
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
|
||||
|
||||
assertFalse(graph.unused(toplevelModule))
|
||||
assertFalse(graph.unused(importedModule))
|
||||
|
||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
|
||||
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
|
||||
|
||||
assertTrue(startSub in graph.calls, "start 'calls' (references) empty")
|
||||
assertFalse(emptySub in graph.calls, "empty doesn't call anything")
|
||||
assertTrue(emptySub in graph.calledBy, "empty gets 'called'")
|
||||
assertFalse(startSub in graph.calledBy, "start doesn't get called (except as entrypoint ofc.)")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
@@ -18,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")
|
||||
@@ -41,14 +42,14 @@ class TestCompilerOnExamples {
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget = platform.name,
|
||||
libdirs = listOf(),
|
||||
sourceDirs = listOf(),
|
||||
outputDir
|
||||
).assertSuccess("; $displayName")
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
// @Disabled
|
||||
// @Disabled("disable to save some time")
|
||||
fun bothCx16AndC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"animals",
|
||||
@@ -79,7 +80,7 @@ class TestCompilerOnExamples {
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled
|
||||
// @Disabled("disable to save some time")
|
||||
fun onlyC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"balloonflight",
|
||||
@@ -98,7 +99,7 @@ class TestCompilerOnExamples {
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled
|
||||
// @Disabled("disable to save some time")
|
||||
fun onlyCx16() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"vtui/testvtui",
|
||||
|
||||
@@ -46,29 +46,6 @@ class TestCompilerOnImportsAndIncludes {
|
||||
assertEquals("main", strLits[0].definingScope.name)
|
||||
assertEquals("foo", strLits[1].definingScope.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: why would we not accept string literals as argument to %import?")
|
||||
fun testImportFromSameFolder_strLit() {
|
||||
val filepath = assumeReadableFile(fixturesDir,"importFromSameFolder_strLit.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "foo_bar.p8")
|
||||
|
||||
val platform = Cx16Target
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
||||
.assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val strLits = startSub.statements
|
||||
.filterIsInstance<FunctionCallStatement>()
|
||||
.map { it.args[0] as IdentifierReference }
|
||||
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
|
||||
|
||||
assertEquals("main.bar", strLits[0].value)
|
||||
assertEquals("foo.bar", strLits[1].value)
|
||||
assertEquals("main", strLits[0].definingScope.name)
|
||||
assertEquals("foo", strLits[1].definingScope.name)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
@@ -19,7 +19,7 @@ import kotlin.io.path.writeText
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOptionLibdirs {
|
||||
class TestCompilerOptionSourcedirs {
|
||||
|
||||
private lateinit var tempFileInWorkingDir: Path
|
||||
|
||||
@@ -39,14 +39,14 @@ class TestCompilerOptionLibdirs {
|
||||
tempFileInWorkingDir.deleteExisting()
|
||||
}
|
||||
|
||||
private fun compileFile(filePath: Path, libdirs: List<String>) =
|
||||
private fun compileFile(filePath: Path, sourceDirs: List<String>) =
|
||||
compileProgram(
|
||||
filepath = filePath,
|
||||
optimize = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget = Cx16Target.name,
|
||||
libdirs,
|
||||
sourceDirs,
|
||||
outputDir
|
||||
)
|
||||
|
||||
@@ -65,7 +65,7 @@ class TestCompilerOptionLibdirs {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeTo1stInLibdirs() {
|
||||
fun testFilePathInWorkingDirRelativeTo1stInSourcedirs() {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(workingDir.toString()))
|
||||
.assertSuccess()
|
||||
@@ -86,10 +86,10 @@ class TestCompilerOptionLibdirs {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeTo1stInLibdirs() {
|
||||
fun testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val libdirs = listOf("$fixturesDir")
|
||||
compileFile(filepath.fileName, libdirs)
|
||||
val sourcedirs = listOf("$fixturesDir")
|
||||
compileFile(filepath.fileName, sourcedirs)
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+37
-39
@@ -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,7 +12,9 @@ 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
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import kotlin.test.assertFalse
|
||||
@@ -28,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
|
||||
@@ -53,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
|
||||
@@ -74,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 {
|
||||
@@ -91,8 +90,8 @@ class TestMemory {
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
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("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
module.linkParents(program.namespace)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
module.linkIntoProgram(program)
|
||||
return target
|
||||
}
|
||||
|
||||
@@ -100,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
|
||||
@@ -110,11 +108,11 @@ class TestMemory {
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
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("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
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
|
||||
@@ -124,11 +122,11 @@ class TestMemory {
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
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("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
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
|
||||
@@ -138,11 +136,11 @@ class TestMemory {
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
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("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
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
|
||||
@@ -152,11 +150,11 @@ class TestMemory {
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
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("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
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
|
||||
@@ -167,11 +165,11 @@ class TestMemory {
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
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("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
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
|
||||
@@ -182,10 +180,10 @@ class TestMemory {
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
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("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package prog8tests
|
||||
|
||||
import org.hamcrest.CoreMatchers.instanceOf
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Return
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertSame
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestOptimization {
|
||||
@Test
|
||||
fun testRemoveEmptySubroutineExceptStart() {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
sub empty() {
|
||||
; going to be removed
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
||||
val toplevelModule = result.programAst.toplevelModule
|
||||
val mainBlock = toplevelModule.statements.single() as Block
|
||||
val startSub = mainBlock.statements.single() as Subroutine
|
||||
assertSame(result.programAst.entrypoint, startSub)
|
||||
assertEquals("start", startSub.name, "only start sub should remain")
|
||||
assertTrue(startSub.statements.single() is Return, "compiler has inserted return in empty subroutines")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDontRemoveEmptySubroutineIfItsReferenced() {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
uword xx = &empty
|
||||
xx++
|
||||
}
|
||||
sub empty() {
|
||||
; should not be removed
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
||||
val toplevelModule = result.programAst.toplevelModule
|
||||
val mainBlock = toplevelModule.statements.single() as Block
|
||||
val startSub = mainBlock.statements[0] as Subroutine
|
||||
val emptySub = mainBlock.statements[1] as Subroutine
|
||||
assertSame(result.programAst.entrypoint, startSub)
|
||||
assertEquals("start", startSub.name)
|
||||
assertEquals("empty", emptySub.name)
|
||||
assertTrue(emptySub.statements.single() is Return, "compiler has inserted return in empty subroutines")
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8tests
|
||||
|
||||
import com.github.michaelbull.result.Ok
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -9,8 +10,6 @@ import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.left
|
||||
import prog8.right
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@@ -19,8 +18,8 @@ class TestPetscii {
|
||||
|
||||
@Test
|
||||
fun testZero() {
|
||||
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(right(listOf<Short>(0))))
|
||||
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(right(listOf<Short>(0))))
|
||||
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(Ok(listOf<Short>(0))))
|
||||
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(Ok(listOf<Short>(0))))
|
||||
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
|
||||
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
|
||||
}
|
||||
@@ -28,11 +27,11 @@ class TestPetscii {
|
||||
@Test
|
||||
fun testLowercase() {
|
||||
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
|
||||
right(listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
||||
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(right(listOf<Short>(0x12)))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("✓", true), equalTo(right(listOf<Short>(0xfa))))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(right(listOf<Short>(255))))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(right(listOf<Short>(0xd3))))
|
||||
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
||||
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("✓", true), equalTo(Ok(listOf<Short>(0xfa))))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(Ok(listOf<Short>(255))))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(Ok(listOf<Short>(0xd3))))
|
||||
|
||||
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
|
||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
|
||||
@@ -42,11 +41,11 @@ class TestPetscii {
|
||||
@Test
|
||||
fun testUppercase() {
|
||||
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
|
||||
right(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
||||
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(right(listOf<Short>(0x12)))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("♥"), equalTo(right(listOf<Short>(0xd3))))
|
||||
assertThat(Petscii.encodePetscii("π"), equalTo(right(listOf<Short>(0xff))))
|
||||
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(right(listOf<Short>(250))))
|
||||
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
||||
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("♥"), equalTo(Ok(listOf<Short>(0xd3))))
|
||||
assertThat(Petscii.encodePetscii("π"), equalTo(Ok(listOf<Short>(0xff))))
|
||||
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(Ok(listOf<Short>(250))))
|
||||
|
||||
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
|
||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
|
||||
@@ -56,11 +55,11 @@ class TestPetscii {
|
||||
@Test
|
||||
fun testScreencodeLowercase() {
|
||||
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
|
||||
right(listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
|
||||
Ok(listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
|
||||
))
|
||||
assertThat(Petscii.encodeScreencode("✓", true), equalTo(right(listOf<Short>(0x7a))))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(right(listOf<Short>(83))))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(right(listOf<Short>(94))))
|
||||
assertThat(Petscii.encodeScreencode("✓", true), equalTo(Ok(listOf<Short>(0x7a))))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(Ok(listOf<Short>(83))))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(Ok(listOf<Short>(94))))
|
||||
|
||||
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
|
||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
|
||||
@@ -70,12 +69,12 @@ class TestPetscii {
|
||||
@Test
|
||||
fun testScreencodeUppercase() {
|
||||
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
|
||||
right(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))))
|
||||
assertThat(Petscii.encodeScreencode("♥"), equalTo(right(listOf<Short>(0x53))))
|
||||
assertThat(Petscii.encodeScreencode("π"), equalTo(right(listOf<Short>(0x5e))))
|
||||
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(right(listOf<Short>(8, 5, 12, 12, 15))))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(right(listOf<Short>(8, 5, 12, 12, 15))))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(right(listOf<Short>(122))))
|
||||
Ok(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))))
|
||||
assertThat(Petscii.encodeScreencode("♥"), equalTo(Ok(listOf<Short>(0x53))))
|
||||
assertThat(Petscii.encodeScreencode("π"), equalTo(Ok(listOf<Short>(0x5e))))
|
||||
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(Ok(listOf<Short>(122))))
|
||||
|
||||
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
|
||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
%import textio
|
||||
%import "foo_bar.p8"
|
||||
main {
|
||||
str myBar = "main.bar"
|
||||
sub start() {
|
||||
txt.print(myBar)
|
||||
txt.print(foo.bar)
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ internal fun compileFile(
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
platform.name,
|
||||
libdirs = listOf(),
|
||||
sourceDirs = listOf(),
|
||||
outputDir
|
||||
)
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
override fun visit(assignment: Assignment) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null && binExpr.left isSameAs assignment.target
|
||||
&& binExpr.operator !in setOf("and", "or", "xor")
|
||||
&& binExpr.operator !in arrayOf("and", "or", "xor")
|
||||
&& binExpr.operator !in comparisonOperators) {
|
||||
// we only support the inplace assignments of the form A = A <operator> <value>
|
||||
assignment.target.accept(this)
|
||||
|
||||
@@ -246,7 +246,7 @@ class Program(val name: String,
|
||||
|
||||
init {
|
||||
// insert a container module for all interned strings later
|
||||
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, null)
|
||||
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(internedStringsModuleName))
|
||||
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
|
||||
internedStringsModule.statements.add(block)
|
||||
|
||||
@@ -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,19 +343,22 @@ 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(override val name: String,
|
||||
override var statements: MutableList<Statement>,
|
||||
open class Module(final override var statements: MutableList<Statement>,
|
||||
final override val position: Position,
|
||||
val source: SourceCode?) : Node, INameScope {
|
||||
val source: SourceCode) : Node, INameScope {
|
||||
|
||||
override lateinit var parent: Node
|
||||
lateinit var program: Program
|
||||
|
||||
override val name = source.origin
|
||||
.substringBeforeLast(".")
|
||||
.substringAfterLast("/")
|
||||
.substringAfterLast("\\")
|
||||
|
||||
val loadAddress: Int by lazy {
|
||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
||||
address
|
||||
@@ -366,6 +370,11 @@ open class Module(override val name: String,
|
||||
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) {
|
||||
@@ -380,7 +389,7 @@ open class Module(override val name: String,
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
val isLibrary get() = (source == null) || source.isFromResources
|
||||
val isLibrary get() = source.isFromResources
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.parser.Prog8ANTLRParser
|
||||
import prog8.parser.SourceCode
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
|
||||
/***************** Antlr Extension methods to create AST ****************/
|
||||
@@ -14,17 +17,17 @@ private data class NumericLiteral(val number: Number, val datatype: DataType)
|
||||
|
||||
|
||||
private fun ParserRuleContext.toPosition() : Position {
|
||||
/*
|
||||
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||
val filename =
|
||||
when {
|
||||
customTokensource!=null -> customTokensource.modulePath.toString()
|
||||
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
||||
else -> File(start.inputStream.sourceName).name
|
||||
}
|
||||
*/
|
||||
val filename = start.inputStream.sourceName
|
||||
|
||||
val pathString = start.inputStream.sourceName
|
||||
val filename = if(SourceCode.isRegularFilesystemPath(pathString)) {
|
||||
val path = Path.of(pathString)
|
||||
if(path.isRegularFile()) {
|
||||
SourceCode.relative(path).toString()
|
||||
} else {
|
||||
path.toString()
|
||||
}
|
||||
} else {
|
||||
pathString
|
||||
}
|
||||
// note: beware of TAB characters in the source text, they count as 1 column...
|
||||
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
|
||||
}
|
||||
|
||||
@@ -24,16 +24,17 @@ enum class DataType {
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(this) {
|
||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||
UWORD -> targetType in setOf(UWORD, FLOAT)
|
||||
WORD -> targetType in setOf(WORD, FLOAT)
|
||||
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
|
||||
UWORD -> targetType.oneOf(UWORD, FLOAT)
|
||||
WORD -> targetType.oneOf(WORD, FLOAT)
|
||||
FLOAT -> targetType == FLOAT
|
||||
STR -> targetType == STR
|
||||
in ArrayDatatypes -> targetType == this
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun oneOf(vararg types: DataType) = this in types
|
||||
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
|
||||
infix fun isNotAssignableTo(targetType: DataType) = !this.isAssignableTo(targetType)
|
||||
infix fun isNotAssignableTo(targetTypes: Set<DataType>) = !this.isAssignableTo(targetTypes)
|
||||
@@ -127,13 +128,13 @@ enum class VarDeclType {
|
||||
MEMORY
|
||||
}
|
||||
|
||||
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
|
||||
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
|
||||
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||
val StringlyDatatypes = setOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val IterableDatatypes = setOf(
|
||||
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE)
|
||||
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
||||
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val IterableDatatypes = arrayOf(
|
||||
DataType.STR,
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W,
|
||||
@@ -156,7 +157,7 @@ val ElementToArrayTypes = mapOf(
|
||||
DataType.UWORD to DataType.ARRAY_UW,
|
||||
DataType.FLOAT to DataType.ARRAY_F
|
||||
)
|
||||
val Cx16VirtualRegisters = listOf(
|
||||
val Cx16VirtualRegisters = arrayOf(
|
||||
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
||||
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
|
||||
|
||||
@@ -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 {
|
||||
@@ -638,7 +638,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
}
|
||||
|
||||
fun cast(targettype: DataType): ArrayLiteralValue? {
|
||||
if(type.istype(targettype))
|
||||
if(type istype targettype)
|
||||
return this
|
||||
if(targettype in ArrayDatatypes) {
|
||||
val elementType = ArrayToElementTypes.getValue(targettype)
|
||||
@@ -797,7 +797,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
args.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override val isSimple = target.nameInSource.size==1 && (target.nameInSource[0] in setOf("msb", "lsb", "peek", "peekw"))
|
||||
override val isSimple = target.nameInSource.size==1 && (target.nameInSource[0] in arrayOf("msb", "lsb", "peek", "peekw"))
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(node===target)
|
||||
|
||||
@@ -15,6 +15,8 @@ object InferredTypes {
|
||||
fun getOrElse(transform: (InferredType) -> DataType): DataType =
|
||||
if(isUnknown || isVoid) transform(this) else datatype!!
|
||||
infix fun istype(type: DataType): Boolean = if(isUnknown || isVoid) false else this.datatype==type
|
||||
infix fun isnot(type: DataType): Boolean = if(isUnknown || isVoid) true else this.datatype!=type
|
||||
fun oneOf(vararg types: DataType) = if(isUnknown || isVoid) false else this.datatype in types
|
||||
|
||||
companion object {
|
||||
fun unknown() = InferredType(isUnknown = true, isVoid = false, datatype = null)
|
||||
|
||||
@@ -648,8 +648,8 @@ class Subroutine(override val name: String,
|
||||
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
||||
}
|
||||
|
||||
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
fun shouldSaveX() = CpuRegister.X in asmClobbers || regXasResult() || regXasParam()
|
||||
|
||||
class KeepAresult(val saveOnEntry: Boolean, val saveOnReturn: Boolean)
|
||||
|
||||
@@ -31,28 +31,17 @@ 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
|
||||
}
|
||||
|
||||
private class ParsedModule(source: SourceCode) : Module(
|
||||
// FIXME: hacking together a name for the module:
|
||||
name = source.pathString()
|
||||
.substringBeforeLast(".") // must also work with an origin = "<String@123beef>"
|
||||
.substringAfterLast("/")
|
||||
.substringAfterLast("\\")
|
||||
.replace("String@", "anonymous_"),
|
||||
statements = mutableListOf(),
|
||||
position = Position(source.origin, 1, 0, 0),
|
||||
source
|
||||
) {
|
||||
private class ParsedModule(source: SourceCode) :
|
||||
Module(mutableListOf(), Position(source.origin, 1, 0, 0), source)
|
||||
{
|
||||
|
||||
/**
|
||||
* Adds a [Directive] to [statements] and
|
||||
@@ -62,7 +51,7 @@ object Prog8Parser {
|
||||
fun add(child: Directive) {
|
||||
child.linkParents(this)
|
||||
statements.add(child)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Adds a [Block] to [statements] and
|
||||
* sets this Module as its [parent].
|
||||
@@ -71,7 +60,7 @@ object Prog8Parser {
|
||||
fun add(child: Block) {
|
||||
child.linkParents(this)
|
||||
statements.add(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object Prog8ErrorStrategy: BailErrorStrategy() {
|
||||
@@ -103,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,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)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@ package prog8.parser
|
||||
import org.antlr.v4.runtime.CharStream
|
||||
import org.antlr.v4.runtime.CharStreams
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.channels.Channels
|
||||
import java.nio.charset.CodingErrorAction
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.isReadable
|
||||
@@ -13,7 +16,7 @@ import kotlin.io.path.isReadable
|
||||
* Encapsulates - and ties together - actual source code (=text)
|
||||
* and its [origin].
|
||||
*/
|
||||
abstract class SourceCode {
|
||||
sealed class SourceCode {
|
||||
|
||||
/**
|
||||
* To be used *only* by the parser (as input to a TokenStream).
|
||||
@@ -22,29 +25,23 @@ abstract class SourceCode {
|
||||
internal abstract fun getCharStream(): CharStream
|
||||
|
||||
/**
|
||||
* Whether this [SourceCode] instance was created by
|
||||
* factory method [fromResources]
|
||||
* Whether this [SourceCode] instance was created as a [Resource]
|
||||
*/
|
||||
abstract val isFromResources: Boolean
|
||||
|
||||
/**
|
||||
* Where this [SourceCode] instance came from.
|
||||
* This can be one of the following:
|
||||
* * a normal string representation of a [java.nio.file.Path], if it originates from a file (see [fromPath])
|
||||
* * `<String@44c56085>` if was created via [of]
|
||||
* * `library:/x/y/z.ext` if it is a library file that was loaded from resources (see [fromResources])
|
||||
* Whether this [SourceCode] instance was created as a [File]
|
||||
*/
|
||||
abstract val origin: String
|
||||
|
||||
abstract val isFromFilesystem: Boolean
|
||||
|
||||
/**
|
||||
* FIXME: hacking together a [SourceCode]'s "path string"
|
||||
* This is really just [origin] with any stuff removed that would render it an invalid path name.
|
||||
* (Note: a *valid* path name does NOT mean that the denoted file or folder *exists*)
|
||||
* Where this [SourceCode] instance came from.
|
||||
* This can be one of the following:
|
||||
* * a normal string representation of a [java.nio.file.Path], if it originates from a file (see [File])
|
||||
* * `$stringSourcePrefix44c56085>` if was created via [String]
|
||||
* * `library:/x/y/z.ext` if it is a library file that was loaded from resources (see [Resource])
|
||||
*/
|
||||
fun pathString() =
|
||||
origin
|
||||
.substringAfter("<").substringBeforeLast(">") // or from plain string?
|
||||
abstract val origin: String
|
||||
|
||||
/**
|
||||
* The source code as plain string.
|
||||
@@ -58,42 +55,45 @@ abstract class SourceCode {
|
||||
*/
|
||||
final override fun toString() = "${this.javaClass.name}[${this.origin}]"
|
||||
|
||||
// "static" factory methods
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* filename prefix to designate library files that will be retreived from internal resources rather than disk
|
||||
*/
|
||||
const val libraryFilePrefix = "library:"
|
||||
const val stringSourcePrefix = "<String@"
|
||||
val curdir: Path = Path.of(".").toAbsolutePath()
|
||||
fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath())
|
||||
fun isRegularFilesystemPath(pathString: String) =
|
||||
!(pathString.startsWith(libraryFilePrefix) || pathString.startsWith(stringSourcePrefix))
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a plain String into a [SourceCode] object.
|
||||
* [origin] will be something like `<String@44c56085>`.
|
||||
*/
|
||||
fun of(text: String): SourceCode {
|
||||
return object : SourceCode() {
|
||||
override val isFromResources = false
|
||||
override val origin = "<String@${System.identityHashCode(text).toString(16)}>"
|
||||
override fun getCharStream(): CharStream {
|
||||
return CharStreams.fromString(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Turn a plain String into a [SourceCode] object.
|
||||
* [origin] will be something like `$stringSourcePrefix44c56085>`.
|
||||
*/
|
||||
class Text(val text: String): SourceCode() {
|
||||
override val isFromResources = false
|
||||
override val isFromFilesystem = false
|
||||
override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}>"
|
||||
override fun getCharStream(): CharStream = CharStreams.fromString(text, origin)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get [SourceCode] from the file represented by the specified Path.
|
||||
* This does not actually *access* the file, but it does check
|
||||
* whether it
|
||||
* * exists
|
||||
* * is a regular file (ie: not a directory)
|
||||
* * and is actually readable
|
||||
*
|
||||
* [origin] will be the given path in absolute and normalized form.
|
||||
* @throws NoSuchFileException if the file does not exist
|
||||
* @throws AccessDeniedException if the given path points to a directory or the file is non-readable for some other reason
|
||||
*/
|
||||
fun fromPath(path: Path): SourceCode {
|
||||
val normalized = path.normalize()
|
||||
/**
|
||||
* Get [SourceCode] from the file represented by the specified Path.
|
||||
* This does not actually *access* the file, but it does check
|
||||
* whether it
|
||||
* * exists
|
||||
* * is a regular file (ie: not a directory)
|
||||
* * and is actually readable
|
||||
*
|
||||
* [origin] will be the given path in absolute and normalized form.
|
||||
* @throws NoSuchFileException if the file does not exist
|
||||
* @throws AccessDeniedException if the given path points to a directory or the file is non-readable for some other reason
|
||||
*/
|
||||
class File(path: Path): SourceCode() {
|
||||
private val normalized = path.normalize()
|
||||
init {
|
||||
val file = normalized.toFile()
|
||||
if (!path.exists())
|
||||
throw NoSuchFileException(file)
|
||||
@@ -101,40 +101,53 @@ abstract class SourceCode {
|
||||
throw AccessDeniedException(file, reason = "Not a file but a directory")
|
||||
if (!path.isReadable())
|
||||
throw AccessDeniedException(file, reason = "Is not readable")
|
||||
return object : SourceCode() {
|
||||
override val isFromResources = false
|
||||
override val origin = normalized.absolutePathString()
|
||||
override fun getCharStream(): CharStream {
|
||||
return CharStreams.fromPath(normalized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [origin]: `<library:/x/y/z.p8>` for a given `pathString` of "x/y/z.p8"
|
||||
*/
|
||||
fun fromResources(pathString: String): SourceCode {
|
||||
val path = Path.of(pathString).normalize()
|
||||
val sep = "/"
|
||||
val normalized = sep + path.toMutableList().joinToString(sep)
|
||||
val rscURL = object{}.javaClass.getResource(normalized)
|
||||
override val isFromResources = false
|
||||
override val isFromFilesystem = true
|
||||
override val origin = relative(normalized).toString()
|
||||
override fun getCharStream(): CharStream = CharStreams.fromPath(normalized)
|
||||
}
|
||||
|
||||
/**
|
||||
* [origin]: `library:/x/y/z.p8` for a given `pathString` of "x/y/z.p8"
|
||||
*/
|
||||
class Resource(pathString: String): SourceCode() {
|
||||
private val normalized = "/" + Path.of(pathString).normalize().toMutableList().joinToString("/")
|
||||
|
||||
init {
|
||||
val rscURL = object {}.javaClass.getResource(normalized)
|
||||
if (rscURL == null) {
|
||||
val rscRoot = object{}.javaClass.getResource("/")
|
||||
val rscRoot = object {}.javaClass.getResource("/")
|
||||
throw NoSuchFileException(
|
||||
File(normalized),
|
||||
reason = "looked in resources rooted at $rscRoot")
|
||||
}
|
||||
return object : SourceCode() {
|
||||
override val isFromResources = true
|
||||
override val origin = "$libraryFilePrefix$normalized"
|
||||
override fun getCharStream(): CharStream {
|
||||
val inpStr = object {}.javaClass.getResourceAsStream(normalized)
|
||||
return CharStreams.fromStream(inpStr)
|
||||
}
|
||||
reason = "looked in resources rooted at $rscRoot"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: possibly more, like fromURL(..)
|
||||
override val isFromResources = true
|
||||
override val isFromFilesystem = false
|
||||
override val origin = "$libraryFilePrefix$normalized"
|
||||
override fun getCharStream(): CharStream {
|
||||
val inpStr = object {}.javaClass.getResourceAsStream(normalized)!!
|
||||
// CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api
|
||||
val channel = Channels.newChannel(inpStr)
|
||||
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SourceCode for internally generated nodes (usually Modules)
|
||||
*/
|
||||
class Generated(name: String) : SourceCode() {
|
||||
override fun getCharStream(): CharStream = throw IOException("generated code nodes doesn't have a stream to read")
|
||||
override val isFromResources: Boolean = false
|
||||
override val isFromFilesystem: Boolean = false
|
||||
override val origin: String = name
|
||||
}
|
||||
|
||||
// TODO: possibly more, like fromURL(..)
|
||||
/* // For `jar:..` URLs
|
||||
// see https://stackoverflow.com/questions/22605666/java-access-files-in-jar-causes-java-nio-file-filesystemnotfoundexception
|
||||
var url = URL("jar:file:/E:/x16/prog8(meisl)/compiler/build/libs/prog8compiler-7.0-BETA3-all.jar!/prog8lib/c64/textio.p8")
|
||||
@@ -143,5 +156,4 @@ abstract class SourceCode {
|
||||
val fs = FileSystems.newFileSystem(URI.create(parts[0]), mutableMapOf(Pair("", "")) )
|
||||
val path = fs.getPath(parts[1])
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class TestAstToSourceCode {
|
||||
private fun roundTrip(module: Module): Pair<String, Module> {
|
||||
val generatedText = generateP8(module)
|
||||
try {
|
||||
val parsedAgain = parseModule(SourceCode.of(generatedText))
|
||||
val parsedAgain = parseModule(SourceCode.Text(generatedText))
|
||||
return Pair(generatedText, parsedAgain)
|
||||
} catch (e: ParseError) {
|
||||
assert(false) { "should produce valid Prog8 but threw $e" }
|
||||
@@ -41,7 +41,7 @@ class TestAstToSourceCode {
|
||||
|
||||
@Test
|
||||
fun testMentionsInternedStringsModule() {
|
||||
val orig = SourceCode.of("\n")
|
||||
val orig = SourceCode.Text("\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex(";.*$internedStringsModuleName"))
|
||||
@@ -49,7 +49,7 @@ class TestAstToSourceCode {
|
||||
|
||||
@Test
|
||||
fun testImportDirectiveWithLib() {
|
||||
val orig = SourceCode.of("%import textio\n")
|
||||
val orig = SourceCode.Text("%import textio\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("%import +textio"))
|
||||
@@ -57,7 +57,7 @@ class TestAstToSourceCode {
|
||||
|
||||
@Test
|
||||
fun testImportDirectiveWithUserModule() {
|
||||
val orig = SourceCode.of("%import my_own_stuff\n")
|
||||
val orig = SourceCode.Text("%import my_own_stuff\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("%import +my_own_stuff"))
|
||||
@@ -66,7 +66,7 @@ class TestAstToSourceCode {
|
||||
|
||||
@Test
|
||||
fun testStringLiteral_noAlt() {
|
||||
val orig = SourceCode.of("""
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
str s = "fooBar\n"
|
||||
}
|
||||
@@ -78,7 +78,7 @@ class TestAstToSourceCode {
|
||||
|
||||
@Test
|
||||
fun testStringLiteral_withAlt() {
|
||||
val orig = SourceCode.of("""
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
str sAlt = @"fooBar\n"
|
||||
}
|
||||
@@ -90,7 +90,7 @@ class TestAstToSourceCode {
|
||||
|
||||
@Test
|
||||
fun testCharLiteral_noAlt() {
|
||||
val orig = SourceCode.of("""
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
ubyte c = 'x'
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class TestAstToSourceCode {
|
||||
|
||||
@Test
|
||||
fun testCharLiteral_withAlt() {
|
||||
val orig = SourceCode.of("""
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
ubyte cAlt = @'x'
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Disabled
|
||||
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
|
||||
@@ -18,13 +18,11 @@ import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.assumeNotExists
|
||||
import prog8tests.helpers.assumeReadableFile
|
||||
import prog8tests.helpers.fixturesDir
|
||||
import kotlin.io.path.absolutePathString
|
||||
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)
|
||||
@@ -39,7 +37,7 @@ class TestProg8Parser {
|
||||
@Test
|
||||
fun `is not required - #40, fixed by #45`() {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
val src = SourceCode.of("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
|
||||
val src = SourceCode.Text("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
|
||||
|
||||
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
|
||||
val module = parseModule(src)
|
||||
@@ -50,7 +48,7 @@ class TestProg8Parser {
|
||||
fun `is still accepted - #40, fixed by #45`() {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
}
|
||||
@@ -65,22 +63,22 @@ class TestProg8Parser {
|
||||
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed
|
||||
val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
|
||||
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.of(srcBad)) }
|
||||
val module = parseModule(SourceCode.of(srcGood))
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcBad)) }
|
||||
val module = parseModule(SourceCode.Text(srcGood))
|
||||
assertEquals(2, module.statements.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is required between two Blocks or Directives - #47`() {
|
||||
// block and block
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
|
||||
blockA {
|
||||
} blockB {
|
||||
}
|
||||
""")) }
|
||||
|
||||
// block and directive
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
|
||||
blockB {
|
||||
} %import textio
|
||||
""")) }
|
||||
@@ -89,12 +87,12 @@ class TestProg8Parser {
|
||||
// Leaving them in anyways.
|
||||
|
||||
// dir and block
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
|
||||
%import textio blockB {
|
||||
}
|
||||
""")) }
|
||||
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
|
||||
%import textio %import syslib
|
||||
""")) }
|
||||
}
|
||||
@@ -123,7 +121,7 @@ class TestProg8Parser {
|
||||
"}" +
|
||||
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
|
||||
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(2, module.statements.size)
|
||||
}
|
||||
}
|
||||
@@ -142,7 +140,7 @@ class TestProg8Parser {
|
||||
blockA {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
|
||||
@@ -159,7 +157,7 @@ class TestProg8Parser {
|
||||
blockB {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(2, module.statements.size)
|
||||
}
|
||||
|
||||
@@ -174,7 +172,7 @@ class TestProg8Parser {
|
||||
; comment
|
||||
|
||||
"""
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
}
|
||||
@@ -187,7 +185,7 @@ class TestProg8Parser {
|
||||
val importedNoExt = assumeNotExists(fixturesDir, "i_do_not_exist")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.p8")
|
||||
val text = "%import ${importedNoExt.name}"
|
||||
val module = parseModule(SourceCode.of(text))
|
||||
val module = parseModule(SourceCode.Text(text))
|
||||
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
@@ -198,14 +196,14 @@ class TestProg8Parser {
|
||||
inner class EmptySourcecode {
|
||||
@Test
|
||||
fun `from an empty string should result in empty Module`() {
|
||||
val module = parseModule(SourceCode.of(""))
|
||||
val module = parseModule(SourceCode.Text(""))
|
||||
assertEquals(0, module.statements.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `from an empty file should result in empty Module`() {
|
||||
val path = assumeReadableFile(fixturesDir, "empty.p8")
|
||||
val module = parseModule(SourceCode.fromPath(path))
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertEquals(0, module.statements.size)
|
||||
}
|
||||
}
|
||||
@@ -218,16 +216,16 @@ class TestProg8Parser {
|
||||
main {
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
|
||||
// Note: assertContains has *actual* as first param
|
||||
assertContains(module.name, Regex("^anonymous_[0-9a-f]+$"))
|
||||
assertContains(module.name, Regex("^<String@[0-9a-f\\-]+>$"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parsed from a file`() {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val module = parseModule(SourceCode.fromPath(path))
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertEquals(path.nameWithoutExtension, module.name)
|
||||
}
|
||||
}
|
||||
@@ -287,11 +285,11 @@ class TestProg8Parser {
|
||||
fun `in ParseError from bad string source code`() {
|
||||
val srcText = "bad * { }\n"
|
||||
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.of(srcText)) }
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcText)) }
|
||||
try {
|
||||
parseModule(SourceCode.of(srcText))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,11 +297,11 @@ class TestProg8Parser {
|
||||
fun `in ParseError from bad file source code`() {
|
||||
val path = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.fromPath(path)) }
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.File(path)) }
|
||||
try {
|
||||
parseModule(SourceCode.fromPath(path))
|
||||
parseModule(SourceCode.File(path))
|
||||
} catch (e: ParseError) {
|
||||
assertPosition(e.position, path.absolutePathString(), 2, 6) // TODO: endCol wrong
|
||||
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6) // TODO: endCol wrong
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,30 +311,28 @@ class TestProg8Parser {
|
||||
main {
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
assertPositionOf(module, Regex("^<String@[0-9a-f]+>$"), 1, 0) // TODO: endCol wrong
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0) // TODO: endCol wrong
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `of Module parsed from a file`() {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
|
||||
val module = parseModule(SourceCode.fromPath(path))
|
||||
assertPositionOf(module, path.absolutePathString(), 1, 0) // TODO: endCol wrong
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0) // TODO: endCol wrong
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `of non-root Nodes parsed from file`() {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
|
||||
val module = parseModule(SourceCode.fromPath(path))
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
val mpf = module.position.file
|
||||
|
||||
assertPositionOf(module, path.absolutePathString(), 1, 0) // TODO: endCol wrong
|
||||
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!
|
||||
}
|
||||
|
||||
|
||||
@@ -344,10 +340,9 @@ class TestProg8Parser {
|
||||
* TODO: this test is testing way too much at once
|
||||
*/
|
||||
@Test
|
||||
@Disabled("TODO: fix .position of nodes below Module - step 8, 'refactor AST gen'")
|
||||
fun `of non-root Nodes parsed from a string`() {
|
||||
val srcText = """
|
||||
%target 16, "abc" ; DirectiveArg directly inherits from Node - neither an Expression nor a Statement..?
|
||||
%zeropage basicsafe ; DirectiveArg directly inherits from Node - neither an Expression nor a Statement..?
|
||||
main {
|
||||
sub start() {
|
||||
ubyte foo = 42
|
||||
@@ -360,7 +355,7 @@ class TestProg8Parser {
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
val mpf = module.position.file
|
||||
|
||||
val targetDirective = module.statements.filterIsInstance<Directive>()[0]
|
||||
@@ -383,12 +378,65 @@ 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 {
|
||||
|
||||
@Test
|
||||
fun `in argument position, no altEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
chrout('\n')
|
||||
@@ -409,7 +457,7 @@ class TestProg8Parser {
|
||||
|
||||
@Test
|
||||
fun `on rhs of block-level var decl, no AltEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
ubyte c = 'x'
|
||||
}
|
||||
@@ -426,7 +474,7 @@ class TestProg8Parser {
|
||||
|
||||
@Test
|
||||
fun `on rhs of block-level const decl, with AltEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
const ubyte c = @'x'
|
||||
}
|
||||
@@ -443,7 +491,7 @@ class TestProg8Parser {
|
||||
|
||||
@Test
|
||||
fun `on rhs of subroutine-level var decl, no AltEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte c = 'x'
|
||||
@@ -463,7 +511,7 @@ class TestProg8Parser {
|
||||
|
||||
@Test
|
||||
fun `on rhs of subroutine-level const decl, with AltEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte c = @'x'
|
||||
@@ -487,7 +535,7 @@ class TestProg8Parser {
|
||||
|
||||
@Test
|
||||
fun `in for-loops`() {
|
||||
val module = parseModule(SourceCode.of("""
|
||||
val module = parseModule(SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte ub
|
||||
@@ -524,7 +572,6 @@ class TestProg8Parser {
|
||||
assertIs<CharLiteral>(it2.to, "parser should leave it as is")
|
||||
|
||||
val it3 = iterables[3] as RangeExpr
|
||||
// TODO: intro BoolLiteral
|
||||
assertIs<NumericLiteralValue>(it3.from, "parser should leave it as is")
|
||||
assertIs<NumericLiteralValue>(it3.to, "parser should leave it as is")
|
||||
|
||||
|
||||
@@ -1,62 +1,63 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Disabled
|
||||
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.io.path.absolutePathString
|
||||
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.of(text)
|
||||
"""
|
||||
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
|
||||
fun testFromPathWithNonExistingPath() {
|
||||
val filename = "i_do_not_exist.p8"
|
||||
val path = assumeNotExists(fixturesDir, filename)
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.fromPath(path) }
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.File(path) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithMissingExtension_p8() {
|
||||
val pathWithoutExt = assumeNotExists(fixturesDir,"simple_main")
|
||||
assumeReadableFile(fixturesDir,"simple_main.p8")
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.fromPath(pathWithoutExt) }
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithDirectory() {
|
||||
assertFailsWith<AccessDeniedException> { SourceCode.fromPath(fixturesDir) }
|
||||
assertFailsWith<AccessDeniedException> { SourceCode.File(fixturesDir) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingPath() {
|
||||
val filename = "simple_main.p8"
|
||||
val path = assumeReadableFile(fixturesDir, filename)
|
||||
val src = SourceCode.fromPath(path)
|
||||
|
||||
val expectedOrigin = path.normalize().absolutePathString()
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
assertEquals(expectedOrigin, src.origin)
|
||||
assertEquals(path.toFile().readText(), src.asString())
|
||||
assertFalse(src.isFromResources)
|
||||
assertTrue(src.isFromFilesystem)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,9 +65,8 @@ class TestSourceCode {
|
||||
val filename = "simple_main.p8"
|
||||
val path = Path(".", "test", "..", "test", "fixtures", filename)
|
||||
val srcFile = assumeReadableFile(path).toFile()
|
||||
val src = SourceCode.fromPath(path)
|
||||
|
||||
val expectedOrigin = path.normalize().absolutePathString()
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
assertEquals(expectedOrigin, src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
}
|
||||
@@ -75,17 +75,19 @@ class TestSourceCode {
|
||||
fun testFromResourcesWithExistingP8File_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
assertTrue(src.isFromResources)
|
||||
assertFalse(src.isFromFilesystem)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromResourcesWithExistingP8File_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
@@ -95,7 +97,7 @@ class TestSourceCode {
|
||||
fun testFromResourcesWithExistingAsmFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/math.asm"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
@@ -106,7 +108,7 @@ class TestSourceCode {
|
||||
fun testFromResourcesWithExistingAsmFile_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/math.asm"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
@@ -116,7 +118,7 @@ class TestSourceCode {
|
||||
fun testFromResourcesWithNonNormalizedPath() {
|
||||
val pathString = "/prog8lib/../prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/prog8lib/math.p8", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
@@ -129,22 +131,13 @@ class TestSourceCode {
|
||||
val pathString = "/prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString.substring(1))
|
||||
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.fromResources(pathString) }
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString)
|
||||
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.fromResources(pathString) }
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: inside resources: cannot tell apart a folder from a file")
|
||||
fun testFromResourcesWithDirectory() {
|
||||
val pathString = "/prog8lib"
|
||||
assumeDirectory(resourcesDir, pathString.substring(1))
|
||||
assertFailsWith<AccessDeniedException> { SourceCode.fromResources(pathString) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import kotlin.test.assertContains
|
||||
@@ -39,7 +40,7 @@ class ProgramTests {
|
||||
@Test
|
||||
fun withEmptyModule() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
val m1 = Module("bar", mutableListOf(), Position.DUMMY, null)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
|
||||
val retVal = program.addModule(m1)
|
||||
|
||||
@@ -49,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(m1.name, mutableListOf(), m1.position, m1.source)
|
||||
assertFailsWith<IllegalArgumentException> { program.addModule(m2) }
|
||||
.let { assertThat(it.message, containsString(m2.name)) }
|
||||
val m2 = Module(mutableListOf(), m1.position, m1.source)
|
||||
assertThat("other module but with same name may not occur multiple times",
|
||||
assertFailsWith<IllegalArgumentException> { program.addModule(m2) }.message, containsString(m2.name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,15 +74,15 @@ class ProgramTests {
|
||||
@Test
|
||||
fun withForeignModule() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
val m = Module("bar", mutableListOf(), Position.DUMMY, null)
|
||||
val m = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
|
||||
assertFailsWith<IllegalArgumentException> { program.moveModuleToFront(m) }
|
||||
}
|
||||
@Test
|
||||
fun withFirstOfPreviouslyAddedModules() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
val m1 = Module("bar", mutableListOf(), Position.DUMMY, null)
|
||||
val m2 = Module("qmbl", mutableListOf(), Position.DUMMY, null)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
|
||||
program.addModule(m1)
|
||||
program.addModule(m2)
|
||||
|
||||
@@ -92,8 +93,8 @@ class ProgramTests {
|
||||
@Test
|
||||
fun withSecondOfPreviouslyAddedModules() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
val m1 = Module("bar", mutableListOf(), Position.DUMMY, null)
|
||||
val m2 = Module("qmbl", mutableListOf(), Position.DUMMY, null)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
|
||||
program.addModule(m1)
|
||||
program.addModule(m2)
|
||||
|
||||
|
||||
+3
@@ -1,4 +1,7 @@
|
||||
%zeropage basicsafe
|
||||
main {
|
||||
sub start() {
|
||||
ubyte aa=99
|
||||
aa++
|
||||
}
|
||||
}
|
||||
@@ -118,11 +118,13 @@ They are embedded into the packaged release version of the compiler so you don't
|
||||
where they are, but their names are still reserved.
|
||||
|
||||
|
||||
User defined library files and -location
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
You can create library files yourself too that can be shared among programs.
|
||||
You can tell the compiler where it should look for these files, by using
|
||||
the libdirs command line option.
|
||||
Importing other source files and specifying search location(s)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
You can create multiple source files yourself to modularize your large programs into
|
||||
multiple module files. You can also create "library" modules this way with handy routines,
|
||||
that can be shared among programs. By importing those module files, you can use them in other modules.
|
||||
It is possible to tell the compiler where it should look for these files, by using
|
||||
the ``srcdirs`` command line option.
|
||||
|
||||
|
||||
.. _debugging:
|
||||
|
||||
@@ -3,10 +3,7 @@ TODO
|
||||
|
||||
For next compiler release
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- rename libdirs option to srcdirs?
|
||||
- can we derive module.name from module.source (taking just the filename base)?
|
||||
- can Position.file be a Path- making the source variable for nodes unnecessary?
|
||||
- address more questions/issues from the testability discussions.
|
||||
...
|
||||
|
||||
|
||||
Blocked by Commander-x16 v39 release
|
||||
@@ -17,12 +14,13 @@ Blocked by Commander-x16 v39 release
|
||||
|
||||
Future
|
||||
^^^^^^
|
||||
- get rid of all TODO's and FIXME's in the code
|
||||
- improve testability further, add more tests
|
||||
- can we get rid of pieces of asmgen.AssignmentAsmGen by just reusing the AugmentableAssignment ? generated code should not suffer
|
||||
- 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)"
|
||||
- use more of Result<> and Either<> to handle errors/ nulls better
|
||||
- fix the asm-labels problem (github issue #62)
|
||||
- can we get rid of pieces of asmgen.AssignmentAsmGen by just reusing the AugmentableAssignment ? generated code should not suffer
|
||||
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
|
||||
- optimize several inner loops in gfx2 even further?
|
||||
- add modes 2 and 3 to gfx2 (lowres 4 color and 16 color)?
|
||||
|
||||
@@ -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)
|
||||
|
||||
+7
-11
@@ -1,14 +1,10 @@
|
||||
%import textio
|
||||
|
||||
main {
|
||||
str myBar = "main.bar"
|
||||
sub start() {
|
||||
uword address = &irq
|
||||
; cx16.set_irq(&irq, false)
|
||||
address++
|
||||
}
|
||||
|
||||
foo_bar:
|
||||
%asminclude "compiler/test/fixtures/foo_bar.asm" ; FIXME: should be accessible from inside start() but give assembler error
|
||||
|
||||
sub start() {
|
||||
txt.print(myBar)
|
||||
txt.print(&foo_bar)
|
||||
return
|
||||
}
|
||||
sub irq() {
|
||||
}
|
||||
}
|
||||
@@ -34,8 +34,8 @@ class RequestParser : Take {
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = true,
|
||||
compilationTarget = "c64",
|
||||
libdirs = emptyList(),
|
||||
outputDir = Path.of(".")
|
||||
sourceDirs = emptyList(),
|
||||
outputDir = Path.of("")
|
||||
)
|
||||
return RsJson(Jsonding())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user