Compare commits

...
34 Commits
Author SHA1 Message Date
Irmen de Jong 094ecceaac fix bug where non-inlined asmsub didn't always get a proper RTS, causing program crash
was caused by a change in 7.8; 8ae3bad6f7 "fix rts in empty asmsub"
2022-03-03 01:10:33 +01:00
Irmen de Jong 2812736ae5 preparing version 7.9 2022-03-03 00:42:53 +01:00
Irmen de Jong 6f87f8706c can only call unary functions in pipe at this time 2022-03-02 23:16:40 +01:00
Irmen de Jong 38beebe720 fix pipe check for number of args 2022-03-02 21:29:09 +01:00
Irmen de Jong fc1c3c6808 working on altered pipe syntax 2022-03-02 20:58:38 +01:00
Irmen de Jong 96ba895b84 working on altered Pipe syntax 2022-02-27 02:42:28 +01:00
Irmen de Jong df35dfe3bf added atari XEX output format with default $2000 load address 2022-02-26 15:36:22 +01:00
Irmen de Jong c5504c6657 added ATASCII encoding table for atari 2022-02-25 23:48:39 +01:00
Irmen de Jong 530e109433 added altirra as atari emu2 2022-02-25 19:16:37 +01:00
Irmen de Jong 6cce47b2f1 fix launching emulator for atari target 2022-02-24 23:22:02 +01:00
Irmen de Jong 6185d5eca1 Merge remote-tracking branch 'origin/master' 2022-02-24 22:52:08 +01:00
Irmen de Jong 685ad1746e Merge pull request #74 from FreddyOffenga/master
temporary fix for chrout and newline for atari and added two examples
2022-02-24 22:30:59 +01:00
Irmen de Jong 891f870ec0 todo 2022-02-23 21:58:27 +01:00
Freddy ad9933f0f6 fixed chrout for atari and added two examples 2022-02-23 16:42:22 +01:00
Irmen de Jong 1b86117754 todo 2022-02-22 23:38:09 +01:00
Irmen de Jong eeb3c968d6 streamline handling of launcher type and program load address. %address is now required if not using a basic-launcher. 2022-02-22 22:43:14 +01:00
Irmen de Jong 406658a10f reimplemented sys.memcopy and sys.memset on cx16 to work without kernal too 2022-02-22 21:07:19 +01:00
Irmen de Jong 6a0551cea1 added 'atari' compiler target beginnings (Atari 800 XL)
also default char and string encoding now taken from compiler target
2022-02-22 00:52:35 +01:00
Irmen de Jong 553f3b45d2 compile time calculated values of sin/cos routines fixed to be identical to the results of the run-time functions 2022-02-21 21:30:42 +01:00
Irmen de Jong 064a8e785c cleanups 2022-02-21 03:26:17 +01:00
Irmen de Jong 21e9723bb2 allow the last term in a pipe statement to be a variable, rewrites this as var = <rest of pipe> 2022-02-21 02:33:19 +01:00
Irmen de Jong 60b2c44a44 fix returntype handling of builtinfunctions, fix errors in pipe expressions 2022-02-21 01:44:29 +01:00
Irmen de Jong c4fe3ecc0a refactor 2022-02-20 22:04:18 +01:00
Irmen de Jong 2f18a8f6d0 introduced BuiltinFunctionCall (expression) node for codegen 2022-02-20 02:48:27 +01:00
Irmen de Jong 5ac784e18a cleanup 2022-02-19 00:30:59 +01:00
Irmen de Jong 7a2164b4d0 introduced BuiltinFunctionCallStatement node for codegen 2022-02-18 23:27:11 +01:00
Irmen de Jong 0a43eae184 rework registerArgsViaStackEvaluation to use cpu hardware stack instead 2022-02-18 22:38:00 +01:00
Irmen de Jong 3117e2b2a3 more tweaks 2022-02-18 01:25:08 +01:00
Irmen de Jong 41fece4643 slight tweaks related to builtin functions in the ast 2022-02-17 01:25:13 +01:00
Irmen de Jong 7aa807ec7f proper error if attempting to do a containment check against non const range, and some cleanup in asmgen 2022-02-16 00:39:19 +01:00
Irmen de Jong 4d16e1e14a now checks for invalid text encodings for given compilation target 2022-02-15 01:39:12 +01:00
Irmen de Jong 73fc18099e properly report duplicate label names 2022-02-15 00:39:10 +01:00
Irmen de Jong e34dac8dbb remove unit test issue 2022-02-15 00:38:51 +01:00
Irmen de Jong af0e7f7187 searching names in inlined assembly now ignores source comments 2022-02-13 13:41:12 +01:00
97 changed files with 3508 additions and 1536 deletions

No files matched your search

@@ -42,6 +42,7 @@ class AsmGen(internal val program: Program,
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen, allocator)
override fun compileToAssembly(): IAssemblyProgram? {
assemblyLines.clear()
loopEndLabels.clear()
@@ -320,15 +321,8 @@ class AsmGen(internal val program: Program,
is Return -> translate(stmt)
is Subroutine -> programGen.translateSubroutine(stmt)
is InlineAssembly -> translate(stmt)
is FunctionCallStatement -> {
val functionName = stmt.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if (builtinFunc != null) {
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
} else {
functioncallAsmGen.translateFunctionCallStatement(stmt)
}
}
is BuiltinFunctionCallStatement -> builtinFunctionsAsmGen.translateFunctioncallStatement(stmt)
is FunctionCallStatement -> functioncallAsmGen.translateFunctionCallStatement(stmt) // TODO try to remove this last usage of FunctionCallStatement node in the codegen.
is Assignment -> assignmentAsmGen.translate(stmt)
is Jump -> {
val (asmLabel, indirect) = getJumpTarget(stmt)
@@ -343,7 +337,7 @@ class AsmGen(internal val program: Program,
is RepeatLoop -> translate(stmt)
is When -> translate(stmt)
is AnonymousScope -> translate(stmt)
is Pipe -> translatePipeExpression(stmt.expressions, stmt, true, false)
is Pipe -> translatePipeExpression(stmt.source, stmt.segments, stmt, isStatement = true, pushResultOnEstack = false)
is VarDecl -> { /* do nothing; variables are handled elsewhere */ }
is BuiltinFunctionPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore")
is UntilLoop -> throw AssemblyError("do..until should have been converted to jumps")
@@ -450,14 +444,14 @@ class AsmGen(internal val program: Program,
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateBuiltinFunctionCallExpression(functionCallExpr: FunctionCallExpression, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCallExpr, signature, resultToStack, resultRegister)
internal fun translateBuiltinFunctionCallExpression(bfc: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(bfc, resultToStack, resultRegister)
internal fun translateBuiltinFunctionCallExpression(name: String, args: List<AsmAssignSource>, scope: Subroutine): DataType =
builtinFunctionsAsmGen.translateFunctioncall(name, args, false, scope)
private fun translateBuiltinFunctionCallExpression(name: String, singleArg:AsmAssignSource, scope: Subroutine): DataType =
builtinFunctionsAsmGen.translateUnaryFunctioncall(name, singleArg, false, scope)
internal fun translateBuiltinFunctionCallStatement(name: String, args: List<AsmAssignSource>, scope: Subroutine) =
builtinFunctionsAsmGen.translateFunctioncall(name, args, true, scope)
private fun translateBuiltinFunctionCallStatement(name: String, singleArg: AsmAssignSource, scope: Subroutine) =
builtinFunctionsAsmGen.translateUnaryFunctioncall(name, singleArg, true, scope)
internal fun translateFunctionCall(functionCallExpr: FunctionCallExpression, isExpression: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCallExpr, isExpression)
@@ -874,7 +868,7 @@ $repeatLabel lda $counterVar
"%asminclude" -> {
val includedName = stmt.args[0].str!!
if(stmt.definingModule.source is SourceCode.Generated)
TODO("%asminclude inside non-library, non-filesystem module")
throw AssemblyError("%asminclude inside non-library/non-filesystem module not yet supported")
loadAsmIncludeFile(includedName, stmt.definingModule.source).fold(
success = { assemblyLines.add(it.trimEnd().trimStart('\n')) },
failure = { errors.err(it.toString(), stmt.position) }
@@ -885,7 +879,7 @@ $repeatLabel lda $counterVar
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
if(stmt.definingModule.source is SourceCode.Generated)
TODO("%asmbinary inside non-library, non-filesystem module")
throw AssemblyError("%asmbinary inside non-library/non-filesystem module not yet supported")
val sourcePath = Path(stmt.definingModule.source.origin)
val includedPath = sourcePath.resolveSibling(includedName)
val pathForAssembler = options.outputDir // #54: 64tass needs the path *relative to the .asm file*
@@ -975,14 +969,27 @@ $repeatLabel lda $counterVar
assemblyLines.add(assembly)
}
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
internal fun returnRegisterOfFunction(it: IdentifierReference, argumentTypesForBuiltinFunc: List<DataType>?): RegisterOrPair {
return when (val targetRoutine = it.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> {
when (BuiltinFunctions.getValue(targetRoutine.name).known_returntype) {
val func = BuiltinFunctions.getValue(targetRoutine.name)
when (func.returnType) {
in ByteDatatypes -> RegisterOrPair.A
in WordDatatypes -> RegisterOrPair.AY
DataType.FLOAT -> RegisterOrPair.FAC1
else -> throw AssemblyError("weird returntype")
else -> {
if(!func.hasReturn)
throw AssemblyError("func has no returntype")
else {
val args = argumentTypesForBuiltinFunc!!.map { defaultZero(it, Position.DUMMY) }
when(builtinFunctionReturnType(func.name, args, program).getOrElse { DataType.UNDEFINED }) {
in ByteDatatypes -> RegisterOrPair.A
in WordDatatypes -> RegisterOrPair.AY
DataType.FLOAT -> RegisterOrPair.FAC1
else -> throw AssemblyError("weird returntype")
}
}
}
}
}
is Subroutine -> targetRoutine.asmReturnvaluesRegisters.single().registerOrPair!!
@@ -1207,7 +1214,7 @@ $repeatLabel lda $counterVar
}
if(dt==DataType.UBYTE) {
assignExpressionToRegister(left, RegisterOrPair.A, false)
if (left is FunctionCallExpression && !left.isSimple)
if (left is IFunctionCall && !left.isSimple)
out(" cmp #0")
} else {
assignExpressionToRegister(left, RegisterOrPair.AY, false)
@@ -1223,7 +1230,7 @@ $repeatLabel lda $counterVar
}
DataType.BYTE -> {
assignExpressionToRegister(left, RegisterOrPair.A, true)
if (left is FunctionCallExpression && !left.isSimple)
if (left is IFunctionCall && !left.isSimple)
out(" cmp #0")
when (operator) {
"==" -> out(" bne $jumpIfFalseLabel")
@@ -2830,40 +2837,39 @@ $repeatLabel lda $counterVar
}
}
internal fun translatePipeExpression(expressions: Iterable<Expression>, scope: Node, isStatement: Boolean, pushResultOnEstack: Boolean) {
internal fun translatePipeExpression(source: Expression, segments: List<Expression>, scope: Node, isStatement: Boolean, pushResultOnEstack: Boolean) {
// TODO more efficient code generation to avoid needless assignments to the temp var
// the first term: an expression (could be anything) producing a value.
// the source: an expression (could be anything) producing a value.
// one or more segment expressions, all are a IFunctionCall node, and LACKING the implicit first argument.
// when 'isStatement'=true, the last segment expression should be treated as a funcion call statement (discarding any result value if there is one)
val subroutine = scope.definingSubroutine!!
val firstTerm = expressions.first()
var valueDt = firstTerm.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
var valueDt = source.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
var valueSource: AsmAssignSource =
if(firstTerm is IFunctionCall) {
val resultReg = returnRegisterOfFunction(firstTerm.target)
assignExpressionToRegister(firstTerm, resultReg, valueDt in listOf(DataType.BYTE, DataType.WORD, DataType.FLOAT))
if(source is IFunctionCall) {
val resultReg = returnRegisterOfFunction(source.target, listOf(valueDt))
assignExpressionToRegister(source, resultReg, valueDt in listOf(DataType.BYTE, DataType.WORD, DataType.FLOAT))
AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
} else {
AsmAssignSource.fromAstSource(firstTerm, program, this)
AsmAssignSource.fromAstSource(source, program, this)
}
// the 2nd to N-1 terms: unary function calls taking a single param and producing a value.
// the segments (except the last one): unary function calls taking a single param and producing a value.
// directly assign their argument from the previous call's returnvalue.
expressions.drop(1).dropLast(1).forEach {
valueDt = functioncallAsmGen.translateUnaryFunctionCallWithArgSource(it as IdentifierReference, valueSource, false, subroutine)
val resultReg = returnRegisterOfFunction(it)
segments.dropLast(1).forEach {
it as IFunctionCall
valueDt = translateUnaryFunctionCallWithArgSource(it.target, valueSource, false, subroutine)
val resultReg = returnRegisterOfFunction(it.target, listOf(valueDt))
valueSource = AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
}
// the last term: unary function call taking a single param and optionally producing a result value.
// the last segment: unary function call taking a single param and optionally producing a result value.
val lastCall = segments.last() as IFunctionCall
if(isStatement) {
// the last term in the pipe, don't care about return var:
functioncallAsmGen.translateUnaryFunctionCallWithArgSource(
expressions.last() as IdentifierReference, valueSource, true, subroutine)
translateUnaryFunctionCallWithArgSource(lastCall.target, valueSource, true, subroutine)
} else {
// the last term in the pipe, regular function call with returnvalue:
valueDt = functioncallAsmGen.translateUnaryFunctionCallWithArgSource(
expressions.last() as IdentifierReference, valueSource, false, subroutine)
valueDt = translateUnaryFunctionCallWithArgSource(lastCall.target, valueSource, false, subroutine)
if(pushResultOnEstack) {
when (valueDt) {
in ByteDatatypes -> {
@@ -2881,4 +2887,166 @@ $repeatLabel lda $counterVar
}
}
private fun translateUnaryFunctionCallWithArgSource(target: IdentifierReference, singleArg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
when(val targetStmt = target.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> {
return if(isStatement) {
translateBuiltinFunctionCallStatement(targetStmt.name, singleArg, scope)
DataType.UNDEFINED
} else {
translateBuiltinFunctionCallExpression(targetStmt.name, singleArg, scope)
}
}
is Subroutine -> {
val argDt = targetStmt.parameters.single().type
if(targetStmt.isAsmSubroutine) {
// argument via registers
val argRegister = targetStmt.asmParameterRegisters.single().registerOrPair!!
val assignArgument = AsmAssignment(
singleArg,
AsmAssignTarget.fromRegisters(argRegister, argDt in SignedDatatypes, scope, program, this),
false, program.memsizer, target.position
)
translateNormalAssignment(assignArgument)
} else {
val assignArgument: AsmAssignment =
if(functioncallAsmGen.optimizeIntArgsViaRegisters(targetStmt)) {
// argument goes via registers as optimization
val paramReg: RegisterOrPair = when(argDt) {
in ByteDatatypes -> RegisterOrPair.A
in WordDatatypes -> RegisterOrPair.AY
DataType.FLOAT -> RegisterOrPair.FAC1
else -> throw AssemblyError("invalid dt")
}
AsmAssignment(
singleArg,
AsmAssignTarget(TargetStorageKind.REGISTER, program, this, argDt, scope, register = paramReg),
false, program.memsizer, target.position
)
} else {
// arg goes via parameter variable
val argVarName = asmVariableName(targetStmt.scopedName + targetStmt.parameters.single().name)
AsmAssignment(
singleArg,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, argDt, scope, argVarName),
false, program.memsizer, target.position
)
}
translateNormalAssignment(assignArgument)
}
if(targetStmt.shouldSaveX())
saveRegisterLocal(CpuRegister.X, scope)
out(" jsr ${asmSymbolName(target)}")
if(targetStmt.shouldSaveX())
restoreRegisterLocal(CpuRegister.X)
return if(isStatement) DataType.UNDEFINED else targetStmt.returntypes.single()
}
else -> throw AssemblyError("invalid call target")
}
}
internal fun popCpuStack(dt: DataType, target: VarDecl, scope: Subroutine?) {
// note: because A is pushed first so popped last, saving A is often not required here.
val parameter = target.subroutineParameter
if(parameter!=null) {
val sub = parameter.definingSubroutine!!
require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs" }
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null) {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG")
out("""
clc
pla
beq +
sec
+""")
if(shouldKeepA)
out(" lda P8ZP_SCRATCH_REG")
}
else {
if (dt in ByteDatatypes) {
if (isTargetCpu(CpuType.CPU65c02)) {
when (reg.registerOrPair) {
RegisterOrPair.A -> out(" pla")
RegisterOrPair.X -> out(" plx")
RegisterOrPair.Y -> out(" ply")
in Cx16VirtualRegisters -> out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
} else {
when (reg.registerOrPair) {
RegisterOrPair.A -> out(" pla")
RegisterOrPair.X -> {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
out(" pla | tax")
}
RegisterOrPair.Y -> {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
out(" pla | tay")
}
in Cx16VirtualRegisters -> out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
} else {
// word pop
if (isTargetCpu(CpuType.CPU65c02))
when (reg.registerOrPair) {
RegisterOrPair.AX -> out(" plx | pla")
RegisterOrPair.AY -> out(" ply | pla")
RegisterOrPair.XY -> out(" ply | plx")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
else {
when (reg.registerOrPair) {
RegisterOrPair.AX -> out(" pla | tax | pla")
RegisterOrPair.AY -> out(" pla | tay | pla")
RegisterOrPair.XY -> out(" pla | tay | pla | tax")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
}
}
} else {
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, target.datatype, scope, variableAsmName = asmVariableName(target.name))
if (dt in ByteDatatypes) {
out(" pla")
assignRegister(RegisterOrPair.A, tgt)
} else {
if (isTargetCpu(CpuType.CPU65c02))
out(" ply | pla")
else
out(" pla | tay | pla")
assignRegister(RegisterOrPair.AY, tgt)
}
}
}
internal fun pushCpuStack(dt: DataType, value: Expression) {
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(dt in ByteDatatypes) {
assignExpressionToRegister(value, RegisterOrPair.A, signed)
out(" pha")
} else {
assignExpressionToRegister(value, RegisterOrPair.AY, signed)
if (isTargetCpu(CpuType.CPU65c02))
out(" pha | phy")
else
out(" pha | tya | pha")
}
}
}
@@ -17,41 +17,86 @@ internal class AssemblyProgram(
private val compTarget: ICompilationTarget) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg")
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve(viceMonListName(name))
private val listFile = outputDir.resolve("$name.list")
override fun assemble(options: CompilationOptions): Boolean {
// 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",
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
)
if(options.asmQuiet)
command.add("--quiet")
val assemblerCommand: List<String>
if(options.asmListfile)
command.add("--list=$listFile")
when (compTarget.name) {
in setOf("c64", "c128", "cx16") -> {
// CBM machines .prg generation.
// 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",
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
)
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating prg for target ${compTarget.name}.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
}
else -> throw AssemblyError("invalid output type")
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating prg for target ${compTarget.name}.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
"atari" -> {
// Atari800XL .xex generation.
// TODO are these options okay?
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--no-monitor"
)
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
val outFile = when (options.output) {
OutputType.XEX -> {
command.add("--atari-xex")
println("\nCreating xex for target ${compTarget.name}.")
xexFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
}
else -> throw AssemblyError("invalid output type")
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
}
else -> throw AssemblyError("invalid compilation target")
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
val proc = ProcessBuilder(command).inheritIO().start()
val proc = ProcessBuilder(assemblerCommand).inheritIO().start()
val result = proc.waitFor()
if (result == 0) {
if (result == 0 && compTarget.name!="atari") {
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
@@ -6,15 +6,12 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionCallStatement
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.codegen.cpu6502.assignment.*
import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature
import prog8.compilerinterface.*
internal class BuiltinFunctionsAsmGen(private val program: Program,
@@ -22,29 +19,33 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
private val assignAsmGen: AssignmentAsmGen,
private val allocations: VariableAllocator) {
internal fun translateFunctioncallExpression(fcall: FunctionCallExpression, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
internal fun translateFunctioncallExpression(fcall: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val func = BuiltinFunctions.getValue(fcall.target.nameInSource.single())
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
internal fun translateFunctioncallStatement(fcall: BuiltinFunctionCallStatement) {
val func = BuiltinFunctions.getValue(fcall.name)
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
}
internal fun translateFunctioncall(name: String, args: List<AsmAssignSource>, isStatement: Boolean, scope: Subroutine): DataType {
internal fun translateUnaryFunctioncall(name: String, singleArg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
val func = BuiltinFunctions.getValue(name)
val argExpressions = args.map { src ->
when(src.kind) {
SourceStorageKind.LITERALNUMBER -> src.number!!
SourceStorageKind.EXPRESSION -> src.expression!!
SourceStorageKind.ARRAY -> src.array!!
val argExpression =
when(singleArg.kind) {
SourceStorageKind.LITERALNUMBER -> singleArg.number!!
SourceStorageKind.EXPRESSION -> singleArg.expression!!
SourceStorageKind.ARRAY -> singleArg.array!!
else -> {
// TODO make it so that we can assign efficiently from something else as an expression....namely: register(s)
// this is useful in pipe expressions for instance, to skip the use of a temporary variable
// but for now, just assign it to a temporary variable and use that as a source
val tempvar = asmgen.getTempVarName(src.datatype)
// Idea: to do this without having to rewrite every single function in translateFunctioncall(),
// hack a special IdentifierReference like "!6502.A/X/Y/AX/AY/XY" to reference a cpu register
val tempvar = asmgen.getTempVarName(singleArg.datatype)
val assignTempvar = AsmAssignment(
src,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, src.datatype, scope, variableAsmName = asmgen.asmVariableName(tempvar)),
singleArg,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, singleArg.datatype, scope, variableAsmName = asmgen.asmVariableName(tempvar)),
false, program.memsizer, Position.DUMMY
)
assignAsmGen.translateNormalAssignment(assignTempvar)
@@ -54,11 +55,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
ident
}
}
}.toMutableList()
val fcall = FunctionCallExpression(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY)
val argExpressions = mutableListOf(argExpression);
val fcall = BuiltinFunctionCall(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY)
fcall.linkParents(scope)
translateFunctioncall(fcall, func, discardResult = false, resultToStack = false, null)
return if(isStatement) DataType.UNDEFINED else func.known_returntype!!
return if(isStatement) {
DataType.UNDEFINED
} else {
builtinFunctionReturnType(func.name, argExpressions, program).getOrElse { throw AssemblyError("unknown dt") }
}
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
@@ -102,8 +107,20 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"pokew" -> funcPokeW(fcall)
"pokemon" -> { /* meme function */ }
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"push", "pushw" -> funcPush(fcall, func)
"pop", "popw" -> funcPop(fcall, func)
"push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0])
"pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0])
"pop" -> {
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
asmgen.popCpuStack(DataType.UBYTE, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine)
}
"popw" -> {
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
asmgen.popCpuStack(DataType.UWORD, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine)
}
"rsave" -> funcRsave()
"rsavex" -> funcRsaveX()
"rrestore" -> funcRrestore()
@@ -166,117 +183,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
}
private fun funcPop(fcall: IFunctionCall, func: FSignature) {
// note: because A is pushed first so popped last, saving A is often not required here.
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
val target = (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!
val parameter = target.subroutineParameter
if(parameter!=null) {
val sub = parameter.definingSubroutine!!
require(sub.isAsmSubroutine) {
"push/pop arg passing only supported on asmsubs ${(fcall as Node).position}"
}
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null) {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG")
asmgen.out("""
clc
pla
beq +
sec
+""")
if(shouldKeepA)
asmgen.out(" lda P8ZP_SCRATCH_REG")
}
else {
if (func.name == "pop") {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> asmgen.out(" plx")
RegisterOrPair.Y -> asmgen.out(" ply")
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
} else {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tax")
}
RegisterOrPair.Y -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tay")
}
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
} else {
// word pop
if (asmgen.isTargetCpu(CpuType.CPU65c02))
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" plx | pla")
RegisterOrPair.AY -> asmgen.out(" ply | pla")
RegisterOrPair.XY -> asmgen.out(" ply | plx")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
else {
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" pla | tax | pla")
RegisterOrPair.AY -> asmgen.out(" pla | tay | pla")
RegisterOrPair.XY -> asmgen.out(" pla | tay | pla | tax")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
}
}
} else {
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, target.datatype, (fcall as Node).definingSubroutine, variableAsmName = asmgen.asmVariableName(target.name))
if (func.name == "pop") {
asmgen.out(" pla")
asmgen.assignRegister(RegisterOrPair.A, tgt)
} else {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" ply | pla")
else
asmgen.out(" pla | tay | pla")
asmgen.assignRegister(RegisterOrPair.AY, tgt)
}
}
}
private fun funcPush(fcall: IFunctionCall, func: FSignature) {
val signed = fcall.args[0].inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(func.name=="push") {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed)
asmgen.out(" pha")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" pha | phy")
else
asmgen.out(" pha | tya | pha")
}
}
private fun funcCallFar(fcall: IFunctionCall) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time")
@@ -449,7 +355,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is FunctionCallExpression)
if(discardResult || fcall !is BuiltinFunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as StringLiteral).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
@@ -632,7 +538,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
@@ -733,7 +639,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
@@ -3,11 +3,8 @@ package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import kotlin.math.absoluteValue
@@ -39,7 +36,9 @@ internal class ExpressionsAsmGen(private val program: Program,
is NumericLiteral -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
is PipeExpression -> asmgen.translatePipeExpression(expression.expressions, expression,false, true)
is BuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
is PipeExpression -> asmgen.translatePipeExpression(expression.source, expression.segments,
expression, isStatement = false, pushResultOnEstack = true )
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is ArrayLiteral, is StringLiteral -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpression -> throw AssemblyError("range expression should have been changed into array values")
@@ -51,96 +50,90 @@ internal class ExpressionsAsmGen(private val program: Program,
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
// only for use in nested expression evaluation
val sub = call.target.targetStatement(program)
if(sub is BuiltinFunctionPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true, null)
} else {
sub as Subroutine
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call, true)
if(sub.regXasResult()) {
// store the return value in X somewhere that we can acces again below
asmgen.out(" stx P8ZP_SCRATCH_REG")
}
asmgen.restoreXafterCall(call)
val sub = call.target.targetSubroutine(program)!!
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call, true)
if(sub.regXasResult()) {
// store the return value in X somewhere that we can access again below
asmgen.out(" stx P8ZP_SCRATCH_REG")
}
asmgen.restoreXafterCall(call)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) {
when (reg.registerOrPair!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
asmgen.out(
"""
lda cx16.${reg.registerOrPair.toString().lowercase()}
sta P8ESTACK_LO,x
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
sta P8ESTACK_HI,x
dex
""")
}
}
} else when(reg.statusflag) {
Statusflag.Pc -> {
asmgen.out("""
lda #0
rol a
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) {
when (reg.registerOrPair!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
asmgen.out(
"""
lda cx16.${reg.registerOrPair.toString().lowercase()}
sta P8ESTACK_LO,x
dex""")
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
sta P8ESTACK_HI,x
dex
""")
}
Statusflag.Pz -> {
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pv -> {
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pn -> {
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
null -> {}
}
} else when(reg.statusflag) {
Statusflag.Pc -> {
asmgen.out("""
lda #0
rol a
sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pz -> {
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pv -> {
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pn -> {
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
null -> {}
}
}
}
@@ -14,12 +14,11 @@ import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.AsmAssignment
import prog8.codegen.cpu6502.assignment.TargetStorageKind
import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.CpuType
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
internal fun translateFunctionCallStatement(stmt: FunctionCallStatement) {
saveXbeforeCall(stmt)
translateFunctionCall(stmt, false)
restoreXafterCall(stmt)
@@ -120,7 +119,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.out(" pla")
}
} else {
argumentsViaVariables(sub, call)
// arguments via variables
for(arg in sub.parameters.withIndex().zip(call.args))
argumentViaVariable(sub, arg.first.value, arg.second)
}
asmgen.out(" jsr $subAsmName")
}
@@ -128,75 +129,12 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
internal fun translateUnaryFunctionCallWithArgSource(target: IdentifierReference, arg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
when(val targetStmt = target.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> {
return if(isStatement) {
asmgen.translateBuiltinFunctionCallStatement(targetStmt.name, listOf(arg), scope)
DataType.UNDEFINED
} else {
asmgen.translateBuiltinFunctionCallExpression(targetStmt.name, listOf(arg), scope)
}
}
is Subroutine -> {
val argDt = targetStmt.parameters.single().type
if(targetStmt.isAsmSubroutine) {
// argument via registers
val argRegister = targetStmt.asmParameterRegisters.single().registerOrPair!!
val assignArgument = AsmAssignment(
arg,
AsmAssignTarget.fromRegisters(argRegister, argDt in SignedDatatypes, scope, program, asmgen),
false, program.memsizer, target.position
)
asmgen.translateNormalAssignment(assignArgument)
} else {
val assignArgument: AsmAssignment =
if(optimizeIntArgsViaRegisters(targetStmt)) {
// argument goes via registers as optimization
val paramReg: RegisterOrPair = when(argDt) {
in ByteDatatypes -> RegisterOrPair.A
in WordDatatypes -> RegisterOrPair.AY
DataType.FLOAT -> RegisterOrPair.FAC1
else -> throw AssemblyError("invalid dt")
}
AsmAssignment(
arg,
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, argDt, scope, register = paramReg),
false, program.memsizer, target.position
)
} else {
// arg goes via parameter variable
val argVarName = asmgen.asmVariableName(targetStmt.scopedName + targetStmt.parameters.single().name)
AsmAssignment(
arg,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, argDt, scope, argVarName),
false, program.memsizer, target.position
)
}
asmgen.translateNormalAssignment(assignArgument)
}
if(targetStmt.shouldSaveX())
asmgen.saveRegisterLocal(CpuRegister.X, scope)
asmgen.out(" jsr ${asmgen.asmSymbolName(target)}")
if(targetStmt.shouldSaveX())
asmgen.restoreRegisterLocal(CpuRegister.X)
return if(isStatement) DataType.UNDEFINED else targetStmt.returntypes.single()
}
else -> throw AssemblyError("invalid call target")
}
}
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
for(arg in sub.parameters.withIndex().zip(call.args))
argumentViaVariable(sub, arg.first.value, arg.second)
}
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
} else {
if(asmgen.asmsubArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
registerArgsViaStackEvaluation(call, sub)
registerArgsViaCpuStackEvaluation(call, sub)
} else {
asmgen.asmsubArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
@@ -207,129 +145,24 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
private fun registerArgsViaCpuStackEvaluation(call: IFunctionCall, callee: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
// TODO find another way to prepare the arguments, without using the eval stack
if(sub.parameters.isEmpty())
require(callee.isAsmSubroutine)
if(callee.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
// TODO here's an alternative to the above, but for now generates bigger code due to intermediate register steps:
// for (arg in stmt.args.reversed()) {
// // note this stuff below is needed to (eventually) avoid calling asmgen.translateExpression()
// // TODO also This STILL requires the translateNormalAssignment() to be fixed to avoid stack eval for expressions...
// val dt = arg.inferType(program).getOr(DataType.UNDEFINED)
// asmgen.assignExpressionTo(arg, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, dt, sub))
// }
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
asmgen.out(" inx") // align estack pointer
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
val plusIdxStr = if(argi.index==0) "" else "+${argi.index}"
when {
argi.value.second.statusflag == Statusflag.Pc -> {
require(argForCarry == null)
argForCarry = argi
}
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x")
}
argi.value.second.registerOrPair in Cx16VirtualRegisters -> {
// immediately output code to load the virtual register, to avoid clobbering the A register later
when (sub.parameters[argi.index].type) {
in ByteDatatypes -> {
// only load the lsb of the virtual register
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
""")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(
" stz cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
else
asmgen.out(
" lda #0 | sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
}
in WordDatatypes, in IterableDatatypes ->
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
lda P8ESTACK_HI$plusIdxStr,x
sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1
""")
else -> throw AssemblyError("weird dt")
}
}
else -> throw AssemblyError("weird argument")
}
// use the cpu hardware stack as intermediate storage for the arguments.
val argOrder = asmgen.options.compTarget.asmsubArgsEvalOrder(callee)
argOrder.reversed().forEach {
asmgen.pushCpuStack(callee.parameters[it].type, call.args[it])
}
if(argForCarry!=null) {
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
asmgen.out("""
clc
lda P8ESTACK_LO$plusIdxStr,x
beq +
sec
+ php""") // push the status flags
argOrder.forEach {
val param = callee.parameters[it]
val targetVar = callee.searchAsmParameter(param.name)!!
asmgen.popCpuStack(param.type, targetVar, (call as Node).definingSubroutine)
}
if(argForAregister!=null) {
val plusIdxStr = if(argForAregister.index==0) "" else "+${argForAregister.index}"
when(argForAregister.value.second.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x")
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | ldy P8ESTACK_HI$plusIdxStr,x")
else -> throw AssemblyError("weird arg")
}
}
if(argForXregister!=null) {
val plusIdxStr = if(argForXregister.index==0) "" else "+${argForXregister.index}"
if(argForAregister!=null)
asmgen.out(" pha")
when(argForXregister.value.second.registerOrPair) {
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | tax")
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x | lda P8ESTACK_HI$plusIdxStr,x | tax | tya")
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI$plusIdxStr,x | lda P8ESTACK_LO$plusIdxStr,x | tax")
else -> throw AssemblyError("weird arg")
}
if(argForAregister!=null)
asmgen.out(" pla")
} else {
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
}
if(argForCarry!=null)
asmgen.out(" plp") // set the carry flag back to correct value
}
private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) {
@@ -70,9 +70,30 @@ internal class ProgramAndVarsGen(
asmgen.out(".cpu '$cpu'\n.enc 'none'\n")
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0u) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
compTarget.machine.BASIC_LOAD_ADDRESS else compTarget.machine.RAW_LOAD_ADDRESS
if (program.actualLoadAddress == 0u) {
when(options.output) {
OutputType.RAW -> {
errors.err("load address must be specified with %address when using raw output type", program.toplevelModule.position)
return
}
OutputType.PRG -> {
when(options.launcher) {
CbmPrgLauncherType.BASIC -> {
program.actualLoadAddress = compTarget.machine.PROGRAM_LOAD_ADDRESS
}
CbmPrgLauncherType.NONE -> {
errors.err("load address must be specified with %address when not using basic launcher", program.toplevelModule.position)
return
}
}
}
OutputType.XEX -> {
if(options.launcher!=CbmPrgLauncherType.NONE)
throw AssemblyError("atari xex output can't contain BASIC launcher")
program.actualLoadAddress = compTarget.machine.PROGRAM_LOAD_ADDRESS
}
}
}
// the global prog8 variables needed
val zp = zeropage
@@ -83,32 +104,44 @@ internal class ProgramAndVarsGen(
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
when {
options.launcher == LauncherType.BASIC -> {
if (program.actualLoadAddress != options.compTarget.machine.BASIC_LOAD_ADDRESS)
throw AssemblyError("BASIC output must have correct load address")
asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}")
val year = LocalDate.now().year
asmgen.out(" .word (+), $year")
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint\t; assembly code starts here\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
options.output == OutputType.PRG -> {
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
options.output == OutputType.RAW -> {
when(options.output) {
OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}\n")
}
OutputType.PRG -> {
when(options.launcher) {
CbmPrgLauncherType.BASIC -> {
if (program.actualLoadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.toplevelModule.position)
}
asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}")
val year = LocalDate.now().year
asmgen.out(" .word (+), $year")
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint\t; assembly code starts here\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
CbmPrgLauncherType.NONE -> {
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
}
}
OutputType.XEX -> {
asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
}
if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
@@ -47,8 +47,8 @@ internal class VariableAllocator(private val vars: IVariablesAndConsts,
val numberOfExplicitNonZpVariables = allVariables.count { it.zp == ZeropageWish.NOT_IN_ZEROPAGE }
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + numberOfExplicitNonZpVariables == numberOfAllocatableVariables)
var numVariablesAllocatedInZP: Int = 0
var numberOfNonIntegerVariables: Int = 0
var numVariablesAllocatedInZP = 0
var numberOfNonIntegerVariables = 0
varsRequiringZp.forEach { variable ->
val numElements = numArrayElements(variable)
@@ -167,26 +167,20 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
is BuiltinFunctionCall -> {
val returnType = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
}
is FunctionCallExpression -> {
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
?: throw AssemblyError("can't translate zero return values in assignment")
val sub = value.target.targetSubroutine(program)!!
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
is BuiltinFunctionPlaceholder -> {
val returnType = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
}
else -> {
throw AssemblyError("weird call")
}
}
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
else -> {
val dt = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
val returnType = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
}
}
}
@@ -8,7 +8,6 @@ import prog8.ast.toHex
import prog8.codegen.cpu6502.AsmGen
import prog8.codegen.cpu6502.VariableAllocator
import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.builtinFunctionReturnType
@@ -151,180 +150,7 @@ internal class AssignmentAsmGen(private val program: Program,
}
}
SourceStorageKind.EXPRESSION -> {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteral -> throw AssemblyError("source kind should have been literalnumber")
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
is FunctionCallExpression -> {
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value, true)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
when (returnValue.first) {
DataType.STR -> {
asmgen.restoreXafterCall(value)
when(assign.target.datatype) {
DataType.UWORD -> {
// assign the address of the string result value
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
// copy the actual string result into the target string variable
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
else -> throw AssemblyError("weird target dt")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
asmgen.restoreXafterCall(value)
assignFAC1float(assign.target)
}
else -> {
// do NOT restore X register before assigning the result values first
when (returnValue.second.registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
else -> {
val sflag = returnValue.second.statusflag
if(sflag!=null)
assignStatusFlagByte(assign.target, sflag)
else
throw AssemblyError("should be just one register byte result value")
}
}
// we've processed the result value in the X register by now, so it's now finally safe to restore it
asmgen.restoreXafterCall(value)
}
}
}
is BuiltinFunctionPlaceholder -> {
val signature = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(value, signature, false, assign.target.register)
if(assign.target.register==null) {
// still need to assign the result to the target variable/etc.
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.getOr(DataType.UNDEFINED)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
when (assign.target.datatype) {
DataType.STR -> {
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
DataType.UWORD -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
else -> throw AssemblyError("str return value type mismatch with target")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(assign.target)
}
else -> throw AssemblyError("weird result type")
}
}
}
else -> {
throw AssemblyError("weird func call")
}
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
is ContainmentCheck -> {
containmentCheckIntoA(value)
assignRegisterByte(assign.target, CpuRegister.A)
}
is PipeExpression -> {
asmgen.translatePipeExpression(value.expressions, value, false, false)
val resultDt = value.inferType(program)
val register =
if(resultDt.isBytes) RegisterOrPair.A
else if(resultDt.isWords) RegisterOrPair.AY
else if(resultDt istype DataType.FLOAT) RegisterOrPair.FAC1
else throw AssemblyError("invalid dt")
asmgen.assignRegister(register, assign.target)
}
is BinaryExpression -> {
if(value.operator in ComparisonOperators) {
// TODO real optimized code for comparison expressions that yield a boolean result value
// for now generate code for this: assign-false; if expr { assign-true }
translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, DataType.UBYTE, number=NumericLiteral.fromBoolean(false, assign.position)),
assign.target, false, program.memsizer, assign.position
)
)
val origTarget = assign.target.origAstTarget
if(origTarget!=null) {
val assignTrue = AnonymousScope(mutableListOf(
Assignment(origTarget, NumericLiteral.fromBoolean(true, assign.position), AssignmentOrigin.ASMGEN, assign.position)
), assign.position)
val assignFalse = AnonymousScope(mutableListOf(), assign.position)
val ifelse = IfElse(value.copy(), assignTrue, assignFalse, assign.position)
ifelse.linkParents(value)
asmgen.translate(ifelse)
}
else {
// no orig ast assign target, can't use the workaround, so fallback to stack eval
fallbackToStackEval(value, assign)
}
} else {
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
fallbackToStackEval(value, assign)
}
}
else -> {
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
fallbackToStackEval(value, assign)
}
}
assignExpression(assign)
}
SourceStorageKind.REGISTER -> {
asmgen.assignRegister(assign.source.register!!, assign.target)
@@ -336,9 +162,172 @@ internal class AssignmentAsmGen(private val program: Program,
}
}
private fun fallbackToStackEval(value: Expression, assign: AsmAssignment) {
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
asmgen.translateExpression(value)
private fun assignExpression(assign: AsmAssignment) {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteral -> throw AssemblyError("source kind should have been literalnumber")
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
is FunctionCallExpression -> {
val sub = value.target.targetSubroutine(program)!!
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value, true)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
when (returnValue.first) {
DataType.STR -> {
asmgen.restoreXafterCall(value)
when(assign.target.datatype) {
DataType.UWORD -> {
// assign the address of the string result value
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
// copy the actual string result into the target string variable
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
else -> throw AssemblyError("weird target dt")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
asmgen.restoreXafterCall(value)
assignFAC1float(assign.target)
}
else -> {
// do NOT restore X register before assigning the result values first
when (returnValue.second.registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
else -> {
val sflag = returnValue.second.statusflag
if(sflag!=null)
assignStatusFlagByte(assign.target, sflag)
else
throw AssemblyError("should be just one register byte result value")
}
}
// we've processed the result value in the X register by now, so it's now finally safe to restore it
asmgen.restoreXafterCall(value)
}
}
}
is BuiltinFunctionCall -> {
asmgen.translateBuiltinFunctionCallExpression(value, false, assign.target.register)
if(assign.target.register==null) {
// still need to assign the result to the target variable/etc.
val returntype = builtinFunctionReturnType(value.name, value.args, program)
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.getOr(DataType.UNDEFINED)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
when (assign.target.datatype) {
DataType.STR -> {
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
DataType.UWORD -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
else -> throw AssemblyError("str return value type mismatch with target")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(assign.target)
}
else -> throw AssemblyError("weird result type")
}
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
is ContainmentCheck -> {
containmentCheckIntoA(value)
assignRegisterByte(assign.target, CpuRegister.A)
}
is PipeExpression -> {
asmgen.translatePipeExpression(value.source, value.segments, value, false, false)
val resultDt = value.inferType(program)
val register =
if(resultDt.isBytes) RegisterOrPair.A
else if(resultDt.isWords) RegisterOrPair.AY
else if(resultDt istype DataType.FLOAT) RegisterOrPair.FAC1
else throw AssemblyError("invalid dt")
asmgen.assignRegister(register, assign.target)
}
is BinaryExpression -> {
if(value.operator in ComparisonOperators) {
// TODO real optimized code for comparison expressions that yield a boolean result value
assignConstantByte(assign.target, 0)
val origTarget = assign.target.origAstTarget
if(origTarget!=null) {
val assignTrue = AnonymousScope(mutableListOf(
Assignment(origTarget, NumericLiteral.fromBoolean(true, assign.position), AssignmentOrigin.ASMGEN, assign.position)
), assign.position)
val assignFalse = AnonymousScope(mutableListOf(), assign.position)
val ifelse = IfElse(value.copy(), assignTrue, assignFalse, assign.position)
ifelse.linkParents(value)
asmgen.translate(ifelse)
}
else {
// no orig ast assign target so can't use the workaround, so fallback to stack eval
fallbackToStackEval(assign)
}
} else {
// All remaining binary expressions just evaluate via the stack for now.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
fallbackToStackEval(assign)
}
}
else -> throw AssemblyError("weird assignment value type $value")
}
}
private fun fallbackToStackEval(assign: AsmAssignment) {
// TODO DON'T STACK-EVAL... perhaps by using a temp var? so that it becomes augmentable assignment expression?
// or don't try to solve it here in this one case and rather rewrite the whole stack based value evaluation.
// this routine is called for assigning a binaryexpression value:
// - if it's a boolean comparison expression and the workaround isn't possible (no origTarget ast node)
// - for all other binary expressions.
asmgen.translateExpression(assign.source.expression!!)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
if (assign.target.kind != TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
@@ -352,7 +341,7 @@ internal class AssignmentAsmGen(private val program: Program,
val constRange = range.toConstantIntegerRange()
if(constRange!=null)
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), constRange.toList())
TODO("non-const range containment check ${containment.position}")
throw AssemblyError("non const range containment check not supported")
}
val variable = (containment.iterable as? IdentifierReference)?.targetVarDecl(program)
if(variable!=null) {
@@ -685,7 +674,7 @@ $containsLabel lda #1
}
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
val lsb = BuiltinFunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
lsb.linkParents(value.parent)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
@@ -0,0 +1,52 @@
package prog8.codegen.target
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.codegen.target.atari.AtariMachineDefinition
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.*
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = AtariMachineDefinition()
override val supportedEncodings = setOf(Encoding.ATASCII)
override val defaultEncoding = Encoding.ATASCII
companion object {
const val NAME = "atari"
}
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> 6
else -> Int.MIN_VALUE
}
}
override fun memorySize(decl: VarDecl): Int {
return when(decl.type) {
VarDeclType.CONST -> 0
VarDeclType.VAR, VarDeclType.MEMORY -> {
when(val dt = decl.datatype) {
in NumericDatatypes -> return memorySize(dt)
in ArrayDatatypes -> decl.arraysize!!.constIndex()!! * memorySize(ArrayToElementTypes.getValue(dt))
DataType.STR -> (decl.value as StringLiteral).value.length + 1
else -> 0
}
}
}
}
}
@@ -7,14 +7,14 @@ import prog8.codegen.target.c128.C128MachineDefinition
import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
import prog8.compilerinterface.*
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C128MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "c128"
@@ -7,14 +7,14 @@ import prog8.codegen.target.c64.C64MachineDefinition
import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
import prog8.compilerinterface.*
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C64MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "c64"
@@ -7,14 +7,14 @@ import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.codegen.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
import prog8.compilerinterface.*
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = CX16MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
override val defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "cx16"
@@ -1,31 +1,34 @@
package prog8.codegen.target
import com.github.michaelbull.result.fold
import prog8.ast.base.FatalAstException
import prog8.codegen.target.cbm.AtasciiEncoding
import prog8.codegen.target.cbm.IsoEncoding
import prog8.codegen.target.cbm.PetsciiEncoding
import prog8.compilerinterface.Encoding
import prog8.compilerinterface.IStringEncoding
import prog8.compilerinterface.InternalCompilerException
internal object Encoder: IStringEncoding {
override fun encodeString(str: String, encoding: Encoding): List<UByte> { // TODO use Result
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
else -> throw FatalAstException("unsupported encoding $encoding")
Encoding.ATASCII -> AtasciiEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String { // TODO use Result
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
else -> throw FatalAstException("unsupported encoding $encoding")
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return decoded.fold(
failure = { throw it },
@@ -0,0 +1,65 @@
package prog8.codegen.target.atari
import prog8.codegen.target.c64.normal6502instructions
import prog8.compilerinterface.*
import java.nio.file.Path
class AtariMachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 6
override val PROGRAM_LOAD_ADDRESS = 0x2000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO
override val ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = TODO("float from number")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulatorName: String
val cmdline: List<String>
when(selectedEmulator) {
1 -> {
emulatorName = "atari800"
cmdline = listOf(emulatorName, "-xl", "-xl-rev", "2", "-nobasic", "-run", "${programNameWithPath}.xex")
}
2 -> {
emulatorName = "altirra"
cmdline = listOf("Altirra64.exe", "${programNameWithPath.normalize()}.xex")
}
else -> {
System.err.println("Atari target only supports atari800 and altirra emulators.")
return
}
}
// TODO monlist?
println("\nStarting Atari800XL emulator $emulatorName...")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = AtariZeropage(compilerOptions)
}
override val opcodeNames = normal6502instructions
}
@@ -0,0 +1,45 @@
package prog8.codegen.target.atari
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.InternalCompilerException
import prog8.compilerinterface.Zeropage
import prog8.compilerinterface.ZeropageType
class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0xcbu // temp storage for a single byte
override val SCRATCH_REG = 0xccu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xcdu // temp storage 1 for a word $cd+$ce
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
init {
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
when (options.zeropage) {
ZeropageType.FULL -> {
// TODO all atari usable zero page locations, except the ones used by the system's IRQ routine
free.addAll(0x00u..0xffu)
// TODO atari free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x80u..0xffu) // TODO
}
ZeropageType.BASICSAFE,
ZeropageType.FLOATSAFE -> {
free.addAll(0x80u..0xffu) // TODO
free.removeAll(0xd4u .. 0xefu) // floating point storage
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
}
removeReservedFromFreePool()
}
}
@@ -3,7 +3,6 @@ package prog8.codegen.target.c128
import prog8.codegen.target.c64.normal6502instructions
import prog8.codegen.target.cbm.Mflpt5
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
@@ -14,8 +13,7 @@ class C128MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val BASIC_LOAD_ADDRESS = 0x1c01u
override val RAW_LOAD_ADDRESS = 0x1300u
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
@@ -26,7 +24,7 @@ class C128MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
@@ -38,21 +36,13 @@ class C128MachineDefinition: IMachineDefinition {
return
}
for(emulator in listOf("x128")) {
println("\nStarting C-128 emulator $emulator...")
val viceMonlist = viceMonListName(programNameWithPath.toString())
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
println("\nStarting C-128 emulator x128...")
val viceMonlist = viceMonListName(programNameWithPath.toString())
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
@@ -13,8 +13,7 @@ class C64MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0xc000u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
@@ -25,7 +24,7 @@ class C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
@@ -3,8 +3,8 @@ package prog8.codegen.target.cbm
import prog8.ast.base.Cx16VirtualRegisters
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.BuiltinFunctionCall
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
@@ -46,10 +46,10 @@ internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is FunctionCallExpression -> {
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
is BuiltinFunctionCall -> {
if (expr.name == "lsb" || expr.name == "msb")
return isClobberRisk(expr.args[0])
if (expr.target.nameInSource == listOf("mkword"))
if (expr.name == "mkword")
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple
}
@@ -0,0 +1,204 @@
package prog8.codegen.target.cbm
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
object AtasciiEncoding {
private val decodeTable: CharArray = charArrayOf(
// $00
'♥',
'├',
'\uf130', // 🮇 0x02 -> RIGHT ONE QUARTER BLOCK (CUS)
'┘',
'┤',
'┐',
'╱',
'╲',
'◢',
'▗',
'◣',
'▝',
'▘',
'\uf132', // 🮂 0x1d -> UPPER ONE QUARTER BLOCK (CUS)
'▂',
'▖',
// $10
'♣',
'┌',
'─',
'┼',
'•',
'▄',
'▎',
'┬',
'┴',
'▌',
'└',
'\u001b', // $1b = escape
'\ufffe', // UNDEFINED CHAR. $1c = cursor up
'\ufffe', // UNDEFINED CHAR. $1d = cursor down
'\ufffe', // UNDEFINED CHAR. $1e = cursor left
'\ufffe', // UNDEFINED CHAR. $1f = cursor right
// $20
' ',
'!',
'"',
'#',
'$',
'%',
'&',
'\'',
'(',
')',
'*',
'+',
',',
'-',
'.',
'/',
// $30
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
':',
';',
'<',
'=',
'>',
'?',
// $40
'@',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
// $50
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'[',
'\\',
']',
'^',
'_',
// $60
'♦',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
// $70
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'♠',
'|',
'\u000c', // $7d -> FORM FEED (CLEAR SCREEN)
'\u0008', // $7e -> BACKSPACE
'\u0009', // $7f -> TAB
// $80-$ff are reversed video characters + various special characters.
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $90
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe',
'\ufffe',
'\ufffe',
'\n', // $9b -> EOL/RETURN
'\ufffe', // UNDEFINED $9c = DELETE LINE
'\ufffe', // UNDEFINED $9d = INSERT LINE
'\ufffe', // UNDEFINED $9e = CLEAR TAB STOP
'\ufffe', // UNDEFINED $9f = SET TAB STOP
// $a0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $b0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $c0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $d0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $e0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $f0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe',
'\ufffe',
'\ufffe',
'\ufffe',
'\ufffe',
'\u0007', // $fd = bell/beep
'\u007f', // $fe = DELETE
'\ufffe' // UNDEFINED $ff = INSERT
)
private val encodeTable = decodeTable.withIndex().associate{it.value to it.index}
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return Ok(str.map { encodeTable.getValue(it).toUByte() })
}
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
}
}
@@ -159,7 +159,7 @@ object PetsciiEncoding {
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
'\uf11e', //  0x91 -> CURSOR UP (CUS)
'\uf11b', //  0x92 -> REVERSE VIDEO OFF (CUS)
'\u000c', // 0x93 -> FORM FEED
'\u000c', // 0x93 -> FORM FEED (CLEAR SCREEN)
'\uf121', //  0x94 -> INSERT (CUS)
'\uf106', // 0x95 -> BROWN COLOR SWITCH (CUS)
'\uf107', // 0x96 -> LIGHT RED COLOR SWITCH (CUS)
@@ -195,7 +195,7 @@ object PetsciiEncoding {
'\u258e', // ▎ 0xB4 -> LEFT ONE QUARTER BLOCK
'\u258d', // ▍ 0xB5 -> LEFT THREE EIGTHS BLOCK
'\uf131', //  0xB6 -> RIGHT THREE EIGHTHS BLOCK (CUS)
'\uf132', // 0xB7 -> UPPER ONE QUARTER BLOCK (CUS)
'\uf132', // 🮂 0xB7 -> UPPER ONE QUARTER BLOCK (CUS)
'\uf133', //  0xB8 -> UPPER THREE EIGHTS BLOCK (CUS)
'\u2583', // ▃ 0xB9 -> LOWER THREE EIGHTHS BLOCK
'\u2713', // ✓ 0xBA -> CHECK MARK
@@ -246,7 +246,7 @@ object PetsciiEncoding {
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
'\uf12f', //  0xE8 -> LOWER HALF BLOCK MEDIUM SHADE (CUS)
'\uf13a', //  0xE9 -> MEDIUM SHADE SLASHED RIGHT (CUS)
'\uf130', // 0xEA -> RIGHT ONE QUARTER BLOCK (CUS)
'\uf130', // 🮇 0xEA -> RIGHT ONE QUARTER BLOCK (CUS)
'\u251c', // ├ 0xEB -> BOX DRAWINGS LIGHT VERTICAL AND RIGHT
'\u2597', // ▗ 0xEC -> QUADRANT LOWER RIGHT
'\u2514', // └ 0xED -> BOX DRAWINGS LIGHT UP AND RIGHT
@@ -418,7 +418,7 @@ object PetsciiEncoding {
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
'\uf11e', // 0x91 -> CURSOR UP (CUS)
'\uf11b', // 0x92 -> REVERSE VIDEO OFF (CUS)
'\u000c', // 0x93 -> FORM FEED
'\u000c', // 0x93 -> FORM FEED (CLEAR SCREEN)
'\uf121', // 0x94 -> INSERT (CUS)
'\uf106', // 0x95 -> BROWN COLOR SWITCH (CUS)
'\uf107', // 0x96 -> LIGHT RED COLOR SWITCH (CUS)
@@ -2,7 +2,6 @@ package prog8.codegen.target.cx16
import prog8.codegen.target.cbm.Mflpt5
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
@@ -13,8 +12,7 @@ class CX16MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0x8000u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
@@ -24,23 +22,23 @@ class CX16MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulatorName: String
val emulator: String
val extraArgs: List<String>
when(selectedEmulator) {
1 -> {
emulatorName = "x16emu"
emulator = "x16emu"
extraArgs = emptyList()
}
2 -> {
emulatorName = "box16"
emulator = "box16"
extraArgs = listOf("-sym", viceMonListName(programNameWithPath.toString()))
}
else -> {
@@ -49,19 +47,11 @@ class CX16MachineDefinition: IMachineDefinition {
}
}
for(emulator in listOf(emulatorName)) {
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
@@ -98,14 +98,14 @@ X = BinExpr X = LeftExpr
// we can see if we can unwrap the binary expression by working on a new temporary variable
// (that has the type of the expression), and then finally doing the typecast.
// Once it's outside the typecast, the regular splitting can commence.
val tempVar = program.getTempVar(origExpr.inferType(program).getOr(DataType.UNDEFINED))
val (tempVarName, _) = program.getTempVar(origExpr.inferType(program).getOr(DataType.UNDEFINED))
val assignTempVar = Assignment(
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
AssignTarget(IdentifierReference(tempVarName, typecast.position), null, null, typecast.position),
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
)
return listOf(
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVar, typecast.position), typecast)
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVarName, typecast.position), typecast)
)
}
}
@@ -427,7 +427,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
{
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
// todo: this implements only a small set of possible reorderings at this time, we could think of more
// TODO: this implements only a small set of possible reorderings at this time, we could think of more
if(expr.operator==subExpr.operator) {
// both operators are the same.
@@ -9,6 +9,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.InternalCompilerException
// Fix up the literal value's type to match that of the vardecl
// (also check range literal operands types before they get expanded into arrays for instance)
@@ -97,7 +98,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
identifier.parent
)
)
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
in PassByReferenceDatatypes -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> noModifications
}
} catch (x: UndefinedSymbolError) {
@@ -1,8 +1,6 @@
package prog8.optimizer
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.IntegerDatatypes
@@ -16,13 +14,7 @@ import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
/*
todo add more peephole expression optimizations
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*/
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
@@ -339,40 +331,26 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return noModifications
}
override fun after(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> {
val expressions = pipeExpr.expressions
val firstValue = expressions.first()
if(firstValue.isSimple) {
val funcname = expressions[1] as IdentifierReference
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
val newExprs = mutableListOf<Expression>(first)
newExprs.addAll(expressions.drop(2))
return listOf(IAstModification.ReplaceNode(pipeExpr, PipeExpression(newExprs, pipeExpr.position), parent))
}
val singleExpr = expressions.singleOrNull()
if(singleExpr!=null) {
val callExpr = singleExpr as FunctionCallExpression
val call = FunctionCallExpression(callExpr.target, callExpr.args, callExpr.position)
return listOf(IAstModification.ReplaceNode(pipeExpr, call, parent))
}
return noModifications
}
override fun after(pipeExpr: PipeExpression, parent: Node) = processPipe(pipeExpr, parent)
override fun after(pipe: Pipe, parent: Node) = processPipe(pipe, parent)
override fun after(pipe: Pipe, parent: Node): Iterable<IAstModification> {
val expressions = pipe.expressions
val firstValue = expressions.first()
if(firstValue.isSimple) {
val funcname = expressions[1] as IdentifierReference
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
val newExprs = mutableListOf<Expression>(first)
newExprs.addAll(expressions.drop(2))
return listOf(IAstModification.ReplaceNode(pipe, Pipe(newExprs, pipe.position), parent))
}
val singleExpr = expressions.singleOrNull()
if(singleExpr!=null) {
val callExpr = singleExpr as FunctionCallExpression
val call = FunctionCallStatement(callExpr.target, callExpr.args, true, callExpr.position)
return listOf(IAstModification.ReplaceNode(pipe, call, parent))
private fun processPipe(pipe: IPipe, parent: Node): Iterable<IAstModification> {
if(pipe.source.isSimple) {
val segments = pipe.segments
if(segments.size==1) {
// replace the whole pipe with a normal function call
val funcname = (segments[0] as IFunctionCall).target
val call = if(pipe is Pipe)
FunctionCallStatement(funcname, mutableListOf(pipe.source), true, pipe.position)
else
FunctionCallExpression(funcname, mutableListOf(pipe.source), pipe.position)
return listOf(IAstModification.ReplaceNode(pipe as Node, call, parent))
} else if(segments.size>1) {
// replace source+firstsegment by firstsegment(source) call as the new source
val firstSegment = segments.removeAt(0) as IFunctionCall
val call = FunctionCallExpression(firstSegment.target, mutableListOf(pipe.source), pipe.position)
return listOf(IAstModification.ReplaceNode(pipe.source, call, pipe as Node))
}
}
return noModifications
}
@@ -2,9 +2,6 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.InferredTypes
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
@@ -68,15 +65,3 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
opti.visit(this)
return opti.applyModifications()
}
fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
return when {
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
dt istype DataType.BYTE -> listOf("cx16", "r9sL")
dt istype DataType.UWORD -> listOf("cx16", "r9")
dt istype DataType.WORD -> listOf("cx16", "r9s")
dt.isPassByReference -> listOf("cx16", "r9")
else -> throw FatalAstException("invalid dt $dt")
}
}
@@ -61,7 +61,7 @@ class StatementOptimizer(private val program: Program,
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionPlaceholder) {
if(functionCallStatement.target.nameInSource.size==1) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
@@ -125,11 +125,11 @@ class StatementOptimizer(private val program: Program,
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
// see if we can optimize any complex arguments
// see if we can optimize any complex argument expressions to be just a simple variable
// TODO for now, only works for single-argument functions because we use just 1 temp var: R9
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
val arg = functionCallStatement.args[0]
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
if(!arg.isSimple && arg !is IFunctionCall) {
val name = getTempRegisterName(arg.inferType(program))
val tempvar = IdentifierReference(name, functionCallStatement.position)
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
@@ -474,7 +474,7 @@ class StatementOptimizer(private val program: Program,
val returnDt = subr.returntypes.single()
if (returnDt in IntegerDatatypes) {
// first assign to intermediary variable, then return that
val returnVarName = program.getTempVar(returnDt)
val (returnVarName, _) = program.getTempVar(returnDt)
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
val assign = Assignment(tgt, value, AssignmentOrigin.OPTIMIZER, returnStmt.position)
@@ -238,7 +238,8 @@ class UnusedCodeRemover(private val program: Program,
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is FunctionCallExpression -> { /* don't remove */ }
is PipeExpression,
is IFunctionCall -> { /* don't remove */ }
else -> {
if(assign1.value !is IFunctionCall)
linesToRemove.add(assign1)
+308
View File
@@ -0,0 +1,308 @@
; Prog8 definitions for the Atari800XL
; Including memory registers, I/O registers, Basic and Kernal subroutines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
atari {
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; ---- kernal routines ----
; TODO
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; TODO
%asm {{
sei
cld
clc
; TODO reset screen mode etc etc
clv
cli
rts
}}
}
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the Atari
}}
}
}
sys {
; ------- lowlevel system routines --------
const ubyte target = 8 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
; TODO
%asm {{
sei
jmp (atari.RESET_VEC)
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
; TODO
}
asmsub waitvsync() clobbers(A) {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
; TODO
%asm {{
nop
rts
}}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: only works for NON-OVERLAPPING memory regions!
; note: can't be inlined because is called from asm as well
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2 ; target in ZP
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
rts
_longcopy
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
ldy P8ZP_SCRATCH_B1
bne _copyshort
rts
}}
}
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r1
ldy cx16.r1+1
jmp prog8_lib.memset
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
; TODO
%asm {{
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
}
cx16 {
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the Atari as well but their location in memory is different
; TODO
&uword r0 = $1b00
&uword r1 = $1b02
&uword r2 = $1b04
&uword r3 = $1b06
&uword r4 = $1b08
&uword r5 = $1b0a
&uword r6 = $1b0c
&uword r7 = $1b0e
&uword r8 = $1b10
&uword r9 = $1b12
&uword r10 = $1b14
&uword r11 = $1b16
&uword r12 = $1b18
&uword r13 = $1b1a
&uword r14 = $1b1c
&uword r15 = $1b1e
&word r0s = $1b00
&word r1s = $1b02
&word r2s = $1b04
&word r3s = $1b06
&word r4s = $1b08
&word r5s = $1b0a
&word r6s = $1b0c
&word r7s = $1b0e
&word r8s = $1b10
&word r9s = $1b12
&word r10s = $1b14
&word r11s = $1b16
&word r12s = $1b18
&word r13s = $1b1a
&word r14s = $1b1c
&word r15s = $1b1e
&ubyte r0L = $1b00
&ubyte r1L = $1b02
&ubyte r2L = $1b04
&ubyte r3L = $1b06
&ubyte r4L = $1b08
&ubyte r5L = $1b0a
&ubyte r6L = $1b0c
&ubyte r7L = $1b0e
&ubyte r8L = $1b10
&ubyte r9L = $1b12
&ubyte r10L = $1b14
&ubyte r11L = $1b16
&ubyte r12L = $1b18
&ubyte r13L = $1b1a
&ubyte r14L = $1b1c
&ubyte r15L = $1b1e
&ubyte r0H = $1b01
&ubyte r1H = $1b03
&ubyte r2H = $1b05
&ubyte r3H = $1b07
&ubyte r4H = $1b09
&ubyte r5H = $1b0b
&ubyte r6H = $1b0d
&ubyte r7H = $1b0f
&ubyte r8H = $1b11
&ubyte r9H = $1b13
&ubyte r10H = $1b15
&ubyte r11H = $1b17
&ubyte r12H = $1b19
&ubyte r13H = $1b1b
&ubyte r14H = $1b1d
&ubyte r15H = $1b1f
&byte r0sL = $1b00
&byte r1sL = $1b02
&byte r2sL = $1b04
&byte r3sL = $1b06
&byte r4sL = $1b08
&byte r5sL = $1b0a
&byte r6sL = $1b0c
&byte r7sL = $1b0e
&byte r8sL = $1b10
&byte r9sL = $1b12
&byte r10sL = $1b14
&byte r11sL = $1b16
&byte r12sL = $1b18
&byte r13sL = $1b1a
&byte r14sL = $1b1c
&byte r15sL = $1b1e
&byte r0sH = $1b01
&byte r1sH = $1b03
&byte r2sH = $1b05
&byte r3sH = $1b07
&byte r4sH = $1b09
&byte r5sH = $1b0b
&byte r6sH = $1b0d
&byte r7sH = $1b0f
&byte r8sH = $1b11
&byte r9sH = $1b13
&byte r10sH = $1b15
&byte r11sH = $1b17
&byte r12sH = $1b19
&byte r13sH = $1b1b
&byte r14sH = $1b1d
&byte r15sH = $1b1f
}
+419
View File
@@ -0,0 +1,419 @@
; Prog8 definitions for the Text I/O and Screen routines for the Atari 800XL
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
%import syslib
%import conv
txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 24
sub clear_screen() {
txt.chrout(125)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
; TODO
%asm {{
rts
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
; TODO
%asm {{
rts
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
; TODO
%asm {{
rts
}}
}
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address)
; TODO
%asm {{
rts
}}
}
sub color (ubyte txtcol) {
; TODO
}
sub lowercase() {
; TODO
}
sub uppercase() {
; TODO
}
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
; TODO
%asm {{
rts
}}
}
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
; TODO
%asm {{
rts
}}
}
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
; TODO
%asm {{
rts
}}
}
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
; TODO
%asm {{
rts
}}
}
romsub $F2B0 = outchar(ubyte char @ A)
romsub $F2Fd = waitkey()
asmsub chrout(ubyte char @ A) {
%asm {{
sta _tmp_outchar+1
pha
txa
pha
tya
pha
_tmp_outchar
lda #0
jsr outchar
pla
tay
pla
tax
pla
rts
}}
}
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr chrout
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
pha
tya
jsr chrout
pla
jsr chrout
txa
jsr chrout
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr chrout
pla
jsr chrout
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr chrout
_ones txa
jsr chrout
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_b (byte value @ A) clobbers(A,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
pha
cmp #0
bpl +
lda #'-'
jsr chrout
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
stx P8ZP_SCRATCH_REG
bcc +
pha
lda #'$'
jsr chrout
pla
+ jsr conv.ubyte2hex
jsr chrout
tya
jsr chrout
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr chrout
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr chrout
dey
bne -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr chrout
iny
bne -
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldx P8ZP_SCRATCH_REG
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr chrout
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp chrout
}}
}
asmsub print_w (word value @ AY) clobbers(A,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr chrout
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
; TODO
%asm {{
ldy #0
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
; TODO
%asm {{
rts
}}
}
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
; TODO
%asm {{
rts
}}
}
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
; ---- set the color in A on the screen matrix at the given position
; TODO
%asm {{
rts
}}
}
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
; TODO
%asm {{
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; TODO
%asm {{
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- set cursor at specific position
; TODO
%asm {{
rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
; TODO
%asm {{
lda #0
rts
}}
}
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
; TODO
%asm {{
lda #0
rts
}}
}
}
+1
View File
@@ -555,6 +555,7 @@ sys {
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: only works for NON-OVERLAPPING memory regions!
; note: can't be inlined because is called from asm as well
%asm {{
ldx cx16.r0
+1
View File
@@ -520,6 +520,7 @@ sys {
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: only works for NON-OVERLAPPING memory regions!
; note: can't be inlined because is called from asm as well
%asm {{
ldx cx16.r0
+54 -6
View File
@@ -822,17 +822,65 @@ sys {
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: can't be inlined because is called from asm as well
; note: only works for NON-OVERLAPPING memory regions!
; If you have to copy overlapping memory regions, consider using
; the cx16 specific kernal routine `memory_copy` (make sure kernal rom is banked in).
; note: can't be inlined because is called from asm as well.
; also: doesn't use cx16 ROM routine so this always works even when ROM is not banked in.
%asm {{
sta cx16.r2
sty cx16.r2+1
jmp cx16.memory_copy
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda cx16.r0
bne +
dec cx16.r0+1
+ dec cx16.r0
lda cx16.r1
bne +
dec cx16.r1+1
+ dec cx16.r1
- lda (cx16.r0),y
sta (cx16.r1),y
dey
bne -
rts
_longcopy
pha ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (cx16.r0),y
sta (cx16.r1),y
iny
bne -
inc cx16.r0+1
inc cx16.r1+1
dex
bne -
ply
bne _copyshort
rts
}}
}
inline asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
jsr cx16.memory_fill
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r1
ldy cx16.r1+1
jmp prog8_lib.memset
}}
}
+1 -1
View File
@@ -1 +1 @@
7.8
7.9
+15 -7
View File
@@ -2,12 +2,14 @@ package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.codegen.target.AtariTarget
import prog8.codegen.target.C128Target
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compilerinterface.CbmPrgLauncherType
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
@@ -17,7 +19,7 @@ import kotlin.system.exitProcess
fun main(args: Array<String>) {
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
val buildVersion = object {}.javaClass.getResource("/version.txt")?.readText()?.trim()
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")
@@ -44,7 +46,7 @@ private fun compileMain(args: Array<String>): Boolean {
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental codegen")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}')").default(C64Target.NAME)
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}')").default(C64Target.NAME)
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
@@ -71,7 +73,7 @@ private fun compileMain(args: Array<String>): Boolean {
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME)) {
if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME, AtariTarget.NAME)) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
@@ -166,10 +168,16 @@ private fun compileMain(args: Array<String>): Boolean {
val programNameInPath = outputPath.resolve(compilationResult.programName)
if (startEmulator1==true)
compilationResult.compTarget.machine.launchEmulator(1, programNameInPath)
else if (startEmulator2==true)
compilationResult.compTarget.machine.launchEmulator(2, programNameInPath)
if(startEmulator1==true || startEmulator2==true) {
if (compilationResult.compilationOptions.launcher != CbmPrgLauncherType.NONE || compilationTarget=="atari") {
if (startEmulator1 == true)
compilationResult.compilationOptions.compTarget.machine.launchEmulator(1, programNameInPath)
else if (startEmulator2 == true)
compilationResult.compilationOptions.compTarget.machine.launchEmulator(2, programNameInPath)
} else {
println("\nCan't start emulator because program has no launcher type.")
}
}
}
}
+39 -25
View File
@@ -12,6 +12,7 @@ import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive
import prog8.ast.statements.VarDecl
import prog8.ast.walk.IAstVisitor
import prog8.codegen.target.AtariTarget
import prog8.codegen.target.C128Target
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
@@ -29,7 +30,7 @@ import kotlin.system.measureTimeMillis
class CompilationResult(val success: Boolean,
val program: Program,
val programName: String,
val compTarget: ICompilationTarget,
val compilationOptions: CompilationOptions,
val importedFiles: List<Path>)
class CompilerArguments(val filepath: Path,
@@ -59,13 +60,17 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
AtariTarget.NAME -> AtariTarget()
else -> throw IllegalArgumentException("invalid compilation target")
}
var compilationOptions: CompilationOptions
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
val (programresult, options, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
compilationOptions = options
print("Parsed ${args.filepath}")
ModuleImporter.ansiEraseRestOfLine(true)
@@ -104,7 +109,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> {
System.err.println(result.error)
return CompilationResult(false, program, programName, compTarget, importedFiles)
return CompilationResult(false, program, programName, compilationOptions, importedFiles)
}
}
}
@@ -113,7 +118,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
System.err.flush()
val seconds = totalTime/1000.0
println("\nTotal compilation+assemble time: ${round(seconds*100.0)/100.0} sec.")
return CompilationResult(true, program, programName, compTarget, importedFiles)
return CompilationResult(true, program, programName, compilationOptions, importedFiles)
} catch (px: ParseError) {
System.err.print("\n\u001b[91m") // bright red
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
@@ -147,7 +152,13 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
}
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
val dummyoptions = CompilationOptions(
OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(),
floats = false,
noSysInit = true,
compTarget = compTarget
)
return CompilationResult(false, failedProgram, programName, dummyoptions, emptyList())
}
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
@@ -171,8 +182,6 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
null
}
}
else if(func.known_returntype==null)
return null // builtin function $name can't be used here because it doesn't return a value
}
return null
}
@@ -184,7 +193,7 @@ fun parseImports(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${compTarget.name}")
println("Compilation target: ${compTarget.name}")
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
bf.program = program
@@ -206,8 +215,11 @@ fun parseImports(filepath: Path,
importer.importLibraryModule("math")
importer.importLibraryModule("prog8_lib")
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
if(compilerOptions.launcher == CbmPrgLauncherType.BASIC && compTarget.name==AtariTarget.NAME)
errors.err("atari target cannot use CBM BASIC launcher, use NONE", program.toplevelModule.position)
errors.report()
return Triple(program, compilerOptions, importedFiles)
@@ -249,7 +261,12 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
.map { it[0].int!!..it[1].int!! }
.toList()
val outputType = if (outputTypeStr == null) OutputType.PRG else {
val outputType = if (outputTypeStr == null) {
if(compTarget is AtariTarget)
OutputType.XEX
else
OutputType.PRG
} else {
try {
OutputType.valueOf(outputTypeStr)
} catch (x: IllegalArgumentException) {
@@ -257,12 +274,17 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
OutputType.PRG
}
}
val launcherType = if (launcherTypeStr == null) LauncherType.BASIC else {
val launcherType = if (launcherTypeStr == null) {
when(compTarget) {
is AtariTarget -> CbmPrgLauncherType.NONE
else -> CbmPrgLauncherType.BASIC
}
} else {
try {
LauncherType.valueOf(launcherTypeStr)
CbmPrgLauncherType.valueOf(launcherTypeStr)
} catch (x: IllegalArgumentException) {
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
LauncherType.BASIC
CbmPrgLauncherType.BASIC
}
}
@@ -275,16 +297,12 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
}
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Analyzing code...")
program.preprocessAst(errors)
program.preprocessAst(errors, compilerOptions.compTarget)
program.checkIdentifiers(errors, compilerOptions)
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
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
// By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
// Yes this is different from strings that are only encoded in the code gen phase.
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
errors.report()
program.constantFold(errors, compilerOptions.compTarget)
errors.report()
program.desugaring(errors)
@@ -302,13 +320,10 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
}
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
// optimize the parse tree
println("Optimizing...")
val remover = UnusedCodeRemover(program, errors, compTarget)
remover.visit(program)
remover.applyModifications()
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions(errors)
@@ -329,8 +344,8 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
program.variousCleanups(errors, compilerOptions)
val callGraph = CallGraph(program)
callGraph.checkRecursiveCalls(errors)
program.verifyFunctionArgTypes(errors)
errors.report()
program.verifyFunctionArgTypes()
program.moveMainAndStartToFirst()
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
@@ -345,7 +360,6 @@ private fun writeAssembly(program: Program,
errors: IErrorReporter,
compilerOptions: CompilationOptions
): WriteAssemblyResult {
// asm generation directly from the Ast
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val variables = VariableExtractor().extractVars(program)
program.processAstBeforeAsmGeneration(compilerOptions, variables, errors)
+3 -4
View File
@@ -29,10 +29,10 @@ internal class ErrorReporter: IErrorReporter {
MessageSeverity.ERROR -> System.err
}
when(it.severity) {
MessageSeverity.ERROR -> printer.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> printer.print("\u001b[93m") // bright yellow
MessageSeverity.ERROR -> printer.print("\u001b[91mERROR\u001B[0m ") // bright red
MessageSeverity.WARNING -> printer.print("\u001b[93mWARN\u001B[0m ") // bright yellow
}
val msg = "${it.severity} ${it.position.toClickableStr()} ${it.message}".trim()
val msg = "${it.position.toClickableStr()} ${it.message}".trim()
if(msg !in alreadyReportedMessages) {
printer.println(msg)
alreadyReportedMessages.add(msg)
@@ -41,7 +41,6 @@ internal class ErrorReporter: IErrorReporter {
MessageSeverity.ERROR -> numErrors++
}
}
printer.print("\u001b[0m") // reset color
}
System.out.flush()
System.err.flush()
@@ -546,7 +546,7 @@ internal class AstChecker(private val program: Program,
return
}
if(decl.value is RangeExpression)
throw FatalAstException("range expressions in vardecls should have been converted into array values during constFolding $decl")
throw InternalCompilerException("range expressions in vardecls should have been converted into array values during constFolding $decl")
}
when(decl.type) {
@@ -555,7 +555,7 @@ internal class AstChecker(private val program: Program,
null -> {
// a vardecl without an initial value, don't bother with it
}
is RangeExpression -> throw FatalAstException("range expression should have been converted to a true array value")
is RangeExpression -> throw InternalCompilerException("range expression should have been converted to a true array value")
is StringLiteral -> {
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteral)
}
@@ -963,7 +963,7 @@ internal class AstChecker(private val program: Program,
val error = VerifyFunctionArgTypes.checkTypes(functionCallExpr, program)
if(error!=null)
errors.err(error, functionCallExpr.position)
errors.err(error.first, error.second)
// check the functions that return multiple returnvalues.
val stmt = functionCallExpr.target.targetStatement(program)
@@ -992,7 +992,14 @@ internal class AstChecker(private val program: Program,
}
}
else if(targetStatement is BuiltinFunctionPlaceholder) {
if(builtinFunctionReturnType(targetStatement.name, functionCallExpr.args, program).isUnknown) {
val args = if(functionCallExpr.parent is IPipe) {
// pipe segment, add implicit first argument
val firstArgDt = BuiltinFunctions.getValue(targetStatement.name).parameters.first().possibleDatatypes.first()
listOf(defaultZero(firstArgDt, functionCallExpr.position)) + functionCallExpr.args
} else {
functionCallExpr.args
}
if(builtinFunctionReturnType(targetStatement.name, args, program).isUnknown) {
if(functionCallExpr.parent is Expression || functionCallExpr.parent is Assignment)
errors.err("function doesn't return a value", functionCallExpr.position)
}
@@ -1043,9 +1050,8 @@ internal class AstChecker(private val program: Program,
}
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
if(error!=null) {
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
}
if(error!=null)
errors.err(error.first, error.second)
super.visit(functionCallStatement)
}
@@ -1112,6 +1118,19 @@ internal class AstChecker(private val program: Program,
}
}
override fun visit(pipe: PipeExpression) = process(pipe)
override fun visit(pipe: Pipe) = process(pipe)
private fun process(pipe: IPipe) {
if(pipe.source in pipe.segments)
throw InternalCompilerException("pipe source and segments should all be different nodes")
if (pipe.segments.isEmpty())
throw FatalAstException("pipe is missing one or more expressions")
if(pipe.segments.any { it !is IFunctionCall })
throw FatalAstException("pipe segments can only be function calls")
}
override fun visit(postIncrDecr: PostIncrDecr) {
if(postIncrDecr.target.identifier != null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource
@@ -1221,6 +1240,10 @@ internal class AstChecker(private val program: Program,
if(containment.parent is BinaryExpression)
errors.err("containment check is currently not supported in complex expressions", containment.position)
val range = containment.iterable as? RangeExpression
if(range!=null && range.toConstantIntegerRange()==null)
errors.err("containment check requires a constant integer range", range.position)
if(iterableDt.isIterable) {
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
val invalidDt = if (elementDt.isBytes) {
@@ -1239,88 +1262,6 @@ internal class AstChecker(private val program: Program,
super.visit(containment)
}
override fun visit(pipe: PipeExpression) {
processPipe(pipe.expressions, pipe)
val last = pipe.expressions.last() as IdentifierReference
val target = last.targetStatement(program)!!
when(target) {
is BuiltinFunctionPlaceholder -> {
if(BuiltinFunctions.getValue(target.name).known_returntype==null)
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
}
is Subroutine -> {
if(target.returntypes.isEmpty())
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
else if(target.returntypes.size!=1)
errors.err("invalid pipe expression; last term doesn't return a single value", last.position)
}
else -> errors.err("invalid pipe expression; last term doesn't return a value", last.position)
}
super.visit(pipe)
}
override fun visit(pipe: Pipe) {
processPipe(pipe.expressions, pipe)
super.visit(pipe)
}
private fun processPipe(expressions: List<Expression>, scope: Node) {
// first expression is just any expression producing a value
// all other expressions should be the name of a unary function that returns a single value
// the last expression should be the name of a unary function whose return value we don't care about.
if (expressions.size < 2) {
errors.err("pipe is missing one or more expressions", scope.position)
} else {
// invalid size and other issues will be handled by the ast checker later.
var valueDt = expressions[0].inferType(program).getOrElse {
throw FatalAstException("invalid dt ${expressions[0]} @ ${scope.position}")
}
for(expr in expressions.drop(1)) { // just keep the first expression value as-is
val functionName = expr as? IdentifierReference
val function = functionName?.targetStatement(program)
if(functionName!=null && function!=null) {
when (function) {
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(function.name)
if(func.parameters.size!=1)
errors.err("can only use unary function", expr.position)
else if(func.known_returntype==null && expr !== expressions.last())
errors.err("function must return a single value", expr.position)
val paramDts = func.parameters.firstOrNull()?.possibleDatatypes
if(paramDts!=null && !paramDts.any { valueDt isAssignableTo it })
errors.err("pipe value datatype $valueDt incompatible withfunction argument ${paramDts.toList()}", functionName.position)
valueDt = func.known_returntype!!
}
is Subroutine -> {
if(function.parameters.size!=1)
errors.err("can only use unary function", expr.position)
else if(function.returntypes.size!=1 && expr !== expressions.last())
errors.err("function must return a single value", expr.position)
val paramDt = function.parameters.firstOrNull()?.type
if(paramDt!=null && !(valueDt isAssignableTo paramDt))
errors.err("pipe value datatype $valueDt incompatible with function argument $paramDt", functionName.position)
if(function.returntypes.isNotEmpty())
valueDt = function.returntypes.single()
}
else -> {
throw FatalAstException("weird function")
}
}
} else {
if(expr is IFunctionCall)
errors.err("use only the name of the function, not a call", expr.position)
else
errors.err("can only use unary function", expr.position)
}
}
}
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
when (val targetStatement = target.targetStatement(program)) {
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
@@ -10,10 +10,7 @@ import prog8.ast.statements.Directive
import prog8.ast.statements.VarDeclOrigin
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.IStringEncoding
import prog8.compilerinterface.IVariablesAndConsts
import prog8.compilerinterface.*
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
@@ -48,12 +45,17 @@ internal fun Program.reorderStatements(errors: IErrorReporter, options: Compilat
}
}
internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, errors: IErrorReporter) {
val walker = object : AstWalker() {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
require(char.encoding != Encoding.DEFAULT)
if(char.encoding != Encoding.DEFAULT && char.encoding !in target.supportedEncodings) {
errors.err("compilation target doesn't support this text encoding", char.position)
return noModifications
}
return listOf(IAstModification.ReplaceNode(
char,
NumericLiteral(DataType.UBYTE, enc.encodeString(char.value.toString(), char.encoding)[0].toDouble(), char.position),
NumericLiteral(DataType.UBYTE, target.encodeString(char.value.toString(), char.encoding)[0].toDouble(), char.position),
parent
))
}
@@ -74,13 +76,13 @@ fun Program.desugaring(errors: IErrorReporter): Int {
return desugar.applyModifications()
}
internal fun Program.verifyFunctionArgTypes() {
val fixer = VerifyFunctionArgTypes(this)
internal fun Program.verifyFunctionArgTypes(errors: IErrorReporter) {
val fixer = VerifyFunctionArgTypes(this, errors)
fixer.visit(this)
}
internal fun Program.preprocessAst(errors: IErrorReporter) {
val transforms = AstPreprocessor(this, errors)
internal fun Program.preprocessAst(errors: IErrorReporter, target: ICompilationTarget) {
val transforms = AstPreprocessor(this, errors, target)
transforms.visit(this)
var mods = transforms.applyModifications()
while(mods>0)
@@ -96,7 +98,7 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this)
val lit2decl = LiteralsToAutoVars(this, options.compTarget, errors)
lit2decl.visit(this)
if(errors.noErrors())
lit2decl.applyModifications()
@@ -1,6 +1,7 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IPipe
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.Position
@@ -9,7 +10,6 @@ import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.Encoding
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
@@ -108,7 +108,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", label.position)
} else {
val existing = label.definingSubroutine?.getAllLabels(label.name) ?: emptyList()
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)
for(el in existing) {
if(el === label || el.name != label.name)
continue
@@ -133,18 +133,32 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
private fun visitFunctionCall(call: IFunctionCall) {
val isPartOfPipeSegments = (call.parent as? IPipe)?.segments?.contains(call as Node) == true
val errormessageAboutArgs = if(isPartOfPipeSegments) "invalid number of arguments in piped call" else "invalid number of arguments"
when (val target = call.target.targetStatement(program)) {
is Subroutine -> {
if(call.args.size != target.parameters.size) {
// if the call is part of a Pipe, the number of arguments in the call should be 1 less than the number of parameters
val expectedNumberOfArgs: Int = if(isPartOfPipeSegments) {
target.parameters.size - 1
} else {
target.parameters.size
}
if(call.args.size != expectedNumberOfArgs) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err("invalid number of arguments", pos)
errors.err(errormessageAboutArgs, pos)
}
}
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
if(call.args.size != func.parameters.size) {
// if the call is part of a Pipe, the number of arguments in the call should be 1 less than the number of parameters
val expectedNumberOfArgs: Int = if(isPartOfPipeSegments) {
func.parameters.size-1
} else {
func.parameters.size
}
if(call.args.size != expectedNumberOfArgs) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err("invalid number of arguments", pos)
errors.err(errormessageAboutArgs, pos)
}
if(func.name=="memory") {
val name = call.args[0] as? StringLiteral
@@ -155,7 +169,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
else
'_'
}.joinToString("")
call.args[0] = StringLiteral(processed, Encoding.PETSCII, name.position)
call.args[0] = StringLiteral(processed, compTarget.defaultEncoding, name.position)
call.args[0].linkParents(call as Node)
}
}
@@ -1,18 +1,33 @@
package prog8.compiler.astprocessing
import prog8.ast.IPipe
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.SyntaxError
import prog8.ast.base.VarDeclType
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.getTempRegisterName
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.Encoding
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.InternalCompilerException
class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWalker() {
class AstPreprocessor(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() {
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
if(char.encoding==Encoding.DEFAULT)
char.encoding = compTarget.defaultEncoding
return noModifications
}
override fun before(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.encoding==Encoding.DEFAULT)
string.encoding = compTarget.defaultEncoding
return super.before(string, parent)
}
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
@@ -47,7 +62,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
return modifications
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
// move vardecls in Anonymous scope up to the containing subroutine
// and add initialization assignment in its place if needed
@@ -99,4 +114,68 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
}
return noModifications
}
override fun before(pipe: Pipe, parent: Node): Iterable<IAstModification> {
if(pipe.source is PipeExpression) {
// correct Antlr parse tree quirk: turn nested pipe into single flat pipe
val psrc = pipe.source as PipeExpression
val newSource = psrc.source
val newSegments = psrc.segments
newSegments += pipe.segments.single()
return listOf(IAstModification.ReplaceNode(pipe as Node, Pipe(newSource, newSegments, pipe.position), parent))
}
return process(pipe, parent)
}
override fun before(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> {
if(pipeExpr.source is PipeExpression) {
// correct Antlr parse tree quirk; turn nested pipe into single flat pipe
val psrc = pipeExpr.source as PipeExpression
val newSource = psrc.source
val newSegments = psrc.segments
newSegments += pipeExpr.segments.single()
return listOf(IAstModification.ReplaceNode(pipeExpr as Node, PipeExpression(newSource, newSegments, pipeExpr.position), parent))
}
return process(pipeExpr, parent)
}
private fun process(pipe: IPipe, parent: Node): Iterable<IAstModification> {
if(pipe.source is IPipe)
throw InternalCompilerException("pipe source should have been adjusted to be a normal expression")
return noModifications
// TODO don't use artifical inserted args, fix the places that check for arg numbers instead.
// add the "missing" first argument to each function call in the pipe segments
// so that all function call related checks just pass
// might have to remove it again when entering code generation pass, or just replace it there
// with the proper output value of the previous pipe segment.
// val mutations = mutableListOf<IAstModification>()
// var valueDt = pipe.source.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
// pipe.segments.forEach { call->
// val dummyFirstArg = when (valueDt) {
// DataType.UBYTE -> FunctionCallExpression(IdentifierReference(listOf("rnd"), pipe.position), mutableListOf(), pipe.position)
// DataType.UWORD -> FunctionCallExpression(IdentifierReference(listOf("rndw"), pipe.position), mutableListOf(), pipe.position)
// DataType.BYTE, DataType.WORD -> IdentifierReference(
// getTempRegisterName(InferredTypes.InferredType.known(valueDt)),
// pipe.position
// ) // there's no builtin function we can abuse that returns a signed byte or word type // TODO maybe use a typecasted expression around rnd?
// DataType.FLOAT -> FunctionCallExpression(IdentifierReference(listOf("rndf"), pipe.position), mutableListOf(), pipe.position)
// else -> throw FatalAstException("invalid dt")
// }
//
// mutations += IAstModification.SetExpression(
// { newexpr -> call.args.add(0, newexpr) },
// dummyFirstArg, parent
// )
//
// if(call!==pipe.segments.last())
// valueDt = call.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
// }
// return mutations
}
}
@@ -8,7 +8,6 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.*
import prog8.optimizer.getTempRegisterName
internal class BeforeAsmAstChanger(val program: Program,
private val options: CompilationOptions,
@@ -17,15 +16,15 @@ internal class BeforeAsmAstChanger(val program: Program,
) : AstWalker() {
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
throw FatalAstException("break should have been replaced by goto $breakStmt")
throw InternalCompilerException("break should have been replaced by goto $breakStmt")
}
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
throw FatalAstException("while should have been converted to jumps")
throw InternalCompilerException("while should have been converted to jumps")
}
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
throw FatalAstException("do..until should have been converted to jumps")
throw InternalCompilerException("do..until should have been converted to jumps")
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
@@ -52,7 +51,7 @@ internal class BeforeAsmAstChanger(val program: Program,
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(!options.dontReinitGlobals) {
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
}
return noModifications
@@ -119,7 +118,7 @@ internal class BeforeAsmAstChanger(val program: Program,
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
if (!subroutine.isAsmSubroutine && !subroutine.inline) {
if (!subroutine.isAsmSubroutine && (!subroutine.inline || !options.optimize)) {
if(subroutine.statements.isEmpty() ||
(subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
@@ -146,7 +145,7 @@ internal class BeforeAsmAstChanger(val program: Program,
}
}
if (!subroutine.inline) {
if (!subroutine.inline || !options.optimize) {
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && subroutine.amountOfRtsInAsm() == 0) {
// make sure the NOT INLINED asm subroutine actually has a rts at the end
// (non-asm routines get a Return statement as needed, above)
@@ -184,7 +183,7 @@ internal class BeforeAsmAstChanger(val program: Program,
if((binExpr.left as? NumericLiteral)?.number==0.0 &&
(binExpr.right as? NumericLiteral)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0")
throw InternalCompilerException("0==X should have been swapped to if X==0")
// simplify the conditional expression, introduce simple assignments if required.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
@@ -251,12 +250,11 @@ internal class BeforeAsmAstChanger(val program: Program,
)
}
if(separateRightExpr) {
val name = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
val tempvardecl = program.toplevelModule.lookup(name) as VarDecl
variables.addIfUnknown(tempvardecl.definingBlock, tempvardecl)
rightOperandReplacement = IdentifierReference(name, expr.position)
val (tempVarName, tempVarDecl) = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
variables.addIfUnknown(tempVarDecl.definingBlock, tempVarDecl)
rightOperandReplacement = IdentifierReference(tempVarName, expr.position)
rightAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
expr.right,
AssignmentOrigin.BEFOREASMGEN, expr.position
)
@@ -267,30 +265,6 @@ internal class BeforeAsmAstChanger(val program: Program,
)
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource==listOf("cmp")) {
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
val arg1 = functionCallStatement.args[0]
val arg2 = functionCallStatement.args[1]
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes)
return noModifications
val (replaced, cast) = arg1.typecastTo(if(dt1== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
} else {
if(dt2 in WordDatatypes)
return noModifications
val (replaced, cast) = arg2.typecastTo(if(dt2== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val containingStatement = getContainingStatement(arrayIndexedExpression)
@@ -349,10 +323,9 @@ internal class BeforeAsmAstChanger(val program: Program,
val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement
val dt = expr.indexer.indexExpr.inferType(program)
val tempvar = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
val tempvardecl = program.toplevelModule.lookup(tempvar) as VarDecl
variables.addIfUnknown(tempvardecl.definingBlock, tempvardecl)
val target = AssignTarget(IdentifierReference(tempvar, expr.indexer.position), null, null, expr.indexer.position)
val (tempVarName, tempVarDecl) = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
variables.addIfUnknown(tempVarDecl.definingBlock, tempVarDecl)
val target = AssignTarget(IdentifierReference(tempVarName, expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.BEFOREASMGEN, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(
@@ -7,10 +7,9 @@ import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionCallStatement
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.IErrorReporter
@@ -73,4 +72,59 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
return noModifications
}
// also convert calls to builtin functions to BuiltinFunctionCall nodes to make things easier for codegen
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1
&& functionCallStatement.target.nameInSource[0] in program.builtinFunctions.names) {
return listOf(IAstModification.ReplaceNode(
functionCallStatement,
BuiltinFunctionCallStatement(functionCallStatement.target, functionCallStatement.args, functionCallStatement.position),
parent
))
}
return noModifications
}
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
if(functionCallExpr.target.nameInSource.size==1
&& functionCallExpr.target.nameInSource[0] in program.builtinFunctions.names) {
return listOf(IAstModification.ReplaceNode(
functionCallExpr,
BuiltinFunctionCall(functionCallExpr.target, functionCallExpr.args, functionCallExpr.position),
parent
))
}
return noModifications
}
override fun after(bfcs: BuiltinFunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(bfcs.name=="cmp") {
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
val arg1 = bfcs.args[0]
val arg2 = bfcs.args[1]
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes)
return noModifications
val (replaced, cast) = arg1.typecastTo(if(dt1== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg1, cast, bfcs))
} else {
if(dt2 in WordDatatypes)
return noModifications
val (replaced, cast) = arg2.typecastTo(if(dt2== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg2, cast, bfcs))
}
}
return noModifications
}
}
@@ -12,11 +12,20 @@ import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.Encoding
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
internal class LiteralsToAutoVars(private val program: Program,
private val target: ICompilationTarget,
private val errors: IErrorReporter) : AstWalker() {
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.encoding != Encoding.DEFAULT && string.encoding !in target.supportedEncodings) {
errors.err("compilation target doesn't support this text encoding", string.position)
return noModifications
}
if(string.parent !is VarDecl
&& string.parent !is WhenChoice
&& (string.parent !is ContainmentCheck || string.value.length>ContainmentCheck.max_inlined_string_length)) {
@@ -8,6 +8,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal class StatementReorderer(val program: Program,
@@ -409,52 +410,52 @@ internal class StatementReorderer(val program: Program,
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)!!
checkUnusedReturnValues(functionCallStatement, function, program, errors)
return replaceCallByGosub(functionCallStatement, parent, program, options)
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
}
}
internal fun replaceCallByGosub(functionCallStatement: FunctionCallStatement,
parent: Node,
program: Program,
options: CompilationOptions): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)!!
if(function is Subroutine) {
if(function.inline)
internal fun tryReplaceCallWithGosub(functionCallStatement: FunctionCallStatement,
parent: Node,
program: Program,
options: CompilationOptions): Iterable<IAstModification> {
val callee = functionCallStatement.target.targetStatement(program)!!
if(callee is Subroutine) {
if(callee.inline)
return emptyList()
return if(function.isAsmSubroutine)
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent, options)
return if(callee.isAsmSubroutine)
tryReplaceCallAsmSubWithGosub(functionCallStatement, parent, callee, options.compTarget)
else
replaceCallSubStatementWithGosub(function, functionCallStatement, parent, program)
tryReplaceCallNormalSubWithGosub(functionCallStatement, parent, callee, program)
}
return emptyList()
}
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node, program: Program): Iterable<IAstModification> {
private fun tryReplaceCallNormalSubWithGosub(call: FunctionCallStatement, parent: Node, callee: Subroutine, program: Program): Iterable<IAstModification> {
val noModifications = emptyList<IAstModification>()
if(function.parameters.isEmpty()) {
if(callee.parameters.isEmpty()) {
// 0 params -> just GoSub
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
}
if(function.parameters.size==1) {
if(function.parameters[0].type in IntegerDatatypes) {
if(callee.parameters.size==1) {
if(callee.parameters[0].type in IntegerDatatypes) {
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
return noModifications
}
}
else if(function.parameters.size==2) {
if(function.parameters[0].type in ByteDatatypes && function.parameters[1].type in ByteDatatypes) {
else if(callee.parameters.size==2) {
if(callee.parameters[0].type in ByteDatatypes && callee.parameters[1].type in ByteDatatypes) {
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
return noModifications
}
}
val assignParams =
function.parameters.zip(call.args).map {
callee.parameters.zip(call.args).map {
var argumentValue = it.second
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
val paramIdentifier = IdentifierReference(callee.scopedName + it.first.name, argumentValue.position)
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
if(argDt in ArrayDatatypes) {
// pass the address of the array instead
@@ -468,71 +469,83 @@ private fun replaceCallSubStatementWithGosub(function: Subroutine, call: Functio
return listOf(IAstModification.ReplaceNode(call, scope, parent))
}
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node, options: CompilationOptions): Iterable<IAstModification> {
private fun tryReplaceCallAsmSubWithGosub(call: FunctionCallStatement,
parent: Node,
callee: Subroutine,
compTarget: ICompilationTarget): Iterable<IAstModification> {
val noModifications = emptyList<IAstModification>()
if(function.parameters.isEmpty()) {
if(callee.parameters.isEmpty()) {
// 0 params -> just GoSub
val scope = AnonymousScope(mutableListOf(), call.position)
if(function.shouldSaveX()) {
if(callee.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
}
scope.statements += GoSub(null, call.target, null, call.position)
if(function.shouldSaveX()) {
if(callee.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
}
return listOf(IAstModification.ReplaceNode(call, scope, parent))
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
} else if(!compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, callee.asmParameterRegisters)) {
// No register clobber risk, let the asmgen assign values to the registers directly.
// this is more efficient than first evaluating them to the stack.
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
return noModifications
} else {
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
val scope = AnonymousScope(mutableListOf(), call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
}
argOrder.reversed().forEach {
val arg = call.args[it]
val param = function.parameters[it]
scope.statements += pushCall(arg, param.type, arg.position)
}
// ... and pop them off again into the registers.
argOrder.forEach {
val param = function.parameters[it]
val targetName = function.scopedName + param.name
scope.statements += popCall(targetName, param.type, call.position)
}
scope.statements += GoSub(null, call.target, null, call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
}
return listOf(IAstModification.ReplaceNode(call, scope, parent))
return makeGosubWithArgsViaCpuStack(call, call.position, parent, callee, compTarget)
}
}
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
mutableListOf(IdentifierReference(targetName, position)),
true, position
)
}
private fun makeGosubWithArgsViaCpuStack(call: IFunctionCall,
position: Position,
parent: Node,
callee: Subroutine,
compTarget: ICompilationTarget): Iterable<IAstModification> {
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
val pushvalue = when(dt) {
DataType.UBYTE, DataType.UWORD -> value
in PassByReferenceDatatypes -> value
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
else -> throw FatalAstException("invalid dt $dt $value")
fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
mutableListOf(IdentifierReference(targetName, position)),
true, position
)
}
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
mutableListOf(pushvalue),
true, position
)
fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
val pushvalue = when(dt) {
DataType.UBYTE, DataType.UWORD -> value
in PassByReferenceDatatypes -> value
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
else -> throw FatalAstException("invalid dt $dt $value")
}
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
mutableListOf(pushvalue),
true, position
)
}
val argOrder = compTarget.asmsubArgsEvalOrder(callee)
val scope = AnonymousScope(mutableListOf(), position)
if(callee.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), position), mutableListOf(), true, position)
}
argOrder.reversed().forEach {
val arg = call.args[it]
val param = callee.parameters[it]
scope.statements += pushCall(arg, param.type, arg.position)
}
// ... and pop them off again into the registers.
argOrder.forEach {
val param = callee.parameters[it]
val targetName = callee.scopedName + param.name
scope.statements += popCall(targetName, param.type, position)
}
scope.statements += GoSub(null, call.target, null, position)
if(callee.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), position), mutableListOf(), true, position)
}
return listOf(IAstModification.ReplaceNode(call as Node, scope, parent))
}
@@ -164,31 +164,31 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
when(val sub = call.target.targetStatement(program)) {
is Subroutine -> {
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
sub.parameters.zip(call.args).forEach { (param, arg) ->
val argItype = arg.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.getOr(DataType.UNDEFINED)
val requiredType = pair.first.type
val requiredType = param.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
// don't need a cast for pass-by-reference types that are assigned to UWORD
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
addTypecastOrCastedValueModification(modifications, pair.second, requiredType, call as Node)
addTypecastOrCastedValueModification(modifications, arg, requiredType, call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference
val identifier = arg as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, pair.second.position),
AddressOf(identifier, arg.position),
call as Node)
}
} else if(pair.second is NumericLiteral) {
val cast = (pair.second as NumericLiteral).cast(requiredType)
} else if(arg is NumericLiteral) {
val cast = arg.cast(requiredType)
if(cast.isValid)
modifications += IAstModification.ReplaceNode(
pair.second,
arg,
cast.valueOrZero(),
call as Node)
}
@@ -200,7 +200,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return replaceCallByGosub(functionCallStatement, parent, program, options)
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
}
}
@@ -1,28 +1,35 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IPipe
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.base.defaultZero
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.PipeExpression
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.InternalCompilerException
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.builtinFunctionReturnType
internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorReporter) : IAstVisitor {
override fun visit(functionCallExpr: FunctionCallExpression) {
val error = checkTypes(functionCallExpr as IFunctionCall, program)
if(error!=null)
throw InternalCompilerException(error)
errors.err(error.first, error.second)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, program)
if (error!=null)
throw InternalCompilerException(error)
if(error!=null)
errors.err(error.first, error.second)
}
companion object {
@@ -41,22 +48,28 @@ internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
return false
}
fun checkTypes(call: IFunctionCall, program: Program): String? {
fun checkTypes(call: IFunctionCall, program: Program): Pair<String, Position>? {
val argITypes = call.args.map { it.inferType(program) }
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0)
return "argument ${firstUnknownDt+1} invalid argument type"
return Pair("argument ${firstUnknownDt+1} invalid argument type", call.args[firstUnknownDt].position)
val argtypes = argITypes.map { it.getOr(DataType.UNDEFINED) }
val target = call.target.targetStatement(program)
val isPartOfPipeSegments = (call.parent as? IPipe)?.segments?.contains(call as Node) == true
val errormessageAboutArgs = if(isPartOfPipeSegments) "invalid number of arguments in piped call" else "invalid number of arguments"
if (target is Subroutine) {
if(call.args.size != target.parameters.size)
return "invalid number of arguments"
val paramtypes = target.parameters.map { it.type }
val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
val consideredParamTypes: List<DataType> = if(isPartOfPipeSegments) {
target.parameters.drop(1).map { it.type } // skip first one (the implicit first arg), this is checked elsewhere
} else {
target.parameters.map { it.type }
}
if(argtypes.size != consideredParamTypes.size)
return Pair(errormessageAboutArgs, call.position)
val mismatch = argtypes.zip(consideredParamTypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
if(mismatch>=0) {
val actual = argtypes[mismatch].toString()
val expected = paramtypes[mismatch].toString()
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
val expected = consideredParamTypes[mismatch].toString()
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
}
if(target.isAsmSubroutine) {
if(target.asmReturnvaluesRegisters.size>1) {
@@ -70,7 +83,7 @@ internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
else
parent
if (checkParent !is Assignment && checkParent !is VarDecl) {
return "can't use subroutine call that returns multiple return values here"
return Pair("can't use subroutine call that returns multiple return values here", call.position)
}
}
}
@@ -78,19 +91,23 @@ internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
}
else if (target is BuiltinFunctionPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
if(call.args.size != func.parameters.size)
return "invalid number of arguments"
val paramtypes = func.parameters.map { it.possibleDatatypes }
argtypes.zip(paramtypes).forEachIndexed { index, pair ->
val consideredParamTypes: List<Array<DataType>> = if(isPartOfPipeSegments) {
func.parameters.drop(1).map { it.possibleDatatypes } // skip first one (the implicit first arg), this is checked elsewhere
} else {
func.parameters.map { it.possibleDatatypes }
}
if(argtypes.size != consideredParamTypes.size)
return Pair(errormessageAboutArgs, call.position)
argtypes.zip(consideredParamTypes).forEachIndexed { index, pair ->
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
if (!anyCompatible) {
val actual = pair.first.toString()
return if(pair.second.size==1) {
val expected = pair.second[0].toString()
"argument ${index + 1} type mismatch, was: $actual expected: $expected"
Pair("argument ${index + 1} type mismatch, was: $actual expected: $expected", call.args[index].position)
} else {
val expected = pair.second.toList().toString()
"argument ${index + 1} type mismatch, was: $actual expected one of: $expected"
Pair("argument ${index + 1} type mismatch, was: $actual expected one of: $expected", call.args[index].position)
}
}
}
@@ -99,4 +116,91 @@ internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
return null
}
}
override fun visit(pipe: PipeExpression) {
processPipe(pipe.source, pipe.segments, pipe)
if(errors.noErrors()) {
val last = (pipe.segments.last() as IFunctionCall).target
when (val target = last.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> {
if (!BuiltinFunctions.getValue(target.name).hasReturn)
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
}
is Subroutine -> {
if (target.returntypes.isEmpty())
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
else if (target.returntypes.size != 1)
errors.err("invalid pipe expression; last term doesn't return a single value", last.position)
}
else -> errors.err("invalid pipe expression; last term doesn't return a value", last.position)
}
super.visit(pipe)
}
}
override fun visit(pipe: Pipe) {
processPipe(pipe.source, pipe.segments, pipe)
if(errors.noErrors()) {
super.visit(pipe)
}
}
private fun processPipe(source: Expression, segments: List<Expression>, scope: Node) {
val sourceArg = (source as? IFunctionCall)?.args?.firstOrNull()
if(sourceArg!=null && segments.any { (it as IFunctionCall).args.firstOrNull() === sourceArg})
throw FatalAstException("some pipe segment first arg is replicated from the source functioncall arg")
// invalid size and other issues will be handled by the ast checker later.
var valueDt = source.inferType(program).getOrElse {
throw FatalAstException("invalid dt")
}
for(funccall in segments) {
val target = (funccall as IFunctionCall).target.targetStatement(program)
if(target!=null) {
when (target) {
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
if(func.parameters.size!=1)
errors.err("can only use unary function", funccall.position)
else if(!func.hasReturn && funccall !== segments.last())
errors.err("function must return a single value", funccall.position)
val paramDts = func.parameters.firstOrNull()?.possibleDatatypes
if(paramDts!=null && !paramDts.any { valueDt isAssignableTo it })
errors.err("pipe value datatype $valueDt incompatible with function argument ${paramDts.toList()}", funccall.position)
if(errors.noErrors()) {
// type can depend on the argument(s) of the function. For now, we only deal with unary functions,
// so we know there must be a single argument. Take its type from the previous expression in the pipe chain.
val zero = defaultZero(valueDt, funccall.position)
valueDt = builtinFunctionReturnType(func.name, listOf(zero), program).getOrElse { DataType.UNDEFINED }
}
}
is Subroutine -> {
if(target.parameters.size!=1)
errors.err("can only use unary function", funccall.position)
else if(target.returntypes.size!=1 && funccall !== segments.last())
errors.err("function must return a single value", funccall.position)
val paramDt = target.parameters.firstOrNull()?.type
if(paramDt!=null && !(valueDt isAssignableTo paramDt))
errors.err("pipe value datatype $valueDt incompatible with function argument $paramDt", funccall.position)
if(target.returntypes.isNotEmpty())
valueDt = target.returntypes.single()
}
is VarDecl -> {
if(!(valueDt isAssignableTo target.datatype))
errors.err("final pipe value datatype can't be stored in pipe ending variable", funccall.position)
}
else -> {
throw FatalAstException("weird function")
}
}
}
}
}
}
+2 -2
View File
@@ -204,14 +204,14 @@ class TestModuleImporter: FunSpec({
val result = importer.importLibraryModule(filenameNoExt)
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0) no module found with name i_do_not_exist"
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
errors.report()
program.modules.size shouldBe 1
val result2 = importer.importLibraryModule(filenameWithExt)
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0) no module found with name i_do_not_exist.p8"
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
errors.report()
program.modules.size shouldBe 1
}
+2 -2
View File
@@ -53,8 +53,8 @@ class TestAstChecks: FunSpec({
compileText(C64Target(), true, text, writeAssembly = true, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.warnings.size shouldBe 0
errors.errors[0] shouldContain ":7:28) assignment value is invalid"
errors.errors[1] shouldContain ":8:28) assignment value is invalid"
errors.errors[0] shouldContain ":7:28: assignment value is invalid"
errors.errors[1] shouldContain ":8:28: assignment value is invalid"
}
test("can't do str or array expression without using address-of") {
+93
View File
@@ -0,0 +1,93 @@
package prog8tests
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.RegisterOrPair
import prog8.compilerinterface.BuiltinFunctions
class TestBuiltinFunctions: FunSpec({
test("pure func with fixed type") {
val func = BuiltinFunctions.getValue("sin8u")
func.name shouldBe "sin8u"
func.parameters.size shouldBe 1
func.parameters[0].name shouldBe "angle8"
func.parameters[0].possibleDatatypes shouldBe arrayOf(DataType.UBYTE)
func.pure shouldBe true
func.hasReturn shouldBe true
func.returnType shouldBe DataType.UBYTE
val conv = func.callConvention(listOf(DataType.UBYTE))
conv.params.size shouldBe 1
conv.params[0].dt shouldBe DataType.UBYTE
conv.params[0].reg shouldBe RegisterOrPair.A
conv.params[0].variable shouldBe false
conv.returns.dt shouldBe DataType.UBYTE
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe RegisterOrPair.A
}
test("not-pure func with fixed type") {
val func = BuiltinFunctions.getValue("rnd")
func.name shouldBe "rnd"
func.parameters.size shouldBe 0
func.pure shouldBe false
func.hasReturn shouldBe true
func.returnType shouldBe DataType.UBYTE
val conv = func.callConvention(emptyList())
conv.params.size shouldBe 0
conv.returns.dt shouldBe DataType.UBYTE
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe RegisterOrPair.A
}
test("func without return type") {
val func = BuiltinFunctions.getValue("poke")
func.name shouldBe "poke"
func.parameters.size shouldBe 2
func.parameters[0].name shouldBe "address"
func.parameters[0].possibleDatatypes shouldBe arrayOf(DataType.UWORD)
func.parameters[1].name shouldBe "value"
func.parameters[1].possibleDatatypes shouldBe arrayOf(DataType.UBYTE)
func.pure shouldBe false
func.hasReturn shouldBe false
func.returnType shouldBe null
val conv = func.callConvention(listOf(DataType.UWORD, DataType.UBYTE))
conv.params.size shouldBe 2
conv.params[0].dt shouldBe DataType.UWORD
conv.params[0].reg shouldBe null
conv.params[0].variable shouldBe true
conv.params[1].dt shouldBe DataType.UBYTE
conv.params[1].reg shouldBe null
conv.params[1].variable shouldBe true
conv.returns.dt shouldBe null
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe null
}
test("func with variable return type") {
val func = BuiltinFunctions.getValue("abs")
func.name shouldBe "abs"
func.parameters.size shouldBe 1
func.parameters[0].name shouldBe "value"
func.parameters[0].possibleDatatypes.toSet() shouldBe NumericDatatypes.toSet()
func.pure shouldBe true
func.hasReturn shouldBe true
func.returnType shouldBe null
val conv = func.callConvention(listOf(DataType.UWORD))
conv.params.size shouldBe 1
conv.params[0].dt shouldBe DataType.UWORD
conv.params[0].reg shouldBe RegisterOrPair.AY
conv.params[0].variable shouldBe false
conv.returns.dt shouldBe DataType.UWORD
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe RegisterOrPair.AY
}
})
-5
View File
@@ -68,11 +68,6 @@ class TestCompilerOnCharLit: FunSpec({
decl.type shouldBe VarDeclType.VAR
decl.datatype shouldBe DataType.UBYTE
// TODO: assertIs<CharLiteral>(decl.value,
// "char literals should be kept until code gen")
// val initializerValue = decl.value as CharLiteral
// assertEquals('\n', (initializerValue as CharLiteral).value)
withClue("initializer value should have been moved to separate assignment"){
decl.value shouldBe null
}
@@ -67,7 +67,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val id1 = (args[1] as AddressOf).identifier
val lbl1 = id1.targetStatement(program) as Label
lbl1.name shouldBe "foo_bar"
lbl1.definingScope.name shouldBe "start"
lbl1.definingScope.name shouldBe "main"
}
}
+2 -2
View File
@@ -226,8 +226,8 @@ class TestCompilerOnRanges: FunSpec({
}
""", errors, false).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain ".p8:5:30) range expression from value must be integer"
errors.errors[1] shouldContain ".p8:5:45) range expression to value must be integer"
errors.errors[0] shouldContain ".p8:5:30: range expression from value must be integer"
errors.errors[1] shouldContain ".p8:5:45: range expression to value must be integer"
}
test("testForLoopWithIterable_str") {
+2 -2
View File
@@ -20,7 +20,7 @@ import prog8.compiler.astprocessing.VariableExtractor
import prog8.compiler.astprocessing.processAstBeforeAsmGeneration
import prog8.compiler.printProgram
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.LauncherType
import prog8.compilerinterface.CbmPrgLauncherType
import prog8.compilerinterface.OutputType
import prog8.compilerinterface.ZeropageType
import prog8tests.helpers.*
@@ -302,7 +302,7 @@ class TestOptimization: FunSpec({
expr.right.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target(), outputDir= outputDir)
val options = CompilationOptions(OutputType.PRG, CbmPrgLauncherType.BASIC, ZeropageType.DONTUSE, emptyList(), false, true, C64Target(), outputDir= outputDir)
result.program.processAstBeforeAsmGeneration(options, DummyVarsAndConsts, ErrorReporterForTests())
// assignment is now split into:
+373 -44
View File
@@ -4,21 +4,120 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.PipeExpression
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Pipe
import prog8.ast.statements.VarDecl
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import prog8.compiler.astprocessing.AstPreprocessor
import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode
import prog8tests.helpers.*
class TestPipes: FunSpec({
test("correct pipe statements") {
test("pipe expression parse tree after preprocessing") {
val text = """
main {
sub start() {
uword xx = 9999 |> func1() |> func2()
|> func1() |> func2()
|> func1()
}
sub func1(uword arg) -> uword {
return arg+1111
}
sub func2(uword arg) -> uword {
return arg+2222
}
}"""
val src = SourceCode.Text(text)
val module = parseModule(src)
val errors = ErrorReporterForTests()
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val preprocess = AstPreprocessor(program, errors, C64Target())
preprocess.visit(program)
errors.errors.size shouldBe 0
preprocess.applyModifications()
program.entrypoint.statements.size shouldBe 1
val pipe = (program.entrypoint.statements.single() as VarDecl).value as PipeExpression
pipe.source shouldBe NumericLiteral(DataType.UWORD, 9999.0, Position.DUMMY)
pipe.segments.size shouldBe 5
var call = pipe.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[2] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[3] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[4] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
}
test("pipe statement parse tree after preprocessing") {
val text = """
main {
sub start() {
9999 |> func1() |> func2()
|> func1() |> func2()
|> func3()
}
sub func1(uword arg) -> uword {
return arg+1111
}
sub func2(uword arg) -> uword {
return arg+2222
}
sub func3(uword arg) {
; nothing
}
}"""
val src = SourceCode.Text(text)
val module = parseModule(src)
val errors = ErrorReporterForTests()
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val preprocess = AstPreprocessor(program, errors, C64Target())
preprocess.visit(program)
errors.errors.size shouldBe 0
preprocess.applyModifications()
program.entrypoint.statements.size shouldBe 1
val pipe = program.entrypoint.statements.single() as Pipe
pipe.source shouldBe NumericLiteral(DataType.UWORD, 9999.0, Position.DUMMY)
pipe.segments.size shouldBe 5
var call = pipe.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[2] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[3] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[4] as IFunctionCall
call.target.nameInSource shouldBe listOf("func3")
call.args.size shouldBe 0
}
test("correct pipe statements (no opt)") {
val text = """
%import floats
%import textio
@@ -26,34 +125,109 @@ class TestPipes: FunSpec({
main {
sub start() {
1.234 |> addfloat
|> floats.print_f
1.234 |> addfloat()
|> floats.print_f()
9999 |> addword
|> txt.print_uw
startvalue(99) |> addword()
|> txt.print_uw()
9999 |> abs() |> txt.print_uw()
9999 |> txt.print_uw()
99 |> abs() |> txt.print_ub()
99 |> txt.print_ub()
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}
"""
val result = compileText(C64Target(), true, text, writeAssembly = true).assertSuccess()
}"""
val result = compileText(C64Target(), optimize = false, text, writeAssembly = true).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 3
stmts.size shouldBe 7
val pipef = stmts[0] as Pipe
pipef.expressions.size shouldBe 2
pipef.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
pipef.expressions[1] shouldBe instanceOf<IdentifierReference>()
pipef.source shouldBe instanceOf<NumericLiteral>()
pipef.segments.size shouldBe 2
var call = pipef.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
call = pipef.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("floats", "print_f")
val pipew = stmts[1] as Pipe
pipew.expressions.size shouldBe 2
pipew.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
pipew.expressions[1] shouldBe instanceOf<IdentifierReference>()
pipef.source shouldBe instanceOf<NumericLiteral>()
pipew.segments.size shouldBe 2
call = pipew.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
call = pipew.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("txt", "print_uw")
stmts[2] shouldBe instanceOf<Pipe>()
stmts[3] shouldBe instanceOf<Pipe>()
stmts[4] shouldBe instanceOf<Pipe>()
stmts[5] shouldBe instanceOf<Pipe>()
}
test("correct pipe statements (with opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
1.234 |> addfloat()
|> floats.print_f()
startvalue(99) |> addword()
|> txt.print_uw()
; these should be optimized into just the function calls:
9999 |> abs() |> txt.print_uw()
9999 |> txt.print_uw()
99 |> abs() |> txt.print_ub()
99 |> txt.print_ub()
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}"""
val result = compileText(C64Target(), optimize = true, text, writeAssembly = true).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
val pipef = stmts[0] as Pipe
pipef.source shouldBe instanceOf<FunctionCallExpression>()
(pipef.source as IFunctionCall).target.nameInSource shouldBe listOf("addfloat")
pipef.segments.size shouldBe 1
val callf = pipef.segments[0] as IFunctionCall
callf.target.nameInSource shouldBe listOf("floats", "print_f")
val pipew = stmts[1] as Pipe
pipef.source shouldBe instanceOf<FunctionCallExpression>()
(pipew.source as IFunctionCall).target.nameInSource shouldBe listOf("startvalue")
pipew.segments.size shouldBe 2
val callw = pipew.segments[1] as IFunctionCall
callw.target.nameInSource shouldBe listOf("txt", "print_uw")
var stmt = stmts[2] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_uw")
stmt = stmts[3] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_uw")
stmt = stmts[4] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_ub")
stmt = stmts[5] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_ub")
}
test("incorrect type in pipe statement") {
@@ -63,8 +237,8 @@ class TestPipes: FunSpec({
main {
sub start() {
1.234 |> addfloat
|> addword |> addword
1.234 |> addfloat()
|> addword() |> addword()
}
sub addfloat(float fl) -> float {
@@ -73,29 +247,99 @@ class TestPipes: FunSpec({
sub addword(uword ww) -> uword {
return ww+2222
}
}
"""
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, errors=errors).assertFailure()
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "incompatible"
}
test("correct pipe expressions") {
test("correct pipe expressions (no opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
float @shared fl = 1.234 |> addfloat
|> addfloat
float @shared fl = 1.234 |> addfloat()
|> addfloat()
uword @shared ww = 9999 |> addword
|> addword
uword @shared ww = startvalue(99) |> addword()
|> addword()
ubyte @shared cc = 30 |> sin8u() |> cos8u()
cc = cc |> sin8u() |> cos8u()
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}"""
val result = compileText(C64Target(), optimize = false, text, writeAssembly = true).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 8
val assignf = stmts[1] as Assignment
val pipef = assignf.value as PipeExpression
pipef.source shouldBe instanceOf<NumericLiteral>()
pipef.segments.size shouldBe 2
var call = pipef.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
call = pipef.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
val assignw = stmts[3] as Assignment
val pipew = assignw.value as PipeExpression
pipew.source shouldBe instanceOf<IFunctionCall>()
pipew.segments.size shouldBe 2
call = pipew.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
call = pipew.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
var assigncc = stmts[5] as Assignment
val value = assigncc.value as PipeExpression
value.source shouldBe NumericLiteral(DataType.UBYTE, 30.0, Position.DUMMY)
value.segments.size shouldBe 2
call = value.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("sin8u")
call = value.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("cos8u")
assigncc = stmts[6] as Assignment
val pipecc = assigncc.value as PipeExpression
pipecc.source shouldBe instanceOf<IdentifierReference>()
pipecc.segments.size shouldBe 2
pipecc.segments[0] shouldBe instanceOf<BuiltinFunctionCall>()
pipecc.segments[1] shouldBe instanceOf<BuiltinFunctionCall>()
}
test("correct pipe expressions (with opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
float @shared fl = 1.234 |> addfloat()
|> addfloat()
uword @shared ww = startvalue(99) |> addword()
|> addword()
ubyte @shared cc = 30 |> sin8u() |> cos8u() ; will be optimized away into a const number
cc = cc |> sin8u() |> cos8u()
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
@@ -103,21 +347,35 @@ class TestPipes: FunSpec({
return ww+2222
}
}
"""
val result = compileText(C64Target(), true, text, writeAssembly = true).assertSuccess()
"""
val result = compileText(C64Target(), optimize = true, text, writeAssembly = true).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 8
val assignf = stmts[1] as Assignment
val pipef = assignf.value as PipeExpression
pipef.expressions.size shouldBe 2
pipef.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
pipef.expressions[1] shouldBe instanceOf<IdentifierReference>()
pipef.source shouldBe instanceOf<FunctionCallExpression>()
pipef.segments.size shouldBe 1
pipef.segments[0] shouldBe instanceOf<FunctionCallExpression>()
val assignw = stmts[3] as Assignment
val pipew = assignw.value as PipeExpression
pipew.expressions.size shouldBe 2
pipew.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
pipew.expressions[1] shouldBe instanceOf<IdentifierReference>()
pipew.source shouldBe instanceOf<FunctionCallExpression>()
pipew.segments.size shouldBe 2
pipew.segments[0] shouldBe instanceOf<FunctionCallExpression>()
pipew.segments[1] shouldBe instanceOf<FunctionCallExpression>()
var assigncc = stmts[5] as Assignment
val value = assigncc.value as NumericLiteral
value.number shouldBe 190.0
assigncc = stmts[6] as Assignment
val pipecc = assigncc.value as PipeExpression
pipecc.source shouldBe instanceOf<BuiltinFunctionCall>()
(pipecc.source as BuiltinFunctionCall).target.nameInSource shouldBe listOf("sin8u")
pipecc.segments.size shouldBe 1
pipecc.segments[0] shouldBe instanceOf<BuiltinFunctionCall>()
(pipecc.segments[0] as BuiltinFunctionCall).target.nameInSource shouldBe listOf("cos8u")
}
test("incorrect type in pipe expression") {
@@ -126,8 +384,8 @@ class TestPipes: FunSpec({
main {
sub start() {
uword result = 1.234 |> addfloat
|> addword |> addword
uword result = 1.234 |> addfloat()
|> addword() |> addword()
}
sub addfloat(float fl) -> float {
@@ -143,4 +401,75 @@ class TestPipes: FunSpec({
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "incompatible"
}
test("correct pipe statement with builtin expression") {
val text = """
%import textio
main {
sub start() {
uword ww = 9999
ubyte bb = 99
ww |> abs() |> txt.print_uw()
bb |> abs() |> txt.print_ub()
}
}
"""
val result = compileText(C64Target(), true, text, writeAssembly = true).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
val pipeww = stmts[4] as Pipe
pipeww.source shouldBe instanceOf<BuiltinFunctionCall>()
pipeww.segments.size shouldBe 1
pipeww.segments[0] shouldBe instanceOf<IFunctionCall>()
val pipebb = stmts[5] as Pipe
pipebb.source shouldBe instanceOf<BuiltinFunctionCall>()
pipebb.segments.size shouldBe 1
pipebb.segments[0] shouldBe instanceOf<IFunctionCall>()
}
test("pipe statement with type errors") {
val text = """
%import textio
main {
sub start() {
uword ww = 9999
9999 |> abs() |> txt.print_ub()
ww |> abs() |> txt.print_ub()
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target(), optimize = false, text, writeAssembly = true, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "UWORD incompatible"
errors.errors[1] shouldContain "UWORD incompatible"
}
test("pipe detects invalid number of args") {
val text = """
main {
sub start() {
uword ww = startvalue() |> addword()
|> addword()
ubyte cc = 30 |> sin8u(99) |> cos8u(22)
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addword(uword ww) -> uword {
return ww+2222
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), optimize = false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 3
errors.errors[0] shouldContain ":4:32: invalid number of arguments"
errors.errors[1] shouldContain ":7:44: invalid number of arguments"
errors.errors[2] shouldContain ":7:57: invalid number of arguments"
}
})
+87
View File
@@ -6,8 +6,16 @@ import com.github.michaelbull.result.getOrElse
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.codegen.target.cbm.AtasciiEncoding
import prog8.codegen.target.cbm.IsoEncoding
import prog8.codegen.target.cbm.PetsciiEncoding
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestStringEncodings: FunSpec({
@@ -175,6 +183,20 @@ class TestStringEncodings: FunSpec({
}
}
context("atari") {
test("atascii encoding") {
AtasciiEncoding.encode("a") shouldBe Ok(listOf<UByte>(97u))
AtasciiEncoding.encode("A") shouldBe Ok(listOf<UByte>(65u))
AtasciiEncoding.encode("\n") shouldBe Ok(listOf<UByte>(155u))
}
test("atascii decoding") {
AtasciiEncoding.decode(listOf<UByte>(97u)) shouldBe Ok("a")
AtasciiEncoding.decode(listOf<UByte>(65u)) shouldBe Ok("A")
AtasciiEncoding.decode(listOf<UByte>(155u)) shouldBe Ok("\n")
}
}
context("iso") {
test("iso accepts iso-characters") {
val result = IsoEncoding.encode("a_~ëç")
@@ -188,4 +210,69 @@ class TestStringEncodings: FunSpec({
result.expectError { "should not encode" }
}
}
test("invalid encoding immediately errors the parser") {
val source="""
main {
str string5 = unicorns:"wrong"
ubyte char5 = unicorns:'?'
sub start() {
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, source, errors, false).assertFailure()
errors.errors.size shouldBe 0
}
test("unsupported string encoding iso for C64 compilationtarget") {
val source="""
main {
str string1 = "default"
str string2 = sc:"screencodes"
str string3 = iso:"iso"
str string4 = petscii:"petscii"
sub start() {
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, source, errors, writeAssembly = false).assertFailure()
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "text encoding"
}
test("unsupported char encoding iso for C64 compilationtarget") {
val source="""
main {
ubyte char1 = 'd'
ubyte char2 = sc:'s'
ubyte char3 = iso:'i'
ubyte char4 = petscii:'p'
sub start() {
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, source, errors, writeAssembly = false).assertFailure()
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "text encoding"
}
test("all encodings supported for Cx16 target") {
val source="""
main {
str string1 = "default"
str string2 = sc:"screencodes"
str string3 = iso:"iso"
str string4 = petscii:"petscii"
ubyte char1 = 'd'
ubyte char2 = sc:'s'
ubyte char3 = iso:'i'
ubyte char4 = petscii:'p'
sub start() {
}
}"""
compileText(Cx16Target(), false, source, writeAssembly = false).assertSuccess()
}
})
+4 -4
View File
@@ -286,8 +286,8 @@ class TestSubroutines: FunSpec({
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "7:25) invalid number of arguments"
errors.errors[1] shouldContain "9:25) invalid number of arguments"
errors.errors[0] shouldContain "7:25: invalid number of arguments"
errors.errors[1] shouldContain "9:25: invalid number of arguments"
}
test("invalid number of args check on asm subroutine") {
@@ -307,8 +307,8 @@ class TestSubroutines: FunSpec({
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "7:25) invalid number of arguments"
errors.errors[1] shouldContain "9:25) invalid number of arguments"
errors.errors[0] shouldContain "7:25: invalid number of arguments"
errors.errors[1] shouldContain "9:25: invalid number of arguments"
}
test("invalid number of args check on call to label and builtin func") {
+30 -30
View File
@@ -43,7 +43,7 @@ class TestAbstractZeropage: FunSpec({
val zp = DummyZeropage(
CompilationOptions(
OutputType.RAW,
LauncherType.NONE,
CbmPrgLauncherType.NONE,
ZeropageType.FULL,
listOf((0x50u..0x5fu)),
false,
@@ -64,7 +64,7 @@ class TestC64Zeropage: FunSpec({
val zpdummyscope = GlobalNamespace(emptyList())
test("testNames") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target))
var result = zp.allocate(emptyList(), DataType.UBYTE, zpdummyscope, null, null, null, errors)
result.onFailure { fail(it.toString()) }
@@ -78,33 +78,33 @@ class TestC64Zeropage: FunSpec({
}
test("testZpFloatEnable") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
var result = zp.allocate(emptyList(), DataType.FLOAT, zpdummyscope, null, null, null, errors)
result.expectError { "should be allocation error due to disabled floats" }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target))
result = zp2.allocate(emptyList(), DataType.FLOAT, zpdummyscope, null, null, null, errors)
result.expectError { "should be allocation error due to disabled ZP use" }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target))
zp3.allocate(emptyList(), DataType.FLOAT, zpdummyscope, null, null, null, errors)
}
test("testZpModesWithFloats") {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target))
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, c64target))
}
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, c64target))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, c64target))
}
}
test("testZpDontuse") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target))
println(zp.free)
zp.availableBytes() shouldBe 0
val result = zp.allocate(emptyList(), DataType.BYTE, zpdummyscope, null, null, null, errors)
@@ -112,13 +112,13 @@ class TestC64Zeropage: FunSpec({
}
test("testFreeSpacesBytes") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target))
zp1.availableBytes() shouldBe 18
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target))
zp2.availableBytes() shouldBe 85
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target))
zp3.availableBytes() shouldBe 125
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
zp4.availableBytes() shouldBe 239
zp4.allocate(listOf("test"), DataType.UBYTE, zpdummyscope, null, null, null, errors)
zp4.availableBytes() shouldBe 238
@@ -127,7 +127,7 @@ class TestC64Zeropage: FunSpec({
}
test("testReservedSpace") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
zp1.availableBytes() shouldBe 239
50u shouldBeIn zp1.free
100u shouldBeIn zp1.free
@@ -136,7 +136,7 @@ class TestC64Zeropage: FunSpec({
200u shouldBeIn zp1.free
255u shouldBeIn zp1.free
199u shouldBeIn zp1.free
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, c64target))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, c64target))
zp2.availableBytes() shouldBe 139
50u shouldNotBeIn zp2.free
100u shouldNotBeIn zp2.free
@@ -148,7 +148,7 @@ class TestC64Zeropage: FunSpec({
}
test("testBasicsafeAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target))
zp.availableBytes() shouldBe 18
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -170,7 +170,7 @@ class TestC64Zeropage: FunSpec({
}
test("testFullAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target))
zp.availableBytes() shouldBe 239
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -201,7 +201,7 @@ class TestC64Zeropage: FunSpec({
}
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target))
zp.availableBytes() shouldBe 18
zp.allocate(emptyList(), DataType.WORD, zpdummyscope, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x04u
zp.allocate(emptyList(), DataType.UBYTE, zpdummyscope, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x06u
@@ -219,7 +219,7 @@ class TestC64Zeropage: FunSpec({
}
test("testReservedLocations") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
@@ -233,18 +233,18 @@ class TestCx16Zeropage: FunSpec({
val zpdummyscope = GlobalNamespace(emptyList())
test("testReservedLocations") {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, cx16target))
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, cx16target))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
}
test("testFreeSpacesBytes") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, cx16target))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, cx16target))
zp1.availableBytes() shouldBe 88
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, cx16target))
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, cx16target))
zp2.availableBytes() shouldBe 175
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target))
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target))
zp3.availableBytes() shouldBe 216
zp3.allocate(listOf("test"), DataType.UBYTE, zpdummyscope, null, null, null, errors)
zp3.availableBytes() shouldBe 215
@@ -253,7 +253,7 @@ class TestCx16Zeropage: FunSpec({
}
test("testReservedSpace") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target))
zp1.availableBytes() shouldBe 216
0x22u shouldBeIn zp1.free
0x80u shouldBeIn zp1.free
@@ -263,7 +263,7 @@ class TestCx16Zeropage: FunSpec({
}
test("preallocated zp vars") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target))
zp1.allocatedVariables[listOf("test")] shouldBe null
zp1.allocatedVariables[listOf("cx16", "r0")] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r15")] shouldNotBe null
+2 -2
View File
@@ -68,7 +68,7 @@ class TestAstToSourceText: AnnotationSpec() {
}
""")
val (txt, _) = roundTrip(parseModule(orig))
txt shouldContain Regex("str +s += +petscii:\"fooBar\\\\n\"")
txt shouldContain Regex("str +s += +\"fooBar\\\\n\"")
}
@Test
@@ -101,7 +101,7 @@ class TestAstToSourceText: AnnotationSpec() {
}
""")
val (txt, _) = roundTrip(parseModule(orig))
txt shouldContain Regex("ubyte +c += +petscii:'x'")
txt shouldContain Regex("ubyte +c += +'x'")
}
@Test
+3 -4
View File
@@ -425,7 +425,7 @@ class TestProg8Parser: FunSpec( {
.statements.filterIsInstance<VarDecl>()[0]
val rhs = decl.value as CharLiteral
rhs.encoding shouldBe Encoding.PETSCII
rhs.encoding shouldBe Encoding.DEFAULT
rhs.value shouldBe 'x'
}
@@ -478,7 +478,7 @@ class TestProg8Parser: FunSpec( {
val rhs = decl.value as CharLiteral
rhs.value shouldBe 'x'
rhs.encoding shouldBe Encoding.PETSCII
rhs.encoding shouldBe Encoding.DEFAULT
}
test("on rhs of subroutine-level const decl, screencode encoded") {
@@ -532,7 +532,7 @@ class TestProg8Parser: FunSpec( {
.statements.filterIsInstance<Block>()[0]
.statements.filterIsInstance<VarDecl>()[0]
val rhs = decl.value as StringLiteral
rhs.encoding shouldBe Encoding.PETSCII
rhs.encoding shouldBe Encoding.DEFAULT
rhs.value shouldBe "name"
}
@@ -872,7 +872,6 @@ class TestProg8Parser: FunSpec( {
val ff = start.statements[4] as Assignment
ff.value shouldBe NumericLiteral(DataType.UBYTE, 255.0, Position.DUMMY)
val letter = start.statements[6] as Assignment
// TODO characters should perhaps not be encoded until code generation, like strings... however this will prevent optimizing expressions with characters
val encodedletter = PetsciiEncoding.encodePetscii("A", true).getOrElse { fail("petscii error") }.single()
letter.value shouldBe NumericLiteral(DataType.UBYTE, encodedletter.toDouble(), Position.DUMMY)
}
+29
View File
@@ -0,0 +1,29 @@
package prog8tests.ast
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.ast.base.Position
import prog8.ast.statements.InlineAssembly
class TestVarious: FunSpec({
test("symbol names in inline assembly blocks") {
val names1 = InlineAssembly("""
""", Position.DUMMY).names
names1 shouldBe emptySet()
val names2 = InlineAssembly("""
label: lda #<value
sta ${'$'}ea
sta 123
label2:
sta othervalue ; but not these in the comments
; also not these
;; ...or these
// valid words 123456
""", Position.DUMMY).names
names2 shouldBe setOf("label", "lda", "sta", "ea", "value", "label2", "othervalue", "valid", "words")
}
})
@@ -100,7 +100,7 @@ class TestAsmGenSymbols: StringSpec({
fun createTestAsmGen(program: Program, allocation: IVariablesAndConsts): AsmGen {
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target())
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target())
options.compTarget.machine.zeropage = C64Zeropage(options)
return AsmGen(program, errors, allocation, options)
}
+6 -3
View File
@@ -1,13 +1,16 @@
%import textio
main {
str myBar = "main.bar"
;foo_bar:
; %asminclude "foo_bar.asm" ; TODO: should be accessible from inside start() but give assembler error. See github issue #62
foo_bar:
%asminclude "foo_bar.asm"
sub start() {
txt.print(myBar)
txt.print(&foo_bar)
return
foo_bar:
foo_bar_inside_start:
%asminclude "foo_bar.asm"
}
}
+2
View File
@@ -51,6 +51,8 @@ internal object DummyCompilationTarget : ICompilationTarget {
override val name: String = "dummy"
override val machine: IMachineDefinition
get() = throw NotImplementedError("dummy")
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
override val defaultEncoding = Encoding.PETSCII
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
throw NotImplementedError("dummy")
+1 -1
View File
@@ -86,7 +86,7 @@ internal fun generateAssembly(
variables: IVariablesAndConsts,
options: CompilationOptions? = null
): IAssemblyProgram? {
val coptions = options ?: CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, true, C64Target(), outputDir = outputDir)
val coptions = options ?: CompilationOptions(OutputType.RAW, CbmPrgLauncherType.BASIC, ZeropageType.DONTUSE, emptyList(), true, true, C64Target(), outputDir = outputDir)
coptions.compTarget.machine.zeropage = C64Zeropage(coptions)
val asmgen = AsmGen(program, ErrorReporterForTests(), variables, coptions)
return asmgen.compileToAssembly()
@@ -7,6 +7,7 @@ import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.Encoding
/**
@@ -206,10 +207,18 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
printout(functionCallExpr as IFunctionCall)
}
override fun visit(bfc: BuiltinFunctionCall) {
printout(bfc as IFunctionCall)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
printout(functionCallStatement as IFunctionCall)
}
override fun visit(bfcs: BuiltinFunctionCallStatement) {
printout(bfcs as IFunctionCall)
}
private fun printout(call: IFunctionCall) {
call.target.accept(this)
output("(")
@@ -285,11 +294,17 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(char: CharLiteral) {
output("${char.encoding.prefix}:'${escape(char.value.toString())}'")
if(char.encoding==Encoding.DEFAULT)
output("'${escape(char.value.toString())}'")
else
output("${char.encoding.prefix}:'${escape(char.value.toString())}'")
}
override fun visit(string: StringLiteral) {
output("${string.encoding.prefix}:\"${escape(string.value)}\"")
if(string.encoding==Encoding.DEFAULT)
output("\"${escape(string.value)}\"")
else
output("${string.encoding.prefix}:\"${escape(string.value)}\"")
}
override fun visit(array: ArrayLiteral) {
@@ -456,18 +471,19 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(pipe: Pipe) {
printPipe(pipe.expressions)
printPipe(pipe.source, pipe.segments)
}
override fun visit(pipe: PipeExpression) {
printPipe(pipe.expressions)
printPipe(pipe.source, pipe.segments)
}
private fun printPipe(expressions: Iterable<Expression>) {
expressions.first().accept(this)
private fun printPipe(source: Expression, segments: Iterable<Expression>) {
source.accept(this)
segments.first().accept(this)
outputln("")
scopelevel++
expressions.drop(1).forEach {
segments.drop(1).forEach {
outputi("|> ")
it.accept(this)
outputln("")
+10 -6
View File
@@ -4,8 +4,7 @@ import prog8.ast.base.FatalAstException
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.findParentNode
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
@@ -14,13 +13,18 @@ import prog8.parser.SourceCode
const val internedStringsModuleName = "prog8_interned_strings"
interface IAssignable {
// just a tag for now
}
interface IFunctionCall {
var target: IdentifierReference
var args: MutableList<Expression>
val position: Position
var parent: Node // will be linked correctly later (late init)
}
interface IPipe {
var source: Expression
val segments: MutableList<Expression> // are all function calls
val position: Position
var parent: Node // will be linked correctly later (late init)
}
interface IStatementContainer {
+18 -4
View File
@@ -4,6 +4,7 @@ import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.InferredTypes
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclOrigin
import prog8.ast.statements.ZeropageWish
@@ -37,7 +38,7 @@ fun UInt.toHex(): String {
}
}
fun Program.getTempVar(dt: DataType, altNames: Boolean=false): List<String> {
fun Program.getTempVar(dt: DataType, altNames: Boolean=false): Pair<List<String>, VarDecl> {
val tmpvarName = if(altNames) {
when (dt) {
DataType.UBYTE -> listOf("prog8_lib", "tempvar_ub2")
@@ -59,8 +60,9 @@ fun Program.getTempVar(dt: DataType, altNames: Boolean=false): List<String> {
}
val block = this.allBlocks.first { it.name==tmpvarName[0] }
if(block.statements.filterIsInstance<VarDecl>().any { it.name == tmpvarName[1] })
return tmpvarName
val existingDecl = block.statements.firstOrNull { it is VarDecl && it.name == tmpvarName[1] }
if(existingDecl!=null)
return Pair(tmpvarName, existingDecl as VarDecl)
// add new temp variable to the ast directly (we can do this here because we're not iterating inside those container blocks)
val decl = VarDecl(
@@ -68,5 +70,17 @@ fun Program.getTempVar(dt: DataType, altNames: Boolean=false): List<String> {
null, tmpvarName[1], null, false, false, null, Position.DUMMY)
block.statements.add(decl)
decl.linkParents(block)
return tmpvarName
return Pair(tmpvarName, decl)
}
fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
return when {
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
dt istype DataType.BYTE -> listOf("cx16", "r9sL")
dt istype DataType.UWORD -> listOf("cx16", "r9")
dt istype DataType.WORD -> listOf("cx16", "r9s")
dt.isPassByReference -> listOf("cx16", "r9")
else -> throw FatalAstException("invalid dt $dt")
}
}
@@ -443,8 +443,12 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
if(pipe!=null)
return PipeExpression(pipesource.toAst(), pipetarget.toAst(), toPosition())
if(pipesegment()!=null)
return PipeExpression(
expression(0).toAst(),
pipesegment().map { it.functioncall().toAst() }.toMutableList(),
toPosition()
)
throw FatalAstException(text)
}
@@ -457,7 +461,7 @@ private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral {
Encoding.values().singleOrNull { it.prefix == enc }
?: throw SyntaxError("invalid encoding", toPosition())
else
Encoding.PETSCII
Encoding.DEFAULT
return CharLiteral(unescape(text.substring(1, text.length-1), toPosition())[0], encoding, toPosition())
}
@@ -469,7 +473,7 @@ private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteral {
Encoding.values().singleOrNull { it.prefix == enc }
?: throw SyntaxError("invalid encoding", toPosition())
else
Encoding.PETSCII
Encoding.DEFAULT
return StringLiteral(unescape(text.substring(1, text.length-1), toPosition()), encoding, toPosition())
}
@@ -602,7 +606,9 @@ private fun Prog8ANTLRParser.VardeclContext.toAst(type: VarDeclType, value: Expr
}
private fun Prog8ANTLRParser.PipestmtContext.toAst(): Pipe {
val source = this.source.toAst()
val target = this.target.toAst()
return Pipe(source, target, toPosition())
return Pipe(
expression().toAst(),
pipesegment().map { it.functioncall().toAst() }.toMutableList(),
toPosition()
)
}
+2 -2
View File
@@ -193,8 +193,8 @@ object ParentSentinel : Node {
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
fun toClickableStr(): String {
val path = (Path("") / file).absolute().normalize()
return "($path:$line:$startCol)"
val path = Path(file).absolute().normalize()
return "file://$path:$line:$startCol:"
}
companion object {
@@ -287,7 +287,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
val indexer: ArrayIndex,
override val position: Position) : Expression(), IAssignable {
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
@@ -393,7 +393,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
}
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -590,7 +590,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
}
class CharLiteral(val value: Char,
val encoding: Encoding,
var encoding: Encoding,
override val position: Position) : Expression() {
override lateinit var parent: Node
@@ -604,7 +604,8 @@ class CharLiteral(val value: Char,
throw FatalAstException("can't replace here")
}
override fun copy() = CharLiteral(value, encoding, position)
override fun copy() =
CharLiteral(value, encoding, position)
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program): NumericLiteral {
val bytevalue = program.encoding.encodeString(value.toString(), encoding).single()
@@ -625,7 +626,7 @@ class CharLiteral(val value: Char,
}
class StringLiteral(val value: String,
val encoding: Encoding,
var encoding: Encoding,
override val position: Position) : Expression() {
override lateinit var parent: Node
@@ -847,7 +848,7 @@ class RangeExpression(var from: Expression,
}
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() {
override lateinit var parent: Node
override val isSimple = true
@@ -938,6 +939,14 @@ class FunctionCallExpression(override var target: IdentifierReference,
// lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1)
return null
// If the function call is part of a Pipe segments, the number of args will be 1 less than the number of parameters required
// because of the implicit first argument. We don't know this first argument here. Assume it is not a constant,
// which means that this function call cannot be a constant either.
val pipeParentSegments = (parent as? IPipe)?.segments ?: emptyList()
if(this in pipeParentSegments)
return null
val resultValue: NumericLiteral? = program.builtinFunctions.constValue(target.nameInSource[0], args, position)
if(withDatatypeCheck) {
val resultDt = this.inferType(program)
@@ -965,10 +974,6 @@ class FunctionCallExpression(override var target: IdentifierReference,
val stmt = target.targetStatement(program) ?: return InferredTypes.unknown()
when (stmt) {
is BuiltinFunctionPlaceholder -> {
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" ||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return InferredTypes.void() // these have no return value
}
return program.builtinFunctions.returnType(target.nameInSource[0], this.args)
}
is Subroutine -> {
@@ -1077,49 +1082,35 @@ class ContainmentCheck(var element: Expression,
}
}
class PipeExpression(val expressions: MutableList<Expression>, override val position: Position): Expression() {
class PipeExpression(override var source: Expression,
override val segments: MutableList<Expression>, // are all function calls
override val position: Position): Expression(), IPipe {
override lateinit var parent: Node
constructor(source: Expression, target: Expression, position: Position) : this(mutableListOf(), position) {
if(source is PipeExpression) {
expressions.addAll(source.expressions)
expressions.add(target)
} else {
expressions.add(source)
expressions.add(target)
}
}
override val isSimple = false
override fun linkParents(parent: Node) {
this.parent=parent
expressions.forEach { it.linkParents(this) }
source.linkParents(this)
segments.forEach { it.linkParents(this) }
}
override fun copy(): PipeExpression = PipeExpression(expressions.map {it.copy()}.toMutableList(), position)
override fun copy(): PipeExpression = PipeExpression(source.copy(), segments.map {it.copy()}.toMutableList(), position)
override fun constValue(program: Program): NumericLiteral? = null
override fun referencesIdentifier(nameInSource: List<String>) =
source.referencesIdentifier(nameInSource) || segments.any { it.referencesIdentifier(nameInSource) }
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>) =
expressions.any { it.referencesIdentifier(nameInSource) }
override fun inferType(program: Program): InferredTypes.InferredType {
val last = expressions.last()
val type = last.inferType(program)
if(type.isKnown)
return type
val identifier = last as? IdentifierReference
if(identifier!=null) {
val call = FunctionCallExpression(identifier, mutableListOf(), identifier.position)
return call.inferType(program)
}
return InferredTypes.InferredType.unknown()
}
override fun inferType(program: Program) = segments.last().inferType(program)
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Expression)
require(replacement is Expression)
val idx = expressions.indexOf(node)
expressions[idx] = replacement
if(node===source) {
source = replacement
} else {
require(replacement is IFunctionCall)
val idx = segments.indexOf(node)
segments[idx] = replacement
}
}
}
@@ -1132,3 +1123,39 @@ fun invertCondition(cond: Expression): BinaryExpression? {
}
return null
}
class BuiltinFunctionCall(override var target: IdentifierReference,
override var args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
val name = target.nameInSource.single()
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
args.forEach { it.linkParents(this) }
}
override fun copy() = BuiltinFunctionCall(target.copy(), args.map { it.copy() }.toMutableList(), position)
override val isSimple = name in arrayOf("msb", "lsb", "peek", "peekw", "set_carry", "set_irqd", "clear_carry", "clear_irqd")
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target=replacement as IdentifierReference
else {
val idx = args.indexOfFirst { it===node }
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteral? = null
override fun toString() = "BuiltinFunctionCall(name=$name, pos=$position)"
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = target.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)}
override fun inferType(program: Program) = program.builtinFunctions.returnType(name, this.args)
}
@@ -49,6 +49,8 @@ sealed class Statement : Node {
}
// this class is only created as temporary result from looking up the target for a builtin function call.
// this node is never actually part of the Ast.
class BuiltinFunctionPlaceholder(val name: String, override val position: Position, override var parent: Node) : Statement() {
override fun linkParents(parent: Node) {}
override fun accept(visitor: IAstVisitor) = throw FatalAstException("should not iterate over this node")
@@ -602,12 +604,18 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
val names: Set<String> by lazy {
// A cache of all the words (identifiers) present in this block of assembly code
// this is used when checking if prog8 names are referenced from assembly code
// TODO: smarter pattern; don't include words in comments
val wordPattern = Regex("""\b([_a-zA-Z][_a-zA-Z0-9]+?)\b""", RegexOption.MULTILINE)
wordPattern.findAll(assembly).map { it.value }.toSet()
val wordPattern = Regex("""\b([_a-zA-Z][_a-zA-Z0-9]+?)\b""")
assembly.splitToSequence('\n')
.map {
val everythintBeforeComment = it.substringBefore(';')
wordPattern.findAll(everythintBeforeComment)
}
.flatMap { it.map { mr -> mr.value } }
.toSet()
}
}
@@ -1014,34 +1022,60 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
}
class Pipe(val expressions: MutableList<Expression>, override val position: Position): Statement() {
class Pipe(override var source: Expression,
override val segments: MutableList<Expression>, // are all function calls
override val position: Position): Statement(), IPipe {
override lateinit var parent: Node
constructor(source: Expression, target: Expression, position: Position) : this(mutableListOf(), position) {
if(source is PipeExpression)
expressions.addAll(source.expressions)
else
expressions.add(source)
if(target is PipeExpression)
expressions.addAll(target.expressions)
else
expressions.add(target)
}
override fun linkParents(parent: Node) {
this.parent = parent
expressions.forEach { it.linkParents(this) }
source.linkParents(this)
segments.forEach { it.linkParents(this) }
}
override fun copy() = Pipe(expressions.map { it.copy() }.toMutableList(), position)
override fun copy() = Pipe(source.copy(), segments.map { it.copy() }.toMutableList(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Expression)
require(replacement is Expression)
val idx = expressions.indexOf(node)
expressions[idx] = replacement
if(node===source) {
source = replacement
} else {
require(replacement is IFunctionCall)
val idx = segments.indexOf(node)
segments[idx] = replacement
}
}
}
// Calls to builtin functions will be replaced with this node just before handing the Ast to the codegen.
// this is meant to eventually (?) be able to not have any FunctionCallStatement nodes to worry about anymore
// in the codegen, because they have been converted into GoSub (for instance) or this node.
class BuiltinFunctionCallStatement(override var target: IdentifierReference,
override var args: MutableList<Expression>,
override val position: Position) : Statement(), IFunctionCall {
val name: String = target.nameInSource.single()
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
args.forEach { it.linkParents(this) }
}
override fun copy() = throw NotImplementedError("no support for duplicating a BuiltinFunctionCallStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target = replacement as IdentifierReference
else {
val idx = args.indexOfFirst { it===node }
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString() = "BuiltinFunctionCallStatement(name=$name, pos=$position)"
}
+21 -3
View File
@@ -95,7 +95,9 @@ abstract class AstWalker {
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(bfc: BuiltinFunctionCall, parent: Node): Iterable<IAstModification> = noModifications
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(bfcs: BuiltinFunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
open fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
@@ -131,7 +133,6 @@ abstract class AstWalker {
open fun after(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> = noModifications
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
open fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> = noModifications
open fun after(builtinFunctionPlaceholder: BuiltinFunctionPlaceholder, parent: Node): Iterable<IAstModification> = noModifications
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
@@ -139,7 +140,9 @@ abstract class AstWalker {
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(bfc: BuiltinFunctionCall, parent: Node): Iterable<IAstModification> = noModifications
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(bfcs: BuiltinFunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
open fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
@@ -265,6 +268,13 @@ abstract class AstWalker {
track(after(functionCallExpr, parent), functionCallExpr, parent)
}
fun visit(bfc: BuiltinFunctionCall, parent: Node) {
track(before(bfc, parent), bfc, parent)
bfc.target.accept(this, bfc)
bfc.args.forEach { it.accept(this, bfc) }
track(after(bfc, parent), bfc, parent)
}
fun visit(functionCallStatement: FunctionCallStatement, parent: Node) {
track(before(functionCallStatement, parent), functionCallStatement, parent)
functionCallStatement.target.accept(this, functionCallStatement)
@@ -272,6 +282,12 @@ abstract class AstWalker {
track(after(functionCallStatement, parent), functionCallStatement, parent)
}
fun visit(bfcs: BuiltinFunctionCallStatement, parent: Node) {
track(before(bfcs, parent), bfcs, parent)
bfcs.args.forEach { it.accept(this, bfcs) }
track(after(bfcs, parent), bfcs, parent)
}
fun visit(identifier: IdentifierReference, parent: Node) {
track(before(identifier, parent), identifier, parent)
track(after(identifier, parent), identifier, parent)
@@ -457,13 +473,15 @@ abstract class AstWalker {
fun visit(pipe: Pipe, parent: Node) {
track(before(pipe, parent), pipe, parent)
pipe.expressions.forEach { it.accept(this, pipe) }
pipe.source.accept(this, pipe)
pipe.segments.forEach { it.accept(this, pipe) }
track(after(pipe, parent), pipe, parent)
}
fun visit(pipe: PipeExpression, parent: Node) {
track(before(pipe, parent), pipe, parent)
pipe.expressions.forEach { it.accept(this, pipe) }
pipe.source.accept(this, pipe)
pipe.segments.forEach { it.accept(this, pipe) }
track(after(pipe, parent), pipe, parent)
}
}
+13 -2
View File
@@ -49,11 +49,20 @@ interface IAstVisitor {
functionCallExpr.args.forEach { it.accept(this) }
}
fun visit(bfc: BuiltinFunctionCall) {
bfc.target.accept(this)
bfc.args.forEach { it.accept(this) }
}
fun visit(functionCallStatement: FunctionCallStatement) {
functionCallStatement.target.accept(this)
functionCallStatement.args.forEach { it.accept(this) }
}
fun visit(bfcs: BuiltinFunctionCallStatement) {
bfcs.args.forEach { it.accept(this) }
}
fun visit(identifier: IdentifierReference) {
}
@@ -180,10 +189,12 @@ interface IAstVisitor {
}
fun visit(pipe: Pipe) {
pipe.expressions.forEach { it.accept(this) }
pipe.source.accept(this)
pipe.segments.forEach { it.accept(this) }
}
fun visit(pipe: PipeExpression) {
pipe.expressions.forEach { it.accept(this) }
pipe.source.accept(this)
pipe.segments.forEach { it.accept(this) }
}
}
@@ -1,9 +1,11 @@
package prog8.compilerinterface
enum class Encoding(val prefix: String) {
PETSCII("petscii"), // c64/c128/cx16
SCREENCODES("sc"), // c64/c128/cx16
ISO("iso") // cx16
DEFAULT("default"), // depends on compilation target
PETSCII("petscii"), // c64/c128/cx16
SCREENCODES("sc"), // c64/c128/cx16
ATASCII("atascii"), // atari
ISO("iso") // cx16
}
interface IStringEncoding {
+1 -2
View File
@@ -128,11 +128,10 @@ sealed class SourceCode {
/**
* SourceCode for internally generated nodes (usually Modules)
*/
class Generated(name: String) : SourceCode() {
class Generated(override val name: String) : SourceCode() {
override val isFromResources: Boolean = false
override val isFromFilesystem: Boolean = false
override val origin: String = name
override val name: String = name
override val text: String = "<generated code node, no text representation>"
}
}
@@ -9,7 +9,7 @@ import kotlin.math.*
private typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteral
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?, val floatFac1: Boolean)
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
override fun toString(): String {
@@ -35,35 +35,42 @@ class FParam(val name: String, val possibleDatatypes: Array<DataType>)
class FSignature(val name: String,
val pure: Boolean, // does it have side effects?
val parameters: List<FParam>,
val known_returntype: DataType?, // specify return type if fixed, otherwise null if it depends on the arguments
val hasReturn: Boolean, // is there a return value at all?
val returnType: DataType?, // specify return type if fixed, otherwise null if it depends on the arguments
val constExpressionFunc: ConstExpressionCaller? = null) {
init {
require(hasReturn || returnType==null) { "$name has invalid return spec" }
}
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
val returns = when(known_returntype) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(known_returntype, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(known_returntype, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(known_returntype, null, true)
in PassByReferenceDatatypes -> ReturnConvention(known_returntype!!, RegisterOrPair.AY, false)
else -> {
val paramType = actualParamTypes.first()
if(pure)
// return type depends on arg type
when(paramType) {
val returns: ReturnConvention
if(hasReturn) {
returns = when (returnType) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(returnType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY, false)
else -> {
// return type depends on arg type
when (val paramType = actualParamTypes.first()) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(paramType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
else -> ReturnConvention(paramType, null, false)
}
else {
ReturnConvention(paramType, null, false)
}
}
} else {
require(returnType==null)
returns = ReturnConvention(null, null, false)
}
return when {
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
actualParamTypes.size==1 -> {
// one parameter? via register/registerpair
// one parameter goes via register/registerpair
val paramConv = when(val paramType = actualParamTypes[0]) {
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
@@ -74,7 +81,7 @@ class FSignature(val name: String,
CallConvention(listOf(paramConv), returns)
}
else -> {
// multiple parameters? via variables
// multiple parameters go via variables
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
CallConvention(paramConvs, returns)
}
@@ -83,83 +90,80 @@ class FSignature(val name: String,
}
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
private val functionSignatures: List<FSignature> = listOf(
// this set of function have no return value and operate in-place:
FSignature("rol" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("rol2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
FSignature("rol" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), false,null),
FSignature("ror" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), false, null),
FSignature("rol2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), false, null),
FSignature("ror2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), false, null),
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), false, null),
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), false, null),
// cmp returns a status in the carry flag, but not a proper return value
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), false, null),
// these few have a return value depending on the argument(s):
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values())), DataType.UBYTE, ::builtinSizeof),
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), true, null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), true, null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), true, null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), true, null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), true, null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values())), true, DataType.UBYTE, ::builtinSizeof),
// normal functions follow:
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("sinr8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinSinR8 ),
FSignature("sinr8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSinR8u ),
FSignature("sinr16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSinR16 ),
FSignature("sinr16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinSinR16u ),
FSignature("cos" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("cosr8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCosR8 ),
FSignature("cosr8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCosR8u ),
FSignature("cosr16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCosR16 ),
FSignature("cosr16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinCosR16u ),
FSignature("tan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()) } },
FSignature("sqrt" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, ::round) },
FSignature("floor" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } },
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} },
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
FSignature("pokemon" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
FSignature("pop" , false, listOf(FParam("target", ByteDatatypes)), null),
FSignature("popw" , false, listOf(FParam("target", WordDatatypes)), null),
FSignature("push" , false, listOf(FParam("value", ByteDatatypes)), null),
FSignature("pushw" , false, listOf(FParam("value", WordDatatypes)), null),
FSignature("rsave" , false, emptyList(), null),
FSignature("rsavex" , false, emptyList(), null),
FSignature("rrestore" , false, emptyList(), null),
FSignature("rrestorex" , false, emptyList(), null),
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
)
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), true, DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.UWORD, ::builtinSin16u ),
FSignature("sinr8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.BYTE, ::builtinSinR8 ),
FSignature("sinr8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.UBYTE, ::builtinSinR8u ),
FSignature("sinr16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.WORD, ::builtinSinR16 ),
FSignature("sinr16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.UWORD, ::builtinSinR16u ),
FSignature("cos" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.UWORD, ::builtinCos16u ),
FSignature("cosr8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.BYTE, ::builtinCosR8 ),
FSignature("cosr8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.UBYTE, ::builtinCosR8u ),
FSignature("cosr16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.WORD, ::builtinCosR16 ),
FSignature("cosr16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), true, DataType.UWORD, ::builtinCosR16u ),
FSignature("tan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), true, DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()) } },
FSignature("sqrt" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, ::round) },
FSignature("floor" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), true, DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), true, DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), true, DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), true, DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } },
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), true, DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} },
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), true, DataType.UWORD, ::builtinMkword),
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), true, DataType.UBYTE),
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), true, DataType.UWORD),
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), false,null),
FSignature("pokemon" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), false,null),
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), false,null),
FSignature("pop" , false, listOf(FParam("target", ByteDatatypes)), false, null),
FSignature("popw" , false, listOf(FParam("target", WordDatatypes)), false, null),
FSignature("push" , false, listOf(FParam("value", ByteDatatypes)), false, null),
FSignature("pushw" , false, listOf(FParam("value", WordDatatypes)), false, null),
FSignature("rsave" , false, emptyList(), false,null),
FSignature("rsavex" , false, emptyList(), false,null),
FSignature("rrestore" , false, emptyList(), false,null),
FSignature("rrestorex" , false, emptyList(), false,null),
FSignature("rnd" , false, emptyList(), true, DataType.UBYTE),
FSignature("rndw" , false, emptyList(), true, DataType.UWORD),
FSignature("rndf" , false, emptyList(), true, DataType.FLOAT),
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), true, DataType.UWORD),
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), false, null),
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), false, null),
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), false, null),
)
val BuiltinFunctions = functionSignatures.associateBy { it.name }
@@ -176,6 +180,9 @@ private fun builtinAll(array: List<Double>): Double = if(array.all { it!=0.0 })
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType {
if(function in arrayOf("set_carry", "set_irqd", "clear_carry", "clear_irqd"))
return InferredTypes.InferredType.void()
fun datatypeFromIterableArg(arglist: Expression): DataType {
if(arglist is ArrayLiteral) {
val dt = arglist.value.map {it.inferType(program).getOr(DataType.UNDEFINED)}.toSet()
@@ -200,8 +207,10 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
}
val func = BuiltinFunctions.getValue(function)
if(func.known_returntype!=null)
return InferredTypes.knownFor(func.known_returntype)
if(func.returnType!=null)
return InferredTypes.knownFor(func.returnType)
if(!func.hasReturn)
return InferredTypes.InferredType.void()
// function has return values, but the return type depends on the arguments
return when (function) {
@@ -299,7 +308,6 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P
return NumericLiteral.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1)
@@ -339,7 +347,6 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
}
}
@Suppress("UNUSED_PARAMETER")
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
@@ -373,7 +380,6 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
}
@Suppress("UNUSED_PARAMETER")
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position)
@@ -383,151 +389,134 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
return NumericLiteral(DataType.UWORD, result.toDouble(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteral(DataType.BYTE, round(127.0 * sin(rad)), position)
return NumericLiteral(DataType.BYTE, truncate(127.0 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteral(DataType.UBYTE, round(128.0 + 127.5 * sin(rad)), position)
return NumericLiteral(DataType.UBYTE, truncate(128.0 + 127.5 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSinR8(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sinr8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteral(DataType.BYTE, round(127.0 * sin(rad)), position)
return NumericLiteral(DataType.BYTE, truncate(127.0 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSinR8u(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sinr8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteral(DataType.UBYTE, round(128.0 + 127.5 * sin(rad)), position)
return NumericLiteral(DataType.UBYTE, truncate(128.0 + 127.5 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteral(DataType.BYTE, round(127.0 * cos(rad)), position)
return NumericLiteral(DataType.BYTE, truncate(127.0 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteral(DataType.UBYTE, round(128.0 + 127.5 * cos(rad)), position)
return NumericLiteral(DataType.UBYTE, truncate(128.0 + 127.5 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCosR8(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("cosr8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteral(DataType.BYTE, round(127.0 * cos(rad)), position)
return NumericLiteral(DataType.BYTE, truncate(127.0 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCosR8u(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("cosr8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteral(DataType.UBYTE, round(128.0 + 127.5 * cos(rad)), position)
return NumericLiteral(DataType.UBYTE, truncate(128.0 + 127.5 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteral(DataType.WORD, round(32767.0 * sin(rad)), position)
return NumericLiteral(DataType.WORD, truncate(32767.0 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteral(DataType.UWORD, round(32768.0 + 32767.5 * sin(rad)), position)
return NumericLiteral(DataType.UWORD, truncate(32768.0 + 32767.5 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSinR16(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sinr16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteral(DataType.WORD, round(32767.0 * sin(rad)), position)
return NumericLiteral(DataType.WORD, truncate(32767.0 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSinR16u(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sinr16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteral(DataType.UWORD, round(32768.0 + 32767.5 * sin(rad)), position)
return NumericLiteral(DataType.UWORD, truncate(32768.0 + 32767.5 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteral(DataType.WORD, round(32767.0 * cos(rad)), position)
return NumericLiteral(DataType.WORD, truncate(32767.0 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteral(DataType.UWORD, round(32768.0 + 32767.5 * cos(rad)), position)
return NumericLiteral(DataType.UWORD, truncate(32768.0 + 32767.5 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCosR16(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("cosr16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteral(DataType.WORD, round(32767.0 * cos(rad)), position)
return NumericLiteral(DataType.WORD, truncate(32767.0 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCosR16u(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("cosr16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteral(DataType.UWORD, round(32768.0 + 32767.5 * cos(rad)), position)
return NumericLiteral(DataType.UWORD, truncate(32768.0 + 32767.5 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
@@ -5,7 +5,9 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
@@ -126,30 +128,6 @@ class CallGraph(private val program: Program, private val allowMissingIdentifier
allAssemblyNodes.add(inlineAssembly)
}
override fun visit(pipe: PipeExpression) {
processPipe(pipe.expressions, pipe)
super.visit(pipe)
}
override fun visit(pipe: Pipe) {
processPipe(pipe.expressions, pipe)
super.visit(pipe)
}
private fun processPipe(expressions: Iterable<Expression>, pipe: Node) {
expressions.forEach {
if(it is IdentifierReference){
val otherSub = it.targetSubroutine(program)
if(otherSub!=null) {
pipe.definingSubroutine?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub) + otherSub
calledBy[otherSub] = calledBy.getValue(otherSub) + pipe
}
}
}
}
}
fun checkRecursiveCalls(errors: IErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
@@ -5,10 +5,11 @@ import kotlin.io.path.Path
enum class OutputType {
RAW,
PRG
PRG,
XEX
}
enum class LauncherType {
enum class CbmPrgLauncherType {
BASIC,
NONE
}
@@ -22,7 +23,7 @@ enum class ZeropageType {
}
class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val launcher: CbmPrgLauncherType,
val zeropage: ZeropageType,
val zpReserved: List<UIntRange>,
val floats: Boolean,
@@ -37,5 +38,4 @@ class CompilationOptions(val output: OutputType,
var asmListfile: Boolean = false,
var experimentalCodegen: Boolean = false,
var outputDir: Path = Path("")
) {
}
)
@@ -5,13 +5,14 @@ import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
// TODO list of supported string encodings
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, encoding: Encoding): List<UByte> // TODO use Result
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String // TODO use Result
val supportedEncodings: Set<Encoding>
val defaultEncoding: Encoding
override fun encodeString(str: String, encoding: Encoding): List<UByte>
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String
fun asmsubArgsEvalOrder(sub: Subroutine): List<Int>
fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
@@ -19,8 +19,7 @@ interface IMachineDefinition {
val FLOAT_MEM_SIZE: Int
val ESTACK_LO: UInt
val ESTACK_HI: UInt
val BASIC_LOAD_ADDRESS : UInt
val RAW_LOAD_ADDRESS : UInt
val PROGRAM_LOAD_ADDRESS : UInt
val opcodeNames: Set<String>
var zeropage: Zeropage
@@ -4,11 +4,16 @@ import prog8.ast.INameScope
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.statements.*
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
/**
* A more convenient way to pass variable (and constant values) definitions to the code generator,
* so that it doesn't have to scavenge and manipulate the VerDecl nodes in the AST for this information.
*
* note: the string variables are in here as well, they're in blockVars for the block named 'prog8_interned_strings'.
*/
interface IVariablesAndConsts {
data class ConstantNumberSymbol(val type: DataType, val scopedname: List<String>, val value: Double, val position: Position)
@@ -28,5 +33,8 @@ interface IVariablesAndConsts {
val subroutineConsts: Map<Subroutine, Set<ConstantNumberSymbol>>
val subroutineMemvars: Map<Subroutine, Set<MemoryMappedVariable>>
/**
* ability to add another new variable after the tables have already been created.
*/
fun addIfUnknown(definingBlock: Block, variable: VarDecl)
}
+1 -1
View File
@@ -99,7 +99,7 @@ One or more .p8 module files
``-target <compilation target>``
Sets the target output of the compiler, currently 'c64' and 'cx16' are valid targets.
c64 = Commodore 64, c128 = Commodore 128, cx16 = Commander X16.
c64 = Commodore 64, c128 = Commodore 128, cx16 = Commander X16, atari = Atari 800 XL
Default = c64
``-srcdirs <pathlist>``
+7 -2
View File
@@ -18,7 +18,12 @@ This CPU is from the late 1970's and early 1980's and was used in many home comp
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
while still being low level enough to create high performance programs.
You can compile programs for various machines with this CPU such as the Commodore-64 and Commodore-128, and the Commander X16.
You can compile programs for various machines with this CPU:
* Commodore 64
* Commander X16
* Commodore 128 (limited support for now)
* Atari 800 XL (limited support for now)
Prog8 is copyright © Irmen de Jong (irmen@razorvine.net | http://www.razorvine.net).
@@ -67,7 +72,7 @@ Language features
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the other machines.
- If you only use standard kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)!
- If you only use standard kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compilation target flag)!
Code example
+1 -1
View File
@@ -51,7 +51,7 @@ sys (part of syslib)
``memcopy(from, to, numbytes)``
Efficiently copy a number of bytes from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
*Warning:* can only copy *non-overlapping* memory areas correctly!
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
+1 -2
View File
@@ -32,8 +32,7 @@ RAM, ROM, I/O
=============
#. what part(s) of the address space is RAM? What parts of the RAM can be used by user programs?
#. what is the load address of Basic programs?
#. what is a good load address of machine code programs?
#. what is the usual starting memory address of programs?
#. what is the best place to put 2 pages (512 bytes total) of scratch area data in RAM?
#. what part(s) of the address space is ROM?
#. what part(s) of the address space is memory mapped I/O registers?
+6 -5
View File
@@ -191,7 +191,7 @@ Values will usually be part of an expression or assignment statement::
$aa43 ; hex integer number
%100101 ; binary integer number (% is also remainder operator so be careful)
-33.456e52 ; floating point number
"Hi, I am a string" ; text string, encoded with compiler target default encoding
"Hi, I am a string" ; text string, encoded with default encoding
'a' ; byte value (ubyte) for the letter a
sc:"Alternate" ; text string, encoded with c64 screencode encoding
sc:'a' ; byte value of the letter a in c64 screencode encoding
@@ -701,12 +701,13 @@ If you want to ignore a return value of a subroutine, you should prefix the call
Otherwise the compiler will issue a warning about discarding a result value.
Deeply nested function calls can be rewritten as a chain using the *pipe operator* ``|>`` as long as they
are unary functions (taking a single argument).
are unary functions (taking a single argument). Various possibilities of using this operator are explained
in the syntax reference for this operator.
.. note::
**Order of evaluation:**
The order of evaluation of arguments is *unspecified* and should not be relied upon.
The order of evaluation of arguments to a single function call is *unspecified* and should not be relied upon.
There is no guarantee of a left-to-right or right-to-left evaluation of the call arguments.
.. caution::
@@ -985,7 +986,7 @@ memory(name, size, alignment)
The return value is just a simple uword address so it cannot be used as an array in your program.
You can only treat it as a pointer or use it in inline assembly.
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 compiler target for now
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine)
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte).
Notice that bank $00 is used by the Kernal and should not be used by user code.
@@ -996,7 +997,7 @@ callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 compiler t
If the routine requires different arguments or return values, ``callfar`` cannot be used
and you'll have to set up a call to ``jsrfar`` yourself to process this.
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 compiler target for now
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another rom-bank on the CommanderX16
The banked ROM is located in the address range $C000-$FFFF (16 kilobyte).
There are 32 banks (0 to 31).
+16 -16
View File
@@ -90,13 +90,9 @@ Directives
.. data:: %address <address>
Level: module.
Global setting, set the program's start memory address
- default for ``raw`` output is ``$c000``
- default for ``prg`` output is ``$0801``
- cannot be changed if you select ``prg`` with a ``basic`` launcher;
then it is always ``$081e`` (immediately after the BASIC program), and the BASIC program itself is always at ``$0801``.
This is because the C64 expects BASIC programs to start at this address.
Global setting, set the program's start memory address. It's usually fixed at ``$0801`` because the
default launcher type is a CBM-basic program. But you have to specify this address yourself when
you don't use a CBM-basic launcher.
.. data:: %import <name>
@@ -532,22 +528,26 @@ containment check: ``in``
pipe: ``|>``
Used as an alternative to nesting function calls. The pipe operator is used to 'pipe' the value
into the next function. It only works on unary functions (taking a single argument), because you just
specify the *name* of the function that the value has to be piped into. The resulting value will be piped
into the next function as long as there are chained pipes.
into the next function. You write a pipe as a sequence of function calls. You don't write
the arguments to the functions though: the value of one segment in the pipe, will be used as the argument
for the next function call in the sequence.
*note:* It only works on unary functions (taking a single argument) for now.
For example, this: ``txt.print_uw(add_bonus(determine_score(get_player(1))))``
can be rewritten as::
get_player(1)
|> determine_score
|> add_bonus
|> txt.print_uw
|> determine_score()
|> add_bonus()
|> txt.print_uw()
It also works for expressions that return a value, for example ``uword score = add_bonus(determine_score(get_player(1)))`` ::
A pipe can also be written as an expression that returns a value, for example ``uword score = add_bonus(determine_score(get_player(1)))`` ::
uword score = get_player(1)
|> determine_score
|> add_bonus
|> determine_score()
|> add_bonus()
address of: ``&``
This is a prefix operator that can be applied to a string or array variable or literal value.
+4 -3
View File
@@ -9,17 +9,18 @@ Prog8 targets the following hardware:
- optional use of memory mapped I/O registers
- optional use of system ROM routines
Currently these machines can be selected as a compiler target (via the ``-target`` compiler argument):
Currently these machines can be selected as a compilation target (via the ``-target`` compiler argument):
- 'c64': the Commodore 64
- 'c128': the Commodore 128 (*limited support only for now*)
- 'cx16': the `Commander X16 <https://www.commanderx16.com/>`_
- 'c128': the Commodore 128 (*limited support*)
- 'atari': the Atari 800 XL (*experimental support*)
This chapter explains some relevant system details of the c64 and cx16 machines.
.. hint::
If you only use standard kernal and prog8 library routines,
it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
it is possible to compile the *exact same program* for both machines (just change the compilation target flag)!
Memory Model
+12 -9
View File
@@ -3,13 +3,13 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- ...
...
Need help with
^^^^^^^^^^^^^^
- c128 target: various machine specific things (free zp locations, how banking works, getting the floating point routines working, ...)
- other targets such as Atari 800XL: all required details about the machine, I have no clue whatsoever
- atari target: more details details about the machine, fixing library routines. I have no clue whatsoever.
- see the :ref:`portingguide` for details on what information is needed.
@@ -23,19 +23,18 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- pipe operator: allow non-unary function calls in the pipe that specify the other argument(s) in the calls.
- writeAssembly(): make it possible to actually get rid of the VarDecl nodes by fixing the rest of the code mentioned there.
- make everything an expression? (get rid of Statements. Statements are expressions with void return types?).
- allow "xxx" * constexpr (where constexpr is not a number literal), now gives expression error not same type
- unify FunctioncallExpression + FunctioncallStatement and PipeExpression + Pipe statement classes, may require moving Expression/Statement into interfaces instead of abstract base classes
- for the pipe operator: recognise a placeholder (``?`` or ``%`` or ``_``) in a non-unary function call to allow non-unary functions in the chain; ``4 |> mkword(?, $44) |> print_uw``
OR: change pipe syntax and require function call, but always have implicit first argument added.
- for the pipe operator: make it 100% syntactic sugar so there's no need for asm codegen like translatePipeExpression
- make it possible to inline non-asmsub routines that just contain a single statement (return, functioncall, assignment)
but this requires all identifiers in the inlined expression to be changed to fully scoped names.
If we can do that why not perhaps also able to inline multi-line subroutines? Why would it be limited to just 1 line? Maybe to protect against code bloat.
Inlined subroutines cannot contain further nested subroutines!
- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation, but how does it know?
- simplifyConditionalExpression() sometimes introduces needless assignment to r9 tempvar (scenario sought)
- consider adding McCarthy evaluation to shortcircuit and and or expressions. First do ifs by splitting them up? Then do expressions that compute a value?
- use more of Result<> and Either<> to handle errors/ nulls better?
- use more of Result<> to handle errors/ nulls better?
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``p8v_``? Or not worth it (most 3 letter opcodes as variables are nonsensical anyway)
then we can get rid of the instruction lists in the machinedefinitions as well?
- [problematic due to 64tass:] add a compiler option to not remove unused subroutines. this allows for building library programs. But this won't work with 64tass's .proc ...
@@ -48,6 +47,7 @@ Compiler:
Libraries:
- fix the problems in c128 target, and flesh out its libraries.
- fix the problems in atari target, and flesh out its libraries.
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
- optimize several inner loops in gfx2 even further?
- add modes 2 and 3 to gfx2 (lowres 4 color and 16 color)?
@@ -67,9 +67,12 @@ Expressions:
Optimizations:
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served
- translateFunctioncall() in BuiltinFunctionsAsmGen: should be able to assign parameters to a builtin function directly from register(s), this will make the use of a builtin function in a pipe expression more efficient without using a temporary variable
- translateNormalAssignment() -> better code gen for assigning boolean comparison expressions
- translateUnaryFunctioncall() in BuiltinFunctionsAsmGen: should be able to assign parameters to a builtin function directly from register(s), this will make the use of a builtin function in a pipe expression more efficient without using a temporary variable
compare ``aa = startvalue(1) |> sin8u() |> cos8u() |> sin8u() |> cos8u()``
versus: ``aa = cos8u(sin8u(cos8u(sin8u(startvalue(1)))))`` the second one contains no sta cx16.r9L in between.
- AssignmentAsmGen.assignExpression() -> better code gen for assigning boolean comparison expressions
- when a for loop's loopvariable isn't referenced in the body, and the iterations are known, replace the loop by a repeatloop
but we have no efficient way right now to see if the body references a variable.
- AssignmentAsmGen: "real optimized code for comparison expressions that yield a boolean result value"
- automatically convert if statements that test for multiple values (if X==1 or X==2..) to if X in [1,2,..] statements, instead of just a warning.
- introduce byte-index operator to avoid index multiplications in loops over arrays? see github issue #4
+29
View File
@@ -0,0 +1,29 @@
%import textio
%zeropage basicsafe
; This example computes the first 20 values of the Fibonacci sequence.
; Note: this program is compatible with atari.
main {
sub start() {
txt.print("fibonacci sequence")
txt.nl()
repeat 21 {
txt.print_uw(fib_next())
txt.nl()
}
txt.waitkey()
}
uword fib_prev = 0
uword fib_current = 1
sub fib_next() -> uword {
uword new = fib_current + fib_prev
fib_prev = fib_current
fib_current = new
return fib_prev
}
}
+13
View File
@@ -0,0 +1,13 @@
%import textio
%zeropage basicsafe
%address $2000
; hello world test for Atari 8-bit
main {
sub start() {
txt.print("Hello, World!")
txt.nl()
txt.waitkey()
}
}
+21 -5
View File
@@ -1,11 +1,27 @@
%import textio
%import floats
%import test_stack
%zeropage basicsafe
main {
sub start() {
ubyte @shared xx
if xx==1 or xx==2 or xx==3 {
xx++
}
get_player(1)
|> determine_score()
|> add_bonus()
|> txt.print_uw()
}
sub get_player(ubyte xx) -> ubyte {
return xx+33
}
sub determine_score(ubyte zz) -> ubyte {
return zz+22
}
sub add_bonus(ubyte qq) -> ubyte {
return qq+1
}
}
+4 -3
View File
@@ -183,10 +183,9 @@ expression :
| directmemory
| addressof
| expression typecast
| pipesource = expression EOL? pipe=PIPE EOL? pipetarget = expression
| expression pipesegment+
;
typecast : 'as' datatype;
arrayindexed : scoped_identifier arrayindex ;
@@ -208,7 +207,9 @@ returnstmt : 'return' expression? ;
breakstmt : 'break';
pipestmt: source=expression pipe=PIPE EOL? target=expression ;
pipestmt : expression pipesegment+;
pipesegment : EOL? PIPE functioncall ;
identifier : NAME ;
+2 -2
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env sh
rm -f *.jar *.asm *.prg *.vm.txt *.vice-mon-list *.list a.out imgui.ini
rm -f *.bin *.xex *.jar *.asm *.prg *.vm.txt *.vice-mon-list *.list a.out imgui.ini
rm -rf build out
rm -rf compiler/build codeGenTargets/build codeGenCpu6502/build codeOptimizers/build compilerInterfaces/build compilerAst/build dbusCompilerService/build httpCompilerService/build parser/build
rm -rf compiler/build codeGenTargets/build codeGenCpu6502/build codeGenExperimental6502/build codeOptimizers/build compilerInterfaces/build compilerAst/build dbusCompilerService/build httpCompilerService/build parser/build