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>) { fun main(args: Array<String>) {
printSoftwareHeader("compiler")
compileMain(args)
}
internal fun printSoftwareHeader(what: String) {
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim() 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") 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) 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 cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation") 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(".") 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) cli.parse(args)
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
System.err.println(e.message) System.err.println(e.message)
exitProcess(1) return false
} }
val outputPath = pathFrom(outputDir) val outputPath = pathFrom(outputDir)
if(!outputPath.toFile().isDirectory) { if(!outputPath.toFile().isDirectory) {
System.err.println("Output path doesn't exist") System.err.println("Output path doesn't exist")
exitProcess(1) return false
} }
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') } val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
if(faultyOption!=null) { if(faultyOption!=null) {
System.err.println("Unknown command line option given: $faultyOption") System.err.println("Unknown command line option given: $faultyOption")
exitProcess(1) return false
} }
val libdirs = libDirs.toMutableList() val libdirs = libDirs.toMutableList()
@ -114,11 +112,11 @@ private fun compileMain(args: Array<String>) {
try { try {
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath)
if(!compilationResult.success) if(!compilationResult.success)
exitProcess(1) return false
} catch (x: ParsingFailedError) { } catch (x: ParsingFailedError) {
exitProcess(1) return false
} catch (x: AstException) { } catch (x: AstException) {
exitProcess(1) return false
} }
if (startEmulator==true) { 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.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Path import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -81,10 +80,7 @@ fun compileProgram(filepath: Path,
when(compilationTarget) { when(compilationTarget) {
C64Target.name -> C64Target C64Target.name -> C64Target
Cx16Target.name -> Cx16Target Cx16Target.name -> Cx16Target
else -> { else -> throw IllegalArgumentException("invalid compilation target")
System.err.println("invalid compilation target")
exitProcess(1)
}
} }
try { try {
@ -102,8 +98,15 @@ fun compileProgram(filepath: Path,
// printAst(programAst) // printAst(programAst)
if(writeAssembly) if(writeAssembly) {
programName = writeAssembly(programAst, errors, outputDir, compilationOptions) 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.out.flush()
System.err.flush() System.err.flush()
@ -198,10 +201,10 @@ private fun parseImports(filepath: Path,
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions { private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule val mainModule = program.mainModule
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } val outputDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
as? Directive)?.args?.single()?.name?.uppercase() val launcherDirective = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } val outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
as? Directive)?.args?.single()?.name?.uppercase() val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" } val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.uppercase() as? Directive)?.args?.single()?.name?.uppercase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" } 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!! } .map { it[0].int!!..it[1].int!! }
.toList() .toList()
if (outputType != null && !OutputType.values().any { it.name == outputType }) { val outputType = if (outputTypeStr == null) OutputType.PRG else {
System.err.println("invalid output type $outputType") try {
exitProcess(1) 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 }) { val launcherType = if (launcherTypeStr == null) LauncherType.BASIC else {
System.err.println("invalid launcher type $launcherType") try {
exitProcess(1) 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( return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType), outputType,
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType), launcherType,
zpType, zpReserved, floatsEnabled, noSysInit, zpType, zpReserved, floatsEnabled, noSysInit,
compTarget compTarget
) )
@ -316,7 +327,7 @@ private fun postprocessAst(programAst: Program, errors: IErrorReporter, compiler
private fun writeAssembly(programAst: Program, private fun writeAssembly(programAst: Program,
errors: IErrorReporter, errors: IErrorReporter,
outputDir: Path, outputDir: Path,
compilerOptions: CompilationOptions): String { compilerOptions: CompilationOptions): Pair<Boolean, String> {
// asm generation directly from the Ast // asm generation directly from the Ast
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget) programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
errors.report() errors.report()
@ -330,9 +341,13 @@ private fun writeAssembly(programAst: Program,
compilerOptions.compTarget.machine.zeropage, compilerOptions.compTarget.machine.zeropage,
compilerOptions, compilerOptions,
outputDir).compileToAssembly() outputDir).compileToAssembly()
assembly.assemble(compilerOptions) val assemblerReturnStatus = assembly.assemble(compilerOptions)
errors.report() return if(assemblerReturnStatus!=0)
return assembly.name Pair(false, "assembler step failed with return code $assemblerReturnStatus")
else {
errors.report()
Pair(true, assembly.name)
}
} }
fun printAst(programAst: Program) { fun printAst(programAst: Program) {

View File

@ -12,5 +12,5 @@ internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
internal interface IAssemblyProgram { internal interface IAssemblyProgram {
val name: String 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.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram { class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm") 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 binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list") 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) // 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", val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror", "-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 proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor() val result = proc.waitFor()
if (result != 0) { if (result == 0) {
System.err.println("assembler failed with returncode $result") removeGeneratedLabelsFromMonlist()
exitProcess(result) generateBreakpointList()
} }
return result
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
} }
private fun removeGeneratedLabelsFromMonlist() { private fun removeGeneratedLabelsFromMonlist() {

View File

@ -2,7 +2,6 @@ package prog8tests
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.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.IBuiltinFunctions import prog8.ast.IBuiltinFunctions

View File

@ -2,17 +2,19 @@ package prog8tests
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import kotlin.test.*
import kotlin.io.path.*
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.target.Cx16Target
import prog8.compiler.compileProgram 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
/** /**

View File

@ -2,12 +2,14 @@ package prog8tests
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance 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.compileProgram
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
/** /**

View File

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