restructuring more things

This commit is contained in:
Irmen de Jong 2019-07-08 14:09:25 +02:00
parent ade7a4c398
commit 1794f704e7
22 changed files with 220 additions and 201 deletions

View File

@ -30,7 +30,7 @@ compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
verbose = true
freeCompilerArgs += "-XXLanguage:+NewInference -verbose"
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}

View File

@ -1,30 +1,10 @@
package prog8
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.checkIdentifiers
import prog8.ast.base.checkRecursion
import prog8.ast.base.checkValid
import prog8.ast.base.reorderStatements
import prog8.ast.statements.Directive
import prog8.vm.astvm.AstVm
import prog8.compiler.*
import prog8.compiler.target.c64.AsmGen
import prog8.compiler.target.c64.C64Zeropage
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule
import prog8.parser.moduleName
import prog8.vm.stackvm.stackVmMain
import java.io.File
import java.io.PrintStream
import java.lang.Exception
import prog8.compiler.*
import java.nio.file.Paths
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
fun main(args: Array<String>) {
@ -82,115 +62,8 @@ private fun compileMain(args: Array<String>) {
usage()
val filepath = Paths.get(moduleFile).normalize()
var programname = "?"
lateinit var programAst: Program
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
println("Parsing...")
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if(compilerOptions.launcher==LauncherType.BASIC || compilerOptions.output==OutputType.PRG) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
// perform initial syntax checks and constant folding
println("Syntax check...")
val time1= measureTimeMillis {
programAst.checkIdentifiers()
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
programAst.constantFold()
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
}
//println(" time3: $time3")
val time4 = measureTimeMillis {
programAst.checkValid(compilerOptions) // check if tree is valid
}
//println(" time4: $time4")
programAst.checkIdentifiers()
if(optimize) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(optimizeInlining)
if (optsDone1 + optsDone2 == 0)
break
}
}
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
// namespace.debugPrint()
// compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(programAst)
val intermediate = compiler.compile(compilerOptions)
if(optimize)
intermediate.optimize()
if(writeVmCode) {
val stackVmFilename = intermediate.name + ".vm.txt"
val stackvmFile = PrintStream(File(stackVmFilename), "utf-8")
intermediate.writeCode(stackvmFile)
stackvmFile.close()
println("StackVM program code written to '$stackVmFilename'")
}
if(writeAssembly) {
val zeropage = C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage)
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programname = assembly.name
}
}
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
}
val (programAst, programName) = compileProgram(filepath, optimize, optimizeInlining, writeVmCode, writeAssembly)
if(launchAstVm) {
println("\nLaunching AST-based vm...")
@ -199,51 +72,19 @@ private fun compileMain(args: Array<String>) {
}
if(emulatorToStart.isNotEmpty()) {
println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programname.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programname+".prg")
val process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
if(programName==null)
println("\nCan't start emulator because no program was assembled.")
else {
println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programName.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
val process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
}
}
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
as? Directive)?.args?.single()?.int ?: 0
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled
)
}
private fun usage() {
System.err.println("Missing argument(s):")
System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation")

View File

@ -6,7 +6,6 @@ import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.RuntimeValue
import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
@ -14,6 +13,10 @@ import prog8.functions.builtinFunctionReturnType
import kotlin.math.abs
import kotlin.math.floor
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
class PrefixExpression(val operator: String, var expression: IExpression, override val position: Position) : IExpression {
override lateinit var parent: Node
@ -253,8 +256,9 @@ class TypecastExpression(var expression: IExpression, var type: DataType, val im
override fun inferType(program: Program): DataType? = type
override fun constValue(program: Program): LiteralValue? {
val cv = expression.constValue(program) ?: return null
val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
return LiteralValue.fromNumber(value.numericValue(), value.type, position)
return cv.cast(type)
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
// return LiteralValue.fromNumber(value.numericValue(), value.type, position).cast(type)
}
override fun toString(): String {
@ -481,7 +485,7 @@ open class LiteralValue(val type: DataType,
throw ExpressionError("cannot order compare type $type with ${other.type}", other.position)
}
fun intoDatatype(targettype: DataType): LiteralValue? {
fun cast(targettype: DataType): LiteralValue? {
if(type==targettype)
return this
when(type) {

View File

@ -177,7 +177,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstPr
for(returnvalue in returnStmt.values.zip(subroutine.returntypes)) {
val lval = returnvalue.first as? LiteralValue
if(lval!=null) {
val adjusted = lval.intoDatatype(returnvalue.second)
val adjusted = lval.cast(returnvalue.second)
if(adjusted!=null && adjusted !== lval)
newValues.add(adjusted)
else

View File

@ -46,7 +46,7 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
val declvalue = decl.value!!
val value =
if(declvalue is LiteralValue) {
val converted = declvalue.intoDatatype(decl.datatype)
val converted = declvalue.cast(decl.datatype)
converted ?: declvalue
}
else

View File

@ -11,6 +11,7 @@ import prog8.compiler.intermediate.Opcode
import prog8.compiler.intermediate.branchOpcodes
import prog8.functions.BuiltinFunctions
import prog8.parser.tryGetEmbeddedResource
import prog8.vm.RuntimeValue
import prog8.vm.stackvm.Syscall
import java.io.File
import java.nio.file.Path

View File

@ -0,0 +1,175 @@
package prog8.compiler
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.base.checkIdentifiers
import prog8.ast.base.checkValid
import prog8.ast.base.reorderStatements
import prog8.ast.statements.Directive
import prog8.compiler.target.c64.AsmGen
import prog8.compiler.target.c64.C64Zeropage
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule
import prog8.parser.moduleName
import java.io.File
import java.io.PrintStream
import java.lang.Exception
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
fun compileProgram(filepath: Path,
optimize: Boolean, optimizeInlining: Boolean,
writeVmCode: Boolean, writeAssembly: Boolean): Pair<Program, String?> {
lateinit var programAst: Program
var programName: String? = null
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
println("Parsing...")
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
// perform initial syntax checks and constant folding
println("Syntax check...")
val time1 = measureTimeMillis {
programAst.checkIdentifiers()
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
programAst.constantFold()
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
}
//println(" time3: $time3")
val time4 = measureTimeMillis {
programAst.checkValid(compilerOptions) // check if tree is valid
}
//println(" time4: $time4")
programAst.checkIdentifiers()
if (optimize) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(optimizeInlining)
if (optsDone1 + optsDone2 == 0)
break
}
}
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
// namespace.debugPrint()
// compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(programAst)
val intermediate = compiler.compile(compilerOptions)
if (optimize)
intermediate.optimize()
if (writeVmCode) {
val stackVmFilename = intermediate.name + ".vm.txt"
val stackvmFile = PrintStream(File(stackVmFilename), "utf-8")
intermediate.writeCode(stackvmFile)
stackvmFile.close()
println("StackVM program code written to '$stackVmFilename'")
}
if (writeAssembly) {
val zeropage = C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage)
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programName = assembly.name
}
}
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
}
return Pair(programAst, programName)
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
as? Directive)?.args?.single()?.int ?: 0
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled
)
}

View File

@ -1,6 +1,6 @@
package prog8.compiler.intermediate
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import prog8.vm.stackvm.Syscall
open class Instruction(val opcode: Opcode,

View File

@ -5,7 +5,7 @@ import prog8.ast.base.*
import prog8.ast.base.printWarning
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.VarDecl
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
import prog8.compiler.Zeropage

View File

@ -7,7 +7,7 @@ import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import prog8.compiler.*
import prog8.compiler.intermediate.*
import prog8.vm.stackvm.Syscall

View File

@ -9,9 +9,6 @@ import prog8.ast.expressions.LiteralValue
import kotlin.math.pow
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
class ConstExprEvaluator {
fun evaluate(left: LiteralValue, operator: String, right: LiteralValue): IExpression {

View File

@ -222,7 +222,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.intoDatatype(expectedDt)
val convertedValue = argConst.cast(expectedDt)
if(convertedValue!=null) {
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
@ -550,11 +550,11 @@ class ConstantFolding(private val program: Program) : IAstProcessor {
override fun process(forLoop: ForLoop): IStatement {
fun adjustRangeDt(rangeFrom: LiteralValue, targetDt: DataType, rangeTo: LiteralValue, stepLiteral: LiteralValue?, range: RangeExpr): RangeExpr {
val newFrom = rangeFrom.intoDatatype(targetDt)
val newTo = rangeTo.intoDatatype(targetDt)
val newFrom = rangeFrom.cast(targetDt)
val newTo = rangeTo.cast(targetDt)
if (newFrom != null && newTo != null) {
val newStep: IExpression =
if (stepLiteral != null) (stepLiteral.intoDatatype(targetDt) ?: stepLiteral) else range.step
if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step
return RangeExpr(newFrom, newTo, newStep, range.position)
}
return range

View File

@ -1,7 +1,8 @@
package prog8.compiler
package prog8.vm
import prog8.ast.base.*
import prog8.ast.expressions.LiteralValue
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
import kotlin.math.pow

View File

@ -6,8 +6,8 @@ import prog8.ast.base.initvarsSubName
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.*
import prog8.compiler.RuntimeValue
import prog8.compiler.RuntimeValueRange
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import prog8.compiler.target.c64.Petscii
import java.awt.EventQueue
import kotlin.NoSuchElementException

View File

@ -1,7 +1,7 @@
package prog8.vm.astvm
import prog8.ast.base.DataType
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import java.util.*

View File

@ -9,8 +9,8 @@ import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.RuntimeValue
import prog8.compiler.RuntimeValueRange
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import kotlin.math.abs
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
@ -106,8 +106,8 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
DataType.STR -> RuntimeValue(DataType.STR, str=ctx.mem.getString(address))
DataType.STR_S -> RuntimeValue(DataType.STR_S, str=ctx.mem.getScreencodeString(address))
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address))
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
else -> TODO("memvar $variable")
}
}

View File

@ -6,7 +6,7 @@ import prog8.ast.expressions.LiteralValue
import prog8.ast.processing.IAstProcessor
import prog8.ast.statements.VarDecl
import prog8.compiler.HeapValues
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
class VariablesCreator(private val runtimeVariables: RuntimeVariables, private val heap: HeapValues) : IAstProcessor {

View File

@ -4,7 +4,7 @@ import prog8.ast.antlr.unescape
import prog8.ast.base.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.*

View File

@ -7,7 +7,7 @@ import prog8.ast.base.Register
import prog8.ast.base.initvarsSubName
import prog8.vm.astvm.BitmapScreenPanel
import prog8.vm.astvm.Memory
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.Instruction

View File

@ -3,7 +3,7 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import kotlin.test.*

View File

@ -8,7 +8,7 @@ import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.IterableDatatypes
import prog8.ast.base.WordDatatypes
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import prog8.compiler.HeapValues
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode

View File

@ -8,7 +8,7 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.LiteralValue
import prog8.compiler.RuntimeValue
import prog8.vm.RuntimeValue
import prog8.compiler.*
import prog8.compiler.target.c64.*
import java.io.CharConversionException