package prog8.code.target import prog8.code.core.* import prog8.code.source.ImportFileSystem.expandTilde import prog8.code.target.encodings.Encoder import prog8.code.target.zp.ConfigurableZeropage import java.io.IOException import java.nio.file.Path import java.util.* import kotlin.io.path.Path import kotlin.io.path.inputStream import kotlin.io.path.isDirectory import kotlin.io.path.nameWithoutExtension class ConfigFileTarget( override val name: String, override val defaultEncoding: Encoding, override val cpu: CpuType, override val PROGRAM_LOAD_ADDRESS: UInt, override val PROGRAM_MEMTOP_ADDRESS: UInt, override val STARTUP_CODE_RESERVED_SIZE: UInt, override val BSSHIGHRAM_START: UInt, override val BSSHIGHRAM_END: UInt, override val BSSGOLDENRAM_START: UInt, override val BSSGOLDENRAM_END: UInt, override val defaultOutputType: OutputType, override val libraryPath: Path, override val customLauncher: List, override val additionalAssemblerOptions: List, val ioAddresses: List, val zpScratchB1: UInt, val zpScratchReg: UInt, val zpScratchW1: UInt, val zpScratchW2: UInt, val zpScratchPtr: UInt, val virtualregistersStart: UInt, val zpFullsafe: List, val zpKernalsafe: List, val zpBasicsafe: List ): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) { companion object { private fun Properties.getString(property: String): String { val value = this.getProperty(property, null) if(value!=null) return value throw NoSuchElementException("string property '$property' not found in config file") } private fun Properties.getInteger(property: String): UInt { val value = this.getProperty(property, null) if(value!=null) return parseInt(value) throw NoSuchElementException("integer property '$property' not found in config file") } private fun parseInt(value: String): UInt { if(value.startsWith("0x")) return value.drop(2).toUInt(16) if(value.startsWith("$")) return value.drop(1).toUInt(16) if(value.startsWith("%")) return value.drop(1).toUInt(2) return value.toUInt() } private fun parseAddressRanges(key: String, props: Properties): List { val rangesStr = props.getString(key) if(rangesStr.isBlank()) return emptyList() val result = mutableListOf() val ranges = rangesStr.split(",").map { it.trim() } for(r in ranges) { if ('-' in r) { val (fromStr, toStr) = r.split("-") val from = parseInt(fromStr.trim()) val to = parseInt(toStr.trim()) result.add(from..to) } else { val address = parseInt(r) result.add(address..address) } } return result } fun fromConfigFile(configfile: Path): ConfigFileTarget { val props = Properties() props.load(configfile.inputStream()) val cpuString = props.getString("cpu").uppercase() val cpuType = try { CpuType.valueOf(cpuString) } catch (_: IllegalArgumentException) { CpuType.valueOf("CPU$cpuString") } val ioAddresses = parseAddressRanges("io_regions", props) val zpFullsafe = parseAddressRanges("zp_fullsafe", props) val zpKernalsafe = parseAddressRanges("zp_kernalsafe", props) val zpBasicsafe = parseAddressRanges("zp_basicsafe", props) val libraryPath = expandTilde(Path(props.getString("library"))) if(!libraryPath.isDirectory()) throw IOException("invalid library path: $libraryPath") val customLauncherStr = props.getProperty("custom_launcher_code", null) val customLauncher = if(customLauncherStr?.isNotBlank()==true) (customLauncherStr+"\n").lines().map { it.trimEnd() } else emptyList() val assemblerOptionsStr = props.getProperty("assembler_options", "").trim() val outputTypeString = props.getProperty("output_type", "PRG") val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase()) return ConfigFileTarget( configfile.nameWithoutExtension, Encoding.entries.first { it.prefix==props.getString("encoding") }, cpuType, props.getInteger("load_address"), props.getInteger("memtop"), 0u, // used only in a very specific error condition check in a certain scenario... props.getInteger("bss_highram_start"), props.getInteger("bss_highram_end"), props.getInteger("bss_goldenram_start"), props.getInteger("bss_goldenram_end"), defaultOutputType, libraryPath, customLauncher, if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "), ioAddresses, props.getInteger("zp_scratch_b1"), props.getInteger("zp_scratch_reg"), props.getInteger("zp_scratch_w1"), props.getInteger("zp_scratch_w2"), props.getInteger("zp_scratch_ptr"), props.getInteger("virtual_registers"), zpFullsafe, zpKernalsafe, zpBasicsafe, ) } } // TODO floats are not yet supported here, just enter some values override val FLOAT_MAX_POSITIVE = 9.999999999e97 override val FLOAT_MAX_NEGATIVE = -9.999999999e97 override val FLOAT_MEM_SIZE = 8 override lateinit var zeropage: Zeropage override lateinit var golden: GoldenRam // TODO this is not yet used override fun getFloatAsmBytes(num: Number) = TODO("floats") override fun convertFloatToBytes(num: Double): List = TODO("floats") override fun convertBytesToFloat(bytes: List): Double = TODO("floats") override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) { throw IllegalArgumentException("Custom compiler target cannot automatically launch an emulator. Do this manually.") } override fun isIOAddress(address: UInt): Boolean = ioAddresses.any { address in it } override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { zeropage = ConfigurableZeropage( zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2, zpScratchPtr, virtualregistersStart, zpBasicsafe, zpKernalsafe, zpFullsafe, compilerOptions ) // note: there's no golden ram yet } }