mirror of
https://github.com/irmen/prog8.git
synced 2024-12-25 23:29:55 +00:00
Merge pull request #53 from meisl/testability_steps_1_2_3_again
Implement plan for testability
This commit is contained in:
commit
0509de76d5
@ -70,7 +70,8 @@ sourceSets {
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
srcDir "${project.projectDir}/test"
|
||||
srcDir "${project(':compilerAst').projectDir}/test/helpers"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,11 @@ import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.target.asmGeneratorFor
|
||||
import prog8.optimizer.*
|
||||
import prog8.parser.ModuleImporter
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.moduleName
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
@ -135,7 +134,7 @@ fun compileProgram(filepath: Path,
|
||||
throw x
|
||||
}
|
||||
|
||||
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
||||
}
|
||||
|
||||
@ -169,27 +168,29 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
builtinFunctionReturnType(name, args, program)
|
||||
}
|
||||
|
||||
private fun parseImports(filepath: Path,
|
||||
fun parseImports(filepath: Path,
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
val compilationTargetName = compTarget.name
|
||||
println("Compiler target: $compilationTargetName. Parsing...")
|
||||
println("Compiler target: ${compTarget.name}. Parsing...")
|
||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
|
||||
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
|
||||
bf.program = programAst
|
||||
|
||||
val importer = ModuleImporter(programAst, compTarget, compilationTargetName, libdirs)
|
||||
val importer = ModuleImporter(programAst, compTarget.name, libdirs)
|
||||
importer.importModule(filepath)
|
||||
errors.report()
|
||||
|
||||
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
||||
val importedFiles = programAst.modules
|
||||
.mapNotNull { it.source }
|
||||
.filter { !it.isFromResources } // TODO: parseImports/importedFiles - maybe rather `source.isFromFilesystem`?
|
||||
.map { Path(it.pathString()) }
|
||||
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.")
|
||||
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
|
||||
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
||||
importer.importLibraryModule(lib)
|
||||
|
||||
// always import prog8_lib and math
|
||||
@ -199,7 +200,7 @@ private fun parseImports(filepath: Path,
|
||||
return Triple(programAst, compilerOptions, importedFiles)
|
||||
}
|
||||
|
||||
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
||||
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)
|
||||
@ -264,6 +265,9 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
|
||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||
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
|
||||
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.constantFold(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.reorderStatements(errors)
|
||||
@ -346,14 +350,14 @@ fun printAst(programAst: Program) {
|
||||
println()
|
||||
}
|
||||
|
||||
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
||||
return if (filename.startsWith("library:")) {
|
||||
fun loadAsmIncludeFile(filename: String, sourcePath: Path): String {
|
||||
return if (filename.startsWith("library:")) { // FIXME: is the prefix "library:" or is it "@embedded@"?
|
||||
val resource = tryGetEmbeddedResource(filename.substring(8))
|
||||
?: throw IllegalArgumentException("library file '$filename' not found")
|
||||
resource.bufferedReader().use { it.readText() }
|
||||
} else {
|
||||
// first try in the isSameAs folder as where the containing file was imported from
|
||||
val sib = source.resolveSibling(filename)
|
||||
val sib = sourcePath.resolveSibling(filename)
|
||||
if (sib.toFile().isFile)
|
||||
sib.toFile().readText()
|
||||
else
|
||||
@ -361,6 +365,9 @@ fun loadAsmIncludeFile(filename: String, source: Path): String {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle via SourceCode
|
||||
*/
|
||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
}
|
||||
|
6
compiler/src/prog8/compiler/IStringEncoding.kt
Normal file
6
compiler/src/prog8/compiler/IStringEncoding.kt
Normal file
@ -0,0 +1,6 @@
|
||||
package prog8.compiler
|
||||
|
||||
interface IStringEncoding {
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
}
|
140
compiler/src/prog8/compiler/ModuleImporter.kt
Normal file
140
compiler/src/prog8/compiler/ModuleImporter.kt
Normal file
@ -0,0 +1,140 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
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.FileSystemException
|
||||
import kotlin.io.path.*
|
||||
|
||||
|
||||
class ModuleImporter(private val program: Program,
|
||||
private val compilationTargetName: String,
|
||||
libdirs: List<String>) {
|
||||
|
||||
private val libpaths: List<Path> = libdirs.map { Path(it) }
|
||||
|
||||
fun importModule(filePath: Path): Module {
|
||||
val currentDir = Path("").absolute()
|
||||
val searchIn = listOf(currentDir) + libpaths
|
||||
val candidates = searchIn
|
||||
.map { it.absolute().div(filePath).normalize().absolute() }
|
||||
.filter { it.exists() }
|
||||
.map { currentDir.relativize(it) }
|
||||
.map { if (it.isAbsolute) it else Path(".", "$it") }
|
||||
|
||||
val srcPath = when (candidates.size) {
|
||||
0 -> throw NoSuchFileException(
|
||||
file = filePath.normalize().toFile(),
|
||||
reason = "searched in $searchIn")
|
||||
1 -> candidates.first()
|
||||
else -> candidates.first() // TODO: report error if more than 1 candidate?
|
||||
}
|
||||
|
||||
var logMsg = "importing '${filePath.nameWithoutExtension}' (from $srcPath)"
|
||||
println(logMsg)
|
||||
|
||||
return importModule(SourceCode.fromPath(srcPath))
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
val import = Directive("%import", listOf(
|
||||
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
return executeImportDirective(import, null)
|
||||
}
|
||||
|
||||
//private fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): 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()
|
||||
.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
|
||||
}
|
||||
|
||||
private fun executeImportDirective(import: Directive, importingModule: Module?): Module? {
|
||||
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
||||
throw SyntaxError("invalid import directive", 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?
|
||||
|
||||
var srcCode = tryGetModuleFromResource("$moduleName.p8", compilationTargetName)
|
||||
val importedModule =
|
||||
if (srcCode != null) {
|
||||
println("importing '$moduleName' (library): ${srcCode.origin}")
|
||||
importModule(srcCode)
|
||||
} else {
|
||||
srcCode = tryGetModuleFromFile(moduleName, importingModule)
|
||||
if (srcCode == null)
|
||||
throw NoSuchFileException(File("$moduleName.p8"))
|
||||
importModule(srcCode)
|
||||
}
|
||||
|
||||
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")
|
||||
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 tryGetModuleFromFile(name: String, importingModule: Module?): SourceCode? {
|
||||
val fileName = "$name.p8"
|
||||
val locations =
|
||||
if (importingModule == null) { // <=> imported from library module
|
||||
libpaths
|
||||
} 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
|
||||
listOf(Path(importingModule.position.file).parent ?: Path("")) +
|
||||
listOf(Path(".", "prog8lib"))
|
||||
}
|
||||
|
||||
locations.forEach {
|
||||
try {
|
||||
return SourceCode.fromPath(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
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import java.io.CharConversionException
|
||||
import java.io.File
|
||||
import kotlin.io.path.*
|
||||
import java.util.*
|
||||
|
||||
internal class AstChecker(private val program: Program,
|
||||
@ -721,11 +722,23 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
private fun checkFileExists(directive: Directive, filename: String) {
|
||||
var definingModule = directive.parent
|
||||
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 (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
|
||||
errors.err("included file not found: $filename", directive.position)
|
||||
if (definingModule.isLibrary())
|
||||
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)
|
||||
}
|
||||
|
||||
override fun visit(array: ArrayLiteralValue) {
|
||||
@ -756,10 +769,20 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(array)
|
||||
}
|
||||
|
||||
override fun visit(char: CharLiteral) {
|
||||
try { // just *try* if it can be encoded, don't actually do it
|
||||
compTarget.encodeString(char.value.toString(), char.altEncoding)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode character", char.position)
|
||||
}
|
||||
|
||||
super.visit(char)
|
||||
}
|
||||
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
checkValueTypeAndRangeString(DataType.STR, string)
|
||||
|
||||
try {
|
||||
try { // just *try* if it can be encoded, don't actually do it
|
||||
compTarget.encodeString(string.value, string.altEncoding)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode string", string.position)
|
||||
|
@ -1,12 +1,65 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.compiler.IStringEncoding
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
fun RangeExpr.size(encoding: IStringEncoding): Int? {
|
||||
val fromLv = (from as? NumericLiteralValue)
|
||||
val toLv = (to as? NumericLiteralValue)
|
||||
if(fromLv==null || toLv==null)
|
||||
return null
|
||||
return toConstantIntegerRange(encoding)?.count()
|
||||
}
|
||||
|
||||
fun RangeExpr.toConstantIntegerRange(encoding: IStringEncoding): IntProgression? {
|
||||
val fromVal: Int
|
||||
val toVal: Int
|
||||
val fromString = from as? StringLiteralValue
|
||||
val toString = to as? StringLiteralValue
|
||||
if(fromString!=null && toString!=null ) {
|
||||
// string range -> int range over character values
|
||||
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||
} else {
|
||||
val fromLv = from as? NumericLiteralValue
|
||||
val toLv = to as? NumericLiteralValue
|
||||
if(fromLv==null || toLv==null)
|
||||
return null // non-constant range
|
||||
// integer range
|
||||
fromVal = fromLv.number.toInt()
|
||||
toVal = toLv.number.toInt()
|
||||
}
|
||||
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
|
||||
return makeRange(fromVal, toVal, stepVal)
|
||||
}
|
||||
|
||||
private fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
return when {
|
||||
fromVal <= toVal -> when {
|
||||
stepVal <= 0 -> IntRange.EMPTY
|
||||
stepVal == 1 -> fromVal..toVal
|
||||
else -> fromVal..toVal step stepVal
|
||||
}
|
||||
else -> when {
|
||||
stepVal >= 0 -> IntRange.EMPTY
|
||||
stepVal == -1 -> fromVal downTo toVal
|
||||
else -> fromVal downTo toVal step abs(stepVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
@ -33,6 +86,20 @@ internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.charLiteralsToUByteLiterals(errors: IErrorReporter, enc: IStringEncoding) {
|
||||
val walker = object : AstWalker() {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toInt(), char.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
}
|
||||
walker.visit(this)
|
||||
walker.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: IErrorReporter) {
|
||||
val caster = TypecastsAdder(this, errors)
|
||||
caster.visit(this)
|
||||
@ -58,8 +125,24 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
|
||||
lit2decl.applyModifications()
|
||||
}
|
||||
|
||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||
throw FatalAstException("modules should all be unique")
|
||||
// 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) {
|
||||
var others = namesToModules[m.name]
|
||||
if (others == null) {
|
||||
namesToModules.put(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.size > 0) {
|
||||
throw FatalAstException("modules must have unique names; of the ttl ${modules.size} these have not: $nonUniqueNames")
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,9 +163,7 @@ internal fun Program.moveMainAndStartToFirst() {
|
||||
val start = this.entrypoint()
|
||||
val mod = start.definingModule()
|
||||
val block = start.definingBlock()
|
||||
if(!modules.remove(mod))
|
||||
throw FatalAstException("module wrong")
|
||||
modules.add(0, mod)
|
||||
moveModuleToFront(mod)
|
||||
mod.remove(block)
|
||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.IStringEncoding
|
||||
import prog8.compiler.IStringEncoding
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
@ -24,6 +24,8 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
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
|
||||
|
@ -14,10 +14,10 @@ import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
|
||||
import prog8.optimizer.CallGraph
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import kotlin.io.path.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
@ -1308,18 +1308,30 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: %asminclude and %asmbinary should be done earlier than code gen (-> put content into AST)
|
||||
*/
|
||||
private fun translate(stmt: Directive) {
|
||||
when(stmt.directive) {
|
||||
"%asminclude" -> {
|
||||
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
|
||||
// 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
|
||||
val sourcecode = loadAsmIncludeFile(includedName, sourcePath)
|
||||
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
|
||||
}
|
||||
"%asmbinary" -> {
|
||||
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 includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
|
||||
val relPath = Paths.get("").relativize(includedSourcePath)
|
||||
out(" .binary \"$relPath\" $offset $length")
|
||||
val sourcePath = Path(stmt.definingModule().source!!.pathString()) // FIXME: %asmbinary inside non-library, non-filesystem module
|
||||
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)
|
||||
.normalize() // avoid assembler warnings (-Wportable; only some, not all)
|
||||
.toString().replace('\\', '/')
|
||||
out(" .binary \"$pathForAssembler\" $offset $length")
|
||||
}
|
||||
"%breakpoint" -> {
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
|
@ -9,6 +9,7 @@ import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@ -19,7 +20,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
throw AssemblyError("unknown dt")
|
||||
when(stmt.iterable) {
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
|
||||
if(range==null) {
|
||||
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||
} else {
|
||||
|
@ -9,11 +9,10 @@ import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// @( &thing ) --> thing
|
||||
@ -223,7 +222,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
range.step
|
||||
}
|
||||
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, compTarget, range.position)
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
|
@ -9,6 +9,8 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.astprocessing.size
|
||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
// Fix up the literal value's type to match that of the vardecl
|
||||
@ -154,14 +156,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
@ -176,6 +177,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
@ -208,15 +210,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
|
||||
errors.err("range expression size (${rangeExpr.size(compTarget)}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
@ -224,15 +224,18 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(rangeExpr==null && litval!=null) {
|
||||
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
val fillvalue = numericLv.number.toDouble()
|
||||
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", litval.position)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
|
||||
if(errors.noErrors()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this, compTarget)
|
||||
val optimizer = ConstantFoldingOptimizer(this)
|
||||
optimizer.visit(this)
|
||||
while (errors.noErrors() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
|
@ -11,6 +11,7 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.astprocessing.size
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.math.floor
|
||||
|
||||
@ -197,7 +198,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
if(range.size()==1) {
|
||||
if (range.size(compTarget) == 1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
|
@ -1,11 +1,11 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8tests.helpers.*
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@ -17,18 +17,9 @@ import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAsmGen6502 {
|
||||
private class DummyFunctions: IBuiltinFunctions {
|
||||
override val names: Set<String> = emptySet()
|
||||
override val purefunctionNames: Set<String> = emptySet()
|
||||
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
|
||||
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
||||
}
|
||||
|
||||
private class DummyMemsizer: IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
}
|
||||
|
||||
private fun createTestProgram(): Program {
|
||||
/*
|
||||
@ -74,10 +65,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, Path.of(""))
|
||||
module.linkParents(ParentSentinel)
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.program = program
|
||||
val module = Module("test", mutableListOf(block), Position.DUMMY, null)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program.namespace)?!
|
||||
return program
|
||||
}
|
||||
|
||||
|
322
compiler/test/ModuleImporterTests.kt
Normal file
322
compiler/test/ModuleImporterTests.kt
Normal file
@ -0,0 +1,322 @@
|
||||
package prog8tests
|
||||
|
||||
import prog8tests.helpers.*
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.hamcrest.Matchers.containsString
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.io.path.*
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.parser.ParseError
|
||||
|
||||
import prog8.compiler.ModuleImporter
|
||||
import kotlin.test.assertContains
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestModuleImporter {
|
||||
private val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
||||
|
||||
private lateinit var program: Program
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
}
|
||||
|
||||
private fun makeImporter(vararg searchIn: String): ModuleImporter = makeImporter(searchIn.asList())
|
||||
|
||||
private fun makeImporter(searchIn: Iterable<String>) =
|
||||
ModuleImporter(program, "blah", searchIn.toList())
|
||||
|
||||
@Nested
|
||||
inner class Constructor {
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: invalid entries in search list")
|
||||
fun testInvalidEntriesInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: literal duplicates in search list")
|
||||
fun testLiteralDuplicatesInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: factual duplicates in search list")
|
||||
fun testFactualDuplicatesInSearchList() {}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ImportModule {
|
||||
|
||||
@Nested
|
||||
inner class WithInvalidPath {
|
||||
@Test
|
||||
fun testNonexisting() {
|
||||
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(dirRel.invariantSeparatorsPathString)
|
||||
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
|
||||
assertThrows<NoSuchFileException> { importer.importModule(srcPathRel) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
|
||||
assertThrows<NoSuchFileException> { importer.importModule(srcPathAbs) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDirectory() {
|
||||
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||
val importer = makeImporter(searchIn)
|
||||
|
||||
assertThrows<AccessDeniedException> { importer.importModule(srcPathRel) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
|
||||
assertThrows<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithValidPath {
|
||||
|
||||
@Test
|
||||
fun testAbsolute() {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importModule(path.absolute())
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeToWorkingDir() {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
assertThat("sanity check: path should NOT be absolute", path.isAbsolute, equalTo(false))
|
||||
|
||||
val module = importer.importModule(path)
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeTo1stDirInSearchList() {
|
||||
val searchIn = Path(".")
|
||||
.div(workingDir.relativize(fixturesDir))
|
||||
.invariantSeparatorsPathString
|
||||
val importer = makeImporter(searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val path = Path(".", fileName)
|
||||
assumeReadableFile(searchIn, path)
|
||||
|
||||
val module = importer.importModule(path)
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: relative to 2nd in search list")
|
||||
fun testRelativeTo2ndDirInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: ambiguous - 2 or more really different candidates")
|
||||
fun testAmbiguousCandidates() {}
|
||||
|
||||
@Nested
|
||||
inner class WithBadFile {
|
||||
@Test
|
||||
fun testWithSyntaxError() {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(srcPath) }
|
||||
|
||||
repeat(2) { n ->
|
||||
assertThrows<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(srcPath.absolutePathString()))
|
||||
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(1))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_once() {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: module that imports faulty module should not be kept in Program.modules")
|
||||
fun testImportingFileWithSyntaxError_twice() {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
|
||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(importing) }
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertThrows<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(imported.absolutePathString()))
|
||||
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))
|
||||
}
|
||||
// TODO("assertThat(program.modules.size, equalTo(2))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ImportLibraryModule {
|
||||
@Nested
|
||||
inner class WithInvalidName {
|
||||
@Test
|
||||
fun testWithNonExistingName() {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(searchIn.invariantSeparatorsPathString)
|
||||
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
|
||||
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
|
||||
|
||||
repeat(2) { n ->
|
||||
assertThrows<NoSuchFileException>(count[n] + " call / NO .p8 extension")
|
||||
{ importer.importLibraryModule(filenameNoExt) }.let {
|
||||
assertThat(it.message!!, containsString(filenameWithExt))
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
|
||||
assertThrows<NoSuchFileException>(count[n] + " call / with .p8 extension")
|
||||
{ importer.importLibraryModule(filenameWithExt) }.let {
|
||||
assertThat(it.message!!, containsString(filenameWithExt))
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithValidName {
|
||||
@Nested
|
||||
inner class WithBadFile {
|
||||
@Test
|
||||
fun testWithSyntaxError() {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
repeat(2) { n ->
|
||||
assertThrows<ParseError>(count[n] + " call")
|
||||
{ importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
assertThat(it.position.file, equalTo(srcPath.absolutePathString()))
|
||||
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(1))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertThrows<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(imported.normalize().absolutePathString()))
|
||||
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))
|
||||
}
|
||||
// TODO("assertThat(program.modules.size, equalTo(1))")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_once() {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: module that imports faulty module should not be kept in Program.modules")
|
||||
fun testImportingFileWithSyntaxError_twice() {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,19 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import kotlin.test.*
|
||||
import prog8tests.helpers.*
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
/**
|
||||
@ -24,31 +23,18 @@ import kotlin.test.assertTrue
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnCharLit {
|
||||
val workingDir = Path("").absolute() // Note: Path(".") does NOT work..!
|
||||
val fixturesDir = workingDir.resolve("test/fixtures")
|
||||
val outputDir = workingDir.resolve("build/tmp/test")
|
||||
|
||||
@Test
|
||||
fun testDirectoriesSanityCheck() {
|
||||
assertEquals("compiler", workingDir.fileName.toString())
|
||||
assertTrue(fixturesDir.isDirectory(), "sanity check; should be directory: $fixturesDir")
|
||||
assertTrue(outputDir.isDirectory(), "sanity check; should be directory: $outputDir")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharLitAsRomsubArg() {
|
||||
val filepath = fixturesDir.resolve("charLitAsRomsubArg.p8")
|
||||
val compilationTarget = Cx16Target
|
||||
val result = compileProgram(
|
||||
filepath,
|
||||
optimize = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget.name,
|
||||
libdirs = listOf(),
|
||||
outputDir
|
||||
)
|
||||
assertTrue(result.success, "compilation should succeed")
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
chrout('\n')
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
@ -58,23 +44,22 @@ class TestCompilerOnCharLit {
|
||||
"char literal should have been replaced by ubyte literal")
|
||||
val arg = funCall.args[0] as NumericLiteralValue
|
||||
assertEquals(DataType.UBYTE, arg.type)
|
||||
assertEquals(compilationTarget.encodeString("\n", false)[0], arg.number)
|
||||
assertEquals(platform.encodeString("\n", false)[0], arg.number.toShort()) // TODO: short/int/UBYTE - which should it be?
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharVarAsRomsubArg() {
|
||||
val filepath = fixturesDir.resolve("charVarAsRomsubArg.p8")
|
||||
val compilationTarget = Cx16Target
|
||||
val result = compileProgram(
|
||||
filepath,
|
||||
optimize = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget.name,
|
||||
libdirs = listOf(),
|
||||
outputDir
|
||||
)
|
||||
assertTrue(result.success, "compilation should succeed")
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
ubyte ch = '\n'
|
||||
chrout(ch)
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
@ -94,23 +79,22 @@ class TestCompilerOnCharLit {
|
||||
"char literal should have been replaced by ubyte literal")
|
||||
val initializerValue = decl.value as NumericLiteralValue
|
||||
assertEquals(DataType.UBYTE, initializerValue.type)
|
||||
assertEquals(compilationTarget.encodeString("\n", false)[0], initializerValue.number)
|
||||
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort()) // TODO: short/int/UBYTE - which should it be?
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharConstAsRomsubArg() {
|
||||
val filepath = fixturesDir.resolve("charConstAsRomsubArg.p8")
|
||||
val compilationTarget = Cx16Target
|
||||
val result = compileProgram(
|
||||
filepath,
|
||||
optimize = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget.name,
|
||||
libdirs = listOf(),
|
||||
outputDir
|
||||
)
|
||||
assertTrue(result.success, "compilation should succeed")
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
const ubyte ch = '\n'
|
||||
chrout(ch)
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
@ -122,16 +106,17 @@ class TestCompilerOnCharLit {
|
||||
assertEquals(VarDeclType.CONST, decl.type)
|
||||
assertEquals(DataType.UBYTE, decl.datatype)
|
||||
assertEquals(
|
||||
compilationTarget.encodeString("\n", false)[0],
|
||||
(decl.value as NumericLiteralValue).number)
|
||||
platform.encodeString("\n", false)[0],
|
||||
(decl.value as NumericLiteralValue).number.toShort()) // TODO: short/int/UBYTE - which should it be?
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
assertEquals(
|
||||
compilationTarget.encodeString("\n", false)[0],
|
||||
arg.number)
|
||||
platform.encodeString("\n", false)[0],
|
||||
arg.number.toShort()) // TODO: short/int/UBYTE - which should it be?
|
||||
}
|
||||
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,18 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
/**
|
||||
@ -17,62 +20,105 @@ import kotlin.test.assertTrue
|
||||
* 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")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnExamples {
|
||||
val workingDir = Path("").absolute() // Note: Path(".") does NOT work..!
|
||||
val examplesDir = workingDir.resolve("../examples")
|
||||
val outputDir = workingDir.resolve("build/tmp/test")
|
||||
|
||||
@Test
|
||||
fun testDirectoriesSanityCheck() {
|
||||
assertEquals("compiler", workingDir.fileName.toString())
|
||||
assertTrue(examplesDir.isDirectory(), "sanity check; should be directory: $examplesDir")
|
||||
assertTrue(outputDir.isDirectory(), "sanity check; should be directory: $outputDir")
|
||||
}
|
||||
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||
|
||||
// TODO: make assembly stage testable - in case of failure (eg of 64tass) it Process.exit s
|
||||
|
||||
fun testExample(nameWithoutExt: String, platform: ICompilationTarget, optimize: Boolean) {
|
||||
val filepath = examplesDir.resolve("$nameWithoutExt.p8")
|
||||
val result = compileProgram(
|
||||
filepath,
|
||||
optimize,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget = platform.name,
|
||||
libdirs = listOf(),
|
||||
outputDir
|
||||
)
|
||||
assertTrue(result.success,
|
||||
"compilation should succeed; ${platform.name}, optimize=$optimize: \"$filepath\"")
|
||||
private fun makeDynamicCompilerTest(name: String, platform: ICompilationTarget, optimize: Boolean) : DynamicTest {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (platform == Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn
|
||||
.map { it.resolve("$name.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
.first { it.exists() }
|
||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${platform.name}, optimize=$optimize"
|
||||
return dynamicTest(displayName) {
|
||||
compileProgram(
|
||||
filepath,
|
||||
optimize,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget = platform.name,
|
||||
libdirs = listOf(),
|
||||
outputDir
|
||||
).assertSuccess("; $displayName")
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
// @Disabled
|
||||
fun bothCx16AndC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"animals",
|
||||
"balls",
|
||||
"cube3d",
|
||||
"cube3d-float",
|
||||
"cube3d-gfx",
|
||||
"cxlogo",
|
||||
"dirlist",
|
||||
"fibonacci",
|
||||
"line-circle-gfx",
|
||||
"line-circle-txt",
|
||||
"mandelbrot",
|
||||
"mandelbrot-gfx",
|
||||
"numbergame",
|
||||
"primes",
|
||||
"rasterbars",
|
||||
"screencodes",
|
||||
"sorting",
|
||||
"swirl",
|
||||
"swirl-float",
|
||||
"tehtriz",
|
||||
"textelite",
|
||||
),
|
||||
dim2 = listOf(Cx16Target, C64Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
|
||||
@Test
|
||||
fun test_cxLogo_noopt() {
|
||||
testExample("cxlogo", Cx16Target, false)
|
||||
}
|
||||
@Test
|
||||
fun test_cxLogo_opt() {
|
||||
testExample("cxlogo", Cx16Target, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_swirl_noopt() {
|
||||
testExample("swirl", Cx16Target, false)
|
||||
}
|
||||
@Test
|
||||
fun test_swirl_opt() {
|
||||
testExample("swirl", Cx16Target, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_animals_noopt() {
|
||||
testExample("animals", Cx16Target, false)
|
||||
}
|
||||
@Test
|
||||
fun test_animals_opt() {
|
||||
testExample("animals", Cx16Target, true)
|
||||
}
|
||||
@TestFactory
|
||||
// @Disabled
|
||||
fun onlyC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
"bdmusic-irq",
|
||||
"charset",
|
||||
"cube3d-sprites",
|
||||
"plasma",
|
||||
"sprites",
|
||||
"turtle-gfx",
|
||||
"wizzine",
|
||||
),
|
||||
dim2 = listOf(C64Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled
|
||||
fun onlyCx16() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"vtui/testvtui",
|
||||
"amiga",
|
||||
"bobs",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"datetime",
|
||||
"highresbitmap",
|
||||
"kefrenbars",
|
||||
"mandelbrot-gfx-colors",
|
||||
"multipalette",
|
||||
"testgfx2",
|
||||
),
|
||||
dim2 = listOf(Cx16Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
}
|
||||
|
154
compiler/test/TestCompilerOnImportsAndIncludes.kt
Normal file
154
compiler/test/TestCompilerOnImportsAndIncludes.kt
Normal file
@ -0,0 +1,154 @@
|
||||
package prog8tests
|
||||
|
||||
import prog8tests.helpers.*
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import kotlin.test.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.compiler.target.Cx16Target
|
||||
|
||||
|
||||
/**
|
||||
* ATTENTION: this is just kludge!
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnImportsAndIncludes {
|
||||
|
||||
@Nested
|
||||
inner class Import {
|
||||
|
||||
@Test
|
||||
fun testImportFromSameFolder() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
||||
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)
|
||||
}
|
||||
|
||||
@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
|
||||
inner class AsmInclude {
|
||||
@Test
|
||||
fun testAsmIncludeFromSameFolder() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
||||
|
||||
val platform = Cx16Target
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
||||
.assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val args = startSub.statements
|
||||
.filterIsInstance<FunctionCallStatement>()
|
||||
.map { it.args[0] }
|
||||
|
||||
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue
|
||||
assertEquals("main.bar", str0.value)
|
||||
assertEquals("main", str0.definingScope().name)
|
||||
|
||||
val id1 = (args[1] as AddressOf).identifier
|
||||
val lbl1 = id1.targetStatement(program) as Label
|
||||
assertEquals("foo_bar", lbl1.name)
|
||||
assertEquals("start", lbl1.definingScope().name)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Asmbinary {
|
||||
@Test
|
||||
fun testAsmbinaryDirectiveWithNonExistingFile() {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAsmbinaryDirectiveWithNonReadableFile() {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
||||
assumeDirectory(fixturesDir, "subFolder")
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
fun asmbinaryDirectiveWithExistingBinFile(): Iterable<DynamicTest> =
|
||||
listOf(
|
||||
Triple("same ", "asmBinaryFromSameFolder.p8", "do_nothing1.bin"),
|
||||
Triple("sub", "asmBinaryFromSubFolder.p8", "subFolder/do_nothing2.bin"),
|
||||
).map {
|
||||
val (where, p8Str, binStr) = it
|
||||
dynamicTest("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
assertNotEquals( // the bug we're testing for (#54) was hidden if outputDir == workinDir
|
||||
workingDir.normalize().toAbsolutePath(),
|
||||
outputDir.normalize().toAbsolutePath(),
|
||||
"sanity check: workingDir and outputDir should not be the same folder"
|
||||
)
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertSuccess(
|
||||
"argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
"NOT relative to .p8 neither current working dir"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
263
compiler/test/TestCompilerOnRanges.kt
Normal file
263
compiler/test/TestCompilerOnRanges.kt
Normal file
@ -0,0 +1,263 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import kotlin.test.*
|
||||
import prog8tests.helpers.*
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.astprocessing.size
|
||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
|
||||
|
||||
/**
|
||||
* ATTENTION: this is just kludge!
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnRanges {
|
||||
|
||||
@Test
|
||||
fun testUByteArrayInitializerWithRange_char_to_char() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] cs = @'a' to 'z' ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val decl = startSub
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
val rhsValues = (decl.value as ArrayLiteralValue)
|
||||
.value // Array<Expression>
|
||||
.map { (it as NumericLiteralValue).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", true)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", false)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.last() - rhsValues.first() + 1, "rangeExpr.size()")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatArrayInitializerWithRange_char_to_char() {
|
||||
val platform = C64Target
|
||||
val result = compileText(platform, optimize = false, """
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
float[] cs = 'a' to 'z' ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val decl = startSub
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
val rhsValues = (decl.value as ArrayLiteralValue)
|
||||
.value // Array<Expression>
|
||||
.map { (it as NumericLiteralValue).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", false)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", false)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.size, "rangeExpr.size()")
|
||||
}
|
||||
|
||||
fun Subroutine.decl(varName: String): VarDecl {
|
||||
return statements.filterIsInstance<VarDecl>()
|
||||
.first { it.name == varName }
|
||||
}
|
||||
inline fun <reified T : Expression> VarDecl.rhs() : T {
|
||||
return value as T
|
||||
}
|
||||
inline fun <reified T : Expression> ArrayLiteralValue.elements() : List<T> {
|
||||
return value.map { it as T }
|
||||
}
|
||||
|
||||
fun <N : Number> assertEndpoints(expFirst: N, expLast: N, actual: Iterable<N>, msg: String = ".first .. .last") {
|
||||
val expectedStr = "$expFirst .. $expLast"
|
||||
val actualStr = "${actual.first()} .. ${actual.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
}
|
||||
|
||||
|
||||
@TestFactory
|
||||
fun floatArrayInitializerWithRange() = mapCombinations(
|
||||
dim1 = listOf("", "42", "41"), // sizeInDecl
|
||||
dim2 = listOf("%option enable_floats", ""), // optEnableFloats
|
||||
dim3 = listOf(Cx16Target, C64Target), // platform
|
||||
dim4 = listOf(false, true), // optimize
|
||||
combine4 = { sizeInDecl, optEnableFloats, platform, optimize ->
|
||||
val displayName =
|
||||
"test failed for: " +
|
||||
when (sizeInDecl) {
|
||||
"" -> "no"
|
||||
"42" -> "correct"
|
||||
else -> "wrong"
|
||||
} + " array size given" +
|
||||
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
|
||||
", ${platform.name}, optimize: $optimize"
|
||||
dynamicTest(displayName) {
|
||||
val result = compileText(platform, optimize, """
|
||||
$optEnableFloats
|
||||
main {
|
||||
sub start() {
|
||||
float[$sizeInDecl] cs = 1 to 42 ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
""")
|
||||
if (optEnableFloats != "" && (sizeInDecl=="" || sizeInDecl=="42"))
|
||||
result.assertSuccess()
|
||||
else
|
||||
result.assertFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_char_to_char() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in @'a' to 'f' {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val iterable = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }[0]
|
||||
val rangeExpr = iterable as RangeExpr
|
||||
|
||||
val expectedStart = platform.encodeString("a", true)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("f", false)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
||||
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(platform), "rangeExpr.size()")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_bool_to_bool() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in false to true {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
|
||||
assertEquals(2, rangeExpr.size(platform))
|
||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
||||
assertEquals(0, intProgression?.first)
|
||||
assertEquals(1, intProgression?.last)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_ubyte_to_ubyte() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in 1 to 9 {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
|
||||
assertEquals(9, rangeExpr.size(platform))
|
||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
||||
assertEquals(1, intProgression?.first)
|
||||
assertEquals(9, intProgression?.last)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_str_downto_str() {
|
||||
compileText(Cx16Target, true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in "start" downto "end" {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertFailure()
|
||||
//TODO("test exact compile error(s)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithIterable_str() {
|
||||
val result = compileText(Cx16Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in "something" {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint()
|
||||
val iterable = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<IdentifierReference>()[0]
|
||||
|
||||
assertEquals(DataType.STR, iterable.inferType(program).typeOrElse(DataType.UNDEFINED))
|
||||
}
|
||||
|
||||
}
|
||||
|
94
compiler/test/TestCompilerOptionLibdirs.kt
Normal file
94
compiler/test/TestCompilerOptionLibdirs.kt
Normal file
@ -0,0 +1,94 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.*
|
||||
import java.nio.file.Path
|
||||
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.*
|
||||
|
||||
/**
|
||||
* ATTENTION: this is just kludge!
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOptionLibdirs {
|
||||
|
||||
private lateinit var tempFileInWorkingDir: Path
|
||||
|
||||
@BeforeAll
|
||||
fun setUp() {
|
||||
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
|
||||
.also { it.writeText("""
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
}
|
||||
""")}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDown() {
|
||||
tempFileInWorkingDir.deleteExisting()
|
||||
}
|
||||
|
||||
private fun compileFile(filePath: Path, libdirs: List<String>) =
|
||||
compileProgram(
|
||||
filepath = filePath,
|
||||
optimize = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget = Cx16Target.name,
|
||||
libdirs,
|
||||
outputDir
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testAbsoluteFilePathInWorkingDir() {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeToWorkingDir() {
|
||||
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeTo1stInLibdirs() {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(workingDir.toString()))
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAbsoluteFilePathOutsideWorkingDir() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
compileFile(filepath.absolute(), listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeToWorkingDir() {
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "simple_main.p8").absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeTo1stInLibdirs() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val libdirs = listOf("$fixturesDir")
|
||||
compileFile(filepath.fileName, libdirs)
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
}
|
97
compiler/test/TestImportedModulesOrderAndOptions.kt
Normal file
97
compiler/test/TestImportedModulesOrderAndOptions.kt
Normal file
@ -0,0 +1,97 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.*
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.*
|
||||
import prog8tests.helpers.*
|
||||
|
||||
import prog8.compiler.target.C64Target
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestImportedModulesOrderAndOptions {
|
||||
|
||||
@Test
|
||||
fun testImportedModuleOrderCorrect() {
|
||||
val result = compileText(C64Target, false, """
|
||||
%import textio
|
||||
%import floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
; nothing
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
assertTrue(result.programAst.mainModule.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")
|
||||
assertEquals(listOf(
|
||||
"prog8_interned_strings",
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
"floats",
|
||||
"math",
|
||||
"prog8_lib"
|
||||
), moduleNames.drop(1), "module order in parse tree")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompilationOptionsCorrectFromMain() {
|
||||
val result = compileText(C64Target, false, """
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage dontuse
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
; nothing
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
assertTrue(result.programAst.mainModule.name.startsWith("on_the_fly_test"))
|
||||
val options = determineCompilationOptions(result.programAst, C64Target)
|
||||
assertTrue(options.floats)
|
||||
assertEquals(ZeropageType.DONTUSE, options.zeropage)
|
||||
assertTrue(options.noSysInit)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
|
||||
val errors = ErrorReporter()
|
||||
val sourceText = """
|
||||
%import textio
|
||||
%import floats
|
||||
%option no_sysinit
|
||||
%zeropage dontuse
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
; nothing
|
||||
}
|
||||
}
|
||||
"""
|
||||
val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16)
|
||||
val filepath = outputDir.resolve("$filenameBase.p8")
|
||||
filepath.toFile().writeText(sourceText)
|
||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
|
||||
|
||||
assertEquals(filenameBase, program.mainModule.name)
|
||||
assertEquals(1, importedfiles.size, "all imports other than the test source must have been internal resources library files")
|
||||
assertEquals(listOf(
|
||||
internedStringsModuleName,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
|
||||
), program.modules.map {it.name}, "module order in parse tree")
|
||||
assertTrue(options.floats)
|
||||
assertEquals(ZeropageType.DONTUSE, options.zeropage, "zeropage option must be correctly taken from main module, not from float module import logic")
|
||||
assertTrue(options.noSysInit)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IMemSizer
|
||||
import kotlin.test.*
|
||||
import prog8tests.helpers.*
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
@ -12,29 +14,17 @@ import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestMemory {
|
||||
private class DummyFunctions: IBuiltinFunctions {
|
||||
override val names: Set<String> = emptySet()
|
||||
override val purefunctionNames: Set<String> = emptySet()
|
||||
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
|
||||
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
||||
}
|
||||
|
||||
private class DummyMemsizer: IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_addresses() {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
@ -59,7 +49,7 @@ class TestMemory {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||
@ -78,7 +68,7 @@ class TestMemory {
|
||||
@Test
|
||||
fun testInValidRamC64_memory_identifiers() {
|
||||
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
|
||||
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
|
||||
@ -98,7 +88,7 @@ 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, Path.of(""))
|
||||
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
module.linkParents(ParentSentinel)
|
||||
return target
|
||||
}
|
||||
@ -107,7 +97,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", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
|
||||
@ -117,9 +107,10 @@ 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, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
|
||||
@ -130,9 +121,10 @@ 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, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
|
||||
@ -143,9 +135,10 @@ 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, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
|
||||
@ -156,9 +149,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, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
|
||||
@ -170,9 +164,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, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
|
||||
@ -184,9 +179,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, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, null)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.*
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestNumbers {
|
||||
|
@ -1,19 +1,15 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.*
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.ArrayLiteralValue
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestNumericLiteralValue {
|
||||
|
@ -1,15 +1,16 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.*
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
|
@ -1,17 +1,15 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.*
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
|
10
compiler/test/fixtures/asmBinaryFromSameFolder.p8
vendored
Normal file
10
compiler/test/fixtures/asmBinaryFromSameFolder.p8
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
main {
|
||||
sub start() {
|
||||
stuff.do_nothing()
|
||||
}
|
||||
}
|
||||
|
||||
stuff $1000 {
|
||||
romsub $1000 = do_nothing()
|
||||
%asmbinary "do_nothing1.bin", 0
|
||||
}
|
10
compiler/test/fixtures/asmBinaryFromSubFolder.p8
vendored
Normal file
10
compiler/test/fixtures/asmBinaryFromSubFolder.p8
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
main {
|
||||
sub start() {
|
||||
stuff.do_nothing()
|
||||
}
|
||||
}
|
||||
|
||||
stuff $1000 {
|
||||
romsub $1000 = do_nothing()
|
||||
%asmbinary "subFolder/do_nothing2.bin", 0
|
||||
}
|
10
compiler/test/fixtures/asmBinaryNonExisting.p8
vendored
Normal file
10
compiler/test/fixtures/asmBinaryNonExisting.p8
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
main {
|
||||
sub start() {
|
||||
stuff.do_nothing()
|
||||
}
|
||||
}
|
||||
|
||||
stuff $1000 {
|
||||
romsub $1000 = do_nothing()
|
||||
%asmbinary "i_do_not_exist.bin", 0
|
||||
}
|
10
compiler/test/fixtures/asmBinaryNonReadable.p8
vendored
Normal file
10
compiler/test/fixtures/asmBinaryNonReadable.p8
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
main {
|
||||
sub start() {
|
||||
stuff.do_nothing()
|
||||
}
|
||||
}
|
||||
|
||||
stuff $1000 {
|
||||
romsub $1000 = do_nothing()
|
||||
%asmbinary "subFolder", 0
|
||||
}
|
13
compiler/test/fixtures/asmIncludeFromSameFolder.p8
vendored
Normal file
13
compiler/test/fixtures/asmIncludeFromSameFolder.p8
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
%import textio
|
||||
main {
|
||||
str myBar = "main.bar"
|
||||
;foo_bar:
|
||||
; %asminclude "foo_bar.asm" ; FIXME: should be accessible from inside start() but give assembler error
|
||||
sub start() {
|
||||
txt.print(myBar)
|
||||
txt.print(&foo_bar)
|
||||
return
|
||||
foo_bar:
|
||||
%asminclude "foo_bar.asm"
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
main {
|
||||
romsub $FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
const ubyte ch = '\n'
|
||||
chrout(ch)
|
||||
}
|
||||
}
|
6
compiler/test/fixtures/charLitAsRomsubArg.p8
vendored
6
compiler/test/fixtures/charLitAsRomsubArg.p8
vendored
@ -1,6 +0,0 @@
|
||||
main {
|
||||
romsub $FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
chrout('\n')
|
||||
}
|
||||
}
|
7
compiler/test/fixtures/charVarAsRomsubArg.p8
vendored
7
compiler/test/fixtures/charVarAsRomsubArg.p8
vendored
@ -1,7 +0,0 @@
|
||||
main {
|
||||
romsub $FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
ubyte ch = '\n'
|
||||
chrout(ch)
|
||||
}
|
||||
}
|
7
compiler/test/fixtures/do_nothing.asm
vendored
Normal file
7
compiler/test/fixtures/do_nothing.asm
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
; this is the source code for do_nothing1.bin and subFolder/do_nothing2.bin
|
||||
; command lines:
|
||||
; 64tass --ascii --nostart do_nothing.asm --output do_nothing1.bin
|
||||
; 64tass --ascii --nostart do_nothing.asm --output subFolder/do_nothing2.bin
|
||||
*=0
|
||||
rts
|
||||
|
1
compiler/test/fixtures/do_nothing1.bin
vendored
Normal file
1
compiler/test/fixtures/do_nothing1.bin
vendored
Normal file
@ -0,0 +1 @@
|
||||
`
|
2
compiler/test/fixtures/file_with_syntax_error.p8
vendored
Normal file
2
compiler/test/fixtures/file_with_syntax_error.p8
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
; test expects the following 2nd (!) line:
|
||||
bad { } ; -> missing EOL at '}' (ie: *after* the '{')
|
2
compiler/test/fixtures/foo_bar.asm
vendored
Normal file
2
compiler/test/fixtures/foo_bar.asm
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
bar .text "foo.bar",0
|
||||
|
3
compiler/test/fixtures/foo_bar.p8
vendored
Normal file
3
compiler/test/fixtures/foo_bar.p8
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
foo {
|
||||
str bar = "foo.bar"
|
||||
}
|
9
compiler/test/fixtures/importFromSameFolder.p8
vendored
Normal file
9
compiler/test/fixtures/importFromSameFolder.p8
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
%import textio
|
||||
%import foo_bar
|
||||
main {
|
||||
str myBar = "main.bar"
|
||||
sub start() {
|
||||
txt.print(myBar)
|
||||
txt.print(foo.bar)
|
||||
}
|
||||
}
|
9
compiler/test/fixtures/importFromSameFolder_strLit.p8
vendored
Normal file
9
compiler/test/fixtures/importFromSameFolder_strLit.p8
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
%import textio
|
||||
%import "foo_bar.p8"
|
||||
main {
|
||||
str myBar = "main.bar"
|
||||
sub start() {
|
||||
txt.print(myBar)
|
||||
txt.print(foo.bar)
|
||||
}
|
||||
}
|
1
compiler/test/fixtures/import_file_with_syntax_error.p8
vendored
Normal file
1
compiler/test/fixtures/import_file_with_syntax_error.p8
vendored
Normal file
@ -0,0 +1 @@
|
||||
%import file_with_syntax_error
|
1
compiler/test/fixtures/import_import_nonexisting.p8
vendored
Normal file
1
compiler/test/fixtures/import_import_nonexisting.p8
vendored
Normal file
@ -0,0 +1 @@
|
||||
%import import_nonexisting
|
1
compiler/test/fixtures/import_nonexisting.p8
vendored
Normal file
1
compiler/test/fixtures/import_nonexisting.p8
vendored
Normal file
@ -0,0 +1 @@
|
||||
%import i_do_not_exist
|
4
compiler/test/fixtures/simple_main.p8
vendored
Normal file
4
compiler/test/fixtures/simple_main.p8
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
}
|
1
compiler/test/fixtures/subFolder/do_nothing2.bin
vendored
Normal file
1
compiler/test/fixtures/subFolder/do_nothing2.bin
vendored
Normal file
@ -0,0 +1 @@
|
||||
`
|
60
compiler/test/helpers/compileXyz.kt
Normal file
60
compiler/test/helpers/compileXyz.kt
Normal file
@ -0,0 +1,60 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
import kotlin.test.*
|
||||
import kotlin.io.path.*
|
||||
import java.nio.file.Path
|
||||
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
internal fun CompilationResult.assertSuccess(description: String = ""): CompilationResult {
|
||||
assertTrue(success, "expected successful compilation but failed $description")
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun CompilationResult.assertFailure(description: String = ""): CompilationResult {
|
||||
assertFalse(success, "expected failure to compile but succeeded $description")
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CompilationResult.assertSuccess
|
||||
* @see CompilationResult.assertFailure
|
||||
*/
|
||||
internal fun compileFile(
|
||||
platform: ICompilationTarget,
|
||||
optimize: Boolean,
|
||||
fileDir: Path,
|
||||
fileName: String,
|
||||
outputDir: Path = prog8tests.helpers.outputDir
|
||||
) : CompilationResult {
|
||||
val filepath = fileDir.resolve(fileName)
|
||||
assumeReadableFile(filepath)
|
||||
return compileProgram(
|
||||
filepath,
|
||||
optimize,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
platform.name,
|
||||
libdirs = listOf(),
|
||||
outputDir
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a [sourceText] as a String, writes it to a temporary
|
||||
* file and then runs the compiler on that.
|
||||
* @see compileFile
|
||||
*/
|
||||
internal fun compileText(
|
||||
platform: ICompilationTarget,
|
||||
optimize: Boolean,
|
||||
sourceText: String
|
||||
) : CompilationResult {
|
||||
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
|
||||
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
|
||||
filePath.toFile().writeText(sourceText)
|
||||
return compileFile(platform, optimize, filePath.parent, filePath.name)
|
||||
}
|
@ -45,15 +45,15 @@ sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
</content>
|
||||
|
20
compilerAst/res/prog8lib/math.asm
Normal file
20
compilerAst/res/prog8lib/math.asm
Normal file
@ -0,0 +1,20 @@
|
||||
; just for tests - DISFUNCTIONAL!
|
||||
|
||||
|
||||
math_store_reg .byte 0 ; temporary storage
|
||||
|
||||
|
||||
multiply_bytes .proc
|
||||
; -- multiply 2 bytes A and Y, result as byte in A (signed or unsigned)
|
||||
sta P8ZP_SCRATCH_B1 ; num1
|
||||
sty P8ZP_SCRATCH_REG ; num2
|
||||
lda #0
|
||||
beq _enterloop
|
||||
_doAdd clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
_loop asl P8ZP_SCRATCH_B1
|
||||
_enterloop lsr P8ZP_SCRATCH_REG
|
||||
bcs _doAdd
|
||||
bne _loop
|
||||
rts
|
||||
.pend
|
7
compilerAst/res/prog8lib/math.p8
Normal file
7
compilerAst/res/prog8lib/math.p8
Normal file
@ -0,0 +1,7 @@
|
||||
; Internal Math library routines - always included by the compiler
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
math {
|
||||
%asminclude "library:math.asm"
|
||||
}
|
@ -9,6 +9,11 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
|
||||
|
||||
/**
|
||||
* Produces Prog8 source text from a [Program] (AST node),
|
||||
* passing it as a String to the specified receiver function.
|
||||
* TODO: rename/refactor to make proper sense in the presence of class [prog8.SourceCode]
|
||||
*/
|
||||
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
|
||||
private var scopelevel = 0
|
||||
|
||||
@ -18,9 +23,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
private fun outputi(s: Any) = output(indent(s.toString()))
|
||||
|
||||
override fun visit(program: Program) {
|
||||
outputln("============= PROGRAM ${program.name} (FROM AST) ===============")
|
||||
outputln("; ============ PROGRAM ${program.name} (FROM AST) ==============")
|
||||
super.visit(program)
|
||||
outputln("============= END PROGRAM ${program.name} (FROM AST) ===========")
|
||||
outputln("; =========== END PROGRAM ${program.name} (FROM AST) ===========")
|
||||
}
|
||||
|
||||
override fun visit(module: Module) {
|
||||
@ -261,7 +266,15 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
output(numLiteral.number.toString())
|
||||
}
|
||||
|
||||
override fun visit(char: CharLiteral) {
|
||||
if (char.altEncoding)
|
||||
output("@")
|
||||
output("'${escape(char.value.toString())}'")
|
||||
}
|
||||
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
if (string.altEncoding)
|
||||
output("@")
|
||||
output("\"${escape(string.value)}\"")
|
||||
}
|
||||
|
||||
|
@ -5,57 +5,13 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.name
|
||||
import kotlin.math.abs
|
||||
import prog8.parser.SourceCode
|
||||
|
||||
const val internedStringsModuleName = "prog8_interned_strings"
|
||||
|
||||
interface IStringEncoding {
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
}
|
||||
|
||||
interface Node {
|
||||
val position: Position
|
||||
var parent: Node // will be linked correctly later (late init)
|
||||
fun linkParents(parent: Node)
|
||||
|
||||
fun definingModule(): Module {
|
||||
if(this is Module)
|
||||
return this
|
||||
return findParentNode<Module>(this)!!
|
||||
}
|
||||
|
||||
fun definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
|
||||
|
||||
fun definingScope(): INameScope {
|
||||
val scope = findParentNode<INameScope>(this)
|
||||
if(scope!=null) {
|
||||
return scope
|
||||
}
|
||||
if(this is Label && this.name.startsWith("builtin::")) {
|
||||
return BuiltinFunctionScopePlaceholder
|
||||
}
|
||||
if(this is GlobalNamespace)
|
||||
return this
|
||||
throw FatalAstException("scope missing from $this")
|
||||
}
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
return findParentNode<Block>(this)!!
|
||||
}
|
||||
|
||||
fun containingStatement(): Statement {
|
||||
if(this is Statement)
|
||||
return this
|
||||
return findParentNode<Statement>(this)!!
|
||||
}
|
||||
|
||||
fun replaceChildNode(node: Node, replacement: Node)
|
||||
interface IAssignable {
|
||||
// just a tag for now
|
||||
}
|
||||
|
||||
interface IFunctionCall {
|
||||
@ -63,7 +19,6 @@ interface IFunctionCall {
|
||||
var args: MutableList<Expression>
|
||||
}
|
||||
|
||||
|
||||
interface INameScope {
|
||||
val name: String
|
||||
val position: Position
|
||||
@ -229,60 +184,107 @@ interface INameScope {
|
||||
}
|
||||
}
|
||||
|
||||
interface IAssignable {
|
||||
// just a tag for now
|
||||
|
||||
interface Node {
|
||||
val position: Position
|
||||
var parent: Node // will be linked correctly later (late init)
|
||||
fun linkParents(parent: Node)
|
||||
|
||||
fun definingModule(): Module {
|
||||
if(this is Module)
|
||||
return this
|
||||
return findParentNode<Module>(this)!!
|
||||
}
|
||||
|
||||
fun definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
|
||||
|
||||
fun definingScope(): INameScope {
|
||||
val scope = findParentNode<INameScope>(this)
|
||||
if(scope!=null) {
|
||||
return scope
|
||||
}
|
||||
if(this is Label && this.name.startsWith("builtin::")) {
|
||||
return BuiltinFunctionScopePlaceholder
|
||||
}
|
||||
if(this is GlobalNamespace)
|
||||
return this
|
||||
throw FatalAstException("scope missing from $this")
|
||||
}
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
return findParentNode<Block>(this)!!
|
||||
}
|
||||
|
||||
fun containingStatement(): Statement {
|
||||
if(this is Statement)
|
||||
return this
|
||||
return findParentNode<Statement>(this)!!
|
||||
}
|
||||
|
||||
fun replaceChildNode(node: Node, replacement: Node)
|
||||
}
|
||||
|
||||
interface IMemSizer {
|
||||
fun memorySize(dt: DataType): Int
|
||||
}
|
||||
|
||||
interface IBuiltinFunctions {
|
||||
val names: Set<String>
|
||||
val purefunctionNames: Set<String>
|
||||
fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue?
|
||||
fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType
|
||||
}
|
||||
|
||||
/*********** Everything starts from here, the Program; zero or more modules *************/
|
||||
|
||||
|
||||
|
||||
class Program(val name: String,
|
||||
val modules: MutableList<Module>,
|
||||
val builtinFunctions: IBuiltinFunctions,
|
||||
val memsizer: IMemSizer): Node {
|
||||
val namespace = GlobalNamespace(modules, builtinFunctions.names)
|
||||
private val _modules = mutableListOf<Module>()
|
||||
|
||||
val mainModule: Module
|
||||
val modules: List<Module> = _modules
|
||||
val namespace: GlobalNamespace = GlobalNamespace(modules, builtinFunctions.names)
|
||||
|
||||
init {
|
||||
// insert a container module for all interned strings later
|
||||
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, null)
|
||||
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
|
||||
internedStringsModule.statements.add(block)
|
||||
|
||||
_modules.add(0, internedStringsModule)
|
||||
internedStringsModule.linkParents(namespace) // TODO: was .linkParents(this) - probably wrong?!
|
||||
internedStringsModule.program = this
|
||||
}
|
||||
|
||||
fun addModule(module: Module): Program {
|
||||
require(null == _modules.firstOrNull { it.name == module.name })
|
||||
{ "module '${module.name}' already present" }
|
||||
_modules.add(module)
|
||||
module.linkParents(namespace)
|
||||
module.program = this
|
||||
return this
|
||||
}
|
||||
|
||||
fun moveModuleToFront(module: Module): Program {
|
||||
require(_modules.contains(module))
|
||||
{ "Not a module of this program: '${module.name}'"}
|
||||
_modules.remove(module)
|
||||
_modules.add(0, module)
|
||||
return this
|
||||
}
|
||||
|
||||
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
||||
|
||||
fun entrypoint(): Subroutine {
|
||||
val mainBlocks = allBlocks().filter { it.name=="main" }
|
||||
return when (mainBlocks.size) {
|
||||
0 -> throw FatalAstException("no 'main' block")
|
||||
1 -> mainBlocks[0].subScope("start") as Subroutine
|
||||
else -> throw FatalAstException("more than one 'main' block")
|
||||
}
|
||||
}
|
||||
|
||||
val mainModule: Module // TODO: rename Program.mainModule - it's NOT necessarily the one containing the main *block*!
|
||||
get() = modules.first { it.name!=internedStringsModuleName }
|
||||
|
||||
val definedLoadAddress: Int
|
||||
get() = mainModule.loadAddress
|
||||
|
||||
var actualLoadAddress: Int = 0
|
||||
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
|
||||
|
||||
init {
|
||||
// insert a container module for all interned strings later
|
||||
if(modules.firstOrNull()?.name != internedStringsModuleName) {
|
||||
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, Path.of(""))
|
||||
modules.add(0, internedStringsModule)
|
||||
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
|
||||
internedStringsModule.statements.add(block)
|
||||
internedStringsModule.linkParents(this)
|
||||
internedStringsModule.program = this
|
||||
}
|
||||
}
|
||||
|
||||
fun entrypoint(): Subroutine {
|
||||
val mainBlocks = allBlocks().filter { it.name=="main" }
|
||||
if(mainBlocks.size > 1)
|
||||
throw FatalAstException("more than one 'main' block")
|
||||
if(mainBlocks.isEmpty())
|
||||
throw FatalAstException("no 'main' block")
|
||||
return mainBlocks[0].subScope("start") as Subroutine
|
||||
}
|
||||
|
||||
fun internString(string: StringLiteralValue): List<String> {
|
||||
// Move a string literal into the internal, deduplicated, string pool
|
||||
// replace it with a variable declaration that points to the entry in the pool.
|
||||
@ -316,10 +318,6 @@ class Program(val name: String,
|
||||
return scopedName
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
||||
|
||||
override val position: Position = Position.DUMMY
|
||||
override var parent: Node
|
||||
get() = throw FatalAstException("program has no parent")
|
||||
@ -333,16 +331,17 @@ class Program(val name: String,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Module && replacement is Module)
|
||||
val idx = modules.indexOfFirst { it===node }
|
||||
modules[idx] = replacement
|
||||
replacement.parent = this
|
||||
val idx = _modules.indexOfFirst { it===node }
|
||||
_modules[idx] = replacement
|
||||
replacement.parent = this // TODO: why not replacement.program = this; replacement.linkParents(namespace)?!
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Module(override val name: String,
|
||||
open class Module(override val name: String,
|
||||
override var statements: MutableList<Statement>,
|
||||
override val position: Position,
|
||||
val source: Path) : Node, INameScope {
|
||||
val source: SourceCode?) : Node, INameScope {
|
||||
|
||||
override lateinit var parent: Node
|
||||
lateinit var program: Program
|
||||
@ -370,19 +369,11 @@ class Module(override val name: String,
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
companion object {
|
||||
fun pathForResource(resourcePath: String): Path {
|
||||
return Paths.get("@embedded@/$resourcePath")
|
||||
}
|
||||
|
||||
fun isLibrary(source: Path) = source.name=="" || source.startsWith("@embedded@/")
|
||||
}
|
||||
|
||||
fun isLibrary() = isLibrary(source)
|
||||
fun isLibrary() = (source == null) || source.isFromResources
|
||||
}
|
||||
|
||||
|
||||
class GlobalNamespace(val modules: List<Module>, private val builtinFunctionNames: Set<String>): Node, INameScope {
|
||||
class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunctionNames: Set<String>): Node, INameScope {
|
||||
override val name = "<<<global>>>"
|
||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||
override val statements = mutableListOf<Statement>() // not used
|
||||
@ -429,18 +420,3 @@ object BuiltinFunctionScopePlaceholder : INameScope {
|
||||
}
|
||||
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
// negative values are prefixed with '-'.
|
||||
val integer = this.toInt()
|
||||
if(integer<0)
|
||||
return '-' + abs(integer).toHex()
|
||||
return when (integer) {
|
||||
in 0 until 16 -> integer.toString()
|
||||
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
||||
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
||||
|
19
compilerAst/src/prog8/ast/Extensions.kt
Normal file
19
compilerAst/src/prog8/ast/Extensions.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package prog8.ast
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
// negative values are prefixed with '-'.
|
||||
val integer = this.toInt()
|
||||
if(integer<0)
|
||||
return '-' + abs(integer).toHex()
|
||||
return when (integer) {
|
||||
in 0 until 16 -> integer.toString()
|
||||
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
||||
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
13
compilerAst/src/prog8/ast/IBuiltinFunctions.kt
Normal file
13
compilerAst/src/prog8/ast/IBuiltinFunctions.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
|
||||
interface IBuiltinFunctions {
|
||||
val names: Set<String>
|
||||
val purefunctionNames: Set<String>
|
||||
fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue?
|
||||
fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType
|
||||
}
|
7
compilerAst/src/prog8/ast/IMemSizer.kt
Normal file
7
compilerAst/src/prog8/ast/IMemSizer.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
|
||||
interface IMemSizer {
|
||||
fun memorySize(dt: DataType): Int
|
||||
}
|
@ -1,32 +1,20 @@
|
||||
package prog8.ast.antlr
|
||||
|
||||
import org.antlr.v4.runtime.IntStream
|
||||
import org.antlr.v4.runtime.ParserRuleContext
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import prog8.ast.IStringEncoding
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.parser.CustomLexer
|
||||
import prog8.parser.Prog8ANTLRParser
|
||||
import java.io.CharConversionException
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
/***************** Antlr Extension methods to create AST ****************/
|
||||
|
||||
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
||||
|
||||
internal fun Prog8ANTLRParser.ModuleContext.toAst(name: String, source: Path, encoding: IStringEncoding) : Module {
|
||||
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
|
||||
val directives = this.directive().map { it.toAst() }
|
||||
val blocks = this.block().map { it.toAst(Module.isLibrary(source), encoding) }
|
||||
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), source)
|
||||
}
|
||||
|
||||
private fun ParserRuleContext.toPosition() : Position {
|
||||
/*
|
||||
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||
val filename =
|
||||
when {
|
||||
@ -34,15 +22,18 @@ private fun ParserRuleContext.toPosition() : Position {
|
||||
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
||||
else -> File(start.inputStream.sourceName).name
|
||||
}
|
||||
*/
|
||||
val filename = start.inputStream.sourceName
|
||||
|
||||
// note: be ware of TAB characters in the source text, they count as 1 column...
|
||||
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean, encoding: IStringEncoding) : Statement {
|
||||
internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
|
||||
val blockstatements = block_statement().map {
|
||||
when {
|
||||
it.variabledeclaration()!=null -> it.variabledeclaration().toAst(encoding)
|
||||
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst(encoding)
|
||||
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
|
||||
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
|
||||
it.directive()!=null -> it.directive().toAst()
|
||||
it.inlineasm()!=null -> it.inlineasm().toAst()
|
||||
it.labeldef()!=null -> it.labeldef().toAst()
|
||||
@ -52,11 +43,11 @@ private fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean, encoding:
|
||||
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Statement_blockContext.toAst(encoding: IStringEncoding): MutableList<Statement> =
|
||||
statement().asSequence().map { it.toAst(encoding) }.toMutableList()
|
||||
private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||
|
||||
private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringEncoding) : Statement {
|
||||
vardecl()?.let { return it.toAst(encoding) }
|
||||
private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
|
||||
vardecl()?.let { return it.toAst() }
|
||||
|
||||
varinitializer()?.let {
|
||||
val vd = it.vardecl()
|
||||
@ -64,9 +55,9 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringE
|
||||
VarDeclType.VAR,
|
||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(encoding),
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
it.expression().toAst(encoding),
|
||||
it.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED()!=null,
|
||||
@ -81,9 +72,9 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringE
|
||||
VarDeclType.CONST,
|
||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(encoding),
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
cvarinit.expression().toAst(encoding),
|
||||
cvarinit.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED() != null,
|
||||
@ -98,9 +89,9 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringE
|
||||
VarDeclType.MEMORY,
|
||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(encoding),
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
mvarinit.expression().toAst(encoding),
|
||||
mvarinit.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED()!=null,
|
||||
@ -111,33 +102,33 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringE
|
||||
throw FatalAstException("weird variable decl $this")
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.SubroutinedeclarationContext.toAst(encoding: IStringEncoding) : Subroutine {
|
||||
private fun Prog8ANTLRParser.SubroutinedeclarationContext.toAst() : Subroutine {
|
||||
return when {
|
||||
subroutine()!=null -> subroutine().toAst(encoding)
|
||||
asmsubroutine()!=null -> asmsubroutine().toAst(encoding)
|
||||
subroutine()!=null -> subroutine().toAst()
|
||||
asmsubroutine()!=null -> asmsubroutine().toAst()
|
||||
romsubroutine()!=null -> romsubroutine().toAst()
|
||||
else -> throw FatalAstException("weird subroutine decl $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.StatementContext.toAst(encoding: IStringEncoding) : Statement {
|
||||
val vardecl = variabledeclaration()?.toAst(encoding)
|
||||
private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||
val vardecl = variabledeclaration()?.toAst()
|
||||
if(vardecl!=null) return vardecl
|
||||
|
||||
assignment()?.let {
|
||||
return Assignment(it.assign_target().toAst(encoding), it.expression().toAst(encoding), it.toPosition())
|
||||
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
|
||||
}
|
||||
|
||||
augassignment()?.let {
|
||||
// replace A += X with A = A + X
|
||||
val target = it.assign_target().toAst(encoding)
|
||||
val target = it.assign_target().toAst()
|
||||
val oper = it.operator.text.substringBefore('=')
|
||||
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(encoding), it.expression().toPosition())
|
||||
return Assignment(it.assign_target().toAst(encoding), expression, it.toPosition())
|
||||
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
|
||||
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
|
||||
}
|
||||
|
||||
postincrdecr()?.let {
|
||||
return PostIncrDecr(it.assign_target().toAst(encoding), it.operator.text, it.toPosition())
|
||||
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition())
|
||||
}
|
||||
|
||||
val directive = directive()?.toAst()
|
||||
@ -149,49 +140,49 @@ private fun Prog8ANTLRParser.StatementContext.toAst(encoding: IStringEncoding) :
|
||||
val jump = unconditionaljump()?.toAst()
|
||||
if(jump!=null) return jump
|
||||
|
||||
val fcall = functioncall_stmt()?.toAst(encoding)
|
||||
val fcall = functioncall_stmt()?.toAst()
|
||||
if(fcall!=null) return fcall
|
||||
|
||||
val ifstmt = if_stmt()?.toAst(encoding)
|
||||
val ifstmt = if_stmt()?.toAst()
|
||||
if(ifstmt!=null) return ifstmt
|
||||
|
||||
val returnstmt = returnstmt()?.toAst(encoding)
|
||||
val returnstmt = returnstmt()?.toAst()
|
||||
if(returnstmt!=null) return returnstmt
|
||||
|
||||
val subroutine = subroutinedeclaration()?.toAst(encoding)
|
||||
val subroutine = subroutinedeclaration()?.toAst()
|
||||
if(subroutine!=null) return subroutine
|
||||
|
||||
val asm = inlineasm()?.toAst()
|
||||
if(asm!=null) return asm
|
||||
|
||||
val branchstmt = branch_stmt()?.toAst(encoding)
|
||||
val branchstmt = branch_stmt()?.toAst()
|
||||
if(branchstmt!=null) return branchstmt
|
||||
|
||||
val forloop = forloop()?.toAst(encoding)
|
||||
val forloop = forloop()?.toAst()
|
||||
if(forloop!=null) return forloop
|
||||
|
||||
val untilloop = untilloop()?.toAst(encoding)
|
||||
val untilloop = untilloop()?.toAst()
|
||||
if(untilloop!=null) return untilloop
|
||||
|
||||
val whileloop = whileloop()?.toAst(encoding)
|
||||
val whileloop = whileloop()?.toAst()
|
||||
if(whileloop!=null) return whileloop
|
||||
|
||||
val repeatloop = repeatloop()?.toAst(encoding)
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
|
||||
val breakstmt = breakstmt()?.toAst()
|
||||
if(breakstmt!=null) return breakstmt
|
||||
|
||||
val whenstmt = whenstmt()?.toAst(encoding)
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
|
||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(encoding: IStringEncoding): Subroutine {
|
||||
private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
|
||||
val inline = this.inline()!=null
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val statements = statement_block()?.toAst(encoding) ?: mutableListOf()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf()
|
||||
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||
subdecl.asmClobbers, null, true, inline, statements, toPosition())
|
||||
@ -272,28 +263,28 @@ private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutinePar
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Functioncall_stmtContext.toAst(encoding: IStringEncoding): Statement {
|
||||
private fun Prog8ANTLRParser.Functioncall_stmtContext.toAst(): Statement {
|
||||
val void = this.VOID() != null
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
FunctionCallStatement(location, mutableListOf(), void, toPosition())
|
||||
else
|
||||
FunctionCallStatement(location, expression_list().toAst(encoding).toMutableList(), void, toPosition())
|
||||
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.FunctioncallContext.toAst(encoding: IStringEncoding): FunctionCall {
|
||||
private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCall {
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
FunctionCall(location, mutableListOf(), toPosition())
|
||||
else
|
||||
FunctionCall(location, expression_list().toAst(encoding).toMutableList(), toPosition())
|
||||
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.InlineasmContext.toAst() =
|
||||
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.ReturnstmtContext.toAst(encoding: IStringEncoding) : Return {
|
||||
return Return(expression()?.toAst(encoding), toPosition())
|
||||
private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
|
||||
return Return(expression()?.toAst(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
|
||||
@ -305,14 +296,14 @@ private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
|
||||
private fun Prog8ANTLRParser.LabeldefContext.toAst(): Statement =
|
||||
Label(children[0].text, toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.SubroutineContext.toAst(encoding: IStringEncoding) : Subroutine {
|
||||
private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
|
||||
// non-asm subroutine
|
||||
val inline = inline()!=null
|
||||
val returntypes = sub_return_part()?.toAst() ?: emptyList()
|
||||
return Subroutine(identifier().text,
|
||||
sub_params()?.toAst() ?: emptyList(),
|
||||
returntypes,
|
||||
statement_block()?.toAst(encoding) ?: mutableListOf(),
|
||||
statement_block()?.toAst() ?: mutableListOf(),
|
||||
inline,
|
||||
toPosition())
|
||||
}
|
||||
@ -328,12 +319,12 @@ private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter
|
||||
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Assign_targetContext.toAst(encoding: IStringEncoding) : AssignTarget {
|
||||
private fun Prog8ANTLRParser.Assign_targetContext.toAst() : AssignTarget {
|
||||
val identifier = scoped_identifier()
|
||||
return when {
|
||||
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
||||
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(encoding), null, toPosition())
|
||||
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(encoding), toPosition()), toPosition())
|
||||
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
|
||||
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
|
||||
}
|
||||
}
|
||||
@ -345,16 +336,16 @@ private fun Prog8ANTLRParser.ClobberContext.toAst() : Set<CpuRegister> {
|
||||
|
||||
private fun Prog8ANTLRParser.DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
|
||||
|
||||
private fun Prog8ANTLRParser.ArrayindexContext.toAst(encoding: IStringEncoding) : ArrayIndex =
|
||||
ArrayIndex(expression().toAst(encoding), toPosition())
|
||||
private fun Prog8ANTLRParser.ArrayindexContext.toAst() : ArrayIndex =
|
||||
ArrayIndex(expression().toAst(), toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.DirectiveContext.toAst() : Directive =
|
||||
internal fun Prog8ANTLRParser.DirectiveContext.toAst() : Directive =
|
||||
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
|
||||
val str = stringliteral()
|
||||
if(str?.ALT_STRING_ENCODING() != null)
|
||||
throw AstException("${toPosition()} can't use alternate string encodings for directive arguments")
|
||||
throw AstException("${toPosition()} can't use alternate string s for directive arguments")
|
||||
|
||||
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
|
||||
}
|
||||
@ -410,7 +401,7 @@ private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ExpressionContext.toAst(encoding: IStringEncoding) : Expression {
|
||||
private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
|
||||
|
||||
val litval = literalvalue()
|
||||
if(litval!=null) {
|
||||
@ -431,17 +422,9 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst(encoding: IStringEncoding)
|
||||
}
|
||||
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||
litval.charliteral()!=null -> {
|
||||
try {
|
||||
NumericLiteralValue(DataType.UBYTE, encoding.encodeString(
|
||||
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
|
||||
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
|
||||
} catch (ce: CharConversionException) {
|
||||
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
||||
}
|
||||
}
|
||||
litval.charliteral()!=null -> litval.charliteral().toAst()
|
||||
litval.arrayliteral()!=null -> {
|
||||
val array = litval.arrayliteral().toAst(encoding)
|
||||
val array = litval.arrayliteral().toAst()
|
||||
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
|
||||
// the ConstantFold takes care of that and converts the type if needed.
|
||||
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
||||
@ -455,31 +438,31 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst(encoding: IStringEncoding)
|
||||
return scoped_identifier().toAst()
|
||||
|
||||
if(bop!=null)
|
||||
return BinaryExpression(left.toAst(encoding), bop.text, right.toAst(encoding), toPosition())
|
||||
return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition())
|
||||
|
||||
if(prefix!=null)
|
||||
return PrefixExpression(prefix.text, expression(0).toAst(encoding), toPosition())
|
||||
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition())
|
||||
|
||||
val funcall = functioncall()?.toAst(encoding)
|
||||
val funcall = functioncall()?.toAst()
|
||||
if(funcall!=null) return funcall
|
||||
|
||||
if (rangefrom!=null && rangeto!=null) {
|
||||
val defaultstep = if(rto.text == "to") 1 else -1
|
||||
val step = rangestep?.toAst(encoding) ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
|
||||
return RangeExpr(rangefrom.toAst(encoding), rangeto.toAst(encoding), step, encoding, toPosition())
|
||||
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
|
||||
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||
}
|
||||
|
||||
if(childCount==3 && children[0].text=="(" && children[2].text==")")
|
||||
return expression(0).toAst(encoding) // expression within ( )
|
||||
return expression(0).toAst() // expression within ( )
|
||||
|
||||
if(arrayindexed()!=null)
|
||||
return arrayindexed().toAst(encoding)
|
||||
return arrayindexed().toAst()
|
||||
|
||||
if(typecast()!=null)
|
||||
return TypecastExpression(expression(0).toAst(encoding), typecast().datatype().toAst(), false, toPosition())
|
||||
return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition())
|
||||
|
||||
if(directmemory()!=null)
|
||||
return DirectMemoryRead(directmemory().expression().toAst(encoding), toPosition())
|
||||
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
|
||||
|
||||
if(addressof()!=null)
|
||||
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
|
||||
@ -487,16 +470,19 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst(encoding: IStringEncoding)
|
||||
throw FatalAstException(text)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral =
|
||||
CharLiteral(unescape(this.SINGLECHAR().text, toPosition())[0], this.ALT_STRING_ENCODING() != null, toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteralValue =
|
||||
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.ArrayindexedContext.toAst(encoding: IStringEncoding): ArrayIndexedExpression {
|
||||
private fun Prog8ANTLRParser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
||||
return ArrayIndexedExpression(scoped_identifier().toAst(),
|
||||
arrayindex().toAst(encoding),
|
||||
arrayindex().toAst(),
|
||||
toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Expression_listContext.toAst(encoding: IStringEncoding) = expression().map{ it.toAst(encoding) }
|
||||
private fun Prog8ANTLRParser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||
|
||||
private fun Prog8ANTLRParser.IdentifierContext.toAst() : IdentifierReference =
|
||||
IdentifierReference(listOf(text), toPosition())
|
||||
@ -512,27 +498,27 @@ private fun Prog8ANTLRParser.BooleanliteralContext.toAst() = when(text) {
|
||||
else -> throw FatalAstException(text)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ArrayliteralContext.toAst(encoding: IStringEncoding) : Array<Expression> =
|
||||
expression().map { it.toAst(encoding) }.toTypedArray()
|
||||
private fun Prog8ANTLRParser.ArrayliteralContext.toAst() : Array<Expression> =
|
||||
expression().map { it.toAst() }.toTypedArray()
|
||||
|
||||
private fun Prog8ANTLRParser.If_stmtContext.toAst(encoding: IStringEncoding): IfStatement {
|
||||
val condition = expression().toAst(encoding)
|
||||
val trueStatements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
||||
val elseStatements = else_part()?.toAst(encoding) ?: mutableListOf()
|
||||
private fun Prog8ANTLRParser.If_stmtContext.toAst(): IfStatement {
|
||||
val condition = expression().toAst()
|
||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||
return IfStatement(condition, trueScope, elseScope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Else_partContext.toAst(encoding: IStringEncoding): MutableList<Statement> {
|
||||
return statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
||||
private fun Prog8ANTLRParser.Else_partContext.toAst(): MutableList<Statement> {
|
||||
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Branch_stmtContext.toAst(encoding: IStringEncoding): BranchStatement {
|
||||
private fun Prog8ANTLRParser.Branch_stmtContext.toAst(): BranchStatement {
|
||||
val branchcondition = branchcondition().toAst()
|
||||
val trueStatements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
||||
val elseStatements = else_part()?.toAst(encoding) ?: mutableListOf()
|
||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||
@ -543,65 +529,65 @@ private fun Prog8ANTLRParser.BranchconditionContext.toAst() = BranchCondition.va
|
||||
text.substringAfter('_').uppercase()
|
||||
)
|
||||
|
||||
private fun Prog8ANTLRParser.ForloopContext.toAst(encoding: IStringEncoding): ForLoop {
|
||||
private fun Prog8ANTLRParser.ForloopContext.toAst(): ForLoop {
|
||||
val loopvar = identifier().toAst()
|
||||
val iterable = expression()!!.toAst(encoding)
|
||||
val iterable = expression()!!.toAst()
|
||||
val scope =
|
||||
if(statement()!=null)
|
||||
AnonymousScope(mutableListOf(statement().toAst(encoding)), statement().toPosition())
|
||||
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
||||
else
|
||||
AnonymousScope(statement_block().toAst(encoding), statement_block().toPosition())
|
||||
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
||||
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.BreakstmtContext.toAst() = Break(toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.WhileloopContext.toAst(encoding: IStringEncoding): WhileLoop {
|
||||
val condition = expression().toAst(encoding)
|
||||
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
||||
private fun Prog8ANTLRParser.WhileloopContext.toAst(): WhileLoop {
|
||||
val condition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return WhileLoop(condition, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.RepeatloopContext.toAst(encoding: IStringEncoding): RepeatLoop {
|
||||
val iterations = expression()?.toAst(encoding)
|
||||
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
||||
private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
val iterations = expression()?.toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return RepeatLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.UntilloopContext.toAst(encoding: IStringEncoding): UntilLoop {
|
||||
val untilCondition = expression().toAst(encoding)
|
||||
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
||||
private fun Prog8ANTLRParser.UntilloopContext.toAst(): UntilLoop {
|
||||
val untilCondition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return UntilLoop(scope, untilCondition, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.WhenstmtContext.toAst(encoding: IStringEncoding): WhenStatement {
|
||||
val condition = expression().toAst(encoding)
|
||||
val choices = this.when_choice()?.map { it.toAst(encoding) }?.toMutableList() ?: mutableListOf()
|
||||
private fun Prog8ANTLRParser.WhenstmtContext.toAst(): WhenStatement {
|
||||
val condition = expression().toAst()
|
||||
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
|
||||
return WhenStatement(condition, choices, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.When_choiceContext.toAst(encoding: IStringEncoding): WhenChoice {
|
||||
val values = expression_list()?.toAst(encoding)
|
||||
val stmt = statement()?.toAst(encoding)
|
||||
val stmtBlock = statement_block()?.toAst(encoding)?.toMutableList() ?: mutableListOf()
|
||||
private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
|
||||
val values = expression_list()?.toAst()
|
||||
val stmt = statement()?.toAst()
|
||||
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
|
||||
if(stmt!=null)
|
||||
stmtBlock.add(stmt)
|
||||
val scope = AnonymousScope(stmtBlock, toPosition())
|
||||
return WhenChoice(values?.toMutableList(), scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.VardeclContext.toAst(encoding: IStringEncoding): VarDecl {
|
||||
private fun Prog8ANTLRParser.VardeclContext.toAst(): VarDecl {
|
||||
return VarDecl(
|
||||
VarDeclType.VAR,
|
||||
datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
arrayindex()?.toAst(encoding),
|
||||
arrayindex()?.toAst(),
|
||||
varname.text,
|
||||
null,
|
||||
ARRAYSIG() != null || arrayindex() != null,
|
||||
|
@ -7,7 +7,6 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
@ -498,6 +497,37 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
}
|
||||
}
|
||||
|
||||
class CharLiteral(val value: Char,
|
||||
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null // TODO: CharLiteral.constValue can't be NumericLiteralValue...
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(walker: AstWalker, parent: Node) = walker.visit(this, parent)
|
||||
|
||||
override fun toString(): String = "'${escape(value.toString())}'"
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UNDEFINED) // FIXME: CharLiteral.inferType
|
||||
operator fun compareTo(other: CharLiteral): Int = value.compareTo(other.value)
|
||||
override fun hashCode(): Int = Objects.hash(value, altEncoding)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null || other !is CharLiteral)
|
||||
return false
|
||||
return value == other.value && altEncoding == other.altEncoding
|
||||
}
|
||||
}
|
||||
|
||||
class StringLiteralValue(val value: String,
|
||||
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||
override val position: Position) : Expression() {
|
||||
@ -636,7 +666,6 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
class RangeExpr(var from: Expression,
|
||||
var to: Expression,
|
||||
var step: Expression,
|
||||
private val encoding: IStringEncoding,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -689,51 +718,8 @@ class RangeExpr(var from: Expression,
|
||||
return "RangeExpr(from $from, to $to, step $step, pos=$position)"
|
||||
}
|
||||
|
||||
fun size(): Int? {
|
||||
val fromLv = (from as? NumericLiteralValue)
|
||||
val toLv = (to as? NumericLiteralValue)
|
||||
if(fromLv==null || toLv==null)
|
||||
return null
|
||||
return toConstantIntegerRange()?.count()
|
||||
}
|
||||
|
||||
fun toConstantIntegerRange(): IntProgression? {
|
||||
val fromVal: Int
|
||||
val toVal: Int
|
||||
val fromString = from as? StringLiteralValue
|
||||
val toString = to as? StringLiteralValue
|
||||
if(fromString!=null && toString!=null ) {
|
||||
// string range -> int range over character values
|
||||
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||
} else {
|
||||
val fromLv = from as? NumericLiteralValue
|
||||
val toLv = to as? NumericLiteralValue
|
||||
if(fromLv==null || toLv==null)
|
||||
return null // non-constant range
|
||||
// integer range
|
||||
fromVal = fromLv.number.toInt()
|
||||
toVal = toLv.number.toInt()
|
||||
}
|
||||
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
|
||||
return makeRange(fromVal, toVal, stepVal)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
return when {
|
||||
fromVal <= toVal -> when {
|
||||
stepVal <= 0 -> IntRange.EMPTY
|
||||
stepVal == 1 -> fromVal..toVal
|
||||
else -> fromVal..toVal step stepVal
|
||||
}
|
||||
else -> when {
|
||||
stepVal >= 0 -> IntRange.EMPTY
|
||||
stepVal == -1 -> fromVal downTo toVal
|
||||
else -> fromVal downTo toVal step abs(stepVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
|
@ -110,6 +110,7 @@ abstract class AstWalker {
|
||||
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@ -150,6 +151,7 @@ abstract class AstWalker {
|
||||
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@ -300,6 +302,11 @@ abstract class AstWalker {
|
||||
track(after(numLiteral, parent), numLiteral, parent)
|
||||
}
|
||||
|
||||
fun visit(char: CharLiteral, parent: Node) {
|
||||
track(before(char, parent), char, parent)
|
||||
track(after(char, parent), char, parent)
|
||||
}
|
||||
|
||||
fun visit(string: StringLiteralValue, parent: Node) {
|
||||
track(before(string, parent), string, parent)
|
||||
track(after(string, parent), string, parent)
|
||||
|
@ -79,6 +79,9 @@ interface IAstVisitor {
|
||||
fun visit(numLiteral: NumericLiteralValue) {
|
||||
}
|
||||
|
||||
fun visit(char: CharLiteral) {
|
||||
}
|
||||
|
||||
fun visit(string: StringLiteralValue) {
|
||||
}
|
||||
|
||||
|
@ -1,173 +0,0 @@
|
||||
package prog8.parser
|
||||
|
||||
import org.antlr.v4.runtime.*
|
||||
import prog8.ast.IStringEncoding
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.toAst
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.DirectiveArg
|
||||
import java.io.InputStream
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
|
||||
class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
||||
internal class CustomLexer(val modulePath: Path, input: CharStream?) : Prog8ANTLRLexer(input)
|
||||
|
||||
fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
|
||||
|
||||
internal fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
|
||||
|
||||
|
||||
class ModuleImporter(private val program: Program,
|
||||
private val encoder: IStringEncoding,
|
||||
private val compilationTargetName: String,
|
||||
private val libdirs: List<String>) {
|
||||
|
||||
fun importModule(filePath: Path): Module {
|
||||
print("importing '${moduleName(filePath.fileName)}'")
|
||||
if(filePath.parent!=null) {
|
||||
var importloc = filePath.toString()
|
||||
val curdir = Paths.get("").toAbsolutePath().toString()
|
||||
if(importloc.startsWith(curdir))
|
||||
importloc = "." + importloc.substring(curdir.length)
|
||||
println(" (from '$importloc')")
|
||||
}
|
||||
else
|
||||
println("")
|
||||
if(!Files.isReadable(filePath))
|
||||
throw ParsingFailedError("No such file: $filePath")
|
||||
|
||||
return importModule(CharStreams.fromPath(filePath), filePath)
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
val import = Directive("%import", listOf(
|
||||
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
return executeImportDirective(import, Paths.get(""))
|
||||
}
|
||||
|
||||
private class MyErrorListener: ConsoleErrorListener() {
|
||||
var numberOfErrors: Int = 0
|
||||
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
|
||||
numberOfErrors++
|
||||
when (recognizer) {
|
||||
is CustomLexer -> System.err.println("${recognizer.modulePath}:$line:$charPositionInLine: $msg")
|
||||
is Prog8ANTLRParser -> System.err.println("${recognizer.inputStream.sourceName}:$line:$charPositionInLine: $msg")
|
||||
else -> System.err.println("$line:$charPositionInLine $msg")
|
||||
}
|
||||
if(numberOfErrors>=5)
|
||||
throw ParsingFailedError("There are too many parse errors. Stopping.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun importModule(stream: CharStream, modulePath: Path): Module {
|
||||
val moduleName = moduleName(modulePath.fileName)
|
||||
val lexer = CustomLexer(modulePath, stream)
|
||||
lexer.removeErrorListeners()
|
||||
val lexerErrors = MyErrorListener()
|
||||
lexer.addErrorListener(lexerErrors)
|
||||
val tokens = CommentHandlingTokenStream(lexer)
|
||||
val parser = Prog8ANTLRParser(tokens)
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(MyErrorListener())
|
||||
val parseTree = parser.module()
|
||||
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
|
||||
if(numberOfErrors > 0)
|
||||
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
|
||||
|
||||
// You can do something with the parsed comments:
|
||||
// tokens.commentTokens().forEach { println(it) }
|
||||
|
||||
// convert to Ast
|
||||
val moduleAst = parseTree.toAst(moduleName, modulePath, encoder)
|
||||
moduleAst.program = program
|
||||
moduleAst.linkParents(program.namespace)
|
||||
program.modules.add(moduleAst)
|
||||
|
||||
// accept additional imports
|
||||
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, modulePath) }
|
||||
|
||||
moduleAst.statements = lines
|
||||
return moduleAst
|
||||
}
|
||||
|
||||
private fun executeImportDirective(import: Directive, source: Path): Module? {
|
||||
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
||||
throw SyntaxError("invalid import directive", 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
|
||||
|
||||
val rsc = tryGetModuleFromResource("$moduleName.p8", compilationTargetName)
|
||||
val importedModule =
|
||||
if(rsc!=null) {
|
||||
// load the module from the embedded resource
|
||||
val (resource, resourcePath) = rsc
|
||||
resource.use {
|
||||
println("importing '$moduleName' (library)")
|
||||
val content = it.reader().readText().replace("\r\n", "\n")
|
||||
importModule(CharStreams.fromString(content), Module.pathForResource(resourcePath))
|
||||
}
|
||||
} else {
|
||||
val modulePath = tryGetModuleFromFile(moduleName, source, import.position)
|
||||
importModule(modulePath)
|
||||
}
|
||||
|
||||
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")
|
||||
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): Pair<InputStream, String>? {
|
||||
val targetSpecificPath = "/prog8lib/$compilationTargetName/$name"
|
||||
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath)
|
||||
if(targetSpecificResource!=null)
|
||||
return Pair(targetSpecificResource, targetSpecificPath)
|
||||
|
||||
val generalPath = "/prog8lib/$name"
|
||||
val generalResource = object{}.javaClass.getResourceAsStream(generalPath)
|
||||
if(generalResource!=null)
|
||||
return Pair(generalResource, generalPath)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun tryGetModuleFromFile(name: String, source: Path, position: Position?): Path {
|
||||
val fileName = "$name.p8"
|
||||
val libpaths = libdirs.map {Path.of(it)}
|
||||
val locations =
|
||||
(if(source.toString().isEmpty()) libpaths else libpaths.drop(1) + listOf(source.parent ?: Path.of("."))) +
|
||||
listOf(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
|
||||
|
||||
locations.forEach {
|
||||
val file = pathFrom(it.toString(), fileName)
|
||||
if (Files.isReadable(file)) return file
|
||||
}
|
||||
|
||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: embedded libs and $locations)")
|
||||
}
|
||||
}
|
124
compilerAst/src/prog8/parser/Prog8Parser.kt
Normal file
124
compilerAst/src/prog8/parser/Prog8Parser.kt
Normal file
@ -0,0 +1,124 @@
|
||||
package prog8.parser
|
||||
|
||||
import org.antlr.v4.runtime.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.antlr.toAst
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Directive
|
||||
|
||||
|
||||
open class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
||||
class ParseError(override var message: String, val position: Position, cause: RuntimeException)
|
||||
: ParsingFailedError("${position.toClickableStr()}$message") {
|
||||
init {
|
||||
initCause(cause)
|
||||
}
|
||||
}
|
||||
|
||||
object Prog8Parser {
|
||||
|
||||
fun parseModule(src: SourceCode): Module {
|
||||
val antlrErrorListener = AntlrErrorListener(src)
|
||||
val lexer = Prog8ANTLRLexer(src.getCharStream())
|
||||
lexer.removeErrorListeners()
|
||||
lexer.addErrorListener(antlrErrorListener)
|
||||
val tokens = CommonTokenStream(lexer)
|
||||
val parser = Prog8ANTLRParser(tokens)
|
||||
parser.errorHandler = Prog8ErrorStrategy
|
||||
parser.removeErrorListeners()
|
||||
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
|
||||
) {
|
||||
val provenance = Pair(source, Triple(1, 0, 0))
|
||||
|
||||
/**
|
||||
* Adds a [Directive] to [statements] and
|
||||
* sets this Module as its [parent].
|
||||
* Note: you can only add [Directive]s or [Block]s to a Module.
|
||||
*/
|
||||
fun add(child: Directive) {
|
||||
child.linkParents(this)
|
||||
statements.add(child)
|
||||
}
|
||||
/**
|
||||
* Adds a [Block] to [statements] and
|
||||
* sets this Module as its [parent].
|
||||
* Note: you can only add [Directive]s or [Block]s to a Module.
|
||||
*/
|
||||
fun add(child: Block) {
|
||||
child.linkParents(this)
|
||||
statements.add(child)
|
||||
}
|
||||
}
|
||||
|
||||
private object Prog8ErrorStrategy: BailErrorStrategy() {
|
||||
private fun fillIn(e: RecognitionException?, ctx: ParserRuleContext?) {
|
||||
var context = ctx
|
||||
while (context != null) {
|
||||
context.exception = e
|
||||
context = context.getParent()
|
||||
}
|
||||
}
|
||||
|
||||
override fun reportInputMismatch(recognizer: Parser?, e: InputMismatchException?) {
|
||||
super.reportInputMismatch(recognizer, e)
|
||||
}
|
||||
|
||||
override fun recover(recognizer: Parser?, e: RecognitionException?) {
|
||||
fillIn(e, recognizer!!.context)
|
||||
reportError(recognizer, e)
|
||||
}
|
||||
|
||||
override fun recoverInline(recognizer: Parser?): Token {
|
||||
val e = InputMismatchException(recognizer)
|
||||
fillIn(e, recognizer!!.context)
|
||||
reportError(recognizer, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
} else {
|
||||
throw ParseError(msg, e.getPosition(src.origin), e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun RecognitionException.getPosition(file: String) : Position {
|
||||
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 pos = Position(file, line, beginCol, endCol)
|
||||
return pos
|
||||
}
|
||||
|
||||
}
|
140
compilerAst/src/prog8/parser/SourceCode.kt
Normal file
140
compilerAst/src/prog8/parser/SourceCode.kt
Normal file
@ -0,0 +1,140 @@
|
||||
package prog8.parser
|
||||
|
||||
import org.antlr.v4.runtime.CharStream
|
||||
import org.antlr.v4.runtime.CharStreams
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
/**
|
||||
* Encapsulates - and ties together - actual source code (=text)
|
||||
* and its [origin].
|
||||
*/
|
||||
abstract class SourceCode {
|
||||
|
||||
/**
|
||||
* To be used *only* by the parser (as input to a TokenStream).
|
||||
* DO NOT mess around with!
|
||||
*/
|
||||
internal abstract fun getCharStream(): CharStream
|
||||
|
||||
/**
|
||||
* Whether this [SourceCode] instance was created by
|
||||
* factory method [fromResources]
|
||||
*/
|
||||
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]
|
||||
* * `@embedded@/x/y/z.ext` if it came from resources (see [fromResources])
|
||||
*/
|
||||
abstract val origin: String
|
||||
|
||||
|
||||
/**
|
||||
* 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*)
|
||||
*/
|
||||
fun pathString() =
|
||||
origin
|
||||
.substringAfter("<").substringBeforeLast(">") // or from plain string?
|
||||
|
||||
/**
|
||||
* The source code as plain string.
|
||||
* *Note: this is meant for testing and debugging, do NOT use in application code!*
|
||||
*/
|
||||
fun asString() = this.getCharStream().toString()
|
||||
|
||||
/**
|
||||
* Deliberately does NOT return the actual text.
|
||||
* For this - if at all - use [getCharStream].
|
||||
*/
|
||||
final override fun toString() = "${this.javaClass.name}[${this.origin}]"
|
||||
|
||||
// "static" factory methods
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
val file = normalized.toFile()
|
||||
if (!path.exists())
|
||||
throw NoSuchFileException(file)
|
||||
if (path.isDirectory())
|
||||
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]: `<res:/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)
|
||||
if (rscURL == null) {
|
||||
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 = "@embedded@$normalized"
|
||||
override fun getCharStream(): CharStream {
|
||||
val inpStr = object{}.javaClass.getResourceAsStream(normalized)
|
||||
val chars = CharStreams.fromStream(inpStr)
|
||||
return chars
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
val uri = url.toURI()
|
||||
val parts = uri.toString().split("!")
|
||||
val fs = FileSystems.newFileSystem(URI.create(parts[0]), mutableMapOf(Pair("", "")) )
|
||||
val path = fs.getPath(parts[1])
|
||||
*/
|
||||
}
|
||||
}
|
@ -1,259 +0,0 @@
|
||||
package prog8tests
|
||||
|
||||
import org.antlr.v4.runtime.*
|
||||
import org.antlr.v4.runtime.misc.ParseCancellationException
|
||||
import org.junit.jupiter.api.Test
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.IStringEncoding
|
||||
import prog8.ast.antlr.toAst
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.Prog8ANTLRLexer
|
||||
import prog8.parser.Prog8ANTLRParser
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertIs
|
||||
|
||||
class TestAntlrParser {
|
||||
|
||||
class MyErrorListener: ConsoleErrorListener() {
|
||||
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
|
||||
throw ParsingFailedError("line $line:$charPositionInLine $msg")
|
||||
}
|
||||
}
|
||||
|
||||
class MyErrorStrategy: BailErrorStrategy() {
|
||||
override fun recover(recognizer: Parser?, e: RecognitionException?) {
|
||||
try {
|
||||
// let it fill in e in all the contexts
|
||||
super.recover(recognizer, e)
|
||||
} catch (pce: ParseCancellationException) {
|
||||
reportError(recognizer, e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun recoverInline(recognizer: Parser?): Token {
|
||||
throw InputMismatchException(recognizer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseModule(srcText: String): Prog8ANTLRParser.ModuleContext {
|
||||
return parseModule(CharStreams.fromString(srcText))
|
||||
}
|
||||
|
||||
private fun parseModule(srcFile: Path): Prog8ANTLRParser.ModuleContext {
|
||||
return parseModule(CharStreams.fromPath(srcFile))
|
||||
}
|
||||
|
||||
private fun parseModule(srcStream: CharStream): Prog8ANTLRParser.ModuleContext {
|
||||
val errorListener = MyErrorListener()
|
||||
val lexer = Prog8ANTLRLexer(srcStream)
|
||||
lexer.removeErrorListeners()
|
||||
lexer.addErrorListener(errorListener)
|
||||
val tokens = CommonTokenStream(lexer)
|
||||
val parser = Prog8ANTLRParser(tokens)
|
||||
parser.errorHandler = MyErrorStrategy()
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(errorListener)
|
||||
return parser.module()
|
||||
}
|
||||
|
||||
object DummyEncoding: IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
object DummyFunctions: IBuiltinFunctions {
|
||||
override val names: Set<String> = emptySet()
|
||||
override val purefunctionNames: Set<String> = emptySet()
|
||||
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
|
||||
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
||||
}
|
||||
|
||||
object DummyMemsizer: IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModuleSourceNeedNotEndWithNewline() {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
val srcText = "foo {" + nl + "}" // source ends with '}' (= NO newline, issue #40)
|
||||
|
||||
// before the fix, prog8Parser would have reported (thrown) "missing <EOL> at '<EOF>'"
|
||||
val parseTree = parseModule(srcText)
|
||||
assertEquals(parseTree.block().size, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModuleSourceMayEndWithNewline() {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
|
||||
val parseTree = parseModule(srcText)
|
||||
assertEquals(parseTree.block().size, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAllBlocksButLastMustEndWithNewline() {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
|
||||
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
|
||||
val srcBad = "foo {" + nl + "}" + " bar {" + nl + "}" + nl
|
||||
|
||||
// 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<ParsingFailedError> { parseModule(srcBad) }
|
||||
val parseTree = parseModule(srcGood)
|
||||
assertEquals(parseTree.block().size, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWindowsAndMacNewlinesAreAlsoFine() {
|
||||
val nlWin = "\r\n"
|
||||
val nlUnix = "\n"
|
||||
val nlMac = "\r"
|
||||
|
||||
//parseModule(Paths.get("test", "fixtures", "mac_newlines.p8").toAbsolutePath())
|
||||
|
||||
// a good mix of all kinds of newlines:
|
||||
val srcText =
|
||||
"foo {" +
|
||||
nlMac +
|
||||
nlWin +
|
||||
"}" +
|
||||
nlMac + // <-- do test a single \r (!) where an EOL is expected
|
||||
"bar {" +
|
||||
nlUnix +
|
||||
"}" +
|
||||
nlUnix + nlMac // both should be "eaten up" by just one EOL token
|
||||
"combi {" +
|
||||
nlMac + nlWin + nlUnix // all three should be "eaten up" by just one EOL token
|
||||
"}" +
|
||||
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
|
||||
|
||||
val parseTree = parseModule(srcText)
|
||||
assertEquals(parseTree.block().size, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInterleavedEolAndCommentBeforeFirstBlock() {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
blockA {
|
||||
}
|
||||
"""
|
||||
val parseTree = parseModule(srcText)
|
||||
assertEquals(parseTree.block().size, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInterleavedEolAndCommentBetweenBlocks() {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
blockA {
|
||||
}
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
blockB {
|
||||
}
|
||||
"""
|
||||
val parseTree = parseModule(srcText)
|
||||
assertEquals(parseTree.block().size, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInterleavedEolAndCommentAfterLastBlock() {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
blockA {
|
||||
}
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
"""
|
||||
val parseTree = parseModule(srcText)
|
||||
assertEquals(parseTree.block().size, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNewlineBetweenTwoBlocksOrDirectivesStillRequired() {
|
||||
// issue: #47
|
||||
|
||||
// block and block
|
||||
assertFailsWith<ParsingFailedError>{ parseModule("""
|
||||
blockA {
|
||||
} blockB {
|
||||
}
|
||||
""") }
|
||||
|
||||
// block and directive
|
||||
assertFailsWith<ParsingFailedError>{ parseModule("""
|
||||
blockB {
|
||||
} %import textio
|
||||
""") }
|
||||
|
||||
// The following two are bogus due to directive *args* expected to follow the directive name.
|
||||
// Leaving them in anyways.
|
||||
|
||||
// dir and block
|
||||
assertFailsWith<ParsingFailedError>{ parseModule("""
|
||||
%import textio blockB {
|
||||
}
|
||||
""") }
|
||||
|
||||
assertFailsWith<ParsingFailedError>{ parseModule("""
|
||||
%import textio %import syslib
|
||||
""") }
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
fun testImportLibraryModule() {
|
||||
val program = Program("foo", mutableListOf(), DummyFunctions, DummyMemsizer)
|
||||
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures"))
|
||||
|
||||
//assertFailsWith<ParsingFailedError>(){ importer.importLibraryModule("import_file_with_syntax_error") }
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
fun testProg8Ast() {
|
||||
// can create charstreams from many other sources as well;
|
||||
val charstream = CharStreams.fromString("""
|
||||
main {
|
||||
sub start() {
|
||||
return
|
||||
}
|
||||
}
|
||||
""")
|
||||
val lexer = Prog8ANTLRLexer(charstream)
|
||||
val tokens = CommonTokenStream(lexer)
|
||||
val parser = Prog8ANTLRParser(tokens)
|
||||
parser.errorHandler = BailErrorStrategy()
|
||||
// parser.removeErrorListeners()
|
||||
// parser.addErrorListener(MyErrorListener())
|
||||
|
||||
val ast = parser.module().toAst("test", Path.of(""), DummyEncoding)
|
||||
assertIs<Block>(ast.statements.first())
|
||||
assertEquals((ast.statements.first() as Block).name, "main")
|
||||
}
|
||||
}
|
115
compilerAst/test/TestAstToSourceCode.kt
Normal file
115
compilerAst/test/TestAstToSourceCode.kt
Normal file
@ -0,0 +1,115 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import kotlin.test.*
|
||||
import prog8tests.helpers.*
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
|
||||
import prog8.ast.AstToSourceCode
|
||||
import prog8.parser.ParseError
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAstToSourceCode {
|
||||
|
||||
private fun generateP8(module: Module) : String {
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
|
||||
var generatedText = ""
|
||||
val it = AstToSourceCode({ str -> generatedText += str }, program)
|
||||
it.visit(program)
|
||||
|
||||
return generatedText
|
||||
}
|
||||
|
||||
private fun roundTrip(module: Module): Pair<String, Module> {
|
||||
val generatedText = generateP8(module)
|
||||
try {
|
||||
val parsedAgain = parseModule(SourceCode.of(generatedText))
|
||||
return Pair(generatedText, parsedAgain)
|
||||
} catch (e: ParseError) {
|
||||
assert(false) { "should produce valid Prog8 but threw $e" }
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMentionsInternedStringsModule() {
|
||||
val orig = SourceCode.of("\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex(";.*$internedStringsModuleName"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportDirectiveWithLib() {
|
||||
val orig = SourceCode.of("%import textio\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("%import +textio"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportDirectiveWithUserModule() {
|
||||
val orig = SourceCode.of("%import my_own_stuff\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("%import +my_own_stuff"))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testStringLiteral_noAlt() {
|
||||
val orig = SourceCode.of("""
|
||||
main {
|
||||
str s = "fooBar\n"
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("str +s += +\"fooBar\\\\n\""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStringLiteral_withAlt() {
|
||||
val orig = SourceCode.of("""
|
||||
main {
|
||||
str sAlt = @"fooBar\n"
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("str +sAlt += +@\"fooBar\\\\n\""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharLiteral_noAlt() {
|
||||
val orig = SourceCode.of("""
|
||||
main {
|
||||
ubyte c = 'x'
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("ubyte +c += +'x'"), "char literal")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharLiteral_withAlt() {
|
||||
val orig = SourceCode.of("""
|
||||
main {
|
||||
ubyte cAlt = @'x'
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("ubyte +cAlt += +@'x'"), "alt char literal")
|
||||
}
|
||||
|
||||
}
|
527
compilerAst/test/TestProg8Parser.kt
Normal file
527
compilerAst/test/TestProg8Parser.kt
Normal file
@ -0,0 +1,527 @@
|
||||
package prog8tests
|
||||
|
||||
import prog8tests.helpers.*
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Nested
|
||||
import kotlin.test.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
import prog8.ast.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestProg8Parser {
|
||||
|
||||
@Nested
|
||||
inner class Newline {
|
||||
|
||||
@Nested
|
||||
inner class AtEnd {
|
||||
|
||||
@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)
|
||||
|
||||
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
|
||||
val module = parseModule(src)
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
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))
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is required after each block except the last`() {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
|
||||
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
|
||||
val srcBad = "foo {" + nl + "}" + " bar {" + nl + "}" + nl
|
||||
|
||||
// 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))
|
||||
assertEquals(2, module.statements.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is required between two Blocks or Directives - #47`() {
|
||||
// block and block
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
||||
blockA {
|
||||
} blockB {
|
||||
}
|
||||
""")) }
|
||||
|
||||
// block and directive
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
||||
blockB {
|
||||
} %import textio
|
||||
""")) }
|
||||
|
||||
// The following two are bogus due to directive *args* expected to follow the directive name.
|
||||
// Leaving them in anyways.
|
||||
|
||||
// dir and block
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
||||
%import textio blockB {
|
||||
}
|
||||
""")) }
|
||||
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
||||
%import textio %import syslib
|
||||
""")) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can be Win, Unix or mixed, even mixed`() {
|
||||
val nlWin = "\r\n"
|
||||
val nlUnix = "\n"
|
||||
val nlMac = "\r"
|
||||
|
||||
//parseModule(Paths.get("test", "fixtures", "mac_newlines.p8").toAbsolutePath())
|
||||
|
||||
// a good mix of all kinds of newlines:
|
||||
val srcText =
|
||||
"foo {" +
|
||||
nlMac +
|
||||
nlWin +
|
||||
"}" +
|
||||
nlMac + // <-- do test a single \r (!) where an EOL is expected
|
||||
"bar {" +
|
||||
nlUnix +
|
||||
"}" +
|
||||
nlUnix + nlMac // both should be "eaten up" by just one EOL token
|
||||
"combi {" +
|
||||
nlMac + nlWin + nlUnix // all three should be "eaten up" by just one EOL token
|
||||
"}" +
|
||||
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
|
||||
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
assertEquals(2, module.statements.size)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class EOLsInterleavedWithComments {
|
||||
|
||||
@Test
|
||||
fun `are ok before first block - #47`() {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
blockA {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `are ok between blocks - #47`() {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
blockA {
|
||||
}
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
blockB {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
assertEquals(2, module.statements.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `are ok after last block - #47`() {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
blockA {
|
||||
}
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
"""
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
inner class ImportDirectives {
|
||||
@Test
|
||||
fun `should not be looked into by the parser`() {
|
||||
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))
|
||||
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
inner class EmptySourcecode {
|
||||
@Test
|
||||
fun `from an empty string should result in empty Module`() {
|
||||
val module = parseModule(SourceCode.of(""))
|
||||
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))
|
||||
assertEquals(0, module.statements.size)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class NameOfModule {
|
||||
@Test
|
||||
fun `parsed from a string`() {
|
||||
val srcText = """
|
||||
main {
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
|
||||
// Note: assertContains has *actual* as first param
|
||||
assertContains(module.name, Regex("^anonymous_[0-9a-f]+$"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parsed from a file`() {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val module = parseModule(SourceCode.fromPath(path))
|
||||
assertEquals(path.nameWithoutExtension, module.name)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class PositionOfAstNodesAndParseErrors {
|
||||
|
||||
private fun assertPosition(
|
||||
actual: Position,
|
||||
expFile: String? = null,
|
||||
expLine: Int? = null,
|
||||
expStartCol: Int? = null,
|
||||
expEndCol: Int? = null
|
||||
) {
|
||||
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
|
||||
if (expLine != null) assertEquals(expLine, actual.line, ".position.line (1-based)")
|
||||
if (expStartCol != null) assertEquals(expStartCol, actual.startCol, ".position.startCol (0-based)")
|
||||
if (expEndCol != null) assertEquals(expEndCol, actual.endCol, ".position.endCol (0-based)")
|
||||
if (expFile != null) assertEquals(expFile, actual.file, ".position.file")
|
||||
}
|
||||
|
||||
private fun assertPosition(
|
||||
actual: Position,
|
||||
expFile: Regex? = null,
|
||||
expLine: Int? = null,
|
||||
expStartCol: Int? = null,
|
||||
expEndCol: Int? = null
|
||||
) {
|
||||
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
|
||||
if (expLine != null) assertEquals(expLine, actual.line, ".position.line (1-based)")
|
||||
if (expStartCol != null) assertEquals(expStartCol, actual.startCol, ".position.startCol (0-based)")
|
||||
if (expEndCol != null) assertEquals(expEndCol, actual.endCol, ".position.endCol (0-based)")
|
||||
// Note: assertContains expects *actual* value first
|
||||
if (expFile != null) assertContains(actual.file, expFile, ".position.file")
|
||||
}
|
||||
|
||||
private fun assertPositionOf(
|
||||
actual: Node,
|
||||
expFile: String? = null,
|
||||
expLine: Int? = null,
|
||||
expStartCol: Int? = null,
|
||||
expEndCol: Int? = null
|
||||
) =
|
||||
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
|
||||
|
||||
private fun assertPositionOf(
|
||||
actual: Node,
|
||||
expFile: Regex? = null,
|
||||
expLine: Int? = null,
|
||||
expStartCol: Int? = null,
|
||||
expEndCol: Int? = null
|
||||
) =
|
||||
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
|
||||
|
||||
|
||||
@Test
|
||||
fun `in ParseError from bad string source code`() {
|
||||
val srcText = "bad * { }\n"
|
||||
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.of(srcText)) }
|
||||
try {
|
||||
parseModule(SourceCode.of(srcText))
|
||||
} catch (e: ParseError) {
|
||||
assertPosition(e.position, Regex("^<String@[0-9a-f]+>$"), 1, 4, 4)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ParseError from bad file source code`() {
|
||||
val path = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.fromPath(path)) }
|
||||
try {
|
||||
parseModule(SourceCode.fromPath(path))
|
||||
} catch (e: ParseError) {
|
||||
assertPosition(e.position, path.absolutePathString(), 2, 6) // TODO: endCol wrong
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `of Module parsed from a string`() {
|
||||
val srcText = """
|
||||
main {
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.of(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
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `of non-root Nodes parsed from file`() {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
|
||||
val module = parseModule(SourceCode.fromPath(path))
|
||||
val mpf = module.position.file
|
||||
|
||||
assertPositionOf(module, path.absolutePathString(), 1, 0) // TODO: endCol wrong
|
||||
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
||||
assertPositionOf(mainBlock, mpf, 1, 0) // TODO: endCol wrong!
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
||||
assertPositionOf(startSub, mpf, 2, 4) // TODO: endCol wrong!
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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..?
|
||||
main {
|
||||
sub start() {
|
||||
ubyte foo = 42
|
||||
ubyte bar
|
||||
when (foo) {
|
||||
23 -> bar = 'x' ; WhenChoice, also directly inheriting Node
|
||||
42 -> bar = 'y'
|
||||
else -> bar = 'z'
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.of(srcText))
|
||||
val mpf = module.position.file
|
||||
|
||||
val targetDirective = module.statements.filterIsInstance<Directive>()[0]
|
||||
assertPositionOf(targetDirective, mpf, 1, 0) // TODO: endCol wrong!
|
||||
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
||||
assertPositionOf(mainBlock, mpf, 2, 0) // TODO: endCol wrong!
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
||||
assertPositionOf(startSub, mpf, 3, 4) // TODO: endCol wrong!
|
||||
val declFoo = startSub.statements.filterIsInstance<VarDecl>()[0]
|
||||
assertPositionOf(declFoo, mpf, 4, 8) // TODO: endCol wrong!
|
||||
val rhsFoo = declFoo.value!!
|
||||
assertPositionOf(rhsFoo, mpf, 4, 20) // TODO: endCol wrong!
|
||||
val declBar = startSub.statements.filterIsInstance<VarDecl>()[1]
|
||||
assertPositionOf(declBar, mpf, 5, 8) // TODO: endCol wrong!
|
||||
val whenStmt = startSub.statements.filterIsInstance<WhenStatement>()[0]
|
||||
assertPositionOf(whenStmt, mpf, 6, 8) // TODO: endCol wrong!
|
||||
assertPositionOf(whenStmt.choices[0], mpf, 7, 12) // TODO: endCol wrong!
|
||||
assertPositionOf(whenStmt.choices[1], mpf, 8, 12) // TODO: endCol wrong!
|
||||
assertPositionOf(whenStmt.choices[2], mpf, 9, 12) // TODO: endCol wrong!
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class CharLiterals {
|
||||
|
||||
@Test
|
||||
fun `in argument position, no altEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
main {
|
||||
sub start() {
|
||||
chrout('\n')
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
|
||||
val startSub = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>().first()
|
||||
|
||||
assertIs<CharLiteral>(funCall.args[0])
|
||||
val char = funCall.args[0] as CharLiteral
|
||||
assertEquals('\n', char.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on rhs of block-level var decl, no AltEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
main {
|
||||
ubyte c = 'x'
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
assertEquals('x', rhs.value, "char literal's .value")
|
||||
assertEquals(false, rhs.altEncoding, "char literal's .altEncoding")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on rhs of block-level const decl, with AltEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
main {
|
||||
const ubyte c = @'x'
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
assertEquals('x', rhs.value, "char literal's .value")
|
||||
assertEquals(true, rhs.altEncoding, "char literal's .altEncoding")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on rhs of subroutine-level var decl, no AltEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte c = 'x'
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
assertEquals('x', rhs.value, "char literal's .value")
|
||||
assertEquals(false, rhs.altEncoding, "char literal's .altEncoding")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on rhs of subroutine-level const decl, with AltEnc`() {
|
||||
val src = SourceCode.of("""
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte c = @'x'
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
assertEquals('x', rhs.value, "char literal's .value")
|
||||
assertEquals(true, rhs.altEncoding, "char literal's .altEncoding")
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Ranges {
|
||||
|
||||
@Test
|
||||
fun `in for-loops`() {
|
||||
val module = parseModule(SourceCode.of("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte ub
|
||||
for ub in "start" downto "end" { ; #0
|
||||
}
|
||||
for ub in "something" { ; #1
|
||||
}
|
||||
for ub in @'a' to 'f' { ; #2
|
||||
}
|
||||
for ub in false to true { ; #3
|
||||
}
|
||||
for ub in 9 to 1 { ; #4 - yes, *parser* should NOT check!
|
||||
}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
val iterables = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
|
||||
assertEquals(5, iterables.size)
|
||||
|
||||
val it0 = iterables[0] as RangeExpr
|
||||
assertIs<StringLiteralValue>(it0.from, "parser should leave it as is")
|
||||
assertIs<StringLiteralValue>(it0.to, "parser should leave it as is")
|
||||
|
||||
val it1 = iterables[1] as StringLiteralValue
|
||||
assertEquals("something", it1.value, "parser should leave it as is")
|
||||
|
||||
val it2 = iterables[2] as RangeExpr
|
||||
assertIs<CharLiteral>(it2.from, "parser should leave it as is")
|
||||
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")
|
||||
|
||||
val it4 = iterables[4] as RangeExpr
|
||||
assertIs<NumericLiteralValue>(it4.from, "parser should leave it as is")
|
||||
assertIs<NumericLiteralValue>(it4.to, "parser should leave it as is")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
146
compilerAst/test/TestSourceCode.kt
Normal file
146
compilerAst/test/TestSourceCode.kt
Normal file
@ -0,0 +1,146 @@
|
||||
package prog8tests
|
||||
|
||||
import prog8tests.helpers.*
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.*
|
||||
import kotlin.test.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
import prog8.parser.SourceCode
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestSourceCode {
|
||||
|
||||
@Test
|
||||
fun testFactoryMethod_Of() {
|
||||
val text = """
|
||||
main { }
|
||||
""".trimIndent()
|
||||
val src = SourceCode.of(text)
|
||||
val actualText = src.getCharStream().toString()
|
||||
|
||||
assertContains(src.origin, Regex("^<String@[0-9a-f]+>$"))
|
||||
assertEquals(text, actualText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithNonExistingPath() {
|
||||
val filename = "i_do_not_exist.p8"
|
||||
val path = assumeNotExists(fixturesDir, filename)
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.fromPath(path) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithMissingExtension_p8() {
|
||||
val pathWithoutExt = assumeNotExists(fixturesDir,"simple_main")
|
||||
assumeReadableFile(fixturesDir,"simple_main.p8")
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.fromPath(pathWithoutExt) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithDirectory() {
|
||||
assertFailsWith<AccessDeniedException> { SourceCode.fromPath(fixturesDir) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingPath() {
|
||||
val filename = "simple_main.p8"
|
||||
val path = assumeReadableFile(fixturesDir, filename)
|
||||
val src = SourceCode.fromPath(path)
|
||||
|
||||
val expectedOrigin = path.normalize().absolutePathString()
|
||||
assertEquals(expectedOrigin, src.origin)
|
||||
assertEquals(path.toFile().readText(), src.asString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingNonNormalizedPath() {
|
||||
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()
|
||||
assertEquals(expectedOrigin, src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromResourcesWithExistingP8File_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
|
||||
assertEquals("@embedded@/$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromResourcesWithExistingP8File_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
|
||||
assertEquals("@embedded@$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromResourcesWithExistingAsmFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/math.asm"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
|
||||
assertEquals("@embedded@/$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
assertTrue(src.isFromResources, ".isFromResources")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromResourcesWithExistingAsmFile_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/math.asm"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
|
||||
assertEquals("@embedded@$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromResourcesWithNonNormalizedPath() {
|
||||
val pathString = "/prog8lib/../prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.fromResources(pathString)
|
||||
|
||||
assertEquals("@embedded@/prog8lib/math.p8", src.origin)
|
||||
assertEquals(srcFile.readText(), src.asString())
|
||||
assertTrue(src.isFromResources, ".isFromResources")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString.substring(1))
|
||||
|
||||
assertThrows<NoSuchFileException> { SourceCode.fromResources(pathString) }
|
||||
}
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString)
|
||||
|
||||
assertThrows<NoSuchFileException> { SourceCode.fromResources(pathString) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: inside resources: cannot tell apart a folder from a file")
|
||||
fun testFromResourcesWithDirectory() {
|
||||
val pathString = "/prog8lib"
|
||||
assumeDirectory(resourcesDir, pathString.substring(1))
|
||||
assertThrows<AccessDeniedException> { SourceCode.fromResources(pathString) }
|
||||
}
|
||||
|
||||
}
|
118
compilerAst/test/ast/ProgramTests.kt
Normal file
118
compilerAst/test/ast/ProgramTests.kt
Normal file
@ -0,0 +1,118 @@
|
||||
package prog8tests
|
||||
|
||||
import prog8tests.helpers.*
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.containsString
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.*
|
||||
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import java.lang.IllegalArgumentException
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertNotSame
|
||||
import kotlin.test.assertSame
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class ProgramTests {
|
||||
|
||||
@Nested
|
||||
inner class Constructor {
|
||||
@Test
|
||||
fun withNameBuiltinsAndMemsizer() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
|
||||
assertSame(program, program.modules[0].program)
|
||||
assertSame(program.namespace, program.modules[0].parent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class AddModule {
|
||||
@Test
|
||||
fun withEmptyModule() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
val m1 = Module("bar", mutableListOf(), Position.DUMMY, null)
|
||||
|
||||
val retVal = program.addModule(m1)
|
||||
|
||||
assertSame(program, retVal)
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, m1)
|
||||
assertSame(program, m1.program)
|
||||
assertSame(program.namespace, m1.parent)
|
||||
|
||||
assertThrows<IllegalArgumentException> { program.addModule(m1) }
|
||||
.let { assertThat(it.message, containsString(m1.name)) }
|
||||
|
||||
val m2 = Module(m1.name, mutableListOf(), m1.position, m1.source)
|
||||
assertThrows<IllegalArgumentException> { program.addModule(m2) }
|
||||
.let { assertThat(it.message, containsString(m2.name)) }
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class MoveModuleToFront {
|
||||
@Test
|
||||
fun withInternedStringsModule() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
val m = program.modules[0]
|
||||
assertThat(m.name, equalTo(internedStringsModuleName))
|
||||
|
||||
val retVal = program.moveModuleToFront(m)
|
||||
assertSame(program, retVal)
|
||||
assertSame(m, program.modules[0])
|
||||
}
|
||||
@Test
|
||||
fun withForeignModule() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
val m = Module("bar", mutableListOf(), Position.DUMMY, null)
|
||||
|
||||
assertThrows<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)
|
||||
program.addModule(m1)
|
||||
program.addModule(m2)
|
||||
|
||||
val retVal = program.moveModuleToFront(m1)
|
||||
assertSame(program, retVal)
|
||||
assertThat(program.modules.indexOf(m1), equalTo(0))
|
||||
}
|
||||
@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)
|
||||
program.addModule(m1)
|
||||
program.addModule(m2)
|
||||
|
||||
val retVal = program.moveModuleToFront(m2)
|
||||
assertSame(program, retVal)
|
||||
assertThat(program.modules.indexOf(m2), equalTo(0))
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Properties {
|
||||
@Test
|
||||
fun modules() {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
|
||||
val ms1 = program.modules
|
||||
val ms2 = program.modules
|
||||
assertSame(ms1, ms2)
|
||||
}
|
||||
}
|
||||
}
|
0
compilerAst/test/fixtures/empty.p8
vendored
Normal file
0
compilerAst/test/fixtures/empty.p8
vendored
Normal file
2
compilerAst/test/fixtures/file_with_syntax_error.p8
vendored
Normal file
2
compilerAst/test/fixtures/file_with_syntax_error.p8
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
; test expects the following 2nd (!) line:
|
||||
bad { } ; -> missing EOL at '}' (ie: *after* the '{')
|
4
compilerAst/test/fixtures/simple_main.p8
vendored
Normal file
4
compilerAst/test/fixtures/simple_main.p8
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
}
|
21
compilerAst/test/helpers/DummyFunctions.kt
Normal file
21
compilerAst/test/helpers/DummyFunctions.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
|
||||
val DummyFunctions = object : IBuiltinFunctions {
|
||||
override val names: Set<String> = emptySet()
|
||||
override val purefunctionNames: Set<String> = emptySet()
|
||||
override fun constValue(
|
||||
name: String,
|
||||
args: List<Expression>,
|
||||
position: Position,
|
||||
memsizer: IMemSizer
|
||||
): NumericLiteralValue? = null
|
||||
|
||||
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
||||
}
|
8
compilerAst/test/helpers/DummyMemsizer.kt
Normal file
8
compilerAst/test/helpers/DummyMemsizer.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.base.DataType
|
||||
|
||||
val DummyMemsizer = object : IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
}
|
25
compilerAst/test/helpers/mapCombinations.kt
Normal file
25
compilerAst/test/helpers/mapCombinations.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
fun <A, B, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, combine2: (A, B) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
yield(combine2(a, b))
|
||||
}.toList()
|
||||
|
||||
fun <A, B, C, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, dim3: Iterable<C>, combine3: (A, B, C) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
yield(combine3(a, b, c))
|
||||
}.toList()
|
||||
|
||||
fun <A, B, C, D, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, dim3: Iterable<C>, dim4: Iterable<D>, combine4: (A, B, C, D) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
for (d in dim4)
|
||||
yield(combine4(a, b, c, d))
|
||||
}.toList()
|
53
compilerAst/test/helpers/paths.kt
Normal file
53
compilerAst/test/helpers/paths.kt
Normal file
@ -0,0 +1,53 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
import kotlin.test.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
val workingDir = assumeDirectory("").absolute() // Note: "." does NOT work..!
|
||||
val fixturesDir = assumeDirectory(workingDir,"test/fixtures")
|
||||
val resourcesDir = assumeDirectory(workingDir,"res")
|
||||
val outputDir = assumeDirectory(workingDir, "build/tmp/test")
|
||||
|
||||
fun assumeNotExists(path: Path): Path {
|
||||
assertFalse(path.exists(), "sanity check: should not exist: ${path.absolute()}")
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeNotExists(pathStr: String): Path = assumeNotExists(Path(pathStr))
|
||||
fun assumeNotExists(path: Path, other: String): Path = assumeNotExists(path.div(other))
|
||||
|
||||
fun assumeReadable(path: Path): Path {
|
||||
assertTrue(path.isReadable(), "sanity check: should be readable: ${path.absolute()}")
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeReadableFile(path: Path): Path {
|
||||
assertTrue(path.isRegularFile(), "sanity check: should be normal file: ${path.absolute()}")
|
||||
return assumeReadable(path)
|
||||
}
|
||||
|
||||
fun assumeReadableFile(pathStr: String): Path = assumeReadableFile(Path(pathStr))
|
||||
fun assumeReadableFile(pathStr: String, other: Path): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(pathStr: String, other: String): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(path: Path, other: String): Path = assumeReadableFile(path.div(other))
|
||||
fun assumeReadableFile(path: Path, other: Path): Path = assumeReadableFile(path.div(other))
|
||||
|
||||
fun assumeDirectory(path: Path): Path {
|
||||
assertTrue(path.isDirectory(), "sanity check; should be directory: $path")
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeDirectory(pathStr: String): Path = assumeDirectory(Path(pathStr))
|
||||
fun assumeDirectory(path: Path, other: String): Path = assumeDirectory(path.div(other))
|
||||
fun assumeDirectory(pathStr: String, other: String): Path = assumeDirectory(Path(pathStr).div(other))
|
||||
fun assumeDirectory(pathStr: String, other: Path): Path = assumeDirectory(Path(pathStr).div(other))
|
||||
|
||||
|
||||
@Deprecated("Directories are checked automatically at init.",
|
||||
ReplaceWith("/* nothing */"))
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun sanityCheckDirectories(workingDirName: String? = null) {
|
||||
}
|
363
compilerAst/test/helpers_pathsTests.kt
Normal file
363
compilerAst/test/helpers_pathsTests.kt
Normal file
@ -0,0 +1,363 @@
|
||||
package prog8tests
|
||||
|
||||
import prog8tests.helpers.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.*
|
||||
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
||||
import kotlin.io.path.*
|
||||
|
||||
|
||||
// Do not move into folder helpers/!
|
||||
// This folder is also used by compiler/test
|
||||
// but the testing of the helpers themselves must be performed ONLY HERE.
|
||||
//
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class PathsHelpersTests {
|
||||
|
||||
@Nested
|
||||
inner class AssumeNotExists {
|
||||
|
||||
@Nested
|
||||
inner class WithOnePathArg {
|
||||
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
val path = fixturesDir.div("i_do_not_exist")
|
||||
assertThat("should return the path",
|
||||
assumeNotExists(path), `is`(path))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing file`() {
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir.div("simple_main.p8"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing directory`() {
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithOneStringArg {
|
||||
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
val path = fixturesDir.div("i_do_not_exist")
|
||||
assertThat("should return the path",
|
||||
assumeNotExists("$path"), `is`(path))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing file`() {
|
||||
val path = fixturesDir.div("simple_main.p8")
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeNotExists("$path")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing directory`() {
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeNotExists("$fixturesDir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithPathAndStringArgs {
|
||||
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
val path = fixturesDir.div("i_do_not_exist")
|
||||
assertThat("should return the path",
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist"), `is`(path))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing file`() {
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir, "simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing directory`() {
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir, "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class AssumeDirectory {
|
||||
|
||||
@Nested
|
||||
inner class WithOnePathArg {
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
val path = fixturesDir.div("i_do_not_exist")
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory(path)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing file`() {
|
||||
val path = fixturesDir.div("simple_main.p8")
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory(path)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing directory`() {
|
||||
val path = workingDir
|
||||
assertThat("should return the path", assumeDirectory(path), `is`(path))
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithOneStringArg {
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
val path = fixturesDir.div("i_do_not_exist")
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory("$path")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing file`() {
|
||||
val path = fixturesDir.div("simple_main.p8")
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory("$path")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing directory`() {
|
||||
val path = workingDir
|
||||
assertThat("should return the path",
|
||||
assumeDirectory("$path"), `is`(path))
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithPathAndStringArgs {
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory(fixturesDir, "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing file`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory(fixturesDir, "simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing directory`() {
|
||||
val path = workingDir.div("..")
|
||||
assertThat(
|
||||
"should return resulting path",
|
||||
assumeDirectory(workingDir, ".."), `is`(path)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithStringAndStringArgs {
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing file`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", "simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing directory`() {
|
||||
val path = workingDir.div("..")
|
||||
assertThat(
|
||||
"should return resulting path",
|
||||
assumeDirectory("$workingDir", ".."), `is`(path)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithStringAndPathArgs {
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", Path("i_do_not_exist"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing file`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", Path("simple_main.p8"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on existing directory`() {
|
||||
val path = workingDir.div("..")
|
||||
assertThat(
|
||||
"should return resulting path",
|
||||
assumeDirectory("$workingDir", Path("..")), `is`(path)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class AssumeReadableFile {
|
||||
|
||||
@Nested
|
||||
inner class WithOnePathArg {
|
||||
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
val path = fixturesDir.div("i_do_not_exist")
|
||||
assertThrows<AssertionError> {
|
||||
assumeReadableFile(path)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on readable file`() {
|
||||
val path = fixturesDir.div("simple_main.p8")
|
||||
assertThat("should return the path",
|
||||
assumeReadableFile(path), `is`(path))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on directory`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeReadableFile(fixturesDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithOneStringArg {
|
||||
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
val path = fixturesDir.div("i_do_not_exist")
|
||||
assertThrows<AssertionError> {
|
||||
assumeReadableFile("$path")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on readable file`() {
|
||||
val path = fixturesDir.div("simple_main.p8")
|
||||
assertThat("should return the resulting path",
|
||||
assumeReadableFile("$path"), `is`(path))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on directory`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeReadableFile("$fixturesDir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithPathAndStringArgs {
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeReadableFile(fixturesDir, "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on readable file`() {
|
||||
val path = fixturesDir.div("simple_main.p8")
|
||||
assertThat("should return the resulting path",
|
||||
assumeReadableFile(fixturesDir, "simple_main.p8"), `is`(path))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on directory`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeReadableFile(fixturesDir, "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithPathAndPathArgs {
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeReadableFile(fixturesDir, Path("i_do_not_exist"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun `on readable file`() {
|
||||
assertThat("should return the resulting path",
|
||||
assumeReadableFile(fixturesDir, Path("simple_main.p8")),
|
||||
`is`(fixturesDir.div("simple_main.p8"))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on directory`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeReadableFile(fixturesDir, Path(".."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithStringAndStringArgs {
|
||||
@Test
|
||||
fun `on non-existing path`() {
|
||||
assertThrows<java.lang.AssertionError> {
|
||||
assumeReadableFile("$fixturesDir", "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on readable file`() {
|
||||
assertThat("should return the resulting path",
|
||||
assumeReadableFile(fixturesDir.toString(), "simple_main.p8"),
|
||||
`is`(fixturesDir.div("simple_main.p8"))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on directory`() {
|
||||
assertThrows<AssertionError> {
|
||||
assumeReadableFile("$fixturesDir", "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user