mirror of
https://github.com/irmen/prog8.git
synced 2025-01-13 10:29:52 +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 {
|
test {
|
||||||
java {
|
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.ICompilationTarget
|
||||||
import prog8.compiler.target.asmGeneratorFor
|
import prog8.compiler.target.asmGeneratorFor
|
||||||
import prog8.optimizer.*
|
import prog8.optimizer.*
|
||||||
import prog8.parser.ModuleImporter
|
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
import prog8.parser.moduleName
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.*
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
|
||||||
@ -135,7 +134,7 @@ fun compileProgram(filepath: Path,
|
|||||||
throw x
|
throw x
|
||||||
}
|
}
|
||||||
|
|
||||||
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||||
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,27 +168,29 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
|||||||
builtinFunctionReturnType(name, args, program)
|
builtinFunctionReturnType(name, args, program)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseImports(filepath: Path,
|
fun parseImports(filepath: Path,
|
||||||
errors: IErrorReporter,
|
errors: IErrorReporter,
|
||||||
compTarget: ICompilationTarget,
|
compTarget: ICompilationTarget,
|
||||||
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||||
val compilationTargetName = compTarget.name
|
println("Compiler target: ${compTarget.name}. Parsing...")
|
||||||
println("Compiler target: $compilationTargetName. Parsing...")
|
|
||||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
|
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
|
||||||
bf.program = programAst
|
bf.program = programAst
|
||||||
|
|
||||||
val importer = ModuleImporter(programAst, compTarget, compilationTargetName, libdirs)
|
val importer = ModuleImporter(programAst, compTarget.name, libdirs)
|
||||||
importer.importModule(filepath)
|
importer.importModule(filepath)
|
||||||
errors.report()
|
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)
|
val compilerOptions = determineCompilationOptions(programAst, compTarget)
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||||
|
|
||||||
// depending on the machine and compiler options we may have to include some libraries
|
// 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)
|
importer.importLibraryModule(lib)
|
||||||
|
|
||||||
// always import prog8_lib and math
|
// always import prog8_lib and math
|
||||||
@ -199,7 +200,7 @@ private fun parseImports(filepath: Path,
|
|||||||
return Triple(programAst, compilerOptions, importedFiles)
|
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 mainModule = program.mainModule
|
||||||
val outputDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
|
val outputDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
|
||||||
val launcherDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
|
val 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}...")
|
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||||
programAst.checkIdentifiers(errors, compilerOptions)
|
programAst.checkIdentifiers(errors, compilerOptions)
|
||||||
errors.report()
|
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)
|
programAst.constantFold(errors, compilerOptions.compTarget)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.reorderStatements(errors)
|
programAst.reorderStatements(errors)
|
||||||
@ -346,14 +350,14 @@ fun printAst(programAst: Program) {
|
|||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
fun loadAsmIncludeFile(filename: String, sourcePath: Path): String {
|
||||||
return if (filename.startsWith("library:")) {
|
return if (filename.startsWith("library:")) { // FIXME: is the prefix "library:" or is it "@embedded@"?
|
||||||
val resource = tryGetEmbeddedResource(filename.substring(8))
|
val resource = tryGetEmbeddedResource(filename.substring(8))
|
||||||
?: throw IllegalArgumentException("library file '$filename' not found")
|
?: throw IllegalArgumentException("library file '$filename' not found")
|
||||||
resource.bufferedReader().use { it.readText() }
|
resource.bufferedReader().use { it.readText() }
|
||||||
} else {
|
} else {
|
||||||
// first try in the isSameAs folder as where the containing file was imported from
|
// 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)
|
if (sib.toFile().isFile)
|
||||||
sib.toFile().readText()
|
sib.toFile().readText()
|
||||||
else
|
else
|
||||||
@ -361,6 +365,9 @@ fun loadAsmIncludeFile(filename: String, source: Path): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle via SourceCode
|
||||||
|
*/
|
||||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
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 prog8.compiler.target.ICompilationTarget
|
||||||
import java.io.CharConversionException
|
import java.io.CharConversionException
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.io.path.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal class AstChecker(private val program: Program,
|
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) {
|
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)
|
while (definingModule !is Module)
|
||||||
definingModule = definingModule.parent
|
definingModule = definingModule.parent
|
||||||
if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
|
if (definingModule.isLibrary())
|
||||||
errors.err("included file not found: $filename", directive.position)
|
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) {
|
override fun visit(array: ArrayLiteralValue) {
|
||||||
@ -756,10 +769,20 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(array)
|
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) {
|
override fun visit(string: StringLiteralValue) {
|
||||||
checkValueTypeAndRangeString(DataType.STR, string)
|
checkValueTypeAndRangeString(DataType.STR, string)
|
||||||
|
|
||||||
try {
|
try { // just *try* if it can be encoded, don't actually do it
|
||||||
compTarget.encodeString(string.value, string.altEncoding)
|
compTarget.encodeString(string.value, string.altEncoding)
|
||||||
} catch (cx: CharConversionException) {
|
} catch (cx: CharConversionException) {
|
||||||
errors.err(cx.message ?: "can't encode string", string.position)
|
errors.err(cx.message ?: "can't encode string", string.position)
|
||||||
|
@ -1,12 +1,65 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.compiler.IStringEncoding
|
||||||
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.FatalAstException
|
||||||
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
import prog8.compiler.target.ICompilationTarget
|
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) {
|
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) {
|
internal fun Program.addTypecasts(errors: IErrorReporter) {
|
||||||
val caster = TypecastsAdder(this, errors)
|
val caster = TypecastsAdder(this, errors)
|
||||||
caster.visit(this)
|
caster.visit(this)
|
||||||
@ -58,8 +125,24 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
|
|||||||
lit2decl.applyModifications()
|
lit2decl.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
// Check if each module has a unique name.
|
||||||
throw FatalAstException("modules should all be unique")
|
// 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 start = this.entrypoint()
|
||||||
val mod = start.definingModule()
|
val mod = start.definingModule()
|
||||||
val block = start.definingBlock()
|
val block = start.definingBlock()
|
||||||
if(!modules.remove(mod))
|
moveModuleToFront(mod)
|
||||||
throw FatalAstException("module wrong")
|
|
||||||
modules.add(0, mod)
|
|
||||||
mod.remove(block)
|
mod.remove(block)
|
||||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||||
if(afterDirective<0)
|
if(afterDirective<0)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package prog8.compiler.target
|
package prog8.compiler.target
|
||||||
|
|
||||||
import prog8.ast.IMemSizer
|
import prog8.ast.IMemSizer
|
||||||
import prog8.ast.IStringEncoding
|
import prog8.compiler.IStringEncoding
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
@ -24,6 +24,8 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
|||||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
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 {
|
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
|
||||||
val memAddr = target.memoryAddress
|
val memAddr = target.memoryAddress
|
||||||
val arrayIdx = target.arrayindexed
|
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.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
|
||||||
import prog8.optimizer.CallGraph
|
import prog8.optimizer.CallGraph
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.io.path.*
|
||||||
import kotlin.math.absoluteValue
|
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) {
|
private fun translate(stmt: Directive) {
|
||||||
when(stmt.directive) {
|
when(stmt.directive) {
|
||||||
"%asminclude" -> {
|
"%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'))
|
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
|
||||||
}
|
}
|
||||||
"%asmbinary" -> {
|
"%asmbinary" -> {
|
||||||
|
val includedName = stmt.args[0].str!!
|
||||||
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
|
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 length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
|
||||||
val includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
|
val sourcePath = Path(stmt.definingModule().source!!.pathString()) // FIXME: %asmbinary inside non-library, non-filesystem module
|
||||||
val relPath = Paths.get("").relativize(includedSourcePath)
|
val includedPath = sourcePath.resolveSibling(includedName)
|
||||||
out(" .binary \"$relPath\" $offset $length")
|
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" -> {
|
"%breakpoint" -> {
|
||||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||||
|
@ -9,6 +9,7 @@ import prog8.ast.expressions.RangeExpr
|
|||||||
import prog8.ast.statements.ForLoop
|
import prog8.ast.statements.ForLoop
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
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")
|
throw AssemblyError("unknown dt")
|
||||||
when(stmt.iterable) {
|
when(stmt.iterable) {
|
||||||
is RangeExpr -> {
|
is RangeExpr -> {
|
||||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
|
||||||
if(range==null) {
|
if(range==null) {
|
||||||
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||||
} else {
|
} else {
|
||||||
|
@ -9,11 +9,10 @@ import prog8.ast.statements.ForLoop
|
|||||||
import prog8.ast.statements.VarDecl
|
import prog8.ast.statements.VarDecl
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.target.ICompilationTarget
|
|
||||||
import kotlin.math.pow
|
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> {
|
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||||
// @( &thing ) --> thing
|
// @( &thing ) --> thing
|
||||||
@ -223,7 +222,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
|||||||
range.step
|
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.
|
// 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.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.astprocessing.size
|
||||||
|
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
|
||||||
// Fix up the literal value's type to match that of the vardecl
|
// 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 -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
val numericLv = decl.value as? NumericLiteralValue
|
|
||||||
val rangeExpr = decl.value as? RangeExpr
|
val rangeExpr = decl.value as? RangeExpr
|
||||||
if(rangeExpr!=null) {
|
if(rangeExpr!=null) {
|
||||||
// convert the initializer range expression to an actual array
|
// convert the initializer range expression to an actual array
|
||||||
val declArraySize = decl.arraysize?.constIndex()
|
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!!)
|
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) {
|
if(constRange!=null) {
|
||||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||||
val newValue = if(eltType in ByteDatatypes) {
|
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))
|
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val numericLv = decl.value as? NumericLiteralValue
|
||||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||||
errors.err("arraysize requires only integers here", numericLv.position)
|
errors.err("arraysize requires only integers here", numericLv.position)
|
||||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||||
@ -208,15 +210,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
|
||||||
val litval = decl.value as? NumericLiteralValue
|
|
||||||
val rangeExpr = decl.value as? RangeExpr
|
val rangeExpr = decl.value as? RangeExpr
|
||||||
if(rangeExpr!=null) {
|
if(rangeExpr!=null) {
|
||||||
// convert the initializer range expression to an actual array of floats
|
// convert the initializer range expression to an actual array of floats
|
||||||
val declArraySize = decl.arraysize?.constIndex()
|
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!!)
|
errors.err("range expression size (${rangeExpr.size(compTarget)}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||||
val constRange = rangeExpr.toConstantIntegerRange()
|
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
|
||||||
if(constRange!=null) {
|
if(constRange!=null) {
|
||||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
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))
|
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.
|
// 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)
|
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 {
|
else {
|
||||||
// create the array itself, filled with the fillvalue.
|
// create the array itself, filled with the fillvalue.
|
||||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
|
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 = litval.position)
|
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
|
|||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
valuetypefixer.applyModifications()
|
valuetypefixer.applyModifications()
|
||||||
|
|
||||||
val optimizer = ConstantFoldingOptimizer(this, compTarget)
|
val optimizer = ConstantFoldingOptimizer(this)
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
while (errors.noErrors() && optimizer.applyModifications() > 0) {
|
while (errors.noErrors() && optimizer.applyModifications() > 0) {
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
|
@ -11,6 +11,7 @@ import prog8.ast.walk.AstWalker
|
|||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.astprocessing.size
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
@ -197,7 +198,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
|
|
||||||
val range = forLoop.iterable as? RangeExpr
|
val range = forLoop.iterable as? RangeExpr
|
||||||
if(range!=null) {
|
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
|
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||||
// loopvar/reg = range value , follow by block
|
// loopvar/reg = range value , follow by block
|
||||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.hamcrest.Matchers.equalTo
|
import org.hamcrest.Matchers.equalTo
|
||||||
import org.junit.jupiter.api.Test
|
import prog8tests.helpers.*
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import prog8.ast.IBuiltinFunctions
|
|
||||||
import prog8.ast.IMemSizer
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
@ -17,18 +17,9 @@ import prog8.compiler.target.c64.C64MachineDefinition
|
|||||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestAsmGen6502 {
|
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 {
|
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 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 block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
|
||||||
|
|
||||||
val module = Module("test", mutableListOf(block), Position.DUMMY, Path.of(""))
|
val module = Module("test", mutableListOf(block), Position.DUMMY, null)
|
||||||
module.linkParents(ParentSentinel)
|
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
.addModule(module)
|
||||||
module.program = program
|
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program.namespace)?!
|
||||||
return program
|
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
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
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.IFunctionCall
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.VarDeclType
|
import prog8.ast.base.VarDeclType
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
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 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)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestCompilerOnCharLit {
|
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
|
@Test
|
||||||
fun testCharLitAsRomsubArg() {
|
fun testCharLitAsRomsubArg() {
|
||||||
val filepath = fixturesDir.resolve("charLitAsRomsubArg.p8")
|
val platform = Cx16Target
|
||||||
val compilationTarget = Cx16Target
|
val result = compileText(platform, false, """
|
||||||
val result = compileProgram(
|
main {
|
||||||
filepath,
|
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||||
optimize = false,
|
sub start() {
|
||||||
writeAssembly = true,
|
chrout('\n')
|
||||||
slowCodegenWarnings = false,
|
}
|
||||||
compilationTarget.name,
|
}
|
||||||
libdirs = listOf(),
|
""").assertSuccess()
|
||||||
outputDir
|
|
||||||
)
|
|
||||||
assertTrue(result.success, "compilation should succeed")
|
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.programAst
|
||||||
val startSub = program.entrypoint()
|
val startSub = program.entrypoint()
|
||||||
@ -58,23 +44,22 @@ class TestCompilerOnCharLit {
|
|||||||
"char literal should have been replaced by ubyte literal")
|
"char literal should have been replaced by ubyte literal")
|
||||||
val arg = funCall.args[0] as NumericLiteralValue
|
val arg = funCall.args[0] as NumericLiteralValue
|
||||||
assertEquals(DataType.UBYTE, arg.type)
|
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
|
@Test
|
||||||
fun testCharVarAsRomsubArg() {
|
fun testCharVarAsRomsubArg() {
|
||||||
val filepath = fixturesDir.resolve("charVarAsRomsubArg.p8")
|
val platform = Cx16Target
|
||||||
val compilationTarget = Cx16Target
|
val result = compileText(platform, false, """
|
||||||
val result = compileProgram(
|
main {
|
||||||
filepath,
|
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||||
optimize = false,
|
sub start() {
|
||||||
writeAssembly = true,
|
ubyte ch = '\n'
|
||||||
slowCodegenWarnings = false,
|
chrout(ch)
|
||||||
compilationTarget.name,
|
}
|
||||||
libdirs = listOf(),
|
}
|
||||||
outputDir
|
""").assertSuccess()
|
||||||
)
|
|
||||||
assertTrue(result.success, "compilation should succeed")
|
|
||||||
val program = result.programAst
|
val program = result.programAst
|
||||||
val startSub = program.entrypoint()
|
val startSub = program.entrypoint()
|
||||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||||
@ -94,23 +79,22 @@ class TestCompilerOnCharLit {
|
|||||||
"char literal should have been replaced by ubyte literal")
|
"char literal should have been replaced by ubyte literal")
|
||||||
val initializerValue = decl.value as NumericLiteralValue
|
val initializerValue = decl.value as NumericLiteralValue
|
||||||
assertEquals(DataType.UBYTE, initializerValue.type)
|
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
|
@Test
|
||||||
fun testCharConstAsRomsubArg() {
|
fun testCharConstAsRomsubArg() {
|
||||||
val filepath = fixturesDir.resolve("charConstAsRomsubArg.p8")
|
val platform = Cx16Target
|
||||||
val compilationTarget = Cx16Target
|
val result = compileText(platform, false, """
|
||||||
val result = compileProgram(
|
main {
|
||||||
filepath,
|
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||||
optimize = false,
|
sub start() {
|
||||||
writeAssembly = true,
|
const ubyte ch = '\n'
|
||||||
slowCodegenWarnings = false,
|
chrout(ch)
|
||||||
compilationTarget.name,
|
}
|
||||||
libdirs = listOf(),
|
}
|
||||||
outputDir
|
""").assertSuccess()
|
||||||
)
|
|
||||||
assertTrue(result.success, "compilation should succeed")
|
|
||||||
val program = result.programAst
|
val program = result.programAst
|
||||||
val startSub = program.entrypoint()
|
val startSub = program.entrypoint()
|
||||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||||
@ -122,16 +106,17 @@ class TestCompilerOnCharLit {
|
|||||||
assertEquals(VarDeclType.CONST, decl.type)
|
assertEquals(VarDeclType.CONST, decl.type)
|
||||||
assertEquals(DataType.UBYTE, decl.datatype)
|
assertEquals(DataType.UBYTE, decl.datatype)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
compilationTarget.encodeString("\n", false)[0],
|
platform.encodeString("\n", false)[0],
|
||||||
(decl.value as NumericLiteralValue).number)
|
(decl.value as NumericLiteralValue).number.toShort()) // TODO: short/int/UBYTE - which should it be?
|
||||||
}
|
}
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
compilationTarget.encodeString("\n", false)[0],
|
platform.encodeString("\n", false)[0],
|
||||||
arg.number)
|
arg.number.toShort()) // TODO: short/int/UBYTE - which should it be?
|
||||||
}
|
}
|
||||||
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
|
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
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.compileProgram
|
||||||
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.compiler.target.ICompilationTarget
|
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,
|
* They are not really unit tests, but rather tests of the whole process,
|
||||||
* from source file loading all the way through to running 64tass.
|
* from source file loading all the way through to running 64tass.
|
||||||
*/
|
*/
|
||||||
|
//@Disabled("to save some time")
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestCompilerOnExamples {
|
class TestCompilerOnExamples {
|
||||||
val workingDir = Path("").absolute() // Note: Path(".") does NOT work..!
|
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make assembly stage testable - in case of failure (eg of 64tass) it Process.exit s
|
// TODO: make assembly stage testable - in case of failure (eg of 64tass) it Process.exit s
|
||||||
|
|
||||||
fun testExample(nameWithoutExt: String, platform: ICompilationTarget, optimize: Boolean) {
|
private fun makeDynamicCompilerTest(name: String, platform: ICompilationTarget, optimize: Boolean) : DynamicTest {
|
||||||
val filepath = examplesDir.resolve("$nameWithoutExt.p8")
|
val searchIn = mutableListOf(examplesDir)
|
||||||
val result = compileProgram(
|
if (platform == Cx16Target) {
|
||||||
filepath,
|
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||||
optimize,
|
}
|
||||||
writeAssembly = true,
|
val filepath = searchIn
|
||||||
slowCodegenWarnings = false,
|
.map { it.resolve("$name.p8") }
|
||||||
compilationTarget = platform.name,
|
.map { it.normalize().absolute() }
|
||||||
libdirs = listOf(),
|
.map { workingDir.relativize(it) }
|
||||||
outputDir
|
.first { it.exists() }
|
||||||
)
|
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${platform.name}, optimize=$optimize"
|
||||||
assertTrue(result.success,
|
return dynamicTest(displayName) {
|
||||||
"compilation should succeed; ${platform.name}, optimize=$optimize: \"$filepath\"")
|
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
|
@TestFactory
|
||||||
fun test_cxLogo_noopt() {
|
// @Disabled
|
||||||
testExample("cxlogo", Cx16Target, false)
|
fun onlyC64() = mapCombinations(
|
||||||
}
|
dim1 = listOf(
|
||||||
@Test
|
"balloonflight",
|
||||||
fun test_cxLogo_opt() {
|
"bdmusic",
|
||||||
testExample("cxlogo", Cx16Target, true)
|
"bdmusic-irq",
|
||||||
}
|
"charset",
|
||||||
|
"cube3d-sprites",
|
||||||
@Test
|
"plasma",
|
||||||
fun test_swirl_noopt() {
|
"sprites",
|
||||||
testExample("swirl", Cx16Target, false)
|
"turtle-gfx",
|
||||||
}
|
"wizzine",
|
||||||
@Test
|
),
|
||||||
fun test_swirl_opt() {
|
dim2 = listOf(C64Target),
|
||||||
testExample("swirl", Cx16Target, true)
|
dim3 = listOf(false, true),
|
||||||
}
|
combine3 = ::makeDynamicCompilerTest
|
||||||
|
)
|
||||||
@Test
|
|
||||||
fun test_animals_noopt() {
|
|
||||||
testExample("animals", Cx16Target, false)
|
|
||||||
}
|
|
||||||
@Test
|
|
||||||
fun test_animals_opt() {
|
|
||||||
testExample("animals", Cx16Target, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@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
|
package prog8tests
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import prog8.ast.IBuiltinFunctions
|
import kotlin.test.*
|
||||||
import prog8.ast.IMemSizer
|
import prog8tests.helpers.*
|
||||||
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
@ -12,29 +14,17 @@ import prog8.ast.base.VarDeclType
|
|||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.target.C64Target
|
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 {
|
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
|
@Test
|
||||||
fun testInValidRamC64_memory_addresses() {
|
fun testInValidRamC64_memory_addresses() {
|
||||||
|
|
||||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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))
|
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||||
|
|
||||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||||
@ -59,7 +49,7 @@ class TestMemory {
|
|||||||
|
|
||||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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))
|
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||||
|
|
||||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||||
@ -78,7 +68,7 @@ class TestMemory {
|
|||||||
@Test
|
@Test
|
||||||
fun testInValidRamC64_memory_identifiers() {
|
fun testInValidRamC64_memory_identifiers() {
|
||||||
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
|
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
|
||||||
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||||
|
|
||||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
|
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
|
||||||
@ -98,7 +88,7 @@ class TestMemory {
|
|||||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, 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 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)
|
module.linkParents(ParentSentinel)
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
@ -107,7 +97,7 @@ class TestMemory {
|
|||||||
fun testInValidRamC64_memory_expression() {
|
fun testInValidRamC64_memory_expression() {
|
||||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, 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))
|
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,9 +107,10 @@ class TestMemory {
|
|||||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, 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 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)
|
||||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||||
module.linkParents(ParentSentinel)
|
.addModule(module)
|
||||||
|
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,9 +121,10 @@ class TestMemory {
|
|||||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, 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 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)
|
||||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||||
module.linkParents(ParentSentinel)
|
.addModule(module)
|
||||||
|
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,9 +135,10 @@ class TestMemory {
|
|||||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, 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 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)
|
||||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||||
module.linkParents(ParentSentinel)
|
.addModule(module)
|
||||||
|
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,9 +149,10 @@ class TestMemory {
|
|||||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, 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 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)
|
||||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||||
module.linkParents(ParentSentinel)
|
.addModule(module)
|
||||||
|
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,9 +164,10 @@ class TestMemory {
|
|||||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, 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 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)
|
||||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||||
module.linkParents(ParentSentinel)
|
.addModule(module)
|
||||||
|
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,9 +179,10 @@ class TestMemory {
|
|||||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, 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 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)
|
||||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||||
module.linkParents(ParentSentinel)
|
.addModule(module)
|
||||||
|
module.linkParents(ParentSentinel) // TODO: why not module.linkParents(program) or .linkParents(program.namespace)?
|
||||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
package prog8tests
|
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.MatcherAssert.assertThat
|
||||||
import org.hamcrest.Matchers.closeTo
|
import org.hamcrest.Matchers.closeTo
|
||||||
import org.hamcrest.Matchers.equalTo
|
import org.hamcrest.Matchers.equalTo
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.CompilerException
|
import prog8.compiler.CompilerException
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFailsWith
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestNumbers {
|
class TestNumbers {
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.ArrayLiteralValue
|
import prog8.ast.expressions.ArrayLiteralValue
|
||||||
import prog8.ast.expressions.InferredTypes
|
import prog8.ast.expressions.InferredTypes
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
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)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestNumericLiteralValue {
|
class TestNumericLiteralValue {
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package prog8tests
|
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.MatcherAssert.assertThat
|
||||||
import org.hamcrest.Matchers.equalTo
|
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.DataType
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.compiler.target.cbm.Petscii
|
import prog8.compiler.target.cbm.Petscii
|
||||||
import kotlin.test.*
|
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.compiler.*
|
import prog8.compiler.*
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||||
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
|
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)
|
@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 {
|
main {
|
||||||
java {
|
java {
|
||||||
srcDirs = ["${project.projectDir}/src"]
|
srcDirs = ["${project.projectDir}/src"]
|
||||||
}
|
}
|
||||||
resources {
|
|
||||||
srcDirs = ["${project.projectDir}/res"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
test {
|
test {
|
||||||
java {
|
java {
|
||||||
srcDirs = ["${project.projectDir}/test"]
|
srcDirs = ["${project.projectDir}/test"]
|
||||||
}
|
}
|
||||||
|
resources {
|
||||||
|
srcDirs = ["${project.projectDir}/res"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<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$/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
</content>
|
</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
|
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 {
|
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
|
||||||
private var scopelevel = 0
|
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()))
|
private fun outputi(s: Any) = output(indent(s.toString()))
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
override fun visit(program: Program) {
|
||||||
outputln("============= PROGRAM ${program.name} (FROM AST) ===============")
|
outputln("; ============ PROGRAM ${program.name} (FROM AST) ==============")
|
||||||
super.visit(program)
|
super.visit(program)
|
||||||
outputln("============= END PROGRAM ${program.name} (FROM AST) ===========")
|
outputln("; =========== END PROGRAM ${program.name} (FROM AST) ===========")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
override fun visit(module: Module) {
|
||||||
@ -261,7 +266,15 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
output(numLiteral.number.toString())
|
output(numLiteral.number.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(char: CharLiteral) {
|
||||||
|
if (char.altEncoding)
|
||||||
|
output("@")
|
||||||
|
output("'${escape(char.value.toString())}'")
|
||||||
|
}
|
||||||
|
|
||||||
override fun visit(string: StringLiteralValue) {
|
override fun visit(string: StringLiteralValue) {
|
||||||
|
if (string.altEncoding)
|
||||||
|
output("@")
|
||||||
output("\"${escape(string.value)}\"")
|
output("\"${escape(string.value)}\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,57 +5,13 @@ import prog8.ast.expressions.*
|
|||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import java.nio.file.Path
|
import prog8.parser.SourceCode
|
||||||
import java.nio.file.Paths
|
|
||||||
import kotlin.io.path.name
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
const val internedStringsModuleName = "prog8_interned_strings"
|
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 {
|
interface IAssignable {
|
||||||
val position: Position
|
// just a tag for now
|
||||||
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 IFunctionCall {
|
interface IFunctionCall {
|
||||||
@ -63,7 +19,6 @@ interface IFunctionCall {
|
|||||||
var args: MutableList<Expression>
|
var args: MutableList<Expression>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface INameScope {
|
interface INameScope {
|
||||||
val name: String
|
val name: String
|
||||||
val position: Position
|
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 *************/
|
/*********** Everything starts from here, the Program; zero or more modules *************/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Program(val name: String,
|
class Program(val name: String,
|
||||||
val modules: MutableList<Module>,
|
|
||||||
val builtinFunctions: IBuiltinFunctions,
|
val builtinFunctions: IBuiltinFunctions,
|
||||||
val memsizer: IMemSizer): Node {
|
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 }
|
get() = modules.first { it.name!=internedStringsModuleName }
|
||||||
|
|
||||||
val definedLoadAddress: Int
|
val definedLoadAddress: Int
|
||||||
get() = mainModule.loadAddress
|
get() = mainModule.loadAddress
|
||||||
|
|
||||||
var actualLoadAddress: Int = 0
|
var actualLoadAddress: Int = 0
|
||||||
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
|
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> {
|
fun internString(string: StringLiteralValue): List<String> {
|
||||||
// Move a string literal into the internal, deduplicated, string pool
|
// Move a string literal into the internal, deduplicated, string pool
|
||||||
// replace it with a variable declaration that points to the entry in the 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
|
return scopedName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
|
||||||
|
|
||||||
override val position: Position = Position.DUMMY
|
override val position: Position = Position.DUMMY
|
||||||
override var parent: Node
|
override var parent: Node
|
||||||
get() = throw FatalAstException("program has no parent")
|
get() = throw FatalAstException("program has no parent")
|
||||||
@ -333,16 +331,17 @@ class Program(val name: String,
|
|||||||
|
|
||||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
require(node is Module && replacement is Module)
|
require(node is Module && replacement is Module)
|
||||||
val idx = modules.indexOfFirst { it===node }
|
val idx = _modules.indexOfFirst { it===node }
|
||||||
modules[idx] = replacement
|
_modules[idx] = replacement
|
||||||
replacement.parent = this
|
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 var statements: MutableList<Statement>,
|
||||||
override val position: Position,
|
override val position: Position,
|
||||||
val source: Path) : Node, INameScope {
|
val source: SourceCode?) : Node, INameScope {
|
||||||
|
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
lateinit var program: Program
|
lateinit var program: Program
|
||||||
@ -370,19 +369,11 @@ class Module(override val name: String,
|
|||||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
companion object {
|
fun isLibrary() = (source == null) || source.isFromResources
|
||||||
fun pathForResource(resourcePath: String): Path {
|
|
||||||
return Paths.get("@embedded@/$resourcePath")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isLibrary(source: Path) = source.name=="" || source.startsWith("@embedded@/")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isLibrary() = isLibrary(source)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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 name = "<<<global>>>"
|
||||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||||
override val statements = mutableListOf<Statement>() // not used
|
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
|
package prog8.ast.antlr
|
||||||
|
|
||||||
import org.antlr.v4.runtime.IntStream
|
|
||||||
import org.antlr.v4.runtime.ParserRuleContext
|
import org.antlr.v4.runtime.ParserRuleContext
|
||||||
import org.antlr.v4.runtime.tree.TerminalNode
|
import org.antlr.v4.runtime.tree.TerminalNode
|
||||||
import prog8.ast.IStringEncoding
|
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.parser.CustomLexer
|
|
||||||
import prog8.parser.Prog8ANTLRParser
|
import prog8.parser.Prog8ANTLRParser
|
||||||
import java.io.CharConversionException
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
/***************** Antlr Extension methods to create AST ****************/
|
/***************** Antlr Extension methods to create AST ****************/
|
||||||
|
|
||||||
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
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 {
|
private fun ParserRuleContext.toPosition() : Position {
|
||||||
|
/*
|
||||||
val customTokensource = this.start.tokenSource as? CustomLexer
|
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||||
val filename =
|
val filename =
|
||||||
when {
|
when {
|
||||||
@ -34,15 +22,18 @@ private fun ParserRuleContext.toPosition() : Position {
|
|||||||
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
||||||
else -> File(start.inputStream.sourceName).name
|
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...
|
// 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)
|
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 {
|
val blockstatements = block_statement().map {
|
||||||
when {
|
when {
|
||||||
it.variabledeclaration()!=null -> it.variabledeclaration().toAst(encoding)
|
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
|
||||||
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst(encoding)
|
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
|
||||||
it.directive()!=null -> it.directive().toAst()
|
it.directive()!=null -> it.directive().toAst()
|
||||||
it.inlineasm()!=null -> it.inlineasm().toAst()
|
it.inlineasm()!=null -> it.inlineasm().toAst()
|
||||||
it.labeldef()!=null -> it.labeldef().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())
|
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.Statement_blockContext.toAst(encoding: IStringEncoding): MutableList<Statement> =
|
private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||||
statement().asSequence().map { it.toAst(encoding) }.toMutableList()
|
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringEncoding) : Statement {
|
private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
|
||||||
vardecl()?.let { return it.toAst(encoding) }
|
vardecl()?.let { return it.toAst() }
|
||||||
|
|
||||||
varinitializer()?.let {
|
varinitializer()?.let {
|
||||||
val vd = it.vardecl()
|
val vd = it.vardecl()
|
||||||
@ -64,9 +55,9 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringE
|
|||||||
VarDeclType.VAR,
|
VarDeclType.VAR,
|
||||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(encoding),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
it.expression().toAst(encoding),
|
it.expression().toAst(),
|
||||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
false,
|
false,
|
||||||
vd.SHARED()!=null,
|
vd.SHARED()!=null,
|
||||||
@ -81,9 +72,9 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringE
|
|||||||
VarDeclType.CONST,
|
VarDeclType.CONST,
|
||||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(encoding),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
cvarinit.expression().toAst(encoding),
|
cvarinit.expression().toAst(),
|
||||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
false,
|
false,
|
||||||
vd.SHARED() != null,
|
vd.SHARED() != null,
|
||||||
@ -98,9 +89,9 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringE
|
|||||||
VarDeclType.MEMORY,
|
VarDeclType.MEMORY,
|
||||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(encoding),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
mvarinit.expression().toAst(encoding),
|
mvarinit.expression().toAst(),
|
||||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
false,
|
false,
|
||||||
vd.SHARED()!=null,
|
vd.SHARED()!=null,
|
||||||
@ -111,33 +102,33 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst(encoding: IStringE
|
|||||||
throw FatalAstException("weird variable decl $this")
|
throw FatalAstException("weird variable decl $this")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.SubroutinedeclarationContext.toAst(encoding: IStringEncoding) : Subroutine {
|
private fun Prog8ANTLRParser.SubroutinedeclarationContext.toAst() : Subroutine {
|
||||||
return when {
|
return when {
|
||||||
subroutine()!=null -> subroutine().toAst(encoding)
|
subroutine()!=null -> subroutine().toAst()
|
||||||
asmsubroutine()!=null -> asmsubroutine().toAst(encoding)
|
asmsubroutine()!=null -> asmsubroutine().toAst()
|
||||||
romsubroutine()!=null -> romsubroutine().toAst()
|
romsubroutine()!=null -> romsubroutine().toAst()
|
||||||
else -> throw FatalAstException("weird subroutine decl $this")
|
else -> throw FatalAstException("weird subroutine decl $this")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.StatementContext.toAst(encoding: IStringEncoding) : Statement {
|
private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||||
val vardecl = variabledeclaration()?.toAst(encoding)
|
val vardecl = variabledeclaration()?.toAst()
|
||||||
if(vardecl!=null) return vardecl
|
if(vardecl!=null) return vardecl
|
||||||
|
|
||||||
assignment()?.let {
|
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 {
|
augassignment()?.let {
|
||||||
// replace A += X with A = A + X
|
// 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 oper = it.operator.text.substringBefore('=')
|
||||||
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(encoding), it.expression().toPosition())
|
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
|
||||||
return Assignment(it.assign_target().toAst(encoding), expression, it.toPosition())
|
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
postincrdecr()?.let {
|
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()
|
val directive = directive()?.toAst()
|
||||||
@ -149,49 +140,49 @@ private fun Prog8ANTLRParser.StatementContext.toAst(encoding: IStringEncoding) :
|
|||||||
val jump = unconditionaljump()?.toAst()
|
val jump = unconditionaljump()?.toAst()
|
||||||
if(jump!=null) return jump
|
if(jump!=null) return jump
|
||||||
|
|
||||||
val fcall = functioncall_stmt()?.toAst(encoding)
|
val fcall = functioncall_stmt()?.toAst()
|
||||||
if(fcall!=null) return fcall
|
if(fcall!=null) return fcall
|
||||||
|
|
||||||
val ifstmt = if_stmt()?.toAst(encoding)
|
val ifstmt = if_stmt()?.toAst()
|
||||||
if(ifstmt!=null) return ifstmt
|
if(ifstmt!=null) return ifstmt
|
||||||
|
|
||||||
val returnstmt = returnstmt()?.toAst(encoding)
|
val returnstmt = returnstmt()?.toAst()
|
||||||
if(returnstmt!=null) return returnstmt
|
if(returnstmt!=null) return returnstmt
|
||||||
|
|
||||||
val subroutine = subroutinedeclaration()?.toAst(encoding)
|
val subroutine = subroutinedeclaration()?.toAst()
|
||||||
if(subroutine!=null) return subroutine
|
if(subroutine!=null) return subroutine
|
||||||
|
|
||||||
val asm = inlineasm()?.toAst()
|
val asm = inlineasm()?.toAst()
|
||||||
if(asm!=null) return asm
|
if(asm!=null) return asm
|
||||||
|
|
||||||
val branchstmt = branch_stmt()?.toAst(encoding)
|
val branchstmt = branch_stmt()?.toAst()
|
||||||
if(branchstmt!=null) return branchstmt
|
if(branchstmt!=null) return branchstmt
|
||||||
|
|
||||||
val forloop = forloop()?.toAst(encoding)
|
val forloop = forloop()?.toAst()
|
||||||
if(forloop!=null) return forloop
|
if(forloop!=null) return forloop
|
||||||
|
|
||||||
val untilloop = untilloop()?.toAst(encoding)
|
val untilloop = untilloop()?.toAst()
|
||||||
if(untilloop!=null) return untilloop
|
if(untilloop!=null) return untilloop
|
||||||
|
|
||||||
val whileloop = whileloop()?.toAst(encoding)
|
val whileloop = whileloop()?.toAst()
|
||||||
if(whileloop!=null) return whileloop
|
if(whileloop!=null) return whileloop
|
||||||
|
|
||||||
val repeatloop = repeatloop()?.toAst(encoding)
|
val repeatloop = repeatloop()?.toAst()
|
||||||
if(repeatloop!=null) return repeatloop
|
if(repeatloop!=null) return repeatloop
|
||||||
|
|
||||||
val breakstmt = breakstmt()?.toAst()
|
val breakstmt = breakstmt()?.toAst()
|
||||||
if(breakstmt!=null) return breakstmt
|
if(breakstmt!=null) return breakstmt
|
||||||
|
|
||||||
val whenstmt = whenstmt()?.toAst(encoding)
|
val whenstmt = whenstmt()?.toAst()
|
||||||
if(whenstmt!=null) return whenstmt
|
if(whenstmt!=null) return whenstmt
|
||||||
|
|
||||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
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 inline = this.inline()!=null
|
||||||
val subdecl = asmsub_decl().toAst()
|
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,
|
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||||
subdecl.asmClobbers, null, true, inline, statements, toPosition())
|
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())
|
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 void = this.VOID() != null
|
||||||
val location = scoped_identifier().toAst()
|
val location = scoped_identifier().toAst()
|
||||||
return if(expression_list() == null)
|
return if(expression_list() == null)
|
||||||
FunctionCallStatement(location, mutableListOf(), void, toPosition())
|
FunctionCallStatement(location, mutableListOf(), void, toPosition())
|
||||||
else
|
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()
|
val location = scoped_identifier().toAst()
|
||||||
return if(expression_list() == null)
|
return if(expression_list() == null)
|
||||||
FunctionCall(location, mutableListOf(), toPosition())
|
FunctionCall(location, mutableListOf(), toPosition())
|
||||||
else
|
else
|
||||||
FunctionCall(location, expression_list().toAst(encoding).toMutableList(), toPosition())
|
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.InlineasmContext.toAst() =
|
private fun Prog8ANTLRParser.InlineasmContext.toAst() =
|
||||||
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.ReturnstmtContext.toAst(encoding: IStringEncoding) : Return {
|
private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
|
||||||
return Return(expression()?.toAst(encoding), toPosition())
|
return Return(expression()?.toAst(), toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
|
private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
|
||||||
@ -305,14 +296,14 @@ private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
|
|||||||
private fun Prog8ANTLRParser.LabeldefContext.toAst(): Statement =
|
private fun Prog8ANTLRParser.LabeldefContext.toAst(): Statement =
|
||||||
Label(children[0].text, toPosition())
|
Label(children[0].text, toPosition())
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.SubroutineContext.toAst(encoding: IStringEncoding) : Subroutine {
|
private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
|
||||||
// non-asm subroutine
|
// non-asm subroutine
|
||||||
val inline = inline()!=null
|
val inline = inline()!=null
|
||||||
val returntypes = sub_return_part()?.toAst() ?: emptyList()
|
val returntypes = sub_return_part()?.toAst() ?: emptyList()
|
||||||
return Subroutine(identifier().text,
|
return Subroutine(identifier().text,
|
||||||
sub_params()?.toAst() ?: emptyList(),
|
sub_params()?.toAst() ?: emptyList(),
|
||||||
returntypes,
|
returntypes,
|
||||||
statement_block()?.toAst(encoding) ?: mutableListOf(),
|
statement_block()?.toAst() ?: mutableListOf(),
|
||||||
inline,
|
inline,
|
||||||
toPosition())
|
toPosition())
|
||||||
}
|
}
|
||||||
@ -328,12 +319,12 @@ private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter
|
|||||||
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
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()
|
val identifier = scoped_identifier()
|
||||||
return when {
|
return when {
|
||||||
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
||||||
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(encoding), null, toPosition())
|
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
|
||||||
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(encoding), toPosition()), toPosition())
|
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||||
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, 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.DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.ArrayindexContext.toAst(encoding: IStringEncoding) : ArrayIndex =
|
private fun Prog8ANTLRParser.ArrayindexContext.toAst() : ArrayIndex =
|
||||||
ArrayIndex(expression().toAst(encoding), toPosition())
|
ArrayIndex(expression().toAst(), toPosition())
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.DirectiveContext.toAst() : Directive =
|
internal fun Prog8ANTLRParser.DirectiveContext.toAst() : Directive =
|
||||||
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
|
private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
|
||||||
val str = stringliteral()
|
val str = stringliteral()
|
||||||
if(str?.ALT_STRING_ENCODING() != null)
|
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())
|
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()
|
val litval = literalvalue()
|
||||||
if(litval!=null) {
|
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.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||||
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||||
litval.charliteral()!=null -> {
|
litval.charliteral()!=null -> litval.charliteral().toAst()
|
||||||
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.arrayliteral()!=null -> {
|
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 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.
|
// the ConstantFold takes care of that and converts the type if needed.
|
||||||
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
||||||
@ -455,31 +438,31 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst(encoding: IStringEncoding)
|
|||||||
return scoped_identifier().toAst()
|
return scoped_identifier().toAst()
|
||||||
|
|
||||||
if(bop!=null)
|
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)
|
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(funcall!=null) return funcall
|
||||||
|
|
||||||
if (rangefrom!=null && rangeto!=null) {
|
if (rangefrom!=null && rangeto!=null) {
|
||||||
val defaultstep = if(rto.text == "to") 1 else -1
|
val defaultstep = if(rto.text == "to") 1 else -1
|
||||||
val step = rangestep?.toAst(encoding) ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
|
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
|
||||||
return RangeExpr(rangefrom.toAst(encoding), rangeto.toAst(encoding), step, encoding, toPosition())
|
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
if(childCount==3 && children[0].text=="(" && children[2].text==")")
|
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)
|
if(arrayindexed()!=null)
|
||||||
return arrayindexed().toAst(encoding)
|
return arrayindexed().toAst()
|
||||||
|
|
||||||
if(typecast()!=null)
|
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)
|
if(directmemory()!=null)
|
||||||
return DirectMemoryRead(directmemory().expression().toAst(encoding), toPosition())
|
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
|
||||||
|
|
||||||
if(addressof()!=null)
|
if(addressof()!=null)
|
||||||
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
|
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
|
||||||
@ -487,16 +470,19 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst(encoding: IStringEncoding)
|
|||||||
throw FatalAstException(text)
|
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 =
|
private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteralValue =
|
||||||
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
|
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(),
|
return ArrayIndexedExpression(scoped_identifier().toAst(),
|
||||||
arrayindex().toAst(encoding),
|
arrayindex().toAst(),
|
||||||
toPosition())
|
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 =
|
private fun Prog8ANTLRParser.IdentifierContext.toAst() : IdentifierReference =
|
||||||
IdentifierReference(listOf(text), toPosition())
|
IdentifierReference(listOf(text), toPosition())
|
||||||
@ -512,27 +498,27 @@ private fun Prog8ANTLRParser.BooleanliteralContext.toAst() = when(text) {
|
|||||||
else -> throw FatalAstException(text)
|
else -> throw FatalAstException(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.ArrayliteralContext.toAst(encoding: IStringEncoding) : Array<Expression> =
|
private fun Prog8ANTLRParser.ArrayliteralContext.toAst() : Array<Expression> =
|
||||||
expression().map { it.toAst(encoding) }.toTypedArray()
|
expression().map { it.toAst() }.toTypedArray()
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.If_stmtContext.toAst(encoding: IStringEncoding): IfStatement {
|
private fun Prog8ANTLRParser.If_stmtContext.toAst(): IfStatement {
|
||||||
val condition = expression().toAst(encoding)
|
val condition = expression().toAst()
|
||||||
val trueStatements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
val elseStatements = else_part()?.toAst(encoding) ?: mutableListOf()
|
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||||
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
||||||
?: statement().toPosition())
|
?: statement().toPosition())
|
||||||
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||||
return IfStatement(condition, trueScope, elseScope, toPosition())
|
return IfStatement(condition, trueScope, elseScope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.Else_partContext.toAst(encoding: IStringEncoding): MutableList<Statement> {
|
private fun Prog8ANTLRParser.Else_partContext.toAst(): MutableList<Statement> {
|
||||||
return statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
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 branchcondition = branchcondition().toAst()
|
||||||
val trueStatements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
val elseStatements = else_part()?.toAst(encoding) ?: mutableListOf()
|
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||||
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
||||||
?: statement().toPosition())
|
?: statement().toPosition())
|
||||||
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||||
@ -543,65 +529,65 @@ private fun Prog8ANTLRParser.BranchconditionContext.toAst() = BranchCondition.va
|
|||||||
text.substringAfter('_').uppercase()
|
text.substringAfter('_').uppercase()
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.ForloopContext.toAst(encoding: IStringEncoding): ForLoop {
|
private fun Prog8ANTLRParser.ForloopContext.toAst(): ForLoop {
|
||||||
val loopvar = identifier().toAst()
|
val loopvar = identifier().toAst()
|
||||||
val iterable = expression()!!.toAst(encoding)
|
val iterable = expression()!!.toAst()
|
||||||
val scope =
|
val scope =
|
||||||
if(statement()!=null)
|
if(statement()!=null)
|
||||||
AnonymousScope(mutableListOf(statement().toAst(encoding)), statement().toPosition())
|
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
||||||
else
|
else
|
||||||
AnonymousScope(statement_block().toAst(encoding), statement_block().toPosition())
|
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
||||||
return ForLoop(loopvar, iterable, scope, toPosition())
|
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.BreakstmtContext.toAst() = Break(toPosition())
|
private fun Prog8ANTLRParser.BreakstmtContext.toAst() = Break(toPosition())
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.WhileloopContext.toAst(encoding: IStringEncoding): WhileLoop {
|
private fun Prog8ANTLRParser.WhileloopContext.toAst(): WhileLoop {
|
||||||
val condition = expression().toAst(encoding)
|
val condition = expression().toAst()
|
||||||
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
?: statement().toPosition())
|
?: statement().toPosition())
|
||||||
return WhileLoop(condition, scope, toPosition())
|
return WhileLoop(condition, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.RepeatloopContext.toAst(encoding: IStringEncoding): RepeatLoop {
|
private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
|
||||||
val iterations = expression()?.toAst(encoding)
|
val iterations = expression()?.toAst()
|
||||||
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
?: statement().toPosition())
|
?: statement().toPosition())
|
||||||
return RepeatLoop(iterations, scope, toPosition())
|
return RepeatLoop(iterations, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.UntilloopContext.toAst(encoding: IStringEncoding): UntilLoop {
|
private fun Prog8ANTLRParser.UntilloopContext.toAst(): UntilLoop {
|
||||||
val untilCondition = expression().toAst(encoding)
|
val untilCondition = expression().toAst()
|
||||||
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
?: statement().toPosition())
|
?: statement().toPosition())
|
||||||
return UntilLoop(scope, untilCondition, toPosition())
|
return UntilLoop(scope, untilCondition, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.WhenstmtContext.toAst(encoding: IStringEncoding): WhenStatement {
|
private fun Prog8ANTLRParser.WhenstmtContext.toAst(): WhenStatement {
|
||||||
val condition = expression().toAst(encoding)
|
val condition = expression().toAst()
|
||||||
val choices = this.when_choice()?.map { it.toAst(encoding) }?.toMutableList() ?: mutableListOf()
|
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
|
||||||
return WhenStatement(condition, choices, toPosition())
|
return WhenStatement(condition, choices, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.When_choiceContext.toAst(encoding: IStringEncoding): WhenChoice {
|
private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
|
||||||
val values = expression_list()?.toAst(encoding)
|
val values = expression_list()?.toAst()
|
||||||
val stmt = statement()?.toAst(encoding)
|
val stmt = statement()?.toAst()
|
||||||
val stmtBlock = statement_block()?.toAst(encoding)?.toMutableList() ?: mutableListOf()
|
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
|
||||||
if(stmt!=null)
|
if(stmt!=null)
|
||||||
stmtBlock.add(stmt)
|
stmtBlock.add(stmt)
|
||||||
val scope = AnonymousScope(stmtBlock, toPosition())
|
val scope = AnonymousScope(stmtBlock, toPosition())
|
||||||
return WhenChoice(values?.toMutableList(), scope, toPosition())
|
return WhenChoice(values?.toMutableList(), scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prog8ANTLRParser.VardeclContext.toAst(encoding: IStringEncoding): VarDecl {
|
private fun Prog8ANTLRParser.VardeclContext.toAst(): VarDecl {
|
||||||
return VarDecl(
|
return VarDecl(
|
||||||
VarDeclType.VAR,
|
VarDeclType.VAR,
|
||||||
datatype()?.toAst() ?: DataType.UNDEFINED,
|
datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||||
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
arrayindex()?.toAst(encoding),
|
arrayindex()?.toAst(),
|
||||||
varname.text,
|
varname.text,
|
||||||
null,
|
null,
|
||||||
ARRAYSIG() != null || arrayindex() != null,
|
ARRAYSIG() != null || arrayindex() != null,
|
||||||
|
@ -7,7 +7,6 @@ import prog8.ast.statements.*
|
|||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
|
|
||||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
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,
|
class StringLiteralValue(val value: String,
|
||||||
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||||
override val position: Position) : Expression() {
|
override val position: Position) : Expression() {
|
||||||
@ -636,7 +666,6 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
|||||||
class RangeExpr(var from: Expression,
|
class RangeExpr(var from: Expression,
|
||||||
var to: Expression,
|
var to: Expression,
|
||||||
var step: Expression,
|
var step: Expression,
|
||||||
private val encoding: IStringEncoding,
|
|
||||||
override val position: Position) : Expression() {
|
override val position: Position) : Expression() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
@ -689,51 +718,8 @@ class RangeExpr(var from: Expression,
|
|||||||
return "RangeExpr(from $from, to $to, step $step, pos=$position)"
|
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 {
|
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||||
override lateinit var parent: Node
|
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(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||||
open fun before(returnStmt: Return, 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(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(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||||
open fun before(subroutine: Subroutine, 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
|
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(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||||
open fun after(returnStmt: Return, 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(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(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||||
open fun after(subroutine: Subroutine, 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
|
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||||
@ -300,6 +302,11 @@ abstract class AstWalker {
|
|||||||
track(after(numLiteral, parent), numLiteral, parent)
|
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) {
|
fun visit(string: StringLiteralValue, parent: Node) {
|
||||||
track(before(string, parent), string, parent)
|
track(before(string, parent), string, parent)
|
||||||
track(after(string, parent), string, parent)
|
track(after(string, parent), string, parent)
|
||||||
|
@ -79,6 +79,9 @@ interface IAstVisitor {
|
|||||||
fun visit(numLiteral: NumericLiteralValue) {
|
fun visit(numLiteral: NumericLiteralValue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun visit(char: CharLiteral) {
|
||||||
|
}
|
||||||
|
|
||||||
fun visit(string: StringLiteralValue) {
|
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…
x
Reference in New Issue
Block a user