mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
094ecceaac | ||
|
|
2812736ae5 | ||
|
|
6f87f8706c | ||
|
|
38beebe720 | ||
|
|
fc1c3c6808 | ||
|
|
96ba895b84 | ||
|
|
df35dfe3bf | ||
|
|
c5504c6657 | ||
|
|
530e109433 | ||
|
|
6cce47b2f1 | ||
|
|
6185d5eca1 | ||
|
|
685ad1746e | ||
|
|
891f870ec0 | ||
|
|
ad9933f0f6 | ||
|
|
1b86117754 | ||
|
|
eeb3c968d6 | ||
|
|
406658a10f | ||
|
|
6a0551cea1 | ||
|
|
553f3b45d2 | ||
|
|
064a8e785c | ||
|
|
21e9723bb2 | ||
|
|
60b2c44a44 | ||
|
|
c4fe3ecc0a | ||
|
|
2f18a8f6d0 | ||
|
|
5ac784e18a | ||
|
|
7a2164b4d0 | ||
|
|
0a43eae184 | ||
|
|
3117e2b2a3 | ||
|
|
41fece4643 | ||
|
|
7aa807ec7f | ||
|
|
4d16e1e14a | ||
|
|
73fc18099e | ||
|
|
e34dac8dbb | ||
|
|
af0e7f7187 |
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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 @@
|
||||
7.8
|
||||
7.9
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
})
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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("")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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>``
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user