improve testability: use error returnvalues instead of using exitProcess()

This commit is contained in:
Irmen de Jong 2021-07-02 00:11:21 +02:00
parent 2cb1560bbd
commit 9bd3a6758a
9 changed files with 74 additions and 60 deletions

View File

@ -1 +1 @@
7.0
7.1-dev

View File

@ -16,22 +16,20 @@ import kotlin.system.exitProcess
fun main(args: Array<String>) {
printSoftwareHeader("compiler")
compileMain(args)
}
internal fun printSoftwareHeader(what: String) {
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
println("\nProg8 $what v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
println("\nProg8 compiler v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
val succes = compileMain(args)
if(!succes)
exitProcess(1)
}
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
private fun compileMain(args: Array<String>) {
private fun compileMain(args: Array<String>): Boolean {
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
@ -47,19 +45,19 @@ private fun compileMain(args: Array<String>) {
cli.parse(args)
} catch (e: IllegalStateException) {
System.err.println(e.message)
exitProcess(1)
return false
}
val outputPath = pathFrom(outputDir)
if(!outputPath.toFile().isDirectory) {
System.err.println("Output path doesn't exist")
exitProcess(1)
return false
}
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
if(faultyOption!=null) {
System.err.println("Unknown command line option given: $faultyOption")
exitProcess(1)
return false
}
val libdirs = libDirs.toMutableList()
@ -114,11 +112,11 @@ private fun compileMain(args: Array<String>) {
try {
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath)
if(!compilationResult.success)
exitProcess(1)
return false
} catch (x: ParsingFailedError) {
exitProcess(1)
return false
} catch (x: AstException) {
exitProcess(1)
return false
}
if (startEmulator==true) {
@ -130,4 +128,6 @@ private fun compileMain(args: Array<String>) {
}
}
}
return true
}

View File

@ -22,7 +22,6 @@ import prog8.parser.moduleName
import java.io.File
import java.io.InputStream
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
@ -81,10 +80,7 @@ fun compileProgram(filepath: Path,
when(compilationTarget) {
C64Target.name -> C64Target
Cx16Target.name -> Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
else -> throw IllegalArgumentException("invalid compilation target")
}
try {
@ -102,8 +98,15 @@ fun compileProgram(filepath: Path,
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
if(writeAssembly) {
val (success, message) = writeAssembly(programAst, errors, outputDir, compilationOptions)
if(success)
programName = message
else {
System.err.println(message)
return CompilationResult(false, programAst, programName, compTarget, importedFiles)
}
}
}
System.out.flush()
System.err.flush()
@ -198,10 +201,10 @@ private fun parseImports(filepath: Path,
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.uppercase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.uppercase()
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 outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.uppercase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }
@ -231,18 +234,26 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati
.map { it[0].int!!..it[1].int!! }
.toList()
if (outputType != null && !OutputType.values().any { it.name == outputType }) {
System.err.println("invalid output type $outputType")
exitProcess(1)
val outputType = if (outputTypeStr == null) OutputType.PRG else {
try {
OutputType.valueOf(outputTypeStr)
} catch (x: IllegalArgumentException) {
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
OutputType.PRG
}
}
if (launcherType != null && !LauncherType.values().any { it.name == launcherType }) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
val launcherType = if (launcherTypeStr == null) LauncherType.BASIC else {
try {
LauncherType.valueOf(launcherTypeStr)
} catch (x: IllegalArgumentException) {
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
LauncherType.BASIC
}
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
outputType,
launcherType,
zpType, zpReserved, floatsEnabled, noSysInit,
compTarget
)
@ -316,7 +327,7 @@ private fun postprocessAst(programAst: Program, errors: IErrorReporter, compiler
private fun writeAssembly(programAst: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions): String {
compilerOptions: CompilationOptions): Pair<Boolean, String> {
// asm generation directly from the Ast
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
errors.report()
@ -330,9 +341,13 @@ private fun writeAssembly(programAst: Program,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.report()
return assembly.name
val assemblerReturnStatus = assembly.assemble(compilerOptions)
return if(assemblerReturnStatus!=0)
Pair(false, "assembler step failed with return code $assemblerReturnStatus")
else {
errors.report()
Pair(true, assembly.name)
}
}
fun printAst(programAst: Program) {

View File

@ -12,5 +12,5 @@ internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
internal interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions)
fun assemble(options: CompilationOptions): Int
}

View File

@ -5,7 +5,6 @@ import prog8.compiler.OutputType
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
@ -13,7 +12,7 @@ class AssemblyProgram(override val name: String, outputDir: Path, private val co
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
override fun assemble(options: CompilationOptions) {
override fun assemble(options: CompilationOptions): Int {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
@ -35,13 +34,11 @@ class AssemblyProgram(override val name: String, outputDir: Path, private val co
val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor()
if (result != 0) {
System.err.println("assembler failed with returncode $result")
exitProcess(result)
if (result == 0) {
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
return result
}
private fun removeGeneratedLabelsFromMonlist() {

View File

@ -2,7 +2,6 @@ package prog8tests
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.IBuiltinFunctions

View File

@ -2,17 +2,19 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.test.*
import kotlin.io.path.*
import prog8.ast.IFunctionCall
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.compiler.target.Cx16Target
import prog8.compiler.compileProgram
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
/**
@ -135,4 +137,4 @@ class TestCompilerOnCharLit {
}
}
}
}

View File

@ -2,12 +2,14 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.test.*
import kotlin.io.path.*
import prog8.compiler.target.Cx16Target
import prog8.compiler.compileProgram
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.isDirectory
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
@ -75,4 +77,4 @@ class TestCompilerOnExamples {
testExample("animals", Cx16Target, true)
}
}
}

View File

@ -5,7 +5,6 @@ main {
label:
sub start() {
sub2(&label)
sub2(&label_local)
sub2(&main.sub2.label_in_sub2)