Compare commits

...

85 Commits
v8.9 ... v8.10

Author SHA1 Message Date
8acd94fc89 avoid work 2023-03-05 12:32:58 +01:00
1436480eab added a few more comparison expression optimizations 2023-03-04 16:01:40 +01:00
448d176c24 fix vm crash on empty string 2023-03-04 15:35:54 +01:00
fd269453a4 todos 2023-03-04 14:14:01 +01:00
b3b380964c remove searchParameter() from lookups
it shouldn't be needed to look up subroutine parameters by scoped name
2023-03-04 13:24:33 +01:00
6e9025ebf2 cx16 fix irq statusbit handling and kefrenbars example 2023-03-03 21:58:08 +01:00
3922691b3c limit to 48828 hz sample rate (vera max) 2023-03-03 18:04:21 +01:00
0545b77cf4 ask for filename 2023-03-03 17:24:16 +01:00
6b3f39fa1a oops 2023-03-03 17:17:19 +01:00
3114ab87dc add 8 bit sample width support 2023-03-03 17:12:44 +01:00
00bc99cc7b added cx16/stream-wav example, refactor pcmaudio code 2023-03-03 14:18:13 +01:00
540b3ae2f4 tweak BinaryExpression splitting 2023-02-28 21:45:38 +01:00
dbfe4140e1 improved import search paths 2023-02-28 20:08:11 +01:00
d3675ec254 gone, deprecated 2023-02-27 23:41:22 +01:00
ded2483fc0 cx16 startup code now properly turns off mouse cursor 2023-02-27 23:35:42 +01:00
e62ea388e0 tweak cx16 adpcm example 2023-02-24 01:38:03 +01:00
f20356e9be cx16.callfar signature has been changed to be easier to use 2023-02-23 23:06:20 +01:00
d282a2d846 remove cx16.callrom() just use callfar 2023-02-23 23:02:56 +01:00
4641ac46e7 extra question in porting guide for high ram 2023-02-22 22:56:43 +01:00
ba9268a09e added -varshigh compiler option to move BSS section.
Documented BSS a bit in the manual.
2023-02-22 22:44:29 +01:00
fb9902c536 avoid const fold loop on const bool thing=true
fixes #97
2023-02-22 21:27:08 +01:00
5318ba6c6e shrink evalstack from 2 to 1 page
c64=$cf00-$cfff, x16: $0700-$07ff
2023-02-21 22:52:04 +01:00
fd5ebef488 cx16 startup code now also selects ram bank 1 2023-02-21 21:53:32 +01:00
d9e4f39ddc memset BSS section to zero all at once, less individual var=0 assigns 2023-02-21 00:26:21 +01:00
435b9d8973 get rid of 'noreinit' option for now, because it resulted in unreliable code 2023-02-20 23:29:16 +01:00
0ea70ba656 fix proper initialization of zeropagevars with 'noreinit' 2023-02-20 23:05:27 +01:00
92a07b87d2 clearer 2023-02-20 02:32:36 +01:00
c3c82282ba reinitGlobals option is clearer than the inverse 2023-02-19 19:09:29 +01:00
adc15c24ef introduce bss segments 2023-02-19 18:12:37 +01:00
dddf9a9396 remove explicit 'bss' from St var, changed to 'uninitialized' 2023-02-19 16:50:06 +01:00
9ca6860ffa tweak 2023-02-19 15:08:16 +01:00
f7dd388954 remove unsupported floats.FTOSWRDAY routine. Fixes #96 2023-02-17 18:05:46 +01:00
6012839f0e todo 2023-02-16 23:06:09 +01:00
8e9cbab053 todo 2023-02-16 22:53:16 +01:00
aaf375a57b move some utility methods into Pt Ast nodes itself 2023-02-16 22:45:35 +01:00
3cce985f03 check float bits 2023-02-16 22:22:12 +01:00
c59df6ec20 optimize isZpVar 2023-02-16 00:41:20 +01:00
5c3f41f64d reintroduce explicit PtAugmentedAssign ast node 2023-02-15 22:54:32 +01:00
cf3523f49f Merge branch 'codegen-on-new-ast' 2023-02-14 22:48:11 +01:00
db794752cb fix ast error on inline sub 2023-02-14 22:37:33 +01:00
bceaebe856 fix crash on sort/reverse unused arrays
fixes #95
2023-02-14 00:26:29 +01:00
3916de2921 attempt to clarify docs of cx16.numbanks() 2023-02-13 23:45:53 +01:00
9e0f8c1a97 remove avg() from syntax defs, it doesn't exist anymore 2023-02-13 22:31:06 +01:00
0cbc56b82e remove unused ast print func 2023-02-13 00:19:48 +01:00
b95608f68a new common ICodeGeneratorBackend interface for all code generator classes 2023-02-12 23:52:54 +01:00
b6e5dbd06c optimized away VarDecl.subroutineParameter 2023-02-12 23:19:35 +01:00
f09bcf3fcf Merge branch 'master' into codegen-on-new-ast 2023-02-12 17:36:18 +01:00
75d486b124 fix variable node casting 2023-02-12 17:04:58 +01:00
4914609485 local varnames and fix uninitialized parents 2023-02-12 16:00:58 +01:00
75bd66326a fix variable zpwish 2023-02-11 15:18:57 +01:00
8f904f75bb Merge branch 'master' into codegen-on-new-ast 2023-02-11 14:40:23 +01:00
ed68d604d6 fix break as indirect jump
fix subroutine param scoped name
2023-02-11 01:21:27 +01:00
f83752f43b update compiler internals diagram 2023-02-09 23:15:19 +01:00
86c22636eb Merge branch 'master' into codegen-on-new-ast 2023-02-09 23:05:54 +01:00
30d20a453b tweak SymbolTable and fix its unittest 2023-02-09 22:58:21 +01:00
fe29d8a23f tweak codegen of inline sub 2023-02-09 21:59:09 +01:00
694d088160 some cleanups about asmsub return registers and types 2023-02-09 03:19:57 +01:00
6aabbffc62 some cleanups 2023-02-09 02:34:18 +01:00
623329fb33 fix 2023-02-05 17:08:24 +01:00
9f0074eef9 Merge branch 'master' into codegen-on-new-ast
# Conflicts:
#	codeCore/src/prog8/code/ast/AstStatements.kt
2023-02-05 16:44:30 +01:00
f117805129 order 2023-02-05 12:36:32 +01:00
c75b1581d2 lookup via new ST 2023-02-05 01:15:23 +01:00
109e118aba fix sub return register 2023-02-03 21:16:44 +01:00
201b77d5b6 boolean vs byte cast fixing, and pointervar error 2023-02-02 00:57:20 +01:00
a5ca08f33d fix popCpuStack to load values into asmsub register params 2023-02-01 22:00:37 +01:00
988a3e4446 group the three Pt nodes that represent a variable in the p8 source under single interface IPtVariable 2023-01-31 23:29:15 +01:00
0f5cd22bb7 more codegen fixes 2023-01-31 22:57:26 +01:00
2f5bed36b3 remove bool to ubyte typecasts 2023-01-31 01:25:44 +01:00
5b6534bb28 fix symbol lookup in new ast and minor codegen errors 2023-01-31 00:18:21 +01:00
e31e5b2477 got rid of PtScopeVarsDecls 2023-01-29 13:49:27 +01:00
07d5fafe2e Merge branch 'master' into codegen-on-new-ast
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt
2023-01-29 13:34:00 +01:00
8a4979f44c vm target 'zeropage' more robust 2023-01-29 12:47:12 +01:00
e67464325f fix missing symboltable entries for asmgen 2023-01-28 00:00:23 +01:00
94c9b0d23b Merge branch 'master' into codegen-on-new-ast 2023-01-27 22:14:57 +01:00
c78d1e3c39 implemented Pt findTarget and siblings 2023-01-27 01:51:21 +01:00
e94319145f test 2023-01-26 01:41:44 +01:00
3f3b01b5f6 Merge branch 'master' into codegen-on-new-ast 2023-01-26 01:40:30 +01:00
4e8ccf0ef3 Merge branch 'master' into codegen-on-new-ast 2023-01-26 00:38:54 +01:00
48c9349ce9 working on codegen fixes 2023-01-25 01:57:25 +01:00
117d848466 consolidate builtin function definitions into codeCore 2023-01-25 00:23:00 +01:00
99c62aab36 Merge branch 'master' into codegen-on-new-ast
# Conflicts:
#	examples/test.p8
2023-01-24 01:51:20 +01:00
a3c0c7c96f Merge branch 'master' into codegen-on-new-ast
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
#	examples/test.p8
2023-01-22 18:30:37 +01:00
9b209823f6 simple test 2023-01-22 17:10:52 +01:00
b2cb125bd4 more 6502 codegen on new Pt-AST. 2023-01-22 17:10:52 +01:00
5e8f767642 6502 codegen on new Pt-AST. 2023-01-22 17:10:52 +01:00
152 changed files with 4855 additions and 4196 deletions

View File

@ -1,35 +0,0 @@
#### Just a few remarks upfront:
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
#### Problems with `compilerAst`:
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
#### Steps to take, in conceptual (!) order:
(note: all these steps have been implemented, rejected or otherwise solved now.)
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
- from the local file system (use case: user programs)
- from resources (prog8lib)
- from plain strings (for testing)
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
- plus, optionally, method's for registering/unregistering a listener with the parser
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
6. move `IStringEncoding` to module `compiler`
7. same with `ModuleImporter`, then rewrite that (addressing #46)
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
9. re-think `IStringEncoding` to address #38

View File

@ -1,5 +1,7 @@
package prog8.code
import prog8.code.ast.PtNode
import prog8.code.ast.PtProgram
import prog8.code.core.*
@ -7,7 +9,7 @@ import prog8.code.core.*
* Tree structure containing all symbol definitions in the program
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
*/
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GLOBAL, astProgram) {
/**
* The table as a flat mapping of scoped names to the StNode.
* This gives the fastest lookup possible (no need to traverse tree nodes)
@ -52,7 +54,17 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
}
val allMemorySlabs: Collection<StMemorySlab> by lazy {
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null }
val vars = mutableListOf<StMemorySlab>()
fun collect(node: StNode) {
for(child in node.children) {
if(child.value.type== StNodeType.MEMORYSLAB)
vars.add(child.value as StMemorySlab)
else
collect(child.value)
}
}
collect(this)
vars
}
override fun lookup(scopedName: String) = flat[scopedName]
@ -76,7 +88,7 @@ enum class StNodeType {
open class StNode(val name: String,
val type: StNodeType,
val position: Position,
val astNode: PtNode,
val children: MutableMap<String, StNode> = mutableMapOf()
) {
@ -145,33 +157,31 @@ open class StNode(val name: String,
class StStaticVariable(name: String,
val dt: DataType,
val bss: Boolean,
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
val onetimeInitializationStringValue: StString?,
val onetimeInitializationArrayValue: StArray?,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
val zpwish: ZeropageWish,
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
val zpwish: ZeropageWish, // used in the variable allocator
astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null
init {
if(bss) {
require(onetimeInitializationNumericValue==null)
require(onetimeInitializationStringValue==null)
require(onetimeInitializationArrayValue.isNullOrEmpty())
} else {
require(onetimeInitializationNumericValue!=null ||
onetimeInitializationStringValue!=null ||
onetimeInitializationArrayValue!=null)
}
if(length!=null) {
require(onetimeInitializationNumericValue == null)
if(onetimeInitializationArrayValue!=null)
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
}
if(onetimeInitializationNumericValue!=null)
if(onetimeInitializationNumericValue!=null) {
require(dt in NumericDatatypes)
if(onetimeInitializationArrayValue!=null)
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
}
if(onetimeInitializationArrayValue!=null) {
require(dt in ArrayDatatypes)
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
}
}
if(onetimeInitializationStringValue!=null) {
require(dt == DataType.STR)
require(length == onetimeInitializationStringValue.first.length+1)
@ -180,8 +190,8 @@ class StStaticVariable(name: String,
}
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
StNode(name, StNodeType.CONSTANT, position) {
class StConstant(name: String, val dt: DataType, val value: Double, astNode: PtNode) :
StNode(name, StNodeType.CONSTANT, astNode) {
}
@ -189,31 +199,31 @@ class StMemVar(name: String,
val dt: DataType,
val address: UInt,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
position: Position) :
StNode(name, StNodeType.MEMVAR, position) {
astNode: PtNode) :
StNode(name, StNodeType.MEMVAR, astNode) {
}
class StMemorySlab(
name: String,
val size: UInt,
val align: UInt,
position: Position
astNode: PtNode
):
StNode(name, StNodeType.MEMORYSLAB, position) {
StNode(name, StNodeType.MEMORYSLAB, astNode) {
}
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
StNode(name, StNodeType.SUBROUTINE, position) {
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) :
StNode(name, StNodeType.SUBROUTINE, astNode) {
}
class StRomSub(name: String,
val address: UInt,
val parameters: List<StRomSubParameter>,
val returns: List<RegisterOrStatusflag>,
position: Position) :
StNode(name, StNodeType.ROMSUB, position) {
}
val returns: List<StRomSubParameter>,
astNode: PtNode) :
StNode(name, StNodeType.ROMSUB, astNode)
class StSubroutineParameter(val name: String, val type: DataType)

View File

@ -0,0 +1,207 @@
package prog8.code
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.VMTarget
import java.util.*
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
fun make(): SymbolTable {
val st = SymbolTable(program)
BuiltinFunctions.forEach {
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
}
val scopestack = Stack<StNode>()
scopestack.push(st)
program.children.forEach {
addToSt(it, scopestack)
}
require(scopestack.size==1)
if(options.compTarget.name != VMTarget.NAME) {
listOf(
PtMemMapped("P8ZP_SCRATCH_B1", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_B1, null, Position.DUMMY),
PtMemMapped("P8ZP_SCRATCH_REG", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_REG, null, Position.DUMMY),
PtMemMapped("P8ZP_SCRATCH_W1", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W1, null, Position.DUMMY),
PtMemMapped("P8ZP_SCRATCH_W2", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W2, null, Position.DUMMY),
PtMemMapped("P8ESTACK_LO", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_LO, 256u, Position.DUMMY),
PtMemMapped("P8ESTACK_HI", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_HI, 256u, Position.DUMMY)
).forEach {
it.parent = program
st.add(StMemVar(it.name, it.type, it.address, null, it))
}
}
return st
}
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
val stNode = when(node) {
is PtAsmSub -> {
if(node.address==null) {
val params = node.parameters.map { StSubroutineParameter(it.second.name, it.second.type) }
StSub(node.name, params, node.returns.singleOrNull()?.second, node)
} else {
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
StRomSub(node.name, node.address, parameters, returns, node)
}
}
is PtBlock -> {
StNode(node.name, StNodeType.BLOCK, node)
}
is PtConstant -> {
StConstant(node.name, node.type, node.value, node)
}
is PtLabel -> {
StNode(node.name, StNodeType.LABEL, node)
}
is PtMemMapped -> {
StMemVar(node.name, node.type, node.address, node.arraySize?.toInt(), node)
}
is PtSub -> {
val params = node.parameters.map {StSubroutineParameter(it.name, it.type) }
StSub(node.name, params, node.returntype, node)
}
is PtVariable -> {
val initialNumeric: Double?
val initialString: StString?
val initialArray: StArray?
val numElements: Int?
val value = node.value
if(value!=null) {
val number = (value as? PtNumber)?.number
initialNumeric = if(number==0.0) null else number // 0 as init value -> just uninitialized
when (value) {
is PtString -> {
initialString = StString(value.value, value.encoding)
initialArray = null
numElements = value.value.length + 1 // include the terminating 0-byte
}
is PtArray -> {
val array = makeInitialArray(value)
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
initialString = null
numElements = array.size
require(node.arraySize?.toInt()==numElements)
}
else -> {
initialString = null
initialArray = null
numElements = node.arraySize?.toInt()
}
}
} else {
initialNumeric = null
initialArray = null
initialString = null
numElements = node.arraySize?.toInt()
}
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
}
is PtBuiltinFunctionCall -> {
if(node.name=="memory") {
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
require(node.name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val slabname = (node.args[0] as PtString).value
val size = (node.args[1] as PtNumber).number.toUInt()
val align = (node.args[2] as PtNumber).number.toUInt()
// don't add memory slabs in nested scope, just put them in the top level of the ST
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
}
null
}
else -> null // node is not present in the ST
}
if(stNode!=null) {
scope.peek().add(stNode)
scope.push(stNode)
}
node.children.forEach {
addToSt(it, scope)
}
if(stNode!=null)
scope.pop()
}
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
return value.children.map {
when(it) {
is PtAddressOf -> StArrayElement(null, it.identifier.name)
is PtIdentifier -> StArrayElement(null, it.name)
is PtNumber -> StArrayElement(it.number, null)
else -> throw AssemblyError("invalid array element $it")
}
}
}
}
// override fun visit(decl: VarDecl) {
// val node =
// when(decl.type) {
// VarDeclType.VAR -> {
// var initialNumeric = (decl.value as? NumericLiteral)?.number
// if(initialNumeric==0.0)
// initialNumeric=null // variable will go into BSS and this will be set to 0
// val initialStringLit = decl.value as? StringLiteral
// val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
// val initialArrayLit = decl.value as? ArrayLiteral
// val initialArray = makeInitialArray(initialArrayLit)
// if(decl.isArray && decl.datatype !in ArrayDatatypes)
// throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
// val numElements =
// if(decl.isArray)
// decl.arraysize!!.constIndex()
// else if(initialStringLit!=null)
// initialStringLit.value.length+1 // include the terminating 0-byte
// else
// null
// val bss = if(decl.datatype==DataType.STR)
// false
// else if(decl.isArray)
// initialArray.isNullOrEmpty()
// else
// initialNumeric == null
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
// StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, astNode, decl.position)
// }
// VarDeclType.CONST -> {
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
// StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, astNode, decl.position)
// }
// VarDeclType.MEMORY -> {
// val numElements =
// if(decl.datatype in ArrayDatatypes)
// decl.arraysize!!.constIndex()
// else null
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
// StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, astNode, decl.position)
// }
// }
// scopestack.peek().add(node)
// // st.origAstLinks[decl] = node
// }
//
// private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
// if(arrayLit==null)
// return null
// return arrayLit.value.map {
// when(it){
// is AddressOf -> {
// val scopedName = it.identifier.targetNameAndType(program).first
// StArrayElement(null, scopedName)
// }
// is IdentifierReference -> {
// val scopedName = it.targetNameAndType(program).first
// StArrayElement(null, scopedName)
// }
// is NumericLiteral -> StArrayElement(it.number, null)
// else -> throw FatalAstException("weird element dt in array literal")
// }
// }.toList()
// }
//

View File

@ -3,6 +3,7 @@ package prog8.code.ast
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.core.Position
import prog8.code.core.SourceCode
import java.nio.file.Path
// New simplified AST for the code generator.
@ -13,16 +14,6 @@ sealed class PtNode(val position: Position) {
val children = mutableListOf<PtNode>()
lateinit var parent: PtNode
fun printIndented(indent: Int) {
print(" ".repeat(indent))
print("${this.javaClass.simpleName} ")
printProperties()
println()
children.forEach { it.printIndented(indent+1) }
}
abstract fun printProperties()
fun add(child: PtNode) {
children.add(child)
child.parent = this
@ -36,12 +27,11 @@ sealed class PtNode(val position: Position) {
fun definingBlock() = findParentNode<PtBlock>(this)
fun definingSub() = findParentNode<PtSub>(this)
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
fun definingISub() = findParentNode<IPtSubroutine>(this)
}
class PtNodeGroup : PtNode(Position.DUMMY) {
override fun printProperties() {}
}
class PtNodeGroup : PtNode(Position.DUMMY)
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
@ -65,10 +55,6 @@ class PtProgram(
val memsizer: IMemSizer,
val encoding: IStringEncoding
) : PtNode(Position.DUMMY) {
fun print() = printIndented(0)
override fun printProperties() {
print("'$name'")
}
// fun allModuleDirectives(): Sequence<PtDirective> =
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
@ -86,12 +72,9 @@ class PtBlock(name: String,
val library: Boolean,
val forceOutput: Boolean,
val alignment: BlockAlignment,
val source: SourceCode, // taken from the module the block is defined in.
position: Position
) : PtNamedNode(name, position) {
override fun printProperties() {
print("$name addr=$address library=$library forceOutput=$forceOutput alignment=$alignment")
}
enum class BlockAlignment {
NONE,
WORD,
@ -101,8 +84,6 @@ class PtBlock(name: String,
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
override fun printProperties() {}
init {
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
@ -110,28 +91,16 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
}
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print(name)
}
}
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
class PtBreakpoint(position: Position): PtNode(position) {
override fun printProperties() {}
}
class PtBreakpoint(position: Position): PtNode(position)
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position) {
override fun printProperties() {
print("filename=$file offset=$offset length=$length")
}
}
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position)
class PtNop(position: Position): PtNode(position) {
override fun printProperties() {}
}
class PtNop(position: Position): PtNode(position)
// find the parent node of a specific type or interface

View File

@ -1,9 +1,8 @@
package prog8.code.ast
import prog8.code.core.DataType
import prog8.code.core.Encoding
import prog8.code.core.Position
import prog8.code.core.*
import java.util.*
import kotlin.math.abs
import kotlin.math.round
@ -23,10 +22,6 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
}
}
override fun printProperties() {
print(type)
}
infix fun isSameAs(other: PtExpression): Boolean {
return when(this) {
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
@ -43,6 +38,69 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
else -> false
}
}
infix fun isSameAs(target: PtAssignTarget): Boolean {
return when {
target.memory != null && this is PtMemoryByte-> {
target.memory!!.address isSameAs this.address
}
target.identifier != null && this is PtIdentifier -> {
this.name == target.identifier!!.name
}
target.array != null && this is PtArrayIndexer -> {
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index
}
else -> false
}
}
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt()
fun isSimple(): Boolean {
return when(this) {
is PtAddressOf -> true
is PtArray -> true
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
is PtBinaryExpression -> false
is PtBuiltinFunctionCall -> name in arrayOf("msb", "lsb", "peek", "peekw", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd")
is PtContainmentCheck -> false
is PtFunctionCall -> false
is PtIdentifier -> true
is PtMachineRegister -> true
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
is PtNumber -> true
is PtPrefix -> value.isSimple()
is PtRange -> true
is PtString -> true
is PtTypeCast -> value.isSimple()
}
}
/*
fun clone(): PtExpression {
fun withClonedChildrenFrom(orig: PtExpression, clone: PtExpression): PtExpression {
orig.children.forEach { clone.add((it as PtExpression).clone()) }
return clone
}
when(this) {
is PtAddressOf -> return withClonedChildrenFrom(this, PtAddressOf(position))
is PtArray -> return withClonedChildrenFrom(this, PtArray(type, position))
is PtArrayIndexer -> return withClonedChildrenFrom(this, PtArrayIndexer(type, position))
is PtBinaryExpression -> return withClonedChildrenFrom(this, PtBinaryExpression(operator, type, position))
is PtBuiltinFunctionCall -> return withClonedChildrenFrom(this, PtBuiltinFunctionCall(name, void, hasNoSideEffects, type, position))
is PtContainmentCheck -> return withClonedChildrenFrom(this, PtContainmentCheck(position))
is PtFunctionCall -> return withClonedChildrenFrom(this, PtFunctionCall(name, void, type, position))
is PtIdentifier -> return withClonedChildrenFrom(this, PtIdentifier(name, type, position))
is PtMachineRegister -> return withClonedChildrenFrom(this, PtMachineRegister(register, type, position))
is PtMemoryByte -> return withClonedChildrenFrom(this, PtMemoryByte(position))
is PtNumber -> return withClonedChildrenFrom(this, PtNumber(type, number, position))
is PtPrefix -> return withClonedChildrenFrom(this, PtPrefix(operator, type, position))
is PtRange -> return withClonedChildrenFrom(this, PtRange(type, position))
is PtString -> return withClonedChildrenFrom(this, PtString(value, encoding, position))
is PtTypeCast -> return withClonedChildrenFrom(this, PtTypeCast(type, position))
}
}
*/
}
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
@ -51,11 +109,15 @@ class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
}
class PtArrayIndexer(type: DataType, position: Position): PtExpression(type, position) {
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
val variable: PtIdentifier
get() = children[0] as PtIdentifier
val index: PtExpression
get() = children[1] as PtExpression
init {
require(elementType in NumericDatatypes)
}
}
@ -66,6 +128,9 @@ class PtArray(type: DataType, position: Position): PtExpression(type, position)
return false
return type==other.type && children == other.children
}
val size: Int
get() = children.size
}
@ -81,9 +146,6 @@ class PtBuiltinFunctionCall(val name: String,
val args: List<PtExpression>
get() = children.map { it as PtExpression }
override fun printProperties() {
print("$name void=$void noSideFx=$hasNoSideEffects")
}
}
@ -93,10 +155,6 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
get() = children[0] as PtExpression
val right: PtExpression
get() = children[1] as PtExpression
override fun printProperties() {
print("$operator -> $type")
}
}
@ -119,28 +177,25 @@ class PtFunctionCall(val name: String,
val args: List<PtExpression>
get() = children.map { it as PtExpression }
override fun printProperties() {
print("$name void=$void")
}
}
class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position) {
override fun printProperties() {
print("$name $type")
}
}
class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position)
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
val address: PtExpression
get() = children.single() as PtExpression
override fun printProperties() {}
}
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
companion object {
fun fromBoolean(bool: Boolean, position: Position): PtNumber =
PtNumber(DataType.UBYTE, if(bool) 1.0 else 0.0, position)
}
init {
if(type==DataType.BOOL)
throw IllegalArgumentException("bool should have become ubyte @$position")
@ -151,10 +206,6 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
}
}
override fun printProperties() {
print("$number ($type)")
}
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
@ -175,10 +226,6 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
}
override fun printProperties() {
print(operator)
}
}
@ -190,15 +237,36 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
val step: PtNumber
get() = children[2] as PtNumber
override fun printProperties() {}
fun toConstantIntegerRange(): IntProgression? {
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
val fromLv = from as? PtNumber
val toLv = to as? PtNumber
val stepLv = step as? PtNumber
if(fromLv==null || toLv==null || stepLv==null)
return null
val fromVal = fromLv.number.toInt()
val toVal = toLv.number.toInt()
val stepVal = stepLv.number.toInt()
return makeRange(fromVal, toVal, stepVal)
}
}
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
override fun printProperties() {
print("$encoding:\"$value\"")
}
override fun hashCode(): Int = Objects.hash(value, encoding)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtString)
@ -214,12 +282,8 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
}
// special node that isn't created from compiling user code, but used internally
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position) {
override fun printProperties() {
print("reg=$register $type")
}
}
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null

View File

@ -10,8 +10,9 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
fun txt(node: PtNode): String {
return when(node) {
is PtAssignTarget -> ""
is PtAssignTarget -> "<target>"
is PtAssignment -> "<assign>"
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
is PtBreakpoint -> "%breakpoint"
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
is PtAddressOf -> "&"
@ -30,7 +31,10 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
is PtIdentifier -> "${node.name} ${type(node.type)}"
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
is PtMemoryByte -> "@()"
is PtNumber -> "${node.number.toHex()} ${type(node.type)}"
is PtNumber -> {
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
"$numstr ${type(node.type)}"
}
is PtPrefix -> node.operator
is PtRange -> "<range>"
is PtString -> "\"${node.value.escape()}\""
@ -57,7 +61,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
is PtAsmSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
val returns = if (node.returnTypes.isEmpty()) "" else (if (node.returnTypes.size == 1) "-> ${node.returnTypes[0].name.lowercase()}" else "-> ${node.returnTypes.map { it.name.lowercase() }}")
val returns = if (node.returns.isEmpty()) "" else (if (node.returns.size == 1) "-> ${node.returns[0].second.name.lowercase()}" else "-> ${node.returns.map { it.second.name.lowercase() }}")
val str = if (node.inline) "inline " else ""
if(node.address==null) {
str + "asmsub ${node.name}($params) $clobbers $returns"
@ -66,7 +70,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
}
}
is PtBlock -> {
val addr = if(node.address==null) "" else "@${node.address?.toHex()}"
val addr = if(node.address==null) "" else "@${node.address.toHex()}"
val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}"
"\nblock '${node.name}' $addr $align"
}
@ -86,8 +90,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
}
is PtSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
var str = if(node.inline) "inline " else ""
str += "sub ${node.name}($params) "
var str = "sub ${node.name}($params) "
if(node.returntype!=null)
str += "-> ${node.returntype.name.lowercase()}"
str
@ -104,7 +107,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
else
"${node.type.name.lowercase()} ${node.name}"
if(node.value!=null)
str + " = " + txt(node.value!!)
str + " = " + txt(node.value)
else
str
}
@ -135,6 +138,7 @@ fun printAst(root: PtNode, output: (text: String) -> Unit) {
output(" ".repeat(depth) + txt(node))
}
}
println()
} else {
walkAst(root) { node, depth ->
val txt = txt(node)

View File

@ -3,48 +3,39 @@ package prog8.code.ast
import prog8.code.core.*
sealed interface IPtSubroutine {
val name: String
}
class PtAsmSub(
name: String,
val address: UInt?,
val clobbers: Set<CpuRegister>,
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>,
val returnTypes: List<DataType>, // TODO join with register as Pairs ?
val retvalRegisters: List<RegisterOrStatusflag>,
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
val inline: Boolean,
position: Position
) : PtNamedNode(name, position) {
override fun printProperties() {
print("$name inline=$inline")
}
}
) : PtNamedNode(name, position), IPtSubroutine
class PtSub(
name: String,
val parameters: List<PtSubroutineParameter>,
val returntype: DataType?,
val inline: Boolean,
position: Position
) : PtNamedNode(name, position) {
override fun printProperties() {
print(name)
}
) : PtNamedNode(name, position), IPtSubroutine {
init {
// params and return value should not be str
if(parameters.any{ it.type !in NumericDatatypes })
throw AssemblyError("non-numeric parameter")
if(returntype!=null && returntype !in NumericDatatypes)
throw AssemblyError("non-numeric returntype $returntype")
parameters.forEach { it.parent=this }
}
}
class PtSubroutineParameter(val name: String, val type: DataType, position: Position): PtNode(position) {
override fun printProperties() {
print("$type $name")
}
}
class PtSubroutineParameter(name: String, val type: DataType, position: Position): PtNamedNode(name, position)
class PtAssignment(position: Position) : PtNode(position) {
@ -52,38 +43,22 @@ class PtAssignment(position: Position) : PtNode(position) {
get() = children[0] as PtAssignTarget
val value: PtExpression
get() = children[1] as PtExpression
}
override fun printProperties() { }
val isInplaceAssign: Boolean by lazy {
val target = target.children.single() as PtExpression
when(val source = value) {
is PtArrayIndexer -> {
if(target is PtArrayIndexer && source.type==target.type) {
if(target.variable isSameAs source.variable) {
target.index isSameAs source.index
}
}
false
}
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.name==source.name
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
is PtAddressOf -> target is PtAddressOf && target.identifier isSameAs source.identifier
is PtPrefix -> {
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
||
(target is PtIdentifier && (source.value as? PtIdentifier)?.name==target.name)
}
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
is PtBinaryExpression ->
target isSameAs source.left
else -> false
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position) {
val target: PtAssignTarget
get() = children[0] as PtAssignTarget
val value: PtExpression
get() = children[1] as PtExpression
init {
require(operator.endsWith('=') || operator in PrefixOperators) {
"invalid augmented assign operator $operator"
}
}
}
class PtAssignTarget(position: Position) : PtNode(position) {
val identifier: PtIdentifier?
get() = children.single() as? PtIdentifier
@ -102,7 +77,7 @@ class PtAssignTarget(position: Position) : PtNode(position) {
}
}
override fun printProperties() {}
infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
}
@ -111,10 +86,6 @@ class PtConditionalBranch(val condition: BranchCondition, position: Position) :
get() = children[0] as PtNodeGroup
val falseScope: PtNodeGroup
get() = children[1] as PtNodeGroup
override fun printProperties() {
print(condition)
}
}
@ -125,8 +96,6 @@ class PtForLoop(position: Position) : PtNode(position) {
get() = children[1] as PtExpression
val statements: PtNodeGroup
get() = children[2] as PtNodeGroup
override fun printProperties() {}
}
@ -137,8 +106,6 @@ class PtIfElse(position: Position) : PtNode(position) {
get() = children[1] as PtNodeGroup
val elseScope: PtNodeGroup
get() = children[2] as PtNodeGroup
override fun printProperties() {}
}
@ -146,10 +113,8 @@ class PtJump(val identifier: PtIdentifier?,
val address: UInt?,
val generatedLabel: String?,
position: Position) : PtNode(position) {
override fun printProperties() {
identifier?.printProperties()
if(address!=null) print(address.toHex())
if(generatedLabel!=null) print(generatedLabel)
init {
identifier?.let {it.parent = this }
}
}
@ -157,10 +122,6 @@ class PtJump(val identifier: PtIdentifier?,
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
val target: PtAssignTarget
get() = children.single() as PtAssignTarget
override fun printProperties() {
print(operator)
}
}
@ -169,8 +130,6 @@ class PtRepeatLoop(position: Position) : PtNode(position) {
get() = children[0] as PtExpression
val statements: PtNodeGroup
get() = children[1] as PtNodeGroup
override fun printProperties() {}
}
@ -183,30 +142,26 @@ class PtReturn(position: Position) : PtNode(position) {
else
null
}
override fun printProperties() {}
}
class PtVariable(name: String, val type: DataType, var value: PtExpression?, var arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print("$type $name")
sealed interface IPtVariable {
val name: String
val type: DataType
}
class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
init {
value?.let {it.parent=this}
}
}
class PtConstant(name: String, val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print("$type $name = $value")
}
}
class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable
class PtMemMapped(name: String, val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print("&$type $name = ${address.toHex()}")
}
}
class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable
class PtWhen(position: Position) : PtNode(position) {
@ -214,8 +169,6 @@ class PtWhen(position: Position) : PtNode(position) {
get() = children[0] as PtExpression
val choices: PtNodeGroup
get() = children[1] as PtNodeGroup
override fun printProperties() {}
}
@ -224,5 +177,4 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
get() = children[0] as PtNodeGroup
val statements: PtNodeGroup
get() = children[1] as PtNodeGroup
override fun printProperties() {}
}

View File

@ -0,0 +1,110 @@
package prog8.code.core
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 {
val paramConvs = params.mapIndexed { index, it ->
when {
it.reg!=null -> "$index:${it.reg}"
it.variable -> "$index:variable"
else -> "$index:???"
}
}
val returnConv =
when {
returns.reg!=null -> returns.reg.toString()
returns.floatFac1 -> "floatFAC1"
else -> "<no returnvalue>"
}
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
}
}
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
class FSignature(val pure: Boolean, // does it have side effects?
val parameters: List<FParam>,
val returnType: DataType?) {
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
val returns: ReturnConvention = 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)
null -> ReturnConvention(null, null, 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)
}
}
}
return when {
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
actualParamTypes.size==1 -> {
// 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)
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
}
CallConvention(listOf(paramConv), returns)
}
else -> {
// multiple parameters go via variables
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
CallConvention(paramConvs, returns)
}
}
}
}
val BuiltinFunctions: Map<String, FSignature> = mapOf(
// this set of function have no return value and operate in-place:
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// cmp returns a status in the carry flag, but not a proper return value
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
"abs" to FSignature(true, listOf(FParam("value", IntegerDatatypesNoBool)), DataType.UWORD),
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
// normal functions follow:
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values())), DataType.UBYTE),
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
"sqrt16" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
"pushw" to FSignature(false, listOf(FParam("value", WordDatatypes)), null),
"rsave" to FSignature(false, emptyList(), null),
"rsavex" to FSignature(false, emptyList(), null),
"rrestore" to FSignature(false, emptyList(), null),
"rrestorex" to FSignature(false, emptyList(), null),
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
)
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")

View File

@ -16,11 +16,15 @@ class CompilationOptions(val output: OutputType,
var slowCodegenWarnings: Boolean = false,
var optimize: Boolean = false,
var optimizeFloatExpressions: Boolean = false,
var dontReinitGlobals: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
var experimentalCodegen: Boolean = false,
var varsHigh: Boolean = false,
var evalStackBaseAddress: UInt? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
)
) {
init {
compTarget.machine.initializeMemoryAreas(this)
}
}

View File

@ -1,12 +0,0 @@
package prog8.code.core
interface IAssemblyGenerator {
fun compileToAssembly(): IAssemblyProgram?
}
interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions): Boolean
}
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"

View File

@ -0,0 +1,19 @@
package prog8.code.core
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
interface ICodeGeneratorBackend {
fun generate(program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter): IAssemblyProgram?
}
interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions): Boolean
}
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"

View File

@ -7,5 +7,5 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val defaultEncoding: Encoding
override fun encodeString(str: String, encoding: Encoding): List<UByte>
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
}

View File

@ -16,6 +16,8 @@ interface IMachineDefinition {
var ESTACK_LO: UInt
var ESTACK_HI: UInt
val PROGRAM_LOAD_ADDRESS : UInt
val BSSHIGHRAM_START: UInt
val BSSHIGHRAM_END: UInt
val cpu: CpuType
var zeropage: Zeropage

View File

@ -10,5 +10,5 @@ enum class Encoding(val prefix: String) {
interface IStringEncoding {
fun encodeString(str: String, encoding: Encoding): List<UByte>
fun decodeString(bytes: List<UByte>, encoding: Encoding): String
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
}

View File

@ -11,7 +11,7 @@ class MemAllocationError(message: String) : Exception(message)
abstract class MemoryAllocator(protected val options: CompilationOptions) {
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
abstract fun allocate(name: List<String>,
abstract fun allocate(name: String,
datatype: DataType,
numElements: Int?,
position: Position?,
@ -29,7 +29,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
// the variables allocated into Zeropage.
// name (scoped) ==> pair of address to (Datatype + bytesize)
val allocatedVariables = mutableMapOf<List<String>, VarAllocation>()
val allocatedVariables = mutableMapOf<String, VarAllocation>()
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
@ -51,7 +51,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
return free.windowed(2).any { it[0] == it[1] - 1u }
}
override fun allocate(name: List<String>,
override fun allocate(name: String,
datatype: DataType,
numElements: Int?,
position: Position?,
@ -107,7 +107,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
private fun reserve(range: UIntRange) = free.removeAll(range)
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List<String>): UInt {
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: String): UInt {
require(size>=0)
free.removeAll(address until address+size.toUInt())
if(name.isNotEmpty()) {
@ -135,7 +135,7 @@ class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAlloc
private var nextLocation: UInt = region.first
override fun allocate(
name: List<String>,
name: String,
datatype: DataType,
numElements: Int?,
position: Position?,

View File

@ -5,6 +5,7 @@ val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not")
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val BitwiseOperators = setOf("&", "|", "^", "~")
val PrefixOperators = setOf("+", "-", "~", "not")
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
fun invertedComparisonOperator(operator: String) =

View File

@ -23,7 +23,7 @@ object Encoder: IStringEncoding {
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)

View File

@ -13,9 +13,12 @@ class AtariMachineDefinition: IMachineDefinition {
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 var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive // TODO
override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive // TODO
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam

View File

@ -14,9 +14,13 @@ class C128MachineDefinition: IMachineDefinition {
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive
override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam

View File

@ -15,9 +15,13 @@ class C64MachineDefinition: IMachineDefinition {
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x0801u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0xcf00u // $cf00-$cf7f inclusive
override var ESTACK_HI = 0xcf80u // $cf80-$cfff inclusive
override val BSSHIGHRAM_START = 0xc000u
override val BSSHIGHRAM_END = ESTACK_LO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam

View File

@ -83,12 +83,12 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
free.remove((4+reg*2).toUInt())
free.remove((5+reg*2).toUInt())
}

View File

@ -208,7 +208,7 @@ object AtasciiEncoding {
return Ok(mapped)
}
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
}
}

View File

@ -27,7 +27,7 @@ object IsoEncoding {
}
}
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
} catch (ce: CharConversionException) {

View File

@ -14,9 +14,13 @@ class CX16MachineDefinition: IMachineDefinition {
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x0801u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x0700u // $0700-$077f inclusive
override var ESTACK_HI = 0x0780u // $0780-$07ff inclusive
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
override val BSSHIGHRAM_END = 0xc000u // rom starts here.
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam

View File

@ -58,12 +58,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
}
}
}

View File

@ -17,10 +17,18 @@ class VirtualMachineDefinition: IMachineDefinition {
override var ESTACK_LO = 0u // not actually used
override var ESTACK_HI = 0u // not actually used
override val BSSHIGHRAM_START = 0u // not actually used
override val BSSHIGHRAM_END = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
val bits = num.toFloat().toBits().toUInt()
val hexStr = bits.toString(16).padStart(8, '0')
val parts = hexStr.chunked(2).map { "\$" + it }
return parts.joinToString(", ")
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return listOf("syslib")
@ -44,9 +52,24 @@ class VirtualMachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = false
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = VirtualZeropage(compilerOptions)
}
}
interface IVirtualMachineRunner {
fun runProgram(irSource: String)
}
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
override val SCRATCH_B1: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_REG: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W1: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override fun allocateCx16VirtualRegisters() { /* there is no actual zero page in this target to allocate thing in */ }
}

View File

@ -3,6 +3,7 @@ plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.9"
}
java {
@ -25,11 +26,11 @@ compileTestKotlin {
dependencies {
implementation project(':codeCore')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
}
sourceSets {
@ -41,6 +42,22 @@ sourceSets {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDir "${project.projectDir}/test"
}
}
}
// note: there are no unit tests in this module!
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}

View File

@ -4,13 +4,15 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
</component>
</module>

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,15 @@
package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclType
import prog8.code.StConstant
import prog8.code.StMemVar
import prog8.code.SymbolTable
import prog8.code.core.IMachineDefinition
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, symbolTable: SymbolTable): Int {
var numberOfOptimizations = 0
@ -37,7 +36,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods = optimizeStoreLoadSame(linesByFour, machine, program)
mods = optimizeStoreLoadSame(linesByFour, machine, symbolTable)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
@ -52,14 +51,14 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen, machine, program)
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++
}
mods = optimizeSamePointerIndexing(linesByFourteen, machine, program)
mods = optimizeSamePointerIndexing(linesByFourteen)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
@ -129,7 +128,11 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
return mods
}
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
private fun optimizeSameAssignments(
linesByFourteen: List<List<IndexedValue<String>>>,
machine: IMachineDefinition,
symbolTable: SymbolTable
): List<Modification> {
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
@ -154,8 +157,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
val address1 = getAddressArg(first, program)
val address2 = getAddressArg(second, program)
val address1 = getAddressArg(first, symbolTable)
val address2 = getAddressArg(second, symbolTable)
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
mods.add(Modification(lines[4].index, true, null))
mods.add(Modification(lines[5].index, true, null))
@ -168,7 +171,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val secondvalue = third.substring(4)
if(firstvalue==secondvalue) {
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
val address = getAddressArg(first, program)
val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address))
mods.add(Modification(lines[2].index, true, null))
}
@ -251,7 +254,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val thirdvalue = third.substring(4)
val fourthvalue = fourth.substring(4)
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
val address = getAddressArg(first, program)
val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
@ -275,7 +278,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val firstvalue = first.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==thirdvalue) {
val address = getAddressArg(first, program)
val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
@ -295,7 +298,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
val address = getAddressArg(first, program)
val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
val reg2 = second[2]
@ -314,7 +317,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
if(firstvalue==secondvalue) {
val address = getAddressArg(first, program)
val address = getAddressArg(first, symbolTable)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[0].index, true, null))
@ -327,7 +330,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
return mods
}
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
// if Y isn't modified in between we can omit the second LDY:
@ -369,7 +372,11 @@ private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<
return mods
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
private fun optimizeStoreLoadSame(
linesByFour: List<List<IndexedValue<String>>>,
machine: IMachineDefinition,
symbolTable: SymbolTable
): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
@ -397,7 +404,7 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
}
else {
// no branch instruction follows, we can remove the load instruction
val address = getAddressArg(lines[2].value, program)
val address = getAddressArg(lines[2].value, symbolTable)
address==null || !machine.isIOAddress(address)
}
@ -439,7 +446,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
private fun getAddressArg(line: String, program: Program): UInt? {
private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? {
// try to get the constant value address, could return null if it's a symbol instead
val loadArg = line.trimStart().substring(3).trim()
return when {
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
@ -450,15 +458,11 @@ private fun getAddressArg(line: String, program: Program): UInt? {
val identMatch = identifierRegex.find(loadArg)
if(identMatch!=null) {
val identifier = identMatch.value
val decl = program.toplevelModule.lookup(identifier.split('.')) as? VarDecl
if(decl!=null) {
when(decl.type){
VarDeclType.VAR -> null
VarDeclType.CONST,
VarDeclType.MEMORY -> (decl.value as NumericLiteral).number.toUInt()
}
when (val symbol = symbolTable.flat[identifier]) {
is StConstant -> symbol.value.toUInt()
is StMemVar -> symbol.address
else -> null
}
else null
} else null
}
else -> loadArg.substring(1).toUIntOrNull()

View File

@ -1,15 +1,12 @@
package prog8.codegen.cpu6502
import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.BuiltinFunctionCall
import prog8.ast.expressions.Expression
import prog8.ast.statements.Subroutine
import prog8.code.ast.*
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
val order = mutableListOf<Int>()
// order is:
// 1) cx16 virtual word registers,
@ -17,43 +14,45 @@ fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
// 3) single CPU registers (X last), except A,
// 4) CPU Carry status flag
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
val args = sub.parameters.withIndex()
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
regsWithoutA.forEach {
if(it.value.second.registerOrPair != RegisterOrPair.X)
if(it.value.first.registerOrPair != RegisterOrPair.X)
order += it.index
}
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
rest.forEach { order += it.index }
regA.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
paramRegisters: List<RegisterOrStatusflag>): Boolean {
fun isClobberRisk(expr: Expression): Boolean {
fun asmsub6502ArgsHaveRegisterClobberRisk(
args: List<PtExpression>,
params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
): Boolean {
fun isClobberRisk(expr: PtExpression): Boolean {
when (expr) {
is ArrayIndexedExpression -> {
return paramRegisters.any {
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
is PtArrayIndexer -> {
return params.any {
it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is BuiltinFunctionCall -> {
is PtBuiltinFunctionCall -> {
if (expr.name == "lsb" || expr.name == "msb")
return isClobberRisk(expr.args[0])
if (expr.name == "mkword")
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple
return !expr.isSimple()
}
else -> return !expr.isSimple
else -> return !expr.isSimple()
}
}

View File

@ -3,7 +3,6 @@ package prog8.codegen.cpu6502
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import prog8.ast.generatedLabelPrefix
import prog8.code.core.*
import java.io.File
import java.nio.file.Path
@ -63,7 +62,7 @@ internal class AssemblyProgram(
"atari" -> {
// Atari800XL .xex generation.
// TODO are these options okay?
// TODO are these options okay for atari?
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--no-monitor"
@ -104,7 +103,7 @@ internal class AssemblyProgram(
}
private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
val pattern = Regex("""al (\w+) \S+prog8_label_.+?""")
val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) {

View File

@ -1,49 +1,39 @@
package prog8.codegen.cpu6502
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
import prog8.compiler.BuiltinFunctions
import prog8.compiler.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program,
private val asmgen: AsmGen,
internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val assignAsmGen: AssignmentAsmGen) {
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 translateFunctioncallExpression(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? {
return translateFunctioncall(fcall, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
}
internal fun translateFunctioncallStatement(fcall: BuiltinFunctionCallStatement) {
val func = BuiltinFunctions.getValue(fcall.name)
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
internal fun translateFunctioncallStatement(fcall: PtBuiltinFunctionCall) {
translateFunctioncall(fcall, discardResult = true, resultToStack = false, resultRegister = null)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if (discardResult && func.pure)
return // can just ignore the whole function call altogether
private fun translateFunctioncall(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? {
if (discardResult && fcall.hasNoSideEffects)
return null // can just ignore the whole function call altogether
if(discardResult && resultToStack)
throw AssemblyError("cannot both discard the result AND put it onto stack")
val sscope = (fcall as Node).definingSubroutine
val sscope = fcall.definingISub()
when (func.name) {
when (fcall.name) {
"msb" -> funcMsb(fcall, resultToStack, resultRegister)
"lsb" -> funcLsb(fcall, resultToStack, resultRegister)
"mkword" -> funcMkword(fcall, resultToStack, resultRegister)
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
"abs" -> funcAbs(fcall, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, resultToStack, resultRegister, sscope)
"rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall)
@ -59,16 +49,20 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"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}"
require(fcall.args[0] is PtIdentifier) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${fcall.position}"
}
asmgen.popCpuStack(DataType.UBYTE, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine)
val symbol = asmgen.symbolTable.lookup((fcall.args[0] as PtIdentifier).name)
val target = symbol!!.astNode as IPtVariable
asmgen.popCpuStack(DataType.UBYTE, target, fcall.definingISub())
}
"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}"
require(fcall.args[0] is PtIdentifier) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${fcall.position}"
}
asmgen.popCpuStack(DataType.UWORD, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine)
val symbol = asmgen.symbolTable.lookup((fcall.args[0] as PtIdentifier).name)
val target = symbol!!.astNode as IPtVariable
asmgen.popCpuStack(DataType.UWORD, target, fcall.definingISub())
}
"rsave" -> funcRsave()
"rsavex" -> funcRsaveX()
@ -76,9 +70,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"rrestorex" -> funcRrestoreX()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall)
"callrom" -> funcCallRom(fcall)
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
}
return BuiltinFunctions.getValue(fcall.name).returnType
}
private fun funcRsave() {
@ -132,147 +127,60 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
}
private fun funcCallFar(fcall: IFunctionCall) {
private fun funcCallFar(fcall: PtBuiltinFunctionCall) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt() ?: 0
val argAddrArg = fcall.args[2]
if(bank==null)
throw AssemblyError("callfar (jsrfar) bank has to be a constant")
if(fcall.args[1].constValue(program) == null) {
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY, false)
asmgen.out(" sta (+)+0 | sty (+)+1 ; store jsrfar address word")
}
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
jsr cx16.jsrfar
+ .word ${address.toHex()}
.byte ${bank.toHex()}""")
} else {
when(argAddrArg) {
is AddressOf -> {
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
throw AssemblyError("callfar done with 'arg' pointer to variable that's not UBYTE")
asmgen.out("""
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr cx16.jsrfar
+ .word ${address.toHex()}
.byte ${bank.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
}
is NumericLiteral -> {
asmgen.out("""
lda ${argAddrArg.number.toHex()}
jsr cx16.jsrfar
+ .word ${address.toHex()}
.byte ${bank.toHex()}
sta ${argAddrArg.number.toHex()}""")
}
else -> throw AssemblyError("callfar only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
}
}
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.jsrfar
+ .word 0
+ .byte 0""")
// note that by convention the values in A+Y registers are now the return value of the call.
}
private fun funcCallRom(fcall: IFunctionCall) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callrom only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt()
if(bank==null || address==null)
throw AssemblyError("callrom requires constant arguments")
if(address !in 0xc000..0xffff)
throw AssemblyError("callrom done on address outside of cx16 banked rom")
if(bank>=32)
throw AssemblyError("callrom bank must be <32")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
jsr ${address.toHex()}
pla
sta $01""")
} else {
when(argAddrArg) {
is AddressOf -> {
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
throw AssemblyError("callrom done with 'arg' pointer to variable that's not UBYTE")
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr ${address.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}
pla
sta $01""")
}
is NumericLiteral -> {
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
lda ${argAddrArg.number.toHex()}
jsr ${address.toHex()}
sta ${argAddrArg.number.toHex()}
pla
sta $01""")
}
else -> throw AssemblyError("callrom only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
}
}
}
private fun funcCmp(fcall: IFunctionCall) {
private fun funcCmp(fcall: PtBuiltinFunctionCall) {
val arg1 = fcall.args[0]
val arg2 = fcall.args[1]
val dt1 = arg1.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val dt2 = arg2.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes) {
if(arg1.type in ByteDatatypes) {
if(arg2.type in ByteDatatypes) {
when (arg2) {
is IdentifierReference -> {
is PtIdentifier -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
}
is NumericLiteral -> {
is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number.toInt()}")
}
is DirectMemoryRead -> {
if(arg2.addressExpression is NumericLiteral) {
is PtMemoryByte -> {
if(arg2.address is PtNumber) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
asmgen.out(" cmp ${arg2.address.asConstInteger()!!.toHex()}")
} else {
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else {
asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
}
}
}
else -> {
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else {
asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
}
}
@ -280,10 +188,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} else
throw AssemblyError("args for cmp() should have same dt")
} else {
// dt1 is a word
if(dt2 in WordDatatypes) {
// arg1 is a word
if(arg2.type in WordDatatypes) {
when (arg2) {
is IdentifierReference -> {
is PtIdentifier -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy ${asmgen.asmVariableName(arg2)}+1
@ -291,7 +199,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
cmp ${asmgen.asmVariableName(arg2)}
+""")
}
is NumericLiteral -> {
is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy #>${arg2.number.toInt()}
@ -300,8 +208,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+""")
}
else -> {
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, fcall.definingISub())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
@ -310,7 +218,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+""")
} else {
asmgen.pushCpuStack(DataType.UWORD, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, fcall.definingISub())
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
@ -326,25 +234,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is BuiltinFunctionCall)
private fun funcMemory(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult)
throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as StringLiteral).value
val name = (fcall.args[0] as PtString).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
slabname.linkParents(fcall)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
val addressOf = PtAddressOf(fcall.position)
addressOf.add(slabname)
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = addressOf)
val target =
if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, null)
else
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
val assign = AsmAssignment(src, target, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign)
}
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
private fun funcSqrt16(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
else {
@ -353,13 +263,18 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcReverse(fcall: IFunctionCall) {
private fun funcReverse(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program)!!
if (variable is PtIdentifier) {
val symbol = asmgen.symbolTable.lookup(variable.name)
val decl = symbol!!.astNode as IPtVariable
val numElements = when(decl) {
is PtConstant -> throw AssemblyError("cannot reverse a constant")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
}
val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) {
when (decl.type) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
@ -392,13 +307,18 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcSort(fcall: IFunctionCall) {
private fun funcSort(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program)!!
if (variable is PtIdentifier) {
val symbol = asmgen.symbolTable.lookup(variable.name)
val decl = symbol!!.astNode as IPtVariable
val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) {
val numElements = when(decl) {
is PtConstant -> throw AssemblyError("cannot sort a constant")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
}
when (decl.type) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
@ -406,7 +326,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
asmgen.out(if (decl.type == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
@ -415,7 +335,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
asmgen.out(if (decl.type == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
}
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
else -> throw AssemblyError("weird type")
@ -424,26 +344,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
throw AssemblyError("weird type")
}
private fun funcRor2(fcall: IFunctionCall) {
private fun funcRor2(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.getOr(DataType.UNDEFINED)) {
when (what.type) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror2", 'b')
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "ror2", 'b')
asmgen.out(" jsr prog8_lib.ror2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteral).number
is PtMemoryByte -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
}
}
is IdentifierReference -> {
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
}
@ -452,11 +371,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror2", 'w')
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "ror2", 'w')
asmgen.out(" jsr prog8_lib.ror2_array_uw")
}
is IdentifierReference -> {
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
}
@ -467,26 +386,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcRor(fcall: IFunctionCall) {
private fun funcRor(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.getOr(DataType.UNDEFINED)) {
when (what.type) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror", 'b')
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "ror", 'b')
asmgen.out(" jsr prog8_lib.ror_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteral).number
is PtMemoryByte -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" ror ${number.toHex()}")
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
if(ptrAndIndex!=null) {
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
@ -495,7 +413,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+ ror ${'$'}ffff,x ; modified""")
asmgen.restoreRegisterLocal(CpuRegister.X)
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out("""
sta (+) + 1
sty (+) + 2
@ -503,7 +421,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
}
is IdentifierReference -> {
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable")
}
@ -512,11 +430,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror", 'w')
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "ror", 'w')
asmgen.out(" jsr prog8_lib.ror_array_uw")
}
is IdentifierReference -> {
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable+1 | ror $variable")
}
@ -527,26 +445,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcRol2(fcall: IFunctionCall) {
private fun funcRol2(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.getOr(DataType.UNDEFINED)) {
when (what.type) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol2", 'b')
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "rol2", 'b')
asmgen.out(" jsr prog8_lib.rol2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteral).number
is PtMemoryByte -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
}
}
is IdentifierReference -> {
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
}
@ -555,11 +472,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol2", 'w')
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "rol2", 'w')
asmgen.out(" jsr prog8_lib.rol2_array_uw")
}
is IdentifierReference -> {
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
}
@ -570,26 +487,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcRol(fcall: IFunctionCall) {
private fun funcRol(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.getOr(DataType.UNDEFINED)) {
when (what.type) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol", 'b')
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "rol", 'b')
asmgen.out(" jsr prog8_lib.rol_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteral).number
is PtMemoryByte -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" rol ${number.toHex()}")
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
if(ptrAndIndex!=null) {
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
@ -598,7 +514,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+ rol ${'$'}ffff,x ; modified""")
asmgen.restoreRegisterLocal(CpuRegister.X)
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out("""
sta (+) + 1
sty (+) + 2
@ -606,7 +522,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
}
is IdentifierReference -> {
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable")
}
@ -615,11 +531,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol", 'w')
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "rol", 'w')
asmgen.out(" jsr prog8_lib.rol_array_uw")
}
is IdentifierReference -> {
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable | rol $variable+1")
}
@ -630,22 +546,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) {
if(arrayvar.targetVarDecl(program)!!.datatype==DataType.UWORD) {
private fun translateRolRorArrayArgs(arrayvar: PtIdentifier, indexer: PtArrayIndexer, operation: String, dt: Char) {
if(arrayvar.type==DataType.UWORD) {
if(dt!='b')
throw AssemblyError("non-array var indexing requires bytes dt")
asmgen.assignExpressionToVariable(arrayvar, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
} else {
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
val addressOf = PtAddressOf(arrayvar.position)
addressOf.add(arrayvar)
addressOf.parent = arrayvar.parent.parent
asmgen.assignExpressionToVariable(addressOf, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
}
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
asmgen.assignExpressionToVariable(indexer.index, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
}
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program)
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
val dt = fcall.args.single().type
if(resultToStack) {
when (dt.getOr(DataType.UNDEFINED)) {
when (dt) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
@ -654,7 +573,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.getOr(DataType.UNDEFINED)) {
when (dt) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
@ -666,30 +585,30 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcAnyAll(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
val dt = fcall.args.single().type
if(resultToStack) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
when (dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_stack")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_stack")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_stack")
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A | ldy #0")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A | ldy #0")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A | ldy #0")
when (dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A | ldy #0")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A | ldy #0")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_into_A | ldy #0")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
}
}
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
val dt = fcall.args.single().type
if(resultToStack) {
when (dt) {
DataType.UBYTE -> asmgen.out(" ldy #0")
@ -710,19 +629,19 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcPokeW(fcall: IFunctionCall) {
private fun funcPokeW(fcall: PtBuiltinFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is NumericLiteral -> {
is PtNumber -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
val addr = addrExpr.number.toHex()
asmgen.out(" sta $addr | sty ${addr}+1")
return
}
is IdentifierReference -> {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
@ -742,14 +661,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
return
}
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
is PtBinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is PtIdentifier && addrExpr.right is PtNumber) {
val varname = asmgen.asmVariableName(addrExpr.left as PtIdentifier)
if(asmgen.isZpVar(addrExpr.left as PtIdentifier)) {
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val index = (addrExpr.right as NumericLiteral).number.toHex()
val index = (addrExpr.right as PtNumber).number.toHex()
asmgen.out("""
ldy #$index
sta ($varname),y
@ -769,13 +688,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(" jsr prog8_lib.func_pokew")
}
private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
private fun funcPeekW(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
when(val addrExpr = fcall.args[0]) {
is NumericLiteral -> {
is PtNumber -> {
val addr = addrExpr.number.toHex()
asmgen.out(" lda $addr | ldy ${addr}+1")
}
is IdentifierReference -> {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
@ -800,12 +719,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(" jsr prog8_lib.func_peekw")
}
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
is PtBinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is PtIdentifier && addrExpr.right is PtNumber) {
val varname = asmgen.asmVariableName(addrExpr.left as PtIdentifier)
if(asmgen.isZpVar(addrExpr.left as PtIdentifier)) {
// pointervar is already in the zero page, no need to copy
val index = (addrExpr.right as NumericLiteral).number.toHex()
val index = (addrExpr.right as PtNumber).number.toHex()
asmgen.out("""
ldy #$index
lda ($varname),y
@ -845,7 +764,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcMkword(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
private fun funcMkword(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(resultToStack) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
@ -854,12 +773,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
val reg = resultRegister ?: RegisterOrPair.AY
var needAsave = asmgen.needAsaveForExpr(fcall.args[0])
if(!needAsave) {
val mr0 = fcall.args[0] as? DirectMemoryRead
val mr1 = fcall.args[1] as? DirectMemoryRead
val mr0 = fcall.args[0] as? PtMemoryByte
val mr1 = fcall.args[1] as? PtMemoryByte
if (mr0 != null)
needAsave = mr0.addressExpression !is NumericLiteral
needAsave = mr0.address !is PtNumber
if (mr1 != null)
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral)
needAsave = needAsave or (mr1.address !is PtNumber)
}
when(reg) {
RegisterOrPair.AX -> {
@ -898,13 +817,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
private fun funcMsb(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (!arg.inferType(program).isWords)
if (arg.type !in WordDatatypes)
throw AssemblyError("msb required word argument")
if (arg is NumericLiteral)
if (arg is PtNumber)
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) {
if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg)
if(resultToStack) {
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
@ -952,14 +871,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
private fun funcLsb(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (!arg.inferType(program).isWords)
if (arg.type !in WordDatatypes)
throw AssemblyError("lsb required word argument")
if (arg is NumericLiteral)
if (arg is PtNumber)
throw AssemblyError("lsb(const) should have been const-folded away")
if (arg is IdentifierReference) {
if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg)
if(resultToStack) {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
@ -1013,35 +932,39 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
private fun outputAddressAndLenghtOfArray(arg: Expression) {
private fun outputAddressAndLenghtOfArray(arg: PtExpression) {
// address in P8ZP_SCRATCH_W1, number of elements in A
arg as IdentifierReference
val arrayVar = arg.targetVarDecl(program)!!
if(!arrayVar.isArray)
throw AssemblyError("length of non-array requested")
val size = arrayVar.arraysize!!.constIndex()!!
arg as PtIdentifier
val symbol = asmgen.symbolTable.lookup(arg.name)
val arrayVar = symbol!!.astNode as IPtVariable
val numElements = when(arrayVar) {
is PtConstant -> null
is PtMemMapped -> arrayVar.arraySize
is PtVariable -> arrayVar.arraySize
} ?: throw AssemblyError("length of non-array requested")
val identifierName = asmgen.asmVariableName(arg)
asmgen.out("""
lda #<$identifierName
ldy #>$identifierName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$size
lda #$numElements
""")
}
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
val callConv = signature.callConvention(args.map {
it.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
})
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
val signature = BuiltinFunctions.getValue(call.name)
val callConv = signature.callConvention(call.args.map { it.type})
fun getSourceForFloat(value: Expression): AsmAssignSource {
fun getSourceForFloat(value: PtExpression): AsmAssignSource {
return when (value) {
is IdentifierReference -> {
val addr = AddressOf(value, value.position)
is PtIdentifier -> {
val addr = PtAddressOf(value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
}
is NumericLiteral -> {
is PtNumber -> {
throw AssemblyError("float literals should have been converted into autovar")
}
else -> {
@ -1049,27 +972,30 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
throw AssemblyError("cannot use float arguments outside of a subroutine scope")
asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true
val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position)
val addr = AddressOf(variable, value.position)
addr.linkParents(value)
val variable = PtIdentifier(subroutineFloatEvalResultVar2, DataType.FLOAT, value.position)
val addr = PtAddressOf(value.position)
addr.add(variable)
addr.parent = call
asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT, scope)
AsmAssignSource.fromAstSource(addr, program, asmgen)
}
}
}
args.zip(callConv.params).zip(signature.parameters).forEach {
call.args.zip(callConv.params).zip(signature.parameters).forEach {
val paramName = it.second.name
val conv = it.first.second
val value = it.first.first
when {
conv.variable -> {
val varname = "prog8_lib.func_${signature.name}._arg_${paramName}"
val varname = "prog8_lib.func_${call.name}._arg_${paramName}"
val src = when (conv.dt) {
DataType.FLOAT -> getSourceForFloat(value)
in PassByReferenceDatatypes -> {
// put the address of the argument in AY
val addr = AddressOf(value as IdentifierReference, value.position)
val addr = PtAddressOf(value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
}
else -> {
@ -1077,7 +1003,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, conv.dt, null, variableAsmName = varname)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}
conv.reg != null -> {
@ -1085,7 +1011,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
DataType.FLOAT -> getSourceForFloat(value)
in PassByReferenceDatatypes -> {
// put the address of the argument in AY
val addr = AddressOf(value as IdentifierReference, value.position)
val addr = PtAddressOf(value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
}
else -> {
@ -1093,7 +1021,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}
else -> throw AssemblyError("callconv")

View File

@ -1,18 +1,17 @@
package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.code.ast.*
import prog8.code.core.*
import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program,
private val asmgen: AsmGen,
internal class ExpressionsAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator) {
@Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) {
internal fun translateExpression(expression: PtExpression) {
if (this.asmgen.options.slowCodegenWarnings) {
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
asmgen.errors.warn("slow stack evaluation used for expression", expression.position)
}
translateExpressionInternal(expression)
}
@ -21,41 +20,42 @@ internal class ExpressionsAsmGen(private val program: Program,
// the rest of the methods are all PRIVATE
private fun translateExpressionInternal(expression: Expression) {
private fun translateExpressionInternal(expression: PtExpression) {
when(expression) {
is PrefixExpression -> translateExpression(expression)
is BinaryExpression -> translateExpression(expression)
is ArrayIndexedExpression -> translateExpression(expression)
is TypecastExpression -> translateExpression(expression)
is AddressOf -> translateExpression(expression)
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
is NumericLiteral -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
is BuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
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")
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
is PtPrefix -> translateExpression(expression)
is PtBinaryExpression -> translateExpression(expression)
is PtArrayIndexer -> translateExpression(expression)
is PtTypeCast -> translateExpression(expression)
is PtAddressOf -> translateExpression(expression)
is PtMemoryByte -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
is PtNumber -> translateExpression(expression)
is PtIdentifier -> translateExpression(expression)
is PtFunctionCall -> translateFunctionCallResultOntoStack(expression)
is PtBuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
is PtContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is PtArray, is PtString -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is PtRange -> throw AssemblyError("range expression should have been changed into array values")
is PtMachineRegister -> throw AssemblyError("machine register ast node should not occur in 6502 codegen it is for IR code")
else -> TODO("missing expression asmgen for $expression")
}
}
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
private fun translateFunctionCallResultOntoStack(call: PtFunctionCall) {
// only for use in nested expression evaluation
val sub = call.target.targetSubroutine(program)!!
val symbol = asmgen.symbolTable.lookup(call.name)
val sub = symbol!!.astNode as IPtSubroutine
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call, true)
asmgen.translateFunctionCall(call)
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) {
val returns: List<Pair<RegisterOrStatusflag, DataType>> = sub.returnsWhatWhere()
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!!) {
@ -133,9 +133,9 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
private fun translateExpression(typecast: TypecastExpression) {
translateExpressionInternal(typecast.expression)
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
private fun translateExpression(typecast: PtTypeCast) {
translateExpressionInternal(typecast.value)
when(typecast.value.type) {
DataType.UBYTE, DataType.BOOL -> {
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
@ -197,12 +197,12 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
private fun translateExpression(expr: AddressOf) {
private fun translateExpression(expr: PtAddressOf) {
val name = asmgen.asmVariableName(expr.identifier)
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
}
private fun translateExpression(expr: NumericLiteral) {
private fun translateExpression(expr: PtNumber) {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out("""
@ -220,9 +220,9 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
private fun translateExpression(expr: IdentifierReference) {
private fun translateExpression(expr: PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
}
@ -239,22 +239,16 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
private fun translateExpression(expr: BinaryExpression) {
// Uses evalstack to evaluate the given expression.
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw AssemblyError("can't infer type of both expression operands")
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
// see if we can apply some optimized routines
private fun translateExpression(expr: PtBinaryExpression) {
// Uses evalstack to evaluate the given expression. THIS IS SLOW AND SHOULD BE AVOIDED!
val leftDt = expr.left.type
val rightDt = expr.right.type
// see if we can apply some optimized routines still
when(expr.operator) {
"+" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVal = expr.left.constValue(program)?.number?.toInt()
val rightVal = expr.right.constValue(program)?.number?.toInt()
val leftVal = expr.left.asConstInteger()
val rightVal = expr.right.asConstInteger()
if (leftVal!=null && leftVal in -4..4) {
translateExpressionInternal(expr.right)
if(rightDt in ByteDatatypes) {
@ -318,7 +312,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
"-" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
val rightVal = expr.right.asConstInteger()
if (rightVal!=null && rightVal in -4..4)
{
translateExpressionInternal(expr.left)
@ -352,7 +346,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
">>" -> {
val amount = expr.right.constValue(program)?.number?.toInt()
val amount = expr.right.asConstInteger()
if(amount!=null) {
translateExpressionInternal(expr.left)
when (leftDt) {
@ -423,7 +417,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
"<<" -> {
val amount = expr.right.constValue(program)?.number?.toInt()
val amount = expr.right.asConstInteger()
if(amount!=null) {
translateExpressionInternal(expr.left)
if (leftDt in ByteDatatypes) {
@ -450,13 +444,13 @@ internal class ExpressionsAsmGen(private val program: Program,
}
"*" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVar = expr.left as? IdentifierReference
val rightVar = expr.right as? IdentifierReference
val leftVar = expr.left as? PtIdentifier
val rightVar = expr.right as? PtIdentifier
if(leftVar!=null && rightVar!=null && leftVar==rightVar)
return translateSquared(leftVar, leftDt)
}
val value = expr.right.constValue(program)
val value = expr.right as? PtNumber
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
@ -516,7 +510,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
"/" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
val rightVal = expr.right.asConstInteger()
if(rightVal!=null && rightVal==2) {
translateExpressionInternal(expr.left)
when (leftDt) {
@ -557,8 +551,8 @@ internal class ExpressionsAsmGen(private val program: Program,
}
in ComparisonOperators -> {
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
val rightVal = expr.right.constValue(program)?.number
if(rightVal==0.0)
val rightVal = expr.right.asConstInteger()
if(rightVal==0)
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
}
}
@ -572,7 +566,8 @@ internal class ExpressionsAsmGen(private val program: Program,
translateCompareStrings(expr.left, expr.operator, expr.right)
}
else {
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
// the general, non-optimized cases
// TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
translateExpressionInternal(expr.left)
translateExpressionInternal(expr.right)
when (leftDt) {
@ -584,8 +579,8 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
if(expr.isSimple) {
private fun translateComparisonWithZero(expr: PtExpression, dt: DataType, operator: String) {
if(expr.isSimple()) {
if(operator=="!=") {
when (dt) {
in ByteDatatypes -> {
@ -641,7 +636,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
"<" -> {
if(dt==DataType.UBYTE || dt==DataType.UWORD)
return translateExpressionInternal(NumericLiteral.fromBoolean(false, expr.position))
return translateExpressionInternal(PtNumber.fromBoolean(false, expr.position))
when(dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
@ -671,7 +666,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
">=" -> {
if(dt==DataType.UBYTE || dt==DataType.UWORD)
return translateExpressionInternal(NumericLiteral.fromBoolean(true, expr.position))
return translateExpressionInternal(PtNumber.fromBoolean(true, expr.position))
when(dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
@ -683,7 +678,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
private fun translateSquared(variable: PtIdentifier, dt: DataType) {
val asmVar = asmgen.asmVariableName(variable)
when(dt) {
DataType.BYTE, DataType.UBYTE -> {
@ -699,14 +694,12 @@ internal class ExpressionsAsmGen(private val program: Program,
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
}
private fun translateExpression(expr: PrefixExpression) {
translateExpressionInternal(expr.expression)
val itype = expr.inferType(program)
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
private fun translateExpression(expr: PtPrefix) {
translateExpressionInternal(expr.value)
when(expr.operator) {
"+" -> {}
"-" -> {
when(type) {
when(expr.type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
@ -714,7 +707,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
"~" -> {
when(type) {
when(expr.type) {
in ByteDatatypes ->
asmgen.out("""
lda P8ESTACK_LO+1,x
@ -729,22 +722,18 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
val elementIDt = arrayExpr.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
private fun translateExpression(arrayExpr: PtArrayIndexer) {
val elementDt = arrayExpr.type
val arrayVarName = asmgen.asmVariableName(arrayExpr.variable)
val arrayVarDecl = arrayExpr.arrayvar.targetVarDecl(program)!!
if(arrayVarDecl.datatype==DataType.UWORD) {
if(arrayExpr.variable.type==DataType.UWORD) {
// indexing a pointer var instead of a real array or string
if(elementDt !in ByteDatatypes)
throw AssemblyError("non-array var indexing requires bytes dt")
if(arrayExpr.inferType(program) isnot DataType.UBYTE)
if(arrayExpr.index.type != DataType.UBYTE)
throw AssemblyError("non-array var indexing requires bytes index")
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
if(asmgen.isZpVar(arrayExpr.arrayvar)) {
if(asmgen.isZpVar(arrayExpr.variable)) {
asmgen.out(" lda ($arrayVarName),y")
} else {
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
@ -754,7 +743,7 @@ internal class ExpressionsAsmGen(private val program: Program,
return
}
val constIndexNum = arrayExpr.indexer.constIndex()
val constIndexNum = arrayExpr.index.asConstInteger()
if(constIndexNum!=null) {
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
when(elementDt) {
@ -881,7 +870,7 @@ internal class ExpressionsAsmGen(private val program: Program,
}
}
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
private fun translateCompareStrings(s1: PtExpression, operator: String, s2: PtExpression) {
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A

View File

@ -0,0 +1,60 @@
package prog8.codegen.cpu6502
import prog8.code.ast.IPtSubroutine
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtSub
import prog8.code.core.*
internal fun IPtSubroutine.regXasResult(): Boolean =
(this is PtAsmSub) && this.returns.any { it.first.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
internal fun IPtSubroutine.shouldSaveX(): Boolean =
this.regXasResult() || (this is PtAsmSub && (CpuRegister.X in this.clobbers || regXasParam()))
internal fun PtAsmSub.regXasParam(): Boolean =
parameters.any { it.first.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
internal class KeepAresult(val saveOnEntry: Boolean, val saveOnReturn: Boolean)
internal fun PtAsmSub.shouldKeepA(): KeepAresult {
// determine if A's value should be kept when preparing for calling the subroutine, and when returning from it
// it seems that we never have to save A when calling? will be loaded correctly after setup.
// but on return it depends on wether the routine returns something in A.
val saveAonReturn = returns.any { it.first.registerOrPair==RegisterOrPair.A || it.first.registerOrPair==RegisterOrPair.AY || it.first.registerOrPair==RegisterOrPair.AX }
return KeepAresult(false, saveAonReturn)
}
internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
when(this) {
is PtAsmSub -> {
return returns
}
is PtSub -> {
// for non-asm subroutines, determine the return registers based on the type of the return value
return if(returntype==null)
emptyList()
else {
val register = when (returntype!!) {
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
}
listOf(Pair(register, returntype!!))
}
}
}
}
internal fun PtSub.returnRegister(): RegisterOrStatusflag? {
return when(returntype) {
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
null -> null
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
}
}

View File

@ -1,46 +1,43 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import prog8.ast.Program
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpression
import prog8.ast.statements.ForLoop
import prog8.code.ast.*
import prog8.code.core.*
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen, private val zeropage: Zeropage) {
internal class ForLoopsAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val zeropage: Zeropage) {
internal fun translate(stmt: ForLoop) {
val iterableDt = stmt.iterable.inferType(program)
if(!iterableDt.isKnown)
throw AssemblyError("unknown dt")
internal fun translate(stmt: PtForLoop) {
val iterableDt = stmt.iterable.type
when(stmt.iterable) {
is RangeExpression -> {
val range = (stmt.iterable as RangeExpression).toConstantIntegerRange()
is PtRange -> {
val range = (stmt.iterable as PtRange).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression)
translateForOverNonconstRange(stmt, iterableDt, stmt.iterable as PtRange)
} else {
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
translateForOverConstRange(stmt, iterableDt, range)
}
}
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference)
is PtIdentifier -> {
translateForOverIterableVar(stmt, iterableDt, stmt.iterable as PtIdentifier)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
}
}
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
val loopLabel = program.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end")
val modifiedLabel = program.makeLabel("for_modified")
val modifiedLabel2 = program.makeLabel("for_modifiedb")
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
val stepsize=range.step.constValue(program)!!.number.toInt()
val stepsize=range.step.asConstInteger()!!
if(stepsize < -1) {
val limit = range.to.constValue(program)?.number
if(limit==0.0)
val limit = range.to.asConstInteger()
if(limit==0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}
@ -52,11 +49,11 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
$modifiedLabel cmp #0 ; modified
@ -70,11 +67,11 @@ $modifiedLabel cmp #0 ; modified
// bytes, step >= 2 or <= -2
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
if(stepsize>0) {
asmgen.out("""
lda $varname
@ -102,14 +99,14 @@ $modifiedLabel cmp #0 ; modified
// words, step 1 or -1
stepsize == 1 || stepsize == -1 -> {
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname+1
$modifiedLabel cmp #0 ; modified
@ -136,14 +133,14 @@ $modifiedLabel2 cmp #0 ; modified
stepsize > 0 -> {
// (u)words, step >= 2
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
@ -184,14 +181,14 @@ $endLabel""")
else -> {
// (u)words, step <= -2
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
if(iterableDt==DataType.ARRAY_UW) {
asmgen.out("""
@ -237,12 +234,18 @@ $endLabel""")
asmgen.loopEndLabels.pop()
}
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
val loopLabel = program.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end")
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val decl = ident.targetVarDecl(program)!!
val symbol = asmgen.symbolTable.lookup(ident.name)
val decl = symbol!!.astNode as IPtVariable
val numElements = when(decl) {
is PtConstant -> throw AssemblyError("length of non-array requested")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
}
when(iterableDt) {
DataType.STR -> {
asmgen.out("""
@ -252,8 +255,8 @@ $endLabel""")
sty $loopLabel+2
$loopLabel lda ${65535.toHex()} ; modified
beq $endLabel
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
asmgen.translate(stmt.body)
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
inc $loopLabel+1
bne $loopLabel
@ -262,19 +265,18 @@ $loopLabel lda ${65535.toHex()} ; modified
$endLabel""")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
val length = decl.arraysize!!.constIndex()!!
val indexVar = program.makeLabel("for_index")
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
asmgen.translate(stmt.body)
if(length<=255) {
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
if(numElements!!<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$length
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
@ -285,9 +287,9 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16) {
if(numElements>=16u) {
// allocate index var on ZP if possible
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
@ -298,9 +300,9 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
val length = decl.arraysize!!.constIndex()!! * 2
val indexVar = program.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.loopVar)
val length = numElements!! * 2u
val indexVar = asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
@ -308,8 +310,8 @@ $loopLabel sty $indexVar
sta $loopvarName
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.body)
if(length<=127) {
asmgen.translate(stmt.statements)
if(length<=127u) {
asmgen.out("""
ldy $indexVar
iny
@ -326,9 +328,9 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16) {
if(length>=16u) {
// allocate index var on ZP if possible
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
@ -346,7 +348,7 @@ $loopLabel sty $indexVar
asmgen.loopEndLabels.pop()
}
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
if (range.isEmpty() || range.step==0)
throw AssemblyError("empty range or step 0")
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
@ -359,18 +361,18 @@ $loopLabel sty $indexVar
}
// not one of the easy cases, generate more complex code...
val loopLabel = program.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end")
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
// loop over byte range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
@ -430,7 +432,7 @@ $loopLabel""")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// loop over word range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
@ -444,7 +446,7 @@ $loopLabel""")
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #<${range.last}
@ -470,16 +472,16 @@ $loopLabel""")
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) {
val loopLabel = program.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end")
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
if (range.last == 255) {
asmgen.out("""
inc $varname
@ -496,16 +498,16 @@ $endLabel""")
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) {
val loopLabel = program.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end")
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
when (range.last) {
0 -> {
asmgen.out("""
@ -533,18 +535,18 @@ $endLabel""")
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) {
val loopLabel = program.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end")
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #<${range.last}
@ -560,18 +562,18 @@ $loopLabel""")
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) {
val loopLabel = program.makeLabel("for_loop")
val endLabel = program.makeLabel("for_end")
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.loopVar)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #<${range.last}
@ -588,10 +590,10 @@ $loopLabel""")
asmgen.loopEndLabels.pop()
}
private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) =
private fun assignLoopvar(stmt: PtForLoop, range: PtRange) =
asmgen.assignExpressionToVariable(
range.from,
asmgen.asmVariableName(stmt.loopVar),
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") },
stmt.definingSubroutine)
asmgen.asmVariableName(stmt.variable),
stmt.variable.type,
stmt.definingISub())
}

View File

@ -1,16 +1,6 @@
package prog8.codegen.cpu6502
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignSource
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
@ -18,51 +8,60 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
import prog8.codegen.cpu6502.assignment.TargetStorageKind
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
internal fun translateFunctionCallStatement(stmt: FunctionCallStatement) {
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
saveXbeforeCall(stmt)
translateFunctionCall(stmt, false)
translateFunctionCall(stmt)
restoreXafterCall(stmt)
// just ignore any result values from the function call.
}
internal fun saveXbeforeCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
internal fun saveXbeforeCall(stmt: PtFunctionCall) {
val symbol = asmgen.symbolTable.lookup(stmt.name)
val sub = symbol!!.astNode as IPtSubroutine
if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine!!)
if(sub is PtAsmSub) {
val regSaveOnStack = sub.address == null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if (regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
} else
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
}
}
internal fun restoreXafterCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
internal fun restoreXafterCall(stmt: PtFunctionCall) {
val symbol = asmgen.symbolTable.lookup(stmt.name)
val sub = symbol!!.astNode as IPtSubroutine
if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
if(sub is PtAsmSub) {
val regSaveOnStack = sub.address == null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if (regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
} else
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter
internal fun translateFunctionCall(call: PtFunctionCall) {
// Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
val subAsmName = asmgen.asmSymbolName(call.target)
val symbol = asmgen.symbolTable.lookup(call.name)
val sub = symbol!!.astNode as IPtSubroutine
val subAsmName = asmgen.asmSymbolName(call.name)
if(sub.isAsmSubroutine) {
if(sub is PtAsmSub) {
argumentsViaRegisters(sub, call)
if (sub.inline && asmgen.options.optimize) {
// inline the subroutine.
@ -70,16 +69,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
asmgen.out(" \t; inlined routine follows: ${sub.name}")
sub.statements.forEach { asmgen.translate(it as InlineAssembly) }
sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
asmgen.out(" \t; inlined routine end: ${sub.name}")
} else {
asmgen.out(" jsr $subAsmName")
}
}
else {
if(sub.inline)
throw AssemblyError("can only reliably inline asmsub routines at this time")
else if(sub is PtSub) {
if(optimizeIntArgsViaRegisters(sub)) {
if(sub.parameters.size==1) {
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
@ -100,84 +96,82 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
asmgen.out(" jsr $subAsmName")
}
else throw AssemblyError("invalid sub type")
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else {
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
registerArgsViaCpuStackEvaluation(call, sub)
} else {
asmsub6502ArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param), arg)
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
}
}
}
}
private fun registerArgsViaCpuStackEvaluation(call: IFunctionCall, callee: Subroutine) {
private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
// 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.
require(callee.isAsmSubroutine) { "register args only for asm subroutine ${callee.position}" }
if(callee.parameters.isEmpty())
return
// use the cpu hardware stack as intermediate storage for the arguments.
val argOrder = asmsub6502ArgsEvalOrder(callee)
argOrder.reversed().forEach {
asmgen.pushCpuStack(callee.parameters[it].type, call.args[it])
asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
}
argOrder.forEach {
val param = callee.parameters[it]
val targetVar = callee.searchParameter(param.name)!!
asmgen.popCpuStack(param.type, targetVar, (call as Node).definingSubroutine)
asmgen.popCpuStack(callee, param.second, param.first)
}
}
private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) {
private fun argumentViaVariable(sub: PtSub, parameter: PtSubroutineParameter, value: PtExpression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.type))
if(!isArgumentTypeCompatible(value.type, parameter.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedName + parameter.name)
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null)
val paramRegister: RegisterOrStatusflag = when(sub) {
is PtAsmSub -> if(registerOverride==null) sub.parameters[parameter.index].first else RegisterOrStatusflag(registerOverride, null)
is PtSub -> RegisterOrStatusflag(registerOverride!!, null)
}
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type
if(requiredDt!=valueDt) {
if(valueDt largerThan requiredDt)
if(requiredDt!=value.type) {
if(value.type largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
if (statusflag!=null) {
if(requiredDt!=valueDt)
if(requiredDt!=value.type)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteral -> {
is PtNumber -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
is PtIdentifier -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
@ -202,10 +196,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
else {
// via register or register pair
register!!
if(requiredDt largerThan valueDt) {
if(requiredDt largerThan value.type) {
// we need to sign extend the source, do this via temporary word variable
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
} else {
val target: AsmAssignTarget =
@ -215,9 +209,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
AsmAssignTarget.fromRegisters(register, signed, sub, asmgen)
}
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
val src = if(value.type in PassByReferenceDatatypes) {
if(value is PtIdentifier) {
val addr = PtAddressOf(Position.DUMMY)
addr.add(value)
addr.parent = sub as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
@ -225,7 +221,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY))
}
}
}

View File

@ -1,23 +1,23 @@
package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.PostIncrDecr
import prog8.code.ast.PtIdentifier
import prog8.code.ast.PtNumber
import prog8.code.ast.PtPostIncrDecr
import prog8.code.ast.PtProgram
import prog8.code.core.*
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: PostIncrDecr) {
internal class PostIncrDecrAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
internal fun translate(stmt: PtPostIncrDecr) {
val incr = stmt.operator=="++"
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val scope = stmt.definingSubroutine
val targetMemory = stmt.target.memory
val targetArrayIdx = stmt.target.array
val scope = stmt.definingISub()
when {
targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
when (stmt.target.type) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
@ -38,12 +38,12 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
}
targetMemory!=null -> {
when (val addressExpr = targetMemory.addressExpression) {
is NumericLiteral -> {
when (val addressExpr = targetMemory.address) {
is PtNumber -> {
val what = addressExpr.number.toHex()
asmgen.out(if(incr) " inc $what" else " dec $what")
}
is IdentifierReference -> {
is PtIdentifier -> {
val what = asmgen.asmVariableName(addressExpr)
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
if(incr)
@ -62,9 +62,9 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
}
targetArrayIdx!=null -> {
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
val constIndex = targetArrayIdx.indexer.constIndex()
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.variable)
val elementDt = targetArrayIdx.type
val constIndex = targetArrayIdx.index.asConstInteger()
if(constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when(elementDt) {

View File

@ -1,12 +1,10 @@
package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.statements.*
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.TargetStorageKind
import prog8.compiler.CallGraph
import java.time.LocalDate
import java.time.LocalDateTime
import kotlin.math.absoluteValue
@ -20,30 +18,26 @@ import kotlin.math.absoluteValue
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
*/
internal class ProgramAndVarsGen(
val program: Program,
val program: PtProgram,
val options: CompilationOptions,
val errors: IErrorReporter,
private val symboltable: SymbolTable,
private val functioncallAsmGen: FunctionCallAsmGen,
private val asmgen: AsmGen,
private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator,
private val zeropage: Zeropage
) {
private val compTarget = options.compTarget
private val callGraph = CallGraph(program, true)
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
private val blockVariableInitializers = program.allBlocks().associateWith { it.children.filterIsInstance<PtAssignment>() }
internal fun generate() {
val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value }
require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"}
header()
val allBlocks = program.allBlocks
val allBlocks = program.allBlocks()
if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'")
if(errors.noErrors()) {
program.allBlocks.forEach { block2asm(it) }
program.allBlocks().forEach { block2asm(it) }
// the global list of all floating point constants for the whole program
asmgen.out("; global float constants")
@ -71,7 +65,7 @@ internal class ProgramAndVarsGen(
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
asmgen.out("")
asmgen.out(".cpu '$cpu'\n.enc 'none'\n")
asmgen.out(".cpu '$cpu'\n.enc 'none'")
// the global prog8 variables needed
val zp = zeropage
@ -94,13 +88,13 @@ internal class ProgramAndVarsGen(
when(options.output) {
OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}\n")
asmgen.out("* = ${options.loadAddress.toHex()}")
}
OutputType.PRG -> {
when(options.launcher) {
CbmPrgLauncherType.BASIC -> {
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.toplevelModule.position)
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.position)
}
asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
@ -108,14 +102,14 @@ internal class ProgramAndVarsGen(
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")
asmgen.out("prog8_entrypoint\t; assembly code starts here")
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("* = ${options.loadAddress.toHex()}\n")
asmgen.out("* = ${options.loadAddress.toHex()}")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
@ -124,7 +118,7 @@ internal class ProgramAndVarsGen(
}
OutputType.XEX -> {
asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${options.loadAddress.toHex()}\n")
asmgen.out("* = ${options.loadAddress.toHex()}")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
@ -168,61 +162,88 @@ internal class ProgramAndVarsGen(
}
private fun memorySlabs() {
asmgen.out("; memory slabs")
asmgen.out("prog8_slabs\t.block")
for(slab in symboltable.allMemorySlabs) {
if(slab.align>1u)
asmgen.out("\t.align ${slab.align.toHex()}")
asmgen.out("${slab.name}\t.fill ${slab.size}")
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("; memory slabs\n .section slabs_BSS")
asmgen.out("prog8_slabs\t.block")
for (slab in symboltable.allMemorySlabs) {
if (slab.align > 1u)
asmgen.out("\t.align ${slab.align.toHex()}")
asmgen.out("${slab.name}\t.fill ${slab.size}")
}
asmgen.out("\t.bend\n .send slabs_BSS")
}
asmgen.out("\t.bend")
}
private fun footer() {
// program end
asmgen.out("prog8_program_end\t; end of program label for progend()")
asmgen.out("; bss sections")
if(options.varsHigh) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
}
// BSS vars in high ram area, memory() slabs just concatenated at the end of the program.
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out(" .dsection slabs_BSS")
}
asmgen.out("prog8_program_end\t; end of program label for progend()")
asmgen.out(" * = ${options.compTarget.machine.BSSHIGHRAM_START.toHex()}")
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
asmgen.out(" .cerror * >= ${options.compTarget.machine.BSSHIGHRAM_END.toHex()}, \"too many variables for BSS section\"")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
} else {
// BSS vars followed by memory() slabs, concatenated at the end of the program.
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out(" .dsection slabs_BSS")
}
asmgen.out("prog8_program_end\t; end of program label for progend()")
}
}
private fun block2asm(block: Block) {
private fun block2asm(block: PtBlock) {
asmgen.out("")
asmgen.out("; ---- block: '${block.name}' ----")
if(block.address!=null)
asmgen.out("* = ${block.address!!.toHex()}")
else {
if("align_word" in block.options())
if(block.alignment==PtBlock.BlockAlignment.WORD)
asmgen.out("\t.align 2")
else if("align_page" in block.options())
else if(block.alignment==PtBlock.BlockAlignment.PAGE)
asmgen.out("\t.align $100")
}
asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
asmgen.out("${block.name}\t" + (if(block.forceOutput) ".block" else ".proc"))
asmgen.outputSourceLine(block)
createBlockVariables(block)
asmsubs2asm(block.statements)
asmsubs2asm(block.children)
asmgen.out("")
val initializers = blockVariableInitializers.getValue(block)
val notInitializers = block.statements.filterNot { it in initializers }
val notInitializers = block.children.filterNot { it in initializers }
notInitializers.forEach { asmgen.translate(it) }
if(!options.dontReinitGlobals) {
// generate subroutine to initialize block-level (global) variables
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.block\n")
initializers.forEach { assign -> asmgen.translate(assign) }
asmgen.out(" rts\n .bend")
// generate subroutine to initialize block-level (global) variables
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.block")
initializers.forEach { assign ->
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign)
// the other variables that should be set to zero are done so as part of the BSS section.
}
asmgen.out(" rts\n .bend")
}
asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
asmgen.out(if(block.forceOutput) "\n\t.bend" else "\n\t.pend")
}
private fun getVars(scope: StNode): Map<String, StNode> =
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
private fun createBlockVariables(block: Block) {
private fun createBlockVariables(block: PtBlock) {
val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.BLOCK)
val varsInBlock = getVars(scope)
@ -242,23 +263,15 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
}
internal fun translateSubroutine(sub: Subroutine) {
var onlyVariables = false
internal fun translateAsmSubroutine(sub: PtAsmSub) {
if(sub.inline) {
if(options.optimize) {
if(sub.isAsmSubroutine || callGraph.unused(sub))
return
// from an inlined subroutine only the local variables are generated,
// all other code statements are omitted in the subroutine itself
// (they've been inlined at the call site, remember?)
onlyVariables = true
return
}
}
@ -266,7 +279,7 @@ internal class ProgramAndVarsGen(
val asmStartScope: String
val asmEndScope: String
if(sub.definingBlock.options().contains("force_output")) {
if(sub.definingBlock()!!.forceOutput) {
asmStartScope = ".block"
asmEndScope = ".bend"
} else {
@ -274,105 +287,127 @@ internal class ProgramAndVarsGen(
asmEndScope = ".pend"
}
if(sub.isAsmSubroutine) {
if(sub.asmAddress!=null)
return // already done at the memvars section
if(sub.address!=null)
return // already done at the memvars section
// asmsub with most likely just an inline asm in it
asmgen.out("${sub.name}\t$asmStartScope")
sub.statements.forEach { asmgen.translate(it) }
asmgen.out(" $asmEndScope\n")
// asmsub with most likely just an inline asm in it
asmgen.out("${sub.name}\t$asmStartScope")
sub.children.forEach { asmgen.translate(it) }
asmgen.out(" $asmEndScope")
}
internal fun translateSubroutine(sub: PtSub) {
asmgen.out("")
val asmStartScope: String
val asmEndScope: String
if(sub.definingBlock()!!.forceOutput) {
asmStartScope = ".block"
asmEndScope = ".bend"
} else {
// regular subroutine
asmgen.out("${sub.name}\t$asmStartScope")
val scope = symboltable.lookupOrElse(sub.scopedName.joinToString(".")) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.SUBROUTINE)
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
val mvs = varsInSubroutine
.filter { it.value.type==StNodeType.MEMVAR }
.map { it.value as StMemVar }
val consts = varsInSubroutine
.filter { it.value.type==StNodeType.CONSTANT }
.map { it.value as StConstant }
memdefsAndConsts2asm(mvs, consts)
asmsubs2asm(sub.statements)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock.name=="main")
entrypointInitialization()
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
asmgen.out("; simple int arg(s) passed via register(s)")
if(sub.parameters.size==1) {
val dt = sub.parameters[0].type
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
if(dt in ByteDatatypes)
asmgen.assignRegister(RegisterOrPair.A, target)
else
asmgen.assignRegister(RegisterOrPair.AY, target)
} else {
require(sub.parameters.size==2)
// 2 simple byte args, first in A, second in Y
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
asmgen.assignRegister(RegisterOrPair.A, target1)
asmgen.assignRegister(RegisterOrPair.Y, target2)
}
}
if(!onlyVariables) {
asmgen.out("; statements")
sub.statements.forEach { asmgen.translate(it) }
}
asmgen.out("; variables")
val asmGenInfo = asmgen.subroutineExtra(sub)
for((dt, name, addr) in asmGenInfo.extraVars) {
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
DataType.UBYTE -> asmgen.out("$name .byte 0")
DataType.UWORD -> asmgen.out("$name .word 0")
else -> throw AssemblyError("weird dt")
}
}
if(asmGenInfo.usedRegsaveA) // will probably never occur
asmgen.out("prog8_regsaveA .byte 0")
if(asmGenInfo.usedRegsaveX)
asmgen.out("prog8_regsaveX .byte 0")
if(asmGenInfo.usedRegsaveY)
asmgen.out("prog8_regsaveY .byte 0")
if(asmGenInfo.usedFloatEvalResultVar1)
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
asmgen.out(" $asmEndScope\n")
asmStartScope = ".proc"
asmEndScope = ".pend"
}
asmgen.out("${sub.name}\t$asmStartScope")
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.SUBROUTINE)
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
val mvs = varsInSubroutine
.filter { it.value.type==StNodeType.MEMVAR }
.map { it.value as StMemVar }
val consts = varsInSubroutine
.filter { it.value.type==StNodeType.CONSTANT }
.map { it.value as StConstant }
memdefsAndConsts2asm(mvs, consts)
asmsubs2asm(sub.children)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock()!!.name=="main")
entrypointInitialization()
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
asmgen.out("; simple int arg(s) passed via register(s)")
if(sub.parameters.size==1) {
val dt = sub.parameters[0].type
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
if(dt in ByteDatatypes)
asmgen.assignRegister(RegisterOrPair.A, target)
else
asmgen.assignRegister(RegisterOrPair.AY, target)
} else {
require(sub.parameters.size==2)
// 2 simple byte args, first in A, second in Y
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
asmgen.assignRegister(RegisterOrPair.A, target1)
asmgen.assignRegister(RegisterOrPair.Y, target2)
}
}
asmgen.out("; statements")
sub.children.forEach { asmgen.translate(it) }
asmgen.out("; variables")
val asmGenInfo = asmgen.subroutineExtra(sub)
for((dt, name, addr) in asmGenInfo.extraVars) {
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
DataType.UBYTE -> asmgen.out("$name .byte 0")
DataType.UWORD -> asmgen.out("$name .word 0")
else -> throw AssemblyError("weird dt")
}
}
if(asmGenInfo.usedRegsaveA) // will probably never occur
asmgen.out("prog8_regsaveA .byte 0")
if(asmGenInfo.usedRegsaveX)
asmgen.out("prog8_regsaveX .byte 0")
if(asmGenInfo.usedRegsaveY)
asmgen.out("prog8_regsaveY .byte 0")
if(asmGenInfo.usedFloatEvalResultVar1)
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
asmgen.out(" $asmEndScope")
}
private fun entrypointInitialization() {
asmgen.out("; program startup initialization")
asmgen.out(" cld")
if(!options.dontReinitGlobals) {
blockVariableInitializers.forEach {
if (it.value.isNotEmpty())
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
}
asmgen.out(" cld | tsx | stx prog8_lib.orig_stackpointer ; required for sys.exit()")
// set full BSS area to zero
asmgen.out("""
.if prog8_bss_section_size>0
; reset all variables in BSS section to zero
lda #<prog8_bss_section_start
ldy #>prog8_bss_section_start
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #<prog8_bss_section_size
ldy #>prog8_bss_section_size
lda #0
jsr prog8_lib.memset
.endif""")
blockVariableInitializers.forEach {
if (it.value.isNotEmpty())
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
}
// string and array variables in zeropage that have initializer value, should be initialized
@ -420,9 +455,8 @@ internal class ProgramAndVarsGen(
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
}
asmgen.out("""+ tsx
stx prog8_lib.orig_stackpointer ; required for sys.exit()
ldx #255 ; init estack ptr
asmgen.out("""+
ldx #127 ; init estack ptr (half page)
clv
clc""")
}
@ -443,7 +477,7 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpStringWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
for (variable in vars) {
val scopedName = variable.key.joinToString(".")
val scopedName = variable.key
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationStringValue!=null)
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
@ -455,7 +489,7 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpArrayWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
for (variable in vars) {
val scopedName = variable.key.joinToString(".")
val scopedName = variable.key
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationArrayValue!=null)
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
@ -464,29 +498,60 @@ internal class ProgramAndVarsGen(
}
private fun zeropagevars2asm(varNames: Set<String>) {
val namesLists = varNames.map { it.split('.') }.toSet()
val zpVariables = allocator.zeropageVars.filter { it.key in namesLists }.toList().sortedBy { it.second.address }
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }.toList().sortedBy { it.second.address }
for ((scopedName, zpvar) in zpVariables) {
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
if (scopedName.startsWith("cx16.r"))
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
asmgen.out("${scopedName.last()} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
}
}
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
asmgen.out("")
asmgen.out("; non-zeropage variables")
val (stringvars, othervars) = variables.sortedBy { it.name }.partition { it.dt==DataType.STR }
stringvars.forEach {
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
if(varsNoInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables without initialization value")
asmgen.out(" .section BSS")
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
uninitializedVariable2asm(it)
}
asmgen.out(" .send BSS")
}
othervars.sortedBy { it.type }.forEach {
staticVariable2asm(it)
if(varsWithInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables")
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
stringvars.forEach {
outputStringvar(
it.name,
it.onetimeInitializationStringValue!!.second,
it.onetimeInitializationStringValue!!.first
)
}
othervars.sortedBy { it.type }.forEach {
staticVariable2asm(it)
}
}
}
private fun uninitializedVariable2asm(variable: StStaticVariable) {
when (variable.dt) {
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ?")
DataType.BYTE -> asmgen.out("${variable.name}\t.char ?")
DataType.UWORD -> asmgen.out("${variable.name}\t.word ?")
DataType.WORD -> asmgen.out("${variable.name}\t.sint ?")
DataType.FLOAT -> asmgen.out("${variable.name}\t.fill ${compTarget.machine.FLOAT_MEM_SIZE}")
in ArrayDatatypes -> {
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
asmgen.out("${variable.name}\t.fill $numbytes")
}
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun staticVariable2asm(variable: StStaticVariable) {
val name = variable.name
val initialValue: Number =
if(variable.onetimeInitializationNumericValue!=null) {
if(variable.dt== DataType.FLOAT)
@ -496,22 +561,22 @@ internal class ProgramAndVarsGen(
} else 0
when (variable.dt) {
DataType.UBYTE -> asmgen.out("$name\t.byte ${initialValue.toHex()}")
DataType.BYTE -> asmgen.out("$name\t.char $initialValue")
DataType.UWORD -> asmgen.out("$name\t.word ${initialValue.toHex()}")
DataType.WORD -> asmgen.out("$name\t.sint $initialValue")
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
DataType.WORD -> asmgen.out("${variable.name}\t.sint $initialValue")
DataType.FLOAT -> {
if(initialValue==0) {
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
} else {
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
asmgen.out("$name\t.byte $floatFill ; float $initialValue")
asmgen.out("${variable.name}\t.byte $floatFill ; float $initialValue")
}
}
DataType.STR -> {
throw AssemblyError("all string vars should have been interned into prog")
}
in ArrayDatatypes -> arrayVariable2asm(name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
else -> {
throw AssemblyError("weird dt")
}
@ -593,12 +658,12 @@ internal class ProgramAndVarsGen(
}
}
private fun asmsubs2asm(statements: List<Statement>) {
private fun asmsubs2asm(statements: List<PtNode>) {
statements
.filter { it is Subroutine && it.isAsmSubroutine && it.asmAddress!=null }
.filter { it is PtAsmSub && it.address!=null }
.forEach { asmsub ->
asmsub as Subroutine
asmgen.out(" ${asmsub.name} = ${asmsub.asmAddress!!.toHex()}")
asmsub as PtAsmSub
asmgen.out(" ${asmsub.name} = ${asmsub.address!!.toHex()}")
}
}

View File

@ -16,13 +16,14 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
private val zeropage = options.compTarget.machine.zeropage
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
internal val zeropageVars: Map<List<String>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
init {
allocateZeropageVariables()
zeropageVars = zeropage.allocatedVariables
}
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropageVars
internal fun isZpVar(scopedName: String) = scopedName in zeropageVars
internal fun getFloatAsmConst(number: Double): String {
val asmName = globalFloatConsts[number]
@ -56,10 +57,10 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
varsRequiringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName.split('.'),
variable.scopedName,
variable.dt,
variable.length,
variable.position,
variable.astNode.position,
errors
)
result.fold(
@ -67,7 +68,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
numVariablesAllocatedInZP++
},
failure = {
errors.err(it.message!!, variable.position)
errors.err(it.message!!, variable.astNode.position)
}
)
}
@ -75,10 +76,10 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) {
varsPreferringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName.split('.'),
variable.scopedName,
variable.dt,
variable.length,
variable.position,
variable.astNode.position,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
@ -95,10 +96,10 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
break
} else {
val result = zeropage.allocate(
variable.scopedName.split('.'),
variable.scopedName,
variable.dt,
variable.length,
variable.position,
variable.astNode.position,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }

View File

@ -1,13 +1,9 @@
package prog8.codegen.cpu6502.assignment
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.Subroutine
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.returnsWhatWhere
internal enum class TargetStorageKind {
@ -29,25 +25,25 @@ internal enum class SourceStorageKind {
}
internal class AsmAssignTarget(val kind: TargetStorageKind,
private val asmgen: AsmGen,
private val asmgen: AsmGen6502Internal,
val datatype: DataType,
val scope: Subroutine?,
val scope: IPtSubroutine?,
private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryWrite? = null,
val array: PtArrayIndexer? = null,
val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null,
val origAstTarget: AssignTarget? = null
val origAstTarget: PtAssignTarget? = null
)
{
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
val constArrayIndexValue by lazy { array?.index?.asConstInteger()?.toUInt() }
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
asmgen.asmVariableName(array.variable)
}
lateinit var origAssign: AsmAssignment
lateinit var origAssign: AsmAssignmentBase
init {
if(register!=null && datatype !in NumericDatatypes)
@ -55,33 +51,31 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
with(assign.target) {
val idt = inferType(program)
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
fun fromAstAssignment(target: PtAssignTarget, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget {
with(target) {
when {
identifier != null -> {
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
val parameter = asmgen.findSubroutineParameter(identifier!!.name, asmgen)
if (parameter!=null) {
val sub = parameter.definingSubroutine!!
if (sub.isAsmSubroutine) {
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
val sub = parameter.definingAsmSub()
if (sub!=null) {
val reg = sub.parameters.single { it.second===parameter }.first
if(reg.statusflag!=null)
throw AssemblyError("can't assign value to processor statusflag directly")
else
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, type, definingSub, register=reg.registerOrPair, origAstTarget = this)
}
}
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, type, definingSub, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
}
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, array = array, origAstTarget = this)
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, memory = memory, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
}
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, asmgen: AsmGen): AsmAssignTarget =
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget =
when(registers) {
RegisterOrPair.A,
RegisterOrPair.X,
@ -112,69 +106,66 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
internal class AsmAssignSource(val kind: SourceStorageKind,
private val program: Program,
private val asmgen: AsmGen,
private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
val datatype: DataType,
private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null,
val array: PtArrayIndexer? = null,
val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null,
val number: NumericLiteral? = null,
val expression: Expression? = null
val number: PtNumber? = null,
val expression: PtExpression? = null
)
{
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
asmgen.asmVariableName(array.variable)
companion object {
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
val cv = value.constValue(program)
fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
val cv = value as? PtNumber
if(cv!=null)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
return when(value) {
is NumericLiteral -> throw AssemblyError("should have been constant value")
is StringLiteral -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteral -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val parameter = value.targetVarDecl(program)?.subroutineParameter
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
// checked above: is PtNumber -> throw AssemblyError("should have been constant value")
is PtString -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is PtArray -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is PtIdentifier -> {
val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
if(parameter?.definingAsmSub() != null)
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
if(value.type == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
val regStr = varName.lowercase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.uppercase())
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, value.type, register = reg)
} else {
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, value.type, variableAsmName = varName)
}
}
is DirectMemoryRead -> {
is PtMemoryByte -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
}
is ArrayIndexedExpression -> {
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
is PtArrayIndexer -> {
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
}
is BuiltinFunctionCall -> {
val returnType = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
is PtBuiltinFunctionCall -> {
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
}
is FunctionCallExpression -> {
val sub = value.target.targetSubroutine(program)!!
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
is PtFunctionCall -> {
val symbol = asmgen.symbolTable.lookup(value.name)
val sub = symbol!!.astNode as IPtSubroutine
val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
else -> {
val returnType = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
}
}
}
@ -199,17 +190,27 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
}
internal class AsmAssignment(val source: AsmAssignSource,
val target: AsmAssignTarget,
val isAugmentable: Boolean,
memsizer: IMemSizer,
val position: Position) {
internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
val target: AsmAssignTarget,
val memsizer: IMemSizer,
val position: Position) {
init {
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source dt size must be less or equal to target dt size at $position"
"source dt size must be less or equal to target dt size at $position srcdt=${source.datatype} targetdt=${target.datatype}"
}
}
}
internal class AsmAssignment(source: AsmAssignSource,
target: AsmAssignTarget,
memsizer: IMemSizer,
position: Position): AsmAssignmentBase(source, target, memsizer, position)
internal class AsmAugmentedAssignment(source: AsmAssignSource,
val operator: String,
target: AsmAssignTarget,
memsizer: IMemSizer,
position: Position): AsmAssignmentBase(source, target, memsizer, position)

View File

@ -1,166 +1,71 @@
package prog8.codegen.cpu6502.assignment
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.VariableAllocator
internal class AugmentableAssignmentAsmGen(private val program: Program,
internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
private val assignmentAsmGen: AssignmentAsmGen,
private val asmgen: AsmGen,
private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator
) {
fun translate(assign: AsmAssignment) {
require(assign.isAugmentable)
require(assign.source.kind == SourceStorageKind.EXPRESSION) {
"non-expression assign value should be handled elsewhere ${assign.position}"
}
fun translate(assign: AsmAugmentedAssignment) {
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign, false)
"~" -> inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator")
}
when(assign.operator) {
"-" -> {
val a2 = AsmAssignment(assign.source, assign.target, assign.memsizer, assign.position)
assignmentAsmGen.inplaceNegate(a2, false)
}
"~" -> {
val a2 = AsmAssignment(assign.source, assign.target, assign.memsizer, assign.position)
assignmentAsmGen.inplaceInvert(a2)
}
"+" -> { /* is a nop */ }
else -> {
if(assign.operator.endsWith('='))
augmentedAssignExpr(assign)
else
throw AssemblyError("invalid augmented assign operator ${assign.operator}")
}
is TypecastExpression -> inplaceCast(assign.target, value, assign.position)
is BinaryExpression -> inplaceBinary(assign.target, value)
else -> throw AssemblyError("invalid aug assign value type")
}
}
private fun inplaceBinary(target: AsmAssignTarget, binExpr: BinaryExpression) {
val astTarget = target.origAstTarget!!
if (binExpr.left isSameAs astTarget) {
// A = A <operator> Something
return inplaceModification(target, binExpr.operator, binExpr.right)
private fun augmentedAssignExpr(assign: AsmAugmentedAssignment) {
val srcValue = assign.source.toAstExpression(assign.target.scope as PtNamedNode)
when (assign.operator) {
"+=" -> inplaceModification(assign.target, "+", srcValue)
"-=" -> inplaceModification(assign.target, "-", srcValue)
"*=" -> inplaceModification(assign.target, "*", srcValue)
"/=" -> inplaceModification(assign.target, "/", srcValue)
"|=" -> inplaceModification(assign.target, "|", srcValue)
"&=" -> inplaceModification(assign.target, "&", srcValue)
"^=" -> inplaceModification(assign.target, "^", srcValue)
"<<=" -> inplaceModification(assign.target, "<<", srcValue)
">>=" -> inplaceModification(assign.target, ">>", srcValue)
else -> throw AssemblyError("invalid augmented assign operator ${assign.operator}")
}
if (binExpr.operator in AssociativeOperators) {
if (binExpr.right isSameAs astTarget) {
// A = 5 <operator> A
return inplaceModification(target, binExpr.operator, binExpr.left)
}
val leftBinExpr = binExpr.left as? BinaryExpression
if (leftBinExpr?.operator == binExpr.operator) {
// TODO better optimize the chained asm to avoid intermediate stores/loads?
when {
binExpr.right isSameAs astTarget -> {
// A = (x <associative-operator> y) <same-operator> A
inplaceModification(target, binExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, leftBinExpr.right)
return
}
leftBinExpr.left isSameAs astTarget -> {
// A = (A <associative-operator> x) <same-operator> y
inplaceModification(target, binExpr.operator, leftBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.right)
return
}
leftBinExpr.right isSameAs astTarget -> {
// A = (x <associative-operator> A) <same-operator> y
inplaceModification(target, binExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.right)
return
}
}
}
val rightBinExpr = binExpr.right as? BinaryExpression
if (rightBinExpr?.operator == binExpr.operator) {
when {
binExpr.left isSameAs astTarget -> {
// A = A <associative-operator> (x <same-operator> y)
inplaceModification(target, binExpr.operator, rightBinExpr.left)
inplaceModification(target, binExpr.operator, rightBinExpr.right)
return
}
rightBinExpr.left isSameAs astTarget -> {
// A = y <associative-operator> (A <same-operator> x)
inplaceModification(target, binExpr.operator, binExpr.left)
inplaceModification(target, binExpr.operator, rightBinExpr.right)
return
}
rightBinExpr.right isSameAs astTarget -> {
// A = y <associative-operator> (x <same-operator> y)
inplaceModification(target, binExpr.operator, binExpr.left)
inplaceModification(target, binExpr.operator, rightBinExpr.left)
return
}
}
}
}
val leftBinExpr = binExpr.left as? BinaryExpression
val rightBinExpr = binExpr.right as? BinaryExpression
if(leftBinExpr!=null && rightBinExpr==null) {
if(leftBinExpr.left isSameAs astTarget) {
// X = (X <oper> Right) <oper> Something
inplaceModification(target, leftBinExpr.operator, leftBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.right)
return
}
if(leftBinExpr.right isSameAs astTarget) {
// X = (Left <oper> X) <oper> Something
if(leftBinExpr.operator in AssociativeOperators) {
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.right)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
}
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs astTarget) {
// X = Something <oper> (X <oper> Right)
if(binExpr.operator in AssociativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.left)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
if(rightBinExpr.right isSameAs astTarget) {
// X = Something <oper> (Left <oper> X)
if(binExpr.operator in AssociativeOperators && rightBinExpr.operator in AssociativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.left)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
}
throw FatalAstException("assignment should follow augmentable rules $binExpr")
}
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: Expression) {
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: PtExpression) {
// the asm-gen code can deal with situations where you want to assign a byte into a word.
// it will create the most optimized code to do this (so it type-extends for us).
// But we can't deal with writing a word into a byte - explicit typeconversion is required
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOrElse { throw AssemblyError("unknown dt") }) > program.memsizer.memorySize(target.datatype)) {
val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position)
typecast.linkParents(origValue.parent)
val value = if(program.memsizer.memorySize(origValue.type) > program.memsizer.memorySize(target.datatype)) {
val typecast = PtTypeCast(target.datatype, origValue.position)
typecast.add(origValue)
require(typecast.type!=origValue.type)
typecast
}
else {
origValue
}
val valueLv = (value as? NumericLiteral)?.number
val ident = value as? IdentifierReference
val memread = value as? DirectMemoryRead
val valueLv = (value as? PtNumber)?.number
val ident = value as? PtIdentifier
val memread = value as? PtMemoryByte
when (target.kind) {
TargetStorageKind.VARIABLE -> {
@ -170,7 +75,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_byte_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> {
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
}
@ -182,7 +87,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_word_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> {
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator))
return
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
@ -194,7 +99,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when {
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope!!)
value is TypecastExpression -> {
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
}
@ -206,27 +111,27 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
TargetStorageKind.MEMORY -> {
val memory = target.memory!!
when (memory.addressExpression) {
is NumericLiteral -> {
val addr = (memory.addressExpression as NumericLiteral).number.toInt()
when (memory.address) {
is PtNumber -> {
val addr = (memory.address as PtNumber).number.toInt()
// re-use code to assign a variable, instead this time, use a direct memory address
when {
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
value is TypecastExpression -> {
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
}
else -> inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
}
}
is IdentifierReference -> {
val pointer = memory.addressExpression as IdentifierReference
is PtIdentifier -> {
val pointer = memory.address as PtIdentifier
when {
valueLv != null -> inplaceModification_byte_litval_to_pointer(pointer, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_pointer(pointer, operator, ident)
value is TypecastExpression -> {
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_pointer(pointer, operator, value)
}
@ -235,13 +140,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
else -> {
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, memory.definingSubroutine))
asmgen.assignExpressionTo(memory.address, AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, memory.definingISub()))
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
when {
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, memread)
value is TypecastExpression -> {
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
}
@ -252,91 +157,89 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
TargetStorageKind.ARRAY -> {
with(target.array!!.indexer) {
val indexNum = indexExpr as? NumericLiteral
val indexVar = indexExpr as? IdentifierReference
when {
indexNum!=null -> {
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
when (target.datatype) {
in ByteDatatypes -> {
when {
valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
}
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
val indexNum = target.array!!.index as? PtNumber
val indexVar = target.array.index as? PtIdentifier
when {
indexNum!=null -> {
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
when (target.datatype) {
in ByteDatatypes -> {
when {
valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
}
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
}
in WordDatatypes -> {
when {
valueLv != null -> inplaceModification_word_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
}
else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
}
}
DataType.FLOAT -> {
when {
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
value is TypecastExpression -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
}
indexVar!=null -> {
when (target.datatype) {
in ByteDatatypes -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.A,
target.datatype == DataType.BYTE, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
in WordDatatypes -> {
when {
valueLv != null -> inplaceModification_word_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
}
else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
}
in WordDatatypes -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.AY,
target.datatype == DataType.WORD, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.FAC1,
true, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target)
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
DataType.FLOAT -> {
when {
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
value is PtTypeCast -> {
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
}
indexVar!=null -> {
when (target.datatype) {
in ByteDatatypes -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.A,
target.datatype == DataType.BYTE, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
}
in WordDatatypes -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.AY,
target.datatype == DataType.WORD, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.FAC1,
true, null,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target)
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
}
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
}
}
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
@ -344,21 +247,20 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun tryInplaceModifyWithRemovedRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
private fun tryInplaceModifyWithRemovedRedundantCast(value: PtTypeCast, target: AsmAssignTarget, operator: String): Boolean {
if (target.datatype == value.type) {
val childIDt = value.expression.inferType(program)
val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") }
val childDt = value.value.type
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
// (works for integer types, not for float.)
inplaceModification(target, operator, value.expression)
inplaceModification(target, operator, value.value)
return true
}
}
return false
}
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) {
private fun inplaceModification_byte_value_to_pointer(pointervar: PtIdentifier, operator: String, value: PtExpression) {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) {
@ -394,7 +296,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.storeAIntoZpPointerVar(sourceName)
}
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
private fun inplaceModification_byte_variable_to_pointer(pointervar: PtIdentifier, operator: String, value: PtIdentifier) {
val otherName = asmgen.asmVariableName(value)
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
@ -431,7 +333,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.storeAIntoZpPointerVar(sourceName)
}
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) {
private fun inplaceModification_byte_litval_to_pointer(pointervar: PtIdentifier, operator: String, value: Int) {
when (operator) {
// note: ** (power) operator requires floats.
"+" -> {
@ -499,7 +401,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: PtExpression) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
when (operator) {
@ -594,7 +496,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceModification_byte_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
private fun inplaceModification_byte_variable_to_variable(name: String, dt: DataType, operator: String, ident: PtIdentifier) {
val otherName = asmgen.asmVariableName(ident)
when (operator) {
// note: ** (power) operator requires floats.
@ -762,7 +664,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: PtMemoryByte) {
when (operator) {
"+" -> {
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
@ -799,7 +701,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: PtMemoryByte) {
when (operator) {
"+" -> {
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
@ -1111,9 +1013,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: PtIdentifier) {
val otherName = asmgen.asmVariableName(ident)
when (val valueDt = ident.targetVarDecl(program)!!.datatype) {
when (val valueDt = ident.type) {
in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that
when (operator) {
@ -1356,12 +1258,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: PtExpression) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
val valueiDt = value.inferType(program)
val valueDt = valueiDt.getOrElse { throw AssemblyError("unknown dt") }
fun multiplyVarByWordInAY() {
asmgen.out("""
@ -1410,7 +1310,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""")
}
when (valueDt) {
when (val valueDt = value.type) {
in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that
when (operator) {
@ -1581,7 +1481,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression, scope: Subroutine) {
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: PtExpression, scope: IPtSubroutine) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.FAC1)
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
@ -1623,8 +1523,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.restoreRegisterLocal(CpuRegister.X)
}
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine) {
val valueDt = ident.targetVarDecl(program)!!.datatype
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: PtIdentifier, scope: IPtSubroutine) {
val valueDt = ident.type
if(valueDt != DataType.FLOAT)
throw AssemblyError("float variable expected")
@ -1682,7 +1582,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.restoreRegisterLocal(CpuRegister.X)
}
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine) {
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: IPtSubroutine) {
val constValueName = allocator.getFloatAsmConst(value)
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
@ -1743,259 +1643,50 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) {
val outerCastDt = cast.type
val innerCastDt = (cast.expression as? TypecastExpression)?.type
if (innerCastDt == null) {
// simple typecast where the value is the target
when (target.datatype) {
DataType.UBYTE, DataType.BYTE -> { /* byte target can't be typecasted to anything else at all */ }
DataType.UWORD, DataType.WORD -> {
when (outerCastDt) {
DataType.UBYTE, DataType.BYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${target.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
}
TargetStorageKind.ARRAY -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, target.datatype, CpuRegister.Y, true)
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
}
TargetStorageKind.STACK -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
}
else -> throw AssemblyError("weird target")
}
}
DataType.UWORD, DataType.WORD, in IterableDatatypes -> {}
DataType.FLOAT -> throw AssemblyError("can't cast float in-place")
else -> throw AssemblyError("weird cast type")
}
}
DataType.FLOAT -> {
if (outerCastDt != DataType.FLOAT)
throw AssemblyError("in-place cast of a float makes no sense")
}
else -> throw AssemblyError("invalid cast target type")
}
} else {
// typecast with nested typecast, that has the target as a value
// calculate singular cast that is required
val castDt = if (outerCastDt largerThan innerCastDt) innerCastDt else outerCastDt
val value = (cast.expression as TypecastExpression).expression
val resultingCast = TypecastExpression(value, castDt, false, position)
inplaceCast(target, resultingCast, position)
}
}
internal fun inplaceInvert(assign: AsmAssignment) {
val target = assign.target
when (assign.target.datatype) {
DataType.UBYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
eor #255
sta ${target.asmVarname}""")
}
TargetStorageKind.MEMORY -> {
val memory = target.memory!!
when (memory.addressExpression) {
is NumericLiteral -> {
val addr = (memory.addressExpression as NumericLiteral).number.toHex()
asmgen.out("""
lda $addr
eor #255
sta $addr""")
}
is IdentifierReference -> {
val sourceName = asmgen.loadByteFromPointerIntoA(memory.addressExpression as IdentifierReference)
asmgen.out(" eor #255")
asmgen.out(" sta ($sourceName),y")
}
else -> {
asmgen.assignExpressionToVariable(memory.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
eor #255""")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
}
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" eor #255")
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay")
else -> throw AssemblyError("invalid reg dt for byte invert")
}
}
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.UWORD -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
eor #255
sta ${target.asmVarname}
lda ${target.asmVarname}+1
eor #255
sta ${target.asmVarname}+1""")
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
else -> throw AssemblyError("invalid reg dt for word invert")
}
}
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
else -> throw AssemblyError("invert of invalid type")
}
}
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
val target = assign.target
val datatype = if(ignoreDatatype) {
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> DataType.BYTE
DataType.UWORD, DataType.WORD -> DataType.WORD
else -> target.datatype
}
} else target.datatype
when (datatype) {
DataType.BYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda #0
sec
sbc ${target.asmVarname}
sta ${target.asmVarname}""")
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" eor #255 | ina")
else
asmgen.out(" eor #255 | clc | adc #1")
}
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax | inx")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay | iny")
else -> throw AssemblyError("invalid reg dt for byte negate")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.WORD -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda #0
sec
sbc ${target.asmVarname}
sta ${target.asmVarname}
lda #0
sbc ${target.asmVarname}+1
sta ${target.asmVarname}+1""")
}
TargetStorageKind.REGISTER -> {
when(target.register!!) { //P8ZP_SCRATCH_REG
RegisterOrPair.AX -> {
asmgen.out("""
sec
eor #255
adc #0
pha
txa
eor #255
adc #0
tax
pla""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sec
eor #255
adc #0
pha
tya
eor #255
adc #0
tay
pla""")
}
RegisterOrPair.XY -> {
asmgen.out("""
sec
txa
eor #255
adc #0
tax
tya
eor #255
adc #0
tay""")
}
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.FLOAT -> {
when (target.kind) {
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.NEGOP")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.MOVFA | jsr floats.NEGOP | jsr floats.MOVEF")
else -> throw AssemblyError("invalid float register")
}
}
TargetStorageKind.VARIABLE -> {
// simply flip the sign bit in the float
asmgen.out("""
lda ${target.asmVarname}+1
eor #$80
sta ${target.asmVarname}+1
""")
}
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target for in-place float negation")
}
}
else -> throw AssemblyError("negate of invalid type")
}
}
}
private fun AsmAssignSource.toAstExpression(scope: PtNamedNode): PtExpression {
return when(kind) {
SourceStorageKind.LITERALNUMBER -> this.number!!
SourceStorageKind.VARIABLE -> {
val ident = PtIdentifier(scope.scopedName + '.' + asmVarname, datatype, Position.DUMMY)
ident.parent = scope
ident
}
SourceStorageKind.ARRAY -> this.array!!
SourceStorageKind.MEMORY -> this.memory!!
SourceStorageKind.EXPRESSION -> this.expression!!
SourceStorageKind.REGISTER -> {
if(register in Cx16VirtualRegisters) {
val ident = PtIdentifier("cx16.${register!!.name.lowercase()}", DataType.UWORD, position = scope.position)
ident.parent = scope
ident
} else {
throw AssemblyError("no ast expr possible for source register $register")
}
}
else -> throw AssemblyError("invalid assign source kind $kind")
}
}
private fun AsmAssignTarget.toAstExpression(): PtExpression {
return when(kind) {
TargetStorageKind.VARIABLE -> {
val ident = PtIdentifier((this.scope as PtNamedNode).scopedName + '.' + asmVarname, datatype, origAstTarget?.position ?: Position.DUMMY)
ident.parent = this.scope
ident
}
TargetStorageKind.ARRAY -> this.array!!
TargetStorageKind.MEMORY -> this.memory!!
TargetStorageKind.REGISTER -> {
if(register in Cx16VirtualRegisters) {
val ident = PtIdentifier("cx16.${register!!.name.lowercase()}", DataType.UWORD, position = this.origAssign.position)
ident.parent = (this.scope as? PtNamedNode) ?: this.origAstTarget!!
ident
} else {
throw AssemblyError("no ast expr possible for target register $register")
}
}
else -> throw AssemblyError("invalid assign target kind $kind")
}
}

View File

@ -0,0 +1,60 @@
package prog8tests.codegencpu6502
import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType) = when(dt) {
in ByteDatatypes -> 1
DataType.FLOAT -> 5
else -> 2
}
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
DataType.ARRAY_UW -> numElements*2
DataType.ARRAY_W -> numElements*2
DataType.ARRAY_F -> numElements*5
else -> numElements
}
}
internal object DummyStringEncoder : IStringEncoding {
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
return ""
}
}
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
if(!keepMessagesAfterReporting) {
clear()
}
}
fun clear() {
errors.clear()
warnings.clear()
}
}

View File

@ -0,0 +1,106 @@
package prog8tests.codegencpu6502
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.codegen.cpu6502.AsmGen6502
import java.nio.file.Files
import kotlin.io.path.Path
class TestCodegen: FunSpec({
fun getTestOptions(): CompilationOptions {
val target = C64Target()
return CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
floats = true,
noSysInit = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
}
test("augmented assign on arrays") {
//main {
// sub start() {
// ubyte[] particleX = [1,2,3]
// ubyte[] particleDX = [1,2,3]
// particleX[2] += particleDX[2]
//
// word @shared xx = 1
// xx = -xx
// xx += 42
// xx += cx16.r0
// }
//}
val codegen = AsmGen6502()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
}
it.add(targetIdx)
}
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
assign.add(target)
assign.add(value)
sub.add(assign)
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
sub.add(numberAssign)
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
sub.add(cxregAssign)
block.add(sub)
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
val options = getTestOptions()
val st = SymbolTableMaker(program, options).make()
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors)!!
result.name shouldBe "test"
Files.deleteIfExists(Path("${result.name}.asm"))
}
})

View File

@ -3,19 +3,19 @@ package prog8.codegen.experimental
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram
import prog8.code.core.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
class CodeGen(private val program: PtProgram,
private val symbolTable: SymbolTable,
private val options: CompilationOptions,
private val errors: IErrorReporter
): IAssemblyGenerator {
override fun compileToAssembly(): IAssemblyProgram? {
class ExperiCodeGen: ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter
): IAssemblyProgram? {
// you could write a code generator directly on the PtProgram AST,
// but you can also use the Intermediate Representation to build a codegen on:
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)

View File

@ -3,6 +3,7 @@ package prog8.codegen.intermediate
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.PrefixOperators
import prog8.code.core.SignedDatatypes
import prog8.intermediate.*
@ -12,22 +13,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(assignment.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
return if (assignment.isInplaceAssign)
translateInplaceAssign(assignment)
else
translateRegularAssign(assignment)
return translateRegularAssign(assignment)
}
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
internal fun translate(augmentedAssign: PtAugmentedAssign): IRCodeChunks {
if(augmentedAssign.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
return translateInplaceAssign(augmentedAssign)
}
private fun translateInplaceAssign(assignment: PtAugmentedAssign): IRCodeChunks {
val ident = assignment.target.identifier
val memory = assignment.target.memory
val array = assignment.target.array
return if(ident!=null) {
assignSelfInMemory(ident.name, assignment.value, assignment)
assignVarAugmented(ident.name, assignment)
} else if(memory != null) {
if(memory.address is PtNumber)
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
assignMemoryAugmented((memory.address as PtNumber).number.toInt(), assignment)
else
fallbackAssign(assignment)
} else if(array!=null) {
@ -40,120 +45,83 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
}
private fun assignSelfInMemoryKnownAddress(
private fun assignMemoryAugmented(
address: Int,
value: PtExpression,
origAssign: PtAssignment
assignment: PtAugmentedAssign
): IRCodeChunks {
val value = assignment.value
val vmDt = codeGen.irType(value.type)
when(value) {
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
is PtMemoryByte -> {
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
emptyList() // do nothing, mem=mem null assignment.
else {
// read and write a (i/o) memory location to itself.
val tempReg = codeGen.registers.nextFree()
val code = IRCodeChunk(null, null)
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
listOf(code)
}
}
else -> return fallbackAssign(origAssign)
return when(assignment.operator) {
"+" -> expressionEval.operatorPlusInplace(address, null, vmDt, value)
"-" -> expressionEval.operatorMinusInplace(address, null, vmDt, value)
"*" -> expressionEval.operatorMultiplyInplace(address, null, vmDt, value)
"/" -> expressionEval.operatorDivideInplace(address, null, vmDt, value.type in SignedDatatypes, value)
"|" -> expressionEval.operatorOrInplace(address, null, vmDt, value)
"&" -> expressionEval.operatorAndInplace(address, null, vmDt, value)
"^" -> expressionEval.operatorXorInplace(address, null, vmDt, value)
"<<" -> expressionEval.operatorShiftLeftInplace(address, null, vmDt, value)
">>" -> expressionEval.operatorShiftRightInplace(address, null, vmDt, value.type in SignedDatatypes, value)
in PrefixOperators -> inplacePrefix(assignment.operator, vmDt, address, null)
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
}
}
private fun assignSelfInMemory(
symbol: String,
value: PtExpression,
origAssign: PtAssignment
): IRCodeChunks {
val vmDt = codeGen.irType(value.type)
return when(value) {
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
is PtMemoryByte -> {
val code = IRCodeChunk(null, null)
val tempReg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
listOf(code)
}
else -> fallbackAssign(origAssign)
private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks {
val value = assignment.value
val valueVmDt = codeGen.irType(value.type)
return when (assignment.operator) {
"+=" -> expressionEval.operatorPlusInplace(null, symbol, valueVmDt, value)
"-=" -> expressionEval.operatorMinusInplace(null, symbol, valueVmDt, value)
"*=" -> expressionEval.operatorMultiplyInplace(null, symbol, valueVmDt, value)
"/=" -> expressionEval.operatorDivideInplace(null, symbol, valueVmDt, value.type in SignedDatatypes, value)
"|=" -> expressionEval.operatorOrInplace(null, symbol, valueVmDt, value)
"&=" -> expressionEval.operatorAndInplace(null, symbol, valueVmDt, value)
"^=" -> expressionEval.operatorXorInplace(null, symbol, valueVmDt, value)
"<<=" -> expressionEval.operatorShiftLeftInplace(null, symbol, valueVmDt, value)
">>=" -> expressionEval.operatorShiftRightInplace(null, symbol, valueVmDt, value.type in SignedDatatypes, value)
in PrefixOperators -> inplacePrefix(assignment.operator, valueVmDt, null, symbol)
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
}
}
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
if (codeGen.options.slowCodegenWarnings)
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
return translateRegularAssign(origAssign)
}
private fun inplaceBinexpr(
operator: String,
operand: PtExpression,
vmDt: IRDataType,
signed: Boolean,
knownAddress: Int?,
symbol: String?,
origAssign: PtAssignment
): IRCodeChunks {
if(knownAddress!=null) {
when (operator) {
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
"-" -> return expressionEval.operatorMinusInplace(knownAddress, null, vmDt, operand)
"*" -> return expressionEval.operatorMultiplyInplace(knownAddress, null, vmDt, operand)
"/" -> return expressionEval.operatorDivideInplace(knownAddress, null, vmDt, signed, operand)
"|" -> return expressionEval.operatorOrInplace(knownAddress, null, vmDt, operand)
"&" -> return expressionEval.operatorAndInplace(knownAddress, null, vmDt, operand)
"^" -> return expressionEval.operatorXorInplace(knownAddress, null, vmDt, operand)
"<<" -> return expressionEval.operatorShiftLeftInplace(knownAddress, null, vmDt, operand)
">>" -> return expressionEval.operatorShiftRightInplace(knownAddress, null, vmDt, signed, operand)
else -> {}
}
val normalAssign = PtAssignment(origAssign.position)
normalAssign.add(origAssign.target)
val value: PtExpression
if(origAssign.operator in PrefixOperators) {
value = PtPrefix(origAssign.operator, origAssign.value.type, origAssign.value.position)
value.add(origAssign.value)
} else {
symbol!!
when (operator) {
"+" -> return expressionEval.operatorPlusInplace(null, symbol, vmDt, operand)
"-" -> return expressionEval.operatorMinusInplace(null, symbol, vmDt, operand)
"*" -> return expressionEval.operatorMultiplyInplace(null, symbol, vmDt, operand)
"/" -> return expressionEval.operatorDivideInplace(null, symbol, vmDt, signed, operand)
"|" -> return expressionEval.operatorOrInplace(null, symbol, vmDt, operand)
"&" -> return expressionEval.operatorAndInplace(null, symbol, vmDt, operand)
"^" -> return expressionEval.operatorXorInplace(null, symbol, vmDt, operand)
"<<" -> return expressionEval.operatorShiftLeftInplace(null, symbol, vmDt, operand)
">>" -> return expressionEval.operatorShiftRightInplace(null, symbol, vmDt, signed, operand)
else -> {}
}
require(origAssign.operator.endsWith('='))
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
val left: PtExpression = origAssign.target.children.single() as PtExpression
value.add(left)
value.add(origAssign.value)
}
return fallbackAssign(origAssign)
normalAssign.add(value)
return translateRegularAssign(normalAssign)
}
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
private fun inplacePrefix(operator: String, vmDt: IRDataType, address: Int?, symbol: String?): IRCodeChunks {
val code= IRCodeChunk(null, null)
when(operator) {
"+" -> { }
"-" -> {
code += if(knownAddress!=null)
IRInstruction(Opcode.NEGM, vmDt, value = knownAddress)
code += if(address!=null)
IRInstruction(Opcode.NEGM, vmDt, value = address)
else
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = symbol)
}
"~" -> {
val regMask = codeGen.registers.nextFree()
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
code += if(knownAddress!=null)
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
code += if(address!=null)
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = address)
else
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = addressSymbol)
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = symbol)
}
else -> throw AssemblyError("weird prefix operator")
}

View File

@ -26,7 +26,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"rrestore",
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
"msb" -> funcMsb(call, resultRegister)
"lsb" -> funcLsb(call, resultRegister)
"memory" -> funcMemory(call, resultRegister)

View File

@ -988,20 +988,20 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(fcall.type==DataType.FLOAT)
throw AssemblyError("doesn't support float register result in asm romsub")
val returns = callTarget.returns.single()
val regStr = if(returns.registerOrPair!=null) returns.registerOrPair.toString() else returns.statusflag.toString()
val regStr = if(returns.register.registerOrPair!=null) returns.register.registerOrPair.toString() else returns.register.statusflag.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
}
else -> {
val returnRegister = callTarget.returns.singleOrNull{ it.registerOrPair!=null }
val returnRegister = callTarget.returns.singleOrNull{ it.register.registerOrPair!=null }
if(returnRegister!=null) {
// we skip the other values returned in the status flags.
val regStr = returnRegister.registerOrPair.toString()
val regStr = returnRegister.register.registerOrPair.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
} else {
val firstReturnRegister = callTarget.returns.firstOrNull{ it.registerOrPair!=null }
val firstReturnRegister = callTarget.returns.firstOrNull{ it.register.registerOrPair!=null }
if(firstReturnRegister!=null) {
// we just take the first register return value and ignore the rest.
val regStr = firstReturnRegister.registerOrPair.toString()
val regStr = firstReturnRegister.register.registerOrPair.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
} else {
throw AssemblyError("invalid number of return values from call")

View File

@ -33,15 +33,13 @@ class IRCodeGen(
if(options.evalStackBaseAddress!=null)
throw AssemblyError("IR doesn't use eval-stack")
if(!options.dontReinitGlobals) {
// collect global variables initializers
program.allBlocks().forEach {
val result = mutableListOf<IRCodeChunkBase>()
it.children.filterIsInstance<PtAssignment>().forEach { assign -> result += assignmentGen.translate(assign) }
result.forEach { chunk ->
if (chunk is IRCodeChunk) irProg.addGlobalInits(chunk)
else throw AssemblyError("only expect code chunk for global inits")
}
// collect global variables initializers
program.allBlocks().forEach {
val result = mutableListOf<IRCodeChunkBase>()
it.children.filterIsInstance<PtAssignment>().forEach { assign -> result += assignmentGen.translate(assign) }
result.forEach { chunk ->
if (chunk is IRCodeChunk) irProg.addGlobalInits(chunk)
else throw AssemblyError("only expect code chunk for global inits")
}
}
@ -238,6 +236,7 @@ class IRCodeGen(
is PtMemMapped -> emptyList() // memmapped var should be looked up via symbol table
is PtConstant -> emptyList() // constants have all been folded into the code
is PtAssignment -> assignmentGen.translate(node)
is PtAugmentedAssign -> assignmentGen.translate(node)
is PtNodeGroup -> translateGroup(node.children)
is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0)
is PtFunctionCall -> expressionEval.translate(node, 0, 0)
@ -1178,7 +1177,7 @@ class IRCodeGen(
for (child in block.children) {
when(child) {
is PtNop -> { /* nothing */ }
is PtAssignment -> { /* global variable initialization is done elsewhere */ }
is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
is PtSub -> {
val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position)
@ -1203,8 +1202,8 @@ class IRCodeGen(
child.name,
child.address,
child.clobbers,
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.second, it.first.type) }, // note: the name of the asmsub param is not used anymore.
child.returnTypes.zip(child.retvalRegisters).map { IRAsmSubroutine.IRAsmParam(it.second, it.first) },
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.first, it.second.type) }, // note: the name of the asmsub param is not used here anymore
child.returns.map { IRAsmSubroutine.IRAsmParam(it.first, it.second)},
asmChunk,
child.position
)

View File

@ -3,29 +3,28 @@ package prog8.codegen.vm
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram
import prog8.code.core.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
import prog8.intermediate.IRProgram
class VmCodeGen(private val program: PtProgram,
private val symbolTable: SymbolTable,
private val options: CompilationOptions,
private val errors: IErrorReporter
): IAssemblyGenerator {
override fun compileToAssembly(): IAssemblyProgram? {
class VmCodeGen: ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter
): IAssemblyProgram? {
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irProgram = irCodeGen.generate()
return VmAssemblyProgram(irProgram.name, irProgram)
}
}
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
internal class VmAssemblyProgram(override val name: String, internal val irProgram: IRProgram): IAssemblyProgram {
override fun assemble(options: CompilationOptions): Boolean {
// the VM reads the IR file from disk.

View File

@ -1,12 +1,18 @@
import prog8.code.core.DataType
import prog8.code.core.Encoding
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType) = 0
override fun memorySize(arrayDt: DataType, numElements: Int) = 0
override fun memorySize(dt: DataType) = when(dt) {
in ByteDatatypes -> 1
DataType.FLOAT -> 5
else -> 2
}
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
DataType.ARRAY_UW -> numElements*2
DataType.ARRAY_W -> numElements*2
DataType.ARRAY_F -> numElements*5
else -> numElements
}
}
internal object DummyStringEncoder : IStringEncoding {
@ -14,7 +20,39 @@ internal object DummyStringEncoder : IStringEncoding {
return emptyList()
}
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
return ""
}
}
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
if(!keepMessagesAfterReporting) {
clear()
}
}
fun clear() {
errors.clear()
warnings.clear()
}
}

View File

@ -0,0 +1,103 @@
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.codegen.vm.VmAssemblyProgram
import prog8.codegen.vm.VmCodeGen
import prog8.intermediate.IRSubroutine
class TestVmCodeGen: FunSpec({
fun getTestOptions(): CompilationOptions {
val target = VMTarget()
return CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
floats = true,
noSysInit = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
}
test("augmented assigns") {
//main {
// sub start() {
// ubyte[] particleX = [1,2,3]
// ubyte[] particleDX = [1,2,3]
// particleX[2] += particleDX[2]
//
// word @shared xx = 1
// xx = -xx
// xx += 42
// xx += cx16.r0
// }
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
}
it.add(targetIdx)
}
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
assign.add(target)
assign.add(value)
sub.add(assign)
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
sub.add(numberAssign)
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
sub.add(cxregAssign)
block.add(sub)
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
val options = getTestOptions()
val st = SymbolTableMaker(program, options).make()
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBeGreaterThan 4
}
})

View File

@ -4,9 +4,6 @@ import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.getTempVar
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin
@ -31,23 +28,6 @@ class BinExprSplitter(private val program: Program, private val options: Compila
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps.
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN expression ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
X RightExpr.
*/
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.right)
return noModifications
@ -60,20 +40,6 @@ X = BinExpr X = LeftExpr
val rightBx = binExpr.right as? BinaryExpression
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
return noModifications
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
// val dt = assignment.target.inferType(program)
// if(!dt.isInteger)
// return noModifications
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
// val assignTempVar = Assignment(
// AssignTarget(tempVar, null, null, binExpr.right.position),
// binExpr.right, binExpr.right.position
// )
// return listOf(
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
// )
}
if(binExpr.right.isSimple) {
@ -87,30 +53,10 @@ X = BinExpr X = LeftExpr
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also work on
// the expressiontrees that are not used in an assignment statement...
}
val typecast = assignment.value as? TypecastExpression
if(typecast!=null) {
val origExpr = typecast.expression as? BinaryExpression
if(origExpr!=null && options.compTarget.name!=VMTarget.NAME) {
// it's a typecast of a binary expression.
// 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 tempvarDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
val (tempVarName, _) = program.getTempVar(tempvarDt)
val assignTempVar = Assignment(
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(tempVarName, typecast.position), typecast)
)
}
// Further unraveling of binary expressions is really complicated here and
// often results in much bigger code, thereby defeating the purpose a bit.
// All in all this should probably be fixed in a better code generation backend
// that doesn't require this at all.
}
return noModifications

View File

@ -354,9 +354,11 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(valueDt isnot decl.datatype) {
val cast = numval.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
val cast = numval.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
}
}
return noModifications
@ -386,9 +388,9 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification?
{
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
// NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
// TODO: this implements only a small set of possible reorderings at this time, we could perhaps add 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.

View File

@ -18,7 +18,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
class ExpressionSimplifier(private val program: Program,
private val errors: IErrorReporter,
@ -187,12 +187,37 @@ class ExpressionSimplifier(private val program: Program,
}
}
// X <= Y-1 ---> X<Y , X >= Y+1 ---> X>Y
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightExpr = expr.right as? BinaryExpression
if(rightExpr!=null && rightExpr.right.constValue(program)?.number==1.0) {
if (expr.operator == "<=" && rightExpr.operator == "-") {
expr.operator = "<"
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
} else if (expr.operator == ">=" && rightExpr.operator == "+") {
expr.operator = ">"
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
}
}
}
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
// for integers: x >= 1 --> x > 0
expr.operator = ">"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
}
// for signed integers: X <= -1 => X<0 , X > -1 => X>=0
if(leftDt in SignedDatatypes && leftDt!=DataType.FLOAT && rightVal?.number==-1.0) {
if(expr.operator=="<=") {
expr.operator = "<"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt, 0.0, expr.right.position), expr))
} else if(expr.operator==">") {
expr.operator = ">="
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt, 0.0, expr.right.position), expr))
}
}
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
if(expr.operator == ">=" && rightVal?.number == 0.0) {
// unsigned >= 0 --> true

View File

@ -118,9 +118,11 @@ class Inliner(val program: Program): AstWalker() {
}
private fun makeFullyScoped(identifier: IdentifierReference) {
val scoped = (identifier.targetStatement(program)!! as INamedStatement).scopedName
val scopedIdent = IdentifierReference(scoped, identifier.position)
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
identifier.targetStatement(program)?.let { target ->
val scoped = (target as INamedStatement).scopedName
val scopedIdent = IdentifierReference(scoped, identifier.position)
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
}
}
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
@ -130,27 +132,30 @@ class Inliner(val program: Program): AstWalker() {
}
private fun makeFullyScoped(call: FunctionCallStatement) {
val sub = call.target.targetSubroutine(program)!!
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
private fun makeFullyScoped(call: BuiltinFunctionCall) {
val sub = call.target.targetSubroutine(program)!!
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
private fun makeFullyScoped(call: FunctionCallExpression) {
val sub = call.target.targetSubroutine(program)!!
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
private fun makeScopedArgs(args: List<Expression>): List<Expression> {

View File

@ -120,17 +120,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
}}
}
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
rts
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y
%asm {{

View File

@ -28,10 +28,6 @@ romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
@ -147,17 +143,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
}}
}
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_REG
tya
ldy P8ZP_SCRATCH_REG
rts
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y
%asm {{

View File

@ -521,7 +521,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15
; routine from https://forums.nesdev.org/viewtopic.php?p=130363&sid=1944ba8bac4d6afa9c02e3cc42304e6b#p130363
;HexToDec99
; start in A

View File

@ -119,17 +119,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
}}
}
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
rts
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y
%asm {{

View File

@ -454,20 +454,19 @@ inline asmsub getrombank() -> ubyte @A {
}
inline asmsub getrambank() -> ubyte @A {
; -- get the current ram bank
; -- get the current RAM bank
%asm {{
lda $00
}}
}
asmsub numbanks() -> uword @AY {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
; Note that the number of such banks can be bigger than 255 so a word is returned, but mostly
; the A register could suffice as the lsb.
; The maximum number of banks is 256 = 2 Megabytes of banked Ram aka Hiram. (Y=1 and A=0 in this case).
; MEMTOP itself reports 0 in this case which we change into 256 for convenience.
; It reporting 0 doesn't mean 'zero banks', instead it means 256 banks (=2Mb banked RAM),
; as there is no X16 without at least 1 page of banked RAM.
; -- Returns the number of available RAM banks according to the kernal (each bank is 8 Kb).
; Note that the number of such banks can be 256 so a word is returned.
; But just looking at the A register (the LSB of the result word) could suffice if you know that A=0 means 256 banks:
; The maximum number of RAM banks in the X16 is currently 256 (2 Megabytes of banked RAM).
; Kernal's MEMTOP routine reports 0 in this case but that doesn't mean 'zero banks', instead it means 256 banks,
; as there is no X16 without at least 1 page of banked RAM. So this routine returns 256 instead of 0.
%asm {{
phx
sec
@ -634,13 +633,17 @@ asmsub init_system() {
; Called automatically by the loader program logic.
%asm {{
sei
lda #0
tax
tay
jsr cx16.mouse_config ; disable mouse
cld
lda VERA_DC_VIDEO
and #%00000111 ; retain chroma + output mode
sta P8ZP_SCRATCH_REG
lda #$80
sta VERA_CTRL ; reset vera
stz $01 ; select rom bank 0 (enable kernal)
stz $01 ; rom bank 0 (kernal)
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
@ -650,8 +653,9 @@ asmsub init_system() {
sta VERA_DC_VIDEO ; keep old output mode
lda #$90 ; black
jsr c64.CHROUT
lda #1 ; swap fg/bg
jsr c64.CHROUT
lda #1
sta $00 ; select ram bank 1
jsr c64.CHROUT ; swap fg/bg
lda #$9e ; yellow
jsr c64.CHROUT
lda #147 ; clear screen
@ -684,7 +688,7 @@ asmsub cleanup_at_exit() {
lda #1
sta $00 ; ram bank 1
lda #4
sta $01 ; rom bank 4 (kernal)
sta $01 ; rom bank 4 (basic)
stz $2d ; hack to reset machine code monitor bank to 0
rts
}}
@ -714,8 +718,7 @@ _modified jsr $ffff ; modified
lda _use_kernal
bne +
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #1
lda #1
sta cx16.VERA_ISR ; clear Vera Vsync irq status
ply
plx

View File

@ -1 +1 @@
8.9
8.10

View File

@ -43,7 +43,6 @@ private fun compileMain(args: Array<String>): Boolean {
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental)")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
@ -52,6 +51,7 @@ private fun compileMain(args: Array<String>): Boolean {
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}', '${VMTarget.NAME}')").default(C64Target.NAME)
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
val varsHigh by cli.option(ArgType.Boolean, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program")
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try {
@ -120,12 +120,12 @@ private fun compileMain(args: Array<String>): Boolean {
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontReinitGlobals == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
asmListfile == true,
experimentalCodegen == true,
varsHigh == true,
compilationTarget,
evalStackAddr,
processedSymbols,
@ -184,12 +184,12 @@ private fun compileMain(args: Array<String>): Boolean {
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontReinitGlobals == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
asmListfile == true,
experimentalCodegen == true,
varsHigh == true,
compilationTarget,
evalStackAddr,
processedSymbols,
@ -206,13 +206,13 @@ private fun compileMain(args: Array<String>): Boolean {
}
if(startEmulator1==true || startEmulator2==true) {
if (compilationResult.program.name.isEmpty()) {
if (compilationResult.compilerAst.name.isEmpty()) {
println("\nCan't start emulator because no program was assembled.")
return true
}
}
val programNameInPath = outputPath.resolve(compilationResult.program.name)
val programNameInPath = outputPath.resolve(compilationResult.compilerAst.name)
if(startEmulator1==true || startEmulator2==true) {
if (compilationResult.compilationOptions.launcher != CbmPrgLauncherType.NONE || compilationTarget=="atari") {

View File

@ -0,0 +1,158 @@
package prog8.compiler
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.FatalAstException
import prog8.ast.base.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.VarDecl
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.sign
import kotlin.math.sqrt
private typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteral
internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller> = mapOf(
"abs" to ::builtinAbs,
"len" to ::builtinLen,
"sizeof" to ::builtinSizeof,
"sgn" to ::builtinSgn,
"sqrt16" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()) } },
"any" to { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } },
"msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} },
"mkword" to ::builtinMkword
)
private fun builtinAny(array: List<Double>): Double = if(array.any { it!=0.0 }) 1.0 else 0.0
private fun builtinAll(array: List<Double>): Double = if(array.all { it!=0.0 }) 1.0 else 0.0
internal fun builtinFunctionReturnType(function: String): InferredTypes.InferredType {
if(function in arrayOf("set_carry", "set_irqd", "clear_carry", "clear_irqd"))
return InferredTypes.InferredType.void()
val func = BuiltinFunctions.getValue(function)
val returnType = func.returnType
return if(returnType==null)
InferredTypes.InferredType.void()
else
InferredTypes.knownFor(returnType)
}
internal class NotConstArgumentException: AstException("not a const argument to a built-in function")
internal class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Double): NumericLiteral {
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
throw SyntaxError("built-in function requires one integer argument", position)
val integer = constval.number.toInt()
return NumericLiteral.optimalInteger(function(integer).toInt(), args[0].position)
}
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Double>)->Double): NumericLiteral {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val array= args[0] as? ArrayLiteral ?: throw NotConstArgumentException()
val constElements = array.value.map{it.constValue(program)?.number}
if(constElements.contains(null))
throw NotConstArgumentException()
return NumericLiteral.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
}
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = int, result type= uword
if(args.size!=1)
throw SyntaxError("abs requires one integer argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return when (constval.type) {
in IntegerDatatypesNoBool -> NumericLiteral.optimalInteger(abs(constval.number.toInt()), args[0].position)
else -> throw SyntaxError("abs requires one integer argument", position)
}
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
if(args[0] !is IdentifierReference)
throw SyntaxError("sizeof argument should be an identifier", position)
val dt = args[0].inferType(program)
if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target")
return when {
dt.isArray -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
NumericLiteral.optimalInteger(program.memsizer.memorySize(elementDt) * length, position)
}
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteral(DataType.UBYTE, program.memsizer.memorySize(dt.getOr(DataType.UNDEFINED)).toDouble(), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
}
}
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)
throw SyntaxError("len requires one argument", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null)
return NumericLiteral.optimalInteger(arraySize, position)
if(args[0] is ArrayLiteral)
return NumericLiteral.optimalInteger((args[0] as ArrayLiteral).value.size, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) {
in ArrayDatatypes -> {
arraySize = target.arraysize?.constIndex()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
NumericLiteral.optimalInteger(arraySize, args[0].position)
}
DataType.STR -> {
val refLv = target.value as? StringLiteral ?: throw CannotEvaluateException("len", "stringsize unknown")
NumericLiteral.optimalInteger(refLv.value.length, args[0].position)
}
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
else -> throw InternalCompilerException("weird datatype")
}
}
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position)
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
return NumericLiteral(DataType.UWORD, result.toDouble(), position)
}
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return NumericLiteral(DataType.BYTE, constval.number.sign, position)
}

View File

@ -1,17 +1,14 @@
package prog8.compiler
import com.github.michaelbull.result.onFailure
import prog8.ast.AstToSourceTextConverter
import prog8.ast.IBuiltinFunctions
import prog8.ast.IStatementContainer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive
import prog8.ast.statements.VarDecl
import prog8.ast.walk.IAstVisitor
import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker
import prog8.code.ast.PtProgram
import prog8.code.core.*
import prog8.code.target.*
import prog8.codegen.vm.VmCodeGen
@ -25,19 +22,20 @@ import kotlin.math.round
import kotlin.system.measureTimeMillis
class CompilationResult(val program: Program,
class CompilationResult(val compilerAst: Program, // deprecated, use codegenAst instead
val codegenAst: PtProgram?,
val compilationOptions: CompilationOptions,
val importedFiles: List<Path>)
class CompilerArguments(val filepath: Path,
val optimize: Boolean,
val optimizeFloatExpressions: Boolean,
val dontReinitGlobals: Boolean,
val writeAssembly: Boolean,
val slowCodegenWarnings: Boolean,
val quietAssembler: Boolean,
val asmListfile: Boolean,
val experimentalCodegen: Boolean,
val varsHigh: Boolean,
val compilationTarget: String,
val evalStackBaseAddress: UInt?,
val symbolDefs: Map<String, String>,
@ -63,23 +61,21 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
var compilationOptions: CompilationOptions
var ast: PtProgram? = null
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (programresult, options, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
val (programresult, options, imported) = parseMainModule(args.filepath, args.errors, compTarget, args.sourceDirs)
compilationOptions = options
print("Parsed ${args.filepath}")
ModuleImporter.ansiEraseRestOfLine(true)
with(compilationOptions) {
slowCodegenWarnings = args.slowCodegenWarnings
optimize = args.optimize
optimizeFloatExpressions = optimizeFloatExpr
dontReinitGlobals = args.dontReinitGlobals
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen
varsHigh = args.varsHigh
evalStackBaseAddress = args.evalStackBaseAddress
outputDir = args.outputDir.normalize()
symbolDefs = args.symbolDefs
@ -113,10 +109,22 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
args.errors.report()
if (args.writeAssembly) {
if(!createAssemblyAndAssemble(program, args.errors, compilationOptions)) {
compilationOptions.compTarget.machine.initializeMemoryAreas(compilationOptions)
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report()
val intermediateAst = IntermediateAstMaker(program, compilationOptions).transform()
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printAst(intermediateAst, ::println)
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions)) {
System.err.println("Error in codegeneration or assembler")
return null
}
ast = intermediateAst
}
}
@ -124,7 +132,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(program, compilationOptions, importedFiles)
return CompilationResult(program, ast, compilationOptions, importedFiles)
} catch (px: ParseError) {
System.err.print("\n\u001b[91m") // bright red
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
@ -208,7 +216,7 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override fun constValue(funcName: String, args: List<Expression>, position: Position): NumericLiteral? {
val func = BuiltinFunctions[funcName]
if(func!=null) {
val exprfunc = func.constExpressionFunc
val exprfunc = constEvaluatorsForBuiltinFuncs[funcName]
if(exprfunc!=null) {
return try {
exprfunc(args, position, program)
@ -226,17 +234,16 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override fun returnType(funcName: String) = builtinFunctionReturnType(funcName)
}
fun parseImports(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compilation target: ${compTarget.name}")
fun parseMainModule(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
bf.program = program
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
val importedModuleResult = importer.importModule(filepath)
val importedModuleResult = importer.importMainModule(filepath)
importedModuleResult.onFailure { throw it }
errors.report()
@ -246,11 +253,11 @@ fun parseImports(filepath: Path,
val compilerOptions = determineCompilationOptions(program, compTarget)
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
importer.importLibraryModule(lib)
importer.importImplicitLibraryModule(lib)
// always import prog8_lib and math
importer.importLibraryModule("math")
importer.importLibraryModule("prog8_lib")
importer.importImplicitLibraryModule("math")
importer.importImplicitLibraryModule("prog8_lib")
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
@ -328,7 +335,6 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
}
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
println("Analyzing code...")
program.preprocessAst(errors, compilerOptions)
program.checkIdentifiers(errors, compilerOptions)
errors.report()
@ -353,7 +359,6 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
}
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
println("Optimizing...")
val remover = UnusedCodeRemover(program, errors, compTarget)
remover.visit(program)
remover.applyModifications()
@ -385,25 +390,23 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
errors.report()
}
private fun createAssemblyAndAssemble(program: Program,
private fun createAssemblyAndAssemble(program: PtProgram,
errors: IErrorReporter,
compilerOptions: CompilationOptions
): Boolean {
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report()
val symbolTable = SymbolTableMaker(program, compilerOptions).make()
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
// Note: we don't actually *need* to remove the VarDecl nodes, but it is nice as a temporary measure
// to help clean up the code that still depends on them.
// removeAllVardeclsFromAst(program)
val asmgen = if(compilerOptions.experimentalCodegen)
prog8.codegen.experimental.ExperiCodeGen()
else if (compilerOptions.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
prog8.codegen.cpu6502.AsmGen6502()
else if (compilerOptions.compTarget.name == VMTarget.NAME)
VmCodeGen()
else
throw NotImplementedError("no asm generator for cpu ${compilerOptions.compTarget.machine.cpu}")
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program)
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
val stMaker = SymbolTableMaker(program, compilerOptions)
val symbolTable = stMaker.make()
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
errors.report()
return if(assembly!=null && errors.noErrors()) {
@ -412,51 +415,3 @@ private fun createAssemblyAndAssemble(program: Program,
false
}
}
private fun removeAllVardeclsFromAst(program: Program) {
// remove all VarDecl nodes from the AST.
// code generation doesn't require them anymore, it operates only on the 'variables' collection.
class SearchAndRemove: IAstVisitor {
private val allVars = mutableListOf<VarDecl>()
init {
visit(program)
for (it in allVars) {
require((it.parent as IStatementContainer).statements.remove(it))
}
}
override fun visit(decl: VarDecl) {
allVars.add(decl)
}
}
SearchAndRemove()
}
fun printProgram(program: Program) {
println()
val printer = AstToSourceTextConverter(::print, program)
printer.visit(program)
println()
}
internal fun asmGeneratorFor(program: Program,
errors: IErrorReporter,
symbolTable: SymbolTable,
options: CompilationOptions): IAssemblyGenerator
{
if(options.experimentalCodegen) {
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
} else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
if (options.compTarget.name == VMTarget.NAME) {
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return VmCodeGen(intermediateAst, symbolTable, options, errors)
}
}
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")
}

View File

@ -13,7 +13,9 @@ import prog8.code.core.SourceCode
import prog8.parser.Prog8Parser
import java.io.File
import java.nio.file.Path
import kotlin.io.path.*
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.exists
class ModuleImporter(private val program: Program,
@ -21,30 +23,25 @@ class ModuleImporter(private val program: Program,
val errors: IErrorReporter,
sourceDirs: List<String>) {
private val sourcePaths: List<Path> = sourceDirs.map { Path(it) }
private val sourcePaths: List<Path> = sourceDirs.map { Path(it).absolute().normalize() }.toSortedSet().toList()
fun importModule(filePath: Path): Result<Module, NoSuchFileException> {
val currentDir = Path("").absolute()
val searchIn = listOf(currentDir) + sourcePaths
val candidates = searchIn
.map { it.absolute().div(filePath).normalize().absolute() }
.filter { it.exists() }
.map { currentDir.relativize(it) }
.map { if (it.isAbsolute) it else Path(".", "$it") }
val srcPath = when (candidates.size) {
0 -> return Err(NoSuchFileException(
file = filePath.normalize().toFile(),
reason = "Searched in $searchIn"))
1 -> candidates.first()
else -> candidates.first() // when more candiates, pick the one from the first location
fun importMainModule(filePath: Path): Result<Module, NoSuchFileException> {
val searchIn = (listOf(Path("").absolute()) + sourcePaths).toSortedSet()
val normalizedFilePath = filePath.normalize()
for(path in searchIn) {
val programPath = path.resolve(normalizedFilePath)
if(programPath.exists()) {
println("Compiling program ${Path("").absolute().relativize(programPath)}")
val source = SourceCode.File(programPath)
return Ok(importModule(source))
}
}
val source = SourceCode.File(srcPath)
return Ok(importModule(source))
return Err(NoSuchFileException(
file = normalizedFilePath.toFile(),
reason = "Searched in $searchIn"))
}
fun importLibraryModule(name: String): Module? {
fun importImplicitLibraryModule(name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42u, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
@ -52,7 +49,6 @@ class ModuleImporter(private val program: Program,
}
private fun importModule(src: SourceCode) : Module {
printImportingMessage(src.name, src.origin)
val moduleAst = Prog8Parser.parseModule(src)
program.addModule(moduleAst)
@ -146,10 +142,8 @@ class ModuleImporter(private val program: Program,
if (importingModule == null) { // <=> imported from library module
sourcePaths
} else {
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
sourcePaths.drop(dropCurDir) +
listOf(Path(importingModule.position.file).parent ?: Path("")) +
listOf(Path(".", "prog8lib"))
val pathFromImportingModule = (Path(importingModule.position.file).parent ?: Path("")).absolute()
listOf(pathFromImportingModule) + sourcePaths
}
locations.forEach {
@ -161,18 +155,4 @@ class ModuleImporter(private val program: Program,
return Err(NoSuchFileException(File("name")))
}
fun printImportingMessage(module: String, origin: String) {
print(" importing '$module' (from ${origin})")
ansiEraseRestOfLine(false)
print("\r")
}
companion object {
fun ansiEraseRestOfLine(newline: Boolean) {
print("\u001b[0K")
if(newline)
println()
}
}
}

View File

@ -8,8 +8,6 @@ import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions
import prog8.compiler.InplaceModifyingBuiltinFunctions
import prog8.compiler.builtinFunctionReturnType
import java.io.CharConversionException
import java.io.File
@ -62,10 +60,12 @@ internal class AstChecker(private val program: Program,
}
override fun visit(identifier: IdentifierReference) {
val targetParam = identifier.targetVarDecl(program)?.subroutineParameter
if(targetParam!=null) {
if((targetParam.parent as Subroutine).isAsmSubroutine)
errors.err("cannot refer to parameter of asmsub by name", identifier.position)
val target = identifier.targetVarDecl(program)
if(target != null && target.origin==VarDeclOrigin.SUBROUTINEPARAM) {
if(target.definingSubroutine!!.isAsmSubroutine) {
if(target.definingSubroutine!!.parameters.any { it.name == identifier.nameInSource.last() })
errors.err("cannot refer to parameter of asmsub by name", identifier.position)
}
}
}
@ -664,8 +664,12 @@ internal class AstChecker(private val program: Program,
err("string var must be initialized with a string literal")
}
if(decl.value !is StringLiteral)
err("string var must be initialized with a string literal")
if(decl.value !is StringLiteral) {
if(decl.type==VarDeclType.MEMORY)
err("strings can't be memory mapped")
else
err("string var must be initialized with a string literal")
}
}
if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE)

View File

@ -136,12 +136,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
internal fun Program.variousCleanups(errors: IErrorReporter, options: CompilationOptions) {
val process = VariousCleanups(this, errors, options)
process.visit(this)
if(errors.noErrors()) {
if(process.applyModifications()>0) {
process.visit(this)
if(errors.noErrors())
process.applyModifications()
}
while(errors.noErrors() && process.applyModifications()>0) {
process.visit(this)
}
}

View File

@ -7,11 +7,11 @@ import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.BuiltinFunctions
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions
internal class AstIdentifiersChecker(private val errors: IErrorReporter,
@ -92,7 +92,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) {
val symbol = subroutine.searchSymbol(name)
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null)
if(symbol!=null && (symbol as? VarDecl)?.origin!=VarDeclOrigin.SUBROUTINEPARAM)
nameError(name, symbol.position, subroutine)
}
@ -163,7 +163,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err("invalid number of arguments", pos)
}
if(func.name=="memory") {
if(target.name=="memory") {
val name = call.args[0] as? StringLiteral
if(name!=null) {
val processed = name.value.map {

View File

@ -31,7 +31,7 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take
// into account the case where the index value is a word type.
// The replacement here is to fix missing cases in the 6502 codegen.
// TODO make the 6502 codegen better so this work around can be removed
// TODO make the 6502 codegen better so this workaround can be removed
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
if(parent is AssignTarget) {

View File

@ -36,27 +36,9 @@ internal class BeforeAsmAstChanger(val program: Program,
return noModifications
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
// adjust global variables initialization
if(options.dontReinitGlobals) {
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
if(it.type==VarDeclType.VAR) {
it.zeropage = ZeropageWish.NOT_IN_ZEROPAGE
it.findInitializer(program)?.let { initializer ->
it.value = initializer.value // put the init value back into the vardecl
}
}
}
}
return noModifications
}
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 InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
}
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
return noModifications
}
@ -90,7 +72,7 @@ internal class BeforeAsmAstChanger(val program: Program,
"unknown dt"
)
}, sourceDt, implicit=true)
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.BEFOREASMGEN, assignment.position)
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.ASMGEN, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
@ -103,7 +85,7 @@ internal class BeforeAsmAstChanger(val program: Program,
"unknown dt"
)
}, sourceDt, implicit=true)
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.BEFOREASMGEN, assignment.position)
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.ASMGEN, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
@ -293,7 +275,7 @@ internal class BeforeAsmAstChanger(val program: Program,
val dt = expr.indexer.indexExpr.inferType(program)
val (tempVarName, _) = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
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)
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.ASMGEN, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(
IAstModification.ReplaceNode(

View File

@ -30,7 +30,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
newvalue = NumericLiteral(DataType.UBYTE, 1.0, newvalue.position)
}
val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name,
newvalue, decl.isArray, decl.sharedWithAsm, decl.subroutineParameter, decl.position)
newvalue, decl.isArray, decl.sharedWithAsm, decl.position)
return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent))
}
@ -47,7 +47,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
newarray = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), convertedArray, decl.position)
}
val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name,
newarray, true, decl.sharedWithAsm, decl.subroutineParameter, decl.position)
newarray, true, decl.sharedWithAsm, decl.position)
return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent))
}

View File

@ -8,12 +8,11 @@ import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.BuiltinFunctions
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.SourceCode
import prog8.compiler.BuiltinFunctions
import prog8.compiler.builtinFunctionReturnType
import java.io.File
import kotlin.io.path.Path
@ -23,7 +22,7 @@ import kotlin.io.path.isRegularFile
/**
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
*/
class IntermediateAstMaker(private val program: Program, private val symbolTable: SymbolTable, private val options: CompilationOptions) {
class IntermediateAstMaker(private val program: Program, private val options: CompilationOptions) {
fun transform(): PtProgram {
val ptProgram = PtProgram(
program.name,
@ -91,6 +90,54 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
}
private fun transform(srcAssign: Assignment): PtNode {
if(srcAssign.isAugmentable) {
val srcExpr = srcAssign.value
val (operator: String, augmentedValue: Expression?) = when(srcExpr) {
is BinaryExpression -> {
if(srcExpr.operator=="==" || srcExpr.operator=="%") {
// no special code possible for 'in-place comparison and result as boolean' or 'remainder'
Pair("", null)
}
else if(srcExpr.left isSameAs srcAssign.target) {
Pair(srcExpr.operator+'=', srcExpr.right)
} else if(srcExpr.right isSameAs srcAssign.target) {
Pair(srcExpr.operator+'=', srcExpr.left)
} else {
// either left or right is same as target, other combinations are not supported here
Pair("", null)
}
}
is PrefixExpression -> {
require(srcExpr.expression isSameAs srcAssign.target)
Pair(srcExpr.operator, srcExpr.expression)
}
is TypecastExpression -> {
// At this time, there are no special optimized instructions to do an in-place type conversion.
// so we simply revert to a regular type converting assignment.
// Also, an in-place type cast is very uncommon so probably not worth optimizing anyway.
Pair("", null)
// the following is what *could* be used here if such instructions *were* available:
// if(srcExpr.expression isSameAs srcAssign.target)
// Pair("cast", srcExpr.expression)
// else {
// val subTypeCast = srcExpr.expression as? TypecastExpression
// val targetDt = srcAssign.target.inferType(program).getOrElse { DataType.UNDEFINED }
// if (subTypeCast!=null && srcExpr.type==targetDt && subTypeCast.expression isSameAs srcAssign.target) {
// Pair("cast", subTypeCast)
// } else
// Pair("", null)
// }
}
else -> Pair("", null)
}
if(augmentedValue!=null) {
val assign = PtAugmentedAssign(operator, srcAssign.position)
assign.add(transform(srcAssign.target))
assign.add(transformExpression(augmentedValue))
return assign
}
}
val assign = PtAssignment(srcAssign.position)
assign.add(transform(srcAssign.target))
assign.add(transformExpression(srcAssign.value))
@ -130,7 +177,8 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
}
}
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position)
val src = srcBlock.definingModule.source
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, src, srcBlock.position)
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
for (stmt in statements)
block.add(transformStatement(stmt))
@ -285,18 +333,15 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
}
private fun transformAsmSub(srcSub: Subroutine): PtAsmSub {
val params = srcSub.parameters
.map { PtSubroutineParameter(it.name, it.type, it.position) }
.zip(srcSub.asmParameterRegisters)
val params = srcSub.asmParameterRegisters.zip(srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) })
val sub = PtAsmSub(srcSub.name,
srcSub.asmAddress,
srcSub.asmClobbers,
params,
srcSub.returntypes,
srcSub.asmReturnvaluesRegisters,
srcSub.asmReturnvaluesRegisters.zip(srcSub.returntypes),
srcSub.inline,
srcSub.position)
sub.parameters.forEach { it.first.parent=sub }
sub.parameters.forEach { it.second.parent=sub }
if(srcSub.asmAddress==null) {
var combinedTrueAsm = ""
@ -329,10 +374,11 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
var returntype = srcSub.returntypes.singleOrNull()
if(returntype==DataType.STR)
returntype=DataType.UWORD // if a sub returns 'str', replace with uword. Intermediate AST and I.R. don't contain 'str' datatype anymore.
// do not bother about the 'inline' hint of the source subroutine.
val sub = PtSub(srcSub.name,
srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) },
returntype,
srcSub.inline,
srcSub.position)
sub.parameters.forEach { it.parent=sub }
makeScopeVarsDecls(vardecls).forEach { sub.add(it) }
@ -346,7 +392,7 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
return when(srcVar.type) {
VarDeclType.VAR -> {
val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null
PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
@ -385,8 +431,8 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
}
private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer {
val type = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(type, srcArr.position)
val arrayVarType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(arrayVarType, srcArr.position)
array.add(transform(srcArr.arrayvar))
array.add(transformExpression(srcArr.indexer.indexExpr))
return array
@ -464,6 +510,7 @@ class IntermediateAstMaker(private val program: Program, private val symbolTable
private fun transform(srcCast: TypecastExpression): PtTypeCast {
val cast = PtTypeCast(srcCast.type, srcCast.position)
cast.add(transformExpression(srcCast.expression))
require(cast.type!=cast.value.type)
return cast
}

View File

@ -153,17 +153,6 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
return CondExprSimplificationResult(null, null, null, null)
var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple
&& expr.left !is IFunctionCall
&& expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple
&& expr.right !is IFunctionCall
&& expr.right !is ContainmentCheck
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
@ -172,13 +161,24 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
return CondExprSimplificationResult(null, null, null, null)
}
var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple
&& expr.left !is IFunctionCall
&& expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple
&& expr.right !is IFunctionCall
&& expr.right !is ContainmentCheck
if(separateLeftExpr) {
val name = getTempRegisterName(leftDt)
leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.left.copy(),
AssignmentOrigin.BEFOREASMGEN, expr.position
AssignmentOrigin.ASMGEN, expr.position
)
}
if(separateRightExpr) {
@ -187,7 +187,7 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
rightAssignment = Assignment(
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
expr.right.copy(),
AssignmentOrigin.BEFOREASMGEN, expr.position
AssignmentOrigin.ASMGEN, expr.position
)
}
return CondExprSimplificationResult(

View File

@ -153,7 +153,7 @@ internal class StatementReorderer(val program: Program,
subroutine.statements
.asSequence()
.filterIsInstance<VarDecl>()
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
.filter { it.origin==VarDeclOrigin.SUBROUTINEPARAM && it.name in stringParamsByNames }
.map {
val newvar = VarDecl(it.type, it.origin, DataType.UWORD,
it.zeropage,
@ -162,7 +162,6 @@ internal class StatementReorderer(val program: Program,
null,
false,
it.sharedWithAsm,
stringParamsByNames.getValue(it.name),
it.position
)
IAstModification.ReplaceNode(it, newvar, subroutine)

View File

@ -1,138 +0,0 @@
package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.*
import prog8.code.core.ArrayDatatypes
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.Position
import java.util.*
internal class SymbolTableMaker(private val program: Program, private val options: CompilationOptions): IAstVisitor {
private val st = SymbolTable()
private val scopestack = Stack<StNode>()
private var dontReinitGlobals = false
fun make(): SymbolTable {
scopestack.clear()
st.children.clear()
dontReinitGlobals = options.dontReinitGlobals
this.visit(program)
program.builtinFunctions.names.forEach {
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
st.add(node)
}
require(scopestack.isEmpty())
return st
}
override fun visit(block: Block) {
val node = StNode(block.name, StNodeType.BLOCK, block.position)
st.add(node)
scopestack.push(node)
super.visit(block)
scopestack.pop()
// st.origAstLinks[block] = node
}
override fun visit(subroutine: Subroutine) {
if(subroutine.asmAddress!=null) {
val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) }
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmReturnvaluesRegisters, subroutine.position)
scopestack.peek().add(node)
// st.origAstLinks[subroutine] = node
} else {
val parameters = subroutine.parameters.map { StSubroutineParameter(it.name, it.type) }
val returnType = if(subroutine.returntypes.isEmpty()) null else subroutine.returntypes.first()
val node = StSub(subroutine.name, parameters, returnType, subroutine.position)
scopestack.peek().add(node)
scopestack.push(node)
super.visit(subroutine)
scopestack.pop()
// st.origAstLinks[subroutine] = node
}
}
override fun visit(decl: VarDecl) {
val node =
when(decl.type) {
VarDeclType.VAR -> {
var initialNumeric = (decl.value as? NumericLiteral)?.number
if(initialNumeric==0.0)
initialNumeric=null // variable will go into BSS and this will be set to 0
val initialStringLit = decl.value as? StringLiteral
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
val initialArrayLit = decl.value as? ArrayLiteral
val initialArray = makeInitialArray(initialArrayLit)
if(decl.isArray && decl.datatype !in ArrayDatatypes)
throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
val numElements =
if(decl.isArray)
decl.arraysize!!.constIndex()
else if(initialStringLit!=null)
initialStringLit.value.length+1 // include the terminating 0-byte
else
null
val bss = if(decl.datatype==DataType.STR)
false
else if(decl.isArray)
initialArray.isNullOrEmpty()
else
initialNumeric == null
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
}
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
VarDeclType.MEMORY -> {
val numElements =
if(decl.datatype in ArrayDatatypes)
decl.arraysize!!.constIndex()
else null
StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, decl.position)
}
}
scopestack.peek().add(node)
// st.origAstLinks[decl] = node
}
private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
if(arrayLit==null)
return null
return arrayLit.value.map {
when(it){
is AddressOf -> {
val scopedName = it.identifier.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is IdentifierReference -> {
val scopedName = it.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is NumericLiteral -> StArrayElement(it.number, null)
else -> throw FatalAstException("weird element dt in array literal")
}
}.toList()
}
override fun visit(label: Label) {
val node = StNode(label.name, StNodeType.LABEL, label.position)
scopestack.peek().add(node)
// st.origAstLinks[label] = node
}
override fun visit(bfc: BuiltinFunctionCall) {
if(bfc.name=="memory") {
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
val name = (bfc.args[0] as StringLiteral).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val size = (bfc.args[1] as NumericLiteral).number.toUInt()
val align = (bfc.args[2] as NumericLiteral).number.toUInt()
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, bfc.position))
}
super.visit(bfc)
}
}

View File

@ -9,7 +9,6 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.compiler.BuiltinFunctions
class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() {

View File

@ -40,7 +40,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
val sourceDt = typecast.expression.inferType(program)
if(sourceDt istype typecast.type)
if(sourceDt istype typecast.type || (sourceDt istype DataType.BOOL && typecast.type==DataType.UBYTE))
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
if(parent is Assignment) {
@ -62,6 +62,11 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
// remove assignment to self
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
// remove duplicated assignments, but not if it's a memory mapped IO register
val isIO = try {
assignment.target.isIOAddress(options.compTarget.machine)

View File

@ -5,11 +5,7 @@ import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.ByteDatatypes
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.compiler.BuiltinFunctions
import prog8.code.core.*
internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorReporter) : IAstVisitor {

View File

@ -43,7 +43,7 @@ class TestModuleImporter: FunSpec({
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
val srcPathAbs = srcPathRel.absolute()
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
val error1 = importer.importMainModule(srcPathRel).getErrorOrElse { fail("should have import error") }
withClue(".file should be normalized") {
"${error1.file}" shouldBe "${error1.file.normalize()}"
}
@ -51,7 +51,7 @@ class TestModuleImporter: FunSpec({
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
program.modules.size shouldBe 1
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
val error2 = importer.importMainModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
withClue(".file should be normalized") {
"${error2.file}" shouldBe "${error2.file.normalize()}"
}
@ -67,7 +67,7 @@ class TestModuleImporter: FunSpec({
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
val importer = makeImporter(null, searchIn)
shouldThrow<FileSystemException> { importer.importModule(srcPathRel) }
shouldThrow<FileSystemException> { importer.importMainModule(srcPathRel) }
.let {
withClue(".file should be normalized") {
"${it.file}" shouldBe "${it.file.normalize()}"
@ -78,7 +78,7 @@ class TestModuleImporter: FunSpec({
}
program.modules.size shouldBe 1
shouldThrow<FileSystemException> { importer.importModule(srcPathAbs) }
shouldThrow<FileSystemException> { importer.importMainModule(srcPathAbs) }
.let {
withClue(".file should be normalized") {
"${it.file}" shouldBe "${it.file.normalize()}"
@ -101,7 +101,7 @@ class TestModuleImporter: FunSpec({
val fileName = "ast_simple_main.p8"
val path = assumeReadableFile(searchIn[0], fileName)
val module = importer.importModule(path.absolute()).getOrElse { throw it }
val module = importer.importMainModule(path.absolute()).getOrElse { throw it }
program.modules.size shouldBe 2
module shouldBeIn program.modules
module.program shouldBe program
@ -118,7 +118,7 @@ class TestModuleImporter: FunSpec({
path.isAbsolute shouldBe false
}
val module = importer.importModule(path).getOrElse { throw it }
val module = importer.importMainModule(path).getOrElse { throw it }
program.modules.size shouldBe 2
module shouldBeIn program.modules
module.program shouldBe program
@ -133,7 +133,7 @@ class TestModuleImporter: FunSpec({
val path = Path(".", fileName)
assumeReadableFile(searchIn, path)
val module = importer.importModule(path).getOrElse { throw it }
val module = importer.importMainModule(path).getOrElse { throw it }
program.modules.size shouldBe 2
module shouldBeIn program.modules
module.program shouldBe program
@ -145,7 +145,7 @@ class TestModuleImporter: FunSpec({
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
val act = { importer.importModule(srcPath) }
val act = { importer.importMainModule(srcPath) }
repeat(2) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError> { act() }.let {
@ -165,7 +165,7 @@ class TestModuleImporter: FunSpec({
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
val act = { importer.importModule(importing) }
val act = { importer.importMainModule(importing) }
repeat(repetitions) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError> { act() }.let {
@ -201,14 +201,14 @@ class TestModuleImporter: FunSpec({
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
repeat(2) { n ->
val result = importer.importLibraryModule(filenameNoExt)
val result = importer.importImplicitLibraryModule(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.report()
program.modules.size shouldBe 1
val result2 = importer.importLibraryModule(filenameWithExt)
val result2 = importer.importImplicitLibraryModule(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"
@ -228,7 +228,7 @@ class TestModuleImporter: FunSpec({
repeat(2) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError>()
{
importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
importer.importImplicitLibraryModule(srcPath.nameWithoutExtension) }.let {
it.position.file shouldBe SourceCode.relative(srcPath).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
@ -246,7 +246,7 @@ class TestModuleImporter: FunSpec({
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
val act = { importer.importImplicitLibraryModule(importing.nameWithoutExtension) }
repeat(repetitions) { n -> withClue(count[n] + " call") {
shouldThrow<ParseError> {

View File

@ -3,31 +3,19 @@ package prog8tests
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.code.core.BuiltinFunctions
import prog8.code.core.DataType
import prog8.code.core.NumericDatatypesNoBool
import prog8.code.core.RegisterOrPair
import prog8.code.target.Cx16Target
import prog8.compiler.BuiltinFunctions
import prog8tests.helpers.compileText
class TestBuiltinFunctions: FunSpec({
test("push pop") {
val src="""
main {
sub start () {
pushw(cx16.r0)
push(cx16.r1L)
pop(cx16.r1L)
popw(cx16.r0)
}
}"""
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("pure func with fixed type") {
val func = BuiltinFunctions.getValue("sgn")
func.name shouldBe "sgn"
func.parameters.size shouldBe 1
func.parameters[0].name shouldBe "value"
func.parameters[0].possibleDatatypes shouldBe NumericDatatypesNoBool
@ -46,7 +34,6 @@ class TestBuiltinFunctions: FunSpec({
test("not-pure func with varying result value type") {
val func = BuiltinFunctions.getValue("cmp")
func.name shouldBe "cmp"
func.parameters.size shouldBe 2
func.pure shouldBe false
func.returnType shouldBe null
@ -60,7 +47,6 @@ class TestBuiltinFunctions: FunSpec({
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)
@ -81,5 +67,44 @@ class TestBuiltinFunctions: FunSpec({
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe null
}
test("push pop") {
val src="""
main {
sub start () {
pushw(cx16.r0)
push(cx16.r1L)
pop(cx16.r1L)
popw(cx16.r0)
}
}"""
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("certain builtin functions should be compile time evaluated") {
val src="""
main {
sub start() {
uword[] array = [1,2,3]
str name = "hello"
cx16.r0L = len(array)
cx16.r0L = len(name)
cx16.r0L = sizeof(array)
cx16.r0 = mkword(200,100)
}
}"""
val result = compileText(Cx16Target(), false, src, writeAssembly = false)
val statements = result!!.compilerAst.entrypoint.statements
statements.size shouldBe 6
val a1 = statements[2] as Assignment
val a2 = statements[3] as Assignment
val a3 = statements[4] as Assignment
val a4 = statements[5] as Assignment
(a1.value as NumericLiteral).number shouldBe 3.0
(a2.value as NumericLiteral).number shouldBe 5.0
(a3.value as NumericLiteral).number shouldBe 6.0
(a4.value as NumericLiteral).number shouldBe 200*256+100
}
})

View File

@ -27,11 +27,11 @@ class TestCallgraph: FunSpec({
}
"""
val result = compileText(C64Target(), false, sourcecode)!!
val graph = CallGraph(result.program)
val graph = CallGraph(result.compilerAst)
graph.imports.size shouldBe 1
graph.importedBy.size shouldBe 1
val toplevelModule = result.program.toplevelModule
val toplevelModule = result.compilerAst.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single()
importedModule.name shouldBe "string"
val importedBy = graph.importedBy.getValue(importedModule).single()
@ -46,7 +46,7 @@ class TestCallgraph: FunSpec({
graph.calls shouldNotContainKey sub
graph.calledBy shouldNotContainKey sub
if(sub === result.program.entrypoint)
if(sub === result.compilerAst.entrypoint)
withClue("start() should always be marked as used to avoid having it removed") {
graph.unused(sub) shouldBe false
}
@ -68,11 +68,11 @@ class TestCallgraph: FunSpec({
}
"""
val result = compileText(C64Target(), false, sourcecode)!!
val graph = CallGraph(result.program)
val graph = CallGraph(result.compilerAst)
graph.imports.size shouldBe 1
graph.importedBy.size shouldBe 1
val toplevelModule = result.program.toplevelModule
val toplevelModule = result.compilerAst.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single()
importedModule.name shouldBe "string"
val importedBy = graph.importedBy.getValue(importedModule).single()
@ -111,7 +111,7 @@ class TestCallgraph: FunSpec({
}
"""
val result = compileText(C64Target(), false, sourcecode)!!
val graph = CallGraph(result.program)
val graph = CallGraph(result.compilerAst)
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 5
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
empties.size shouldBe 3

View File

@ -33,7 +33,7 @@ class TestCompilerOnCharLit: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@ -57,7 +57,7 @@ class TestCompilerOnCharLit: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@ -92,7 +92,7 @@ class TestCompilerOnCharLit: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]

View File

@ -27,12 +27,12 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
filepath,
optimize,
optimizeFloatExpressions = true,
dontReinitGlobals = false,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
varsHigh = false,
compilationTarget = target.name,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),
@ -188,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
val (displayName, filepath) = prepareTestFiles(it, false, target)
test(displayName) {
val src = filepath.readText()
compileText(target, false, src, writeAssembly = true) shouldNotBe null
compileText(target, false, src, writeAssembly = true) shouldNotBe null
compileText(target, true, src, writeAssembly = true) shouldNotBe null
compileText(target, true, src, writeAssembly = true) shouldNotBe null
}
}
})

View File

@ -30,7 +30,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val platform = Cx16Target()
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>()
@ -52,7 +52,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val platform = Cx16Target()
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val args = startSub.statements
.filterIsInstance<FunctionCallStatement>()

View File

@ -40,7 +40,7 @@ class TestCompilerOnRanges: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val decl = startSub
.statements.filterIsInstance<VarDecl>()[0]
@ -72,7 +72,7 @@ class TestCompilerOnRanges: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val decl = startSub
.statements.filterIsInstance<VarDecl>()[0]
@ -143,7 +143,7 @@ class TestCompilerOnRanges: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val iterable = startSub
.statements.filterIsInstance<ForLoop>()
@ -177,7 +177,7 @@ class TestCompilerOnRanges: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>()
@ -203,7 +203,7 @@ class TestCompilerOnRanges: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>()
@ -247,7 +247,7 @@ class TestCompilerOnRanges: FunSpec({
}
""")!!
val program = result.program
val program = result.compilerAst
val startSub = program.entrypoint
val iterable = startSub
.statements.filterIsInstance<ForLoop>()
@ -279,7 +279,7 @@ class TestCompilerOnRanges: FunSpec({
}
}
""")!!
val statements = result.program.entrypoint.statements
val statements = result.compilerAst.entrypoint.statements
val array = (statements[0] as VarDecl).value
array shouldBe instanceOf<ArrayLiteral>()
(array as ArrayLiteral).value.size shouldBe 26
@ -301,7 +301,7 @@ class TestCompilerOnRanges: FunSpec({
}
}
""")!!
val statements = result.program.entrypoint.statements
val statements = result.compilerAst.entrypoint.statements
val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpression>()
(forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY)

View File

@ -44,12 +44,12 @@ class TestCompilerOptionSourcedirs: FunSpec({
filepath = filePath,
optimize = false,
optimizeFloatExpressions = false,
dontReinitGlobals = false,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
varsHigh = false,
compilationTarget = Cx16Target.NAME,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),

View File

@ -25,7 +25,7 @@ class TestGoldenRam: FunSpec({
test("empty golden ram allocations") {
val errors = ErrorReporterForTests()
val golden = GoldenRam(options, UIntRange.EMPTY)
val result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
val result = golden.allocate("test", DataType.UBYTE, null, null, errors)
result.expectError { "should not be able to allocate anything" }
}
@ -33,28 +33,28 @@ class TestGoldenRam: FunSpec({
val errors = ErrorReporterForTests()
val golden = GoldenRam(options, 0x400u until 0x800u)
var result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
var result = golden.allocate("test", DataType.UBYTE, null, null, errors)
var alloc = result.getOrThrow()
alloc.size shouldBe 1
alloc.address shouldBe 0x400u
result = golden.allocate(listOf("test"), DataType.STR, 100, null, errors)
result = golden.allocate("test", DataType.STR, 100, null, errors)
alloc = result.getOrThrow()
alloc.size shouldBe 100
alloc.address shouldBe 0x401u
repeat(461) {
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors)
result = golden.allocate("test", DataType.UWORD, null, null, errors)
alloc = result.getOrThrow()
alloc.size shouldBe 2
}
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors)
result = golden.allocate("test", DataType.UWORD, null, null, errors)
result.expectError { "just 1 more byte available" }
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
result = golden.allocate("test", DataType.UBYTE, null, null, errors)
alloc = result.getOrThrow()
alloc.size shouldBe 1
alloc.address shouldBe golden.region.last
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
result = golden.allocate("test", DataType.UBYTE, null, null, errors)
result.expectError { "nothing more available" }
}

View File

@ -8,7 +8,7 @@ import prog8.code.core.ZeropageType
import prog8.code.core.internedStringsModuleName
import prog8.code.target.C64Target
import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseImports
import prog8.compiler.parseMainModule
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import prog8tests.helpers.outputDir
@ -28,9 +28,9 @@ main {
}
}
""")!!
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"
val moduleNames = result.program.modules.map { it.name }
val moduleNames = result.compilerAst.modules.map { it.name }
withClue("main module must be first") {
moduleNames[0] shouldStartWith "on_the_fly_test"
}
@ -46,7 +46,7 @@ main {
"prog8_lib"
)
}
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"
}
test("testCompilationOptionsCorrectFromMain") {
@ -63,8 +63,8 @@ main {
}
}
""")!!
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
val options = determineCompilationOptions(result.program, C64Target())
result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"
val options = determineCompilationOptions(result.compilerAst, C64Target())
options.floats shouldBe true
options.zeropage shouldBe ZeropageType.DONTUSE
options.noSysInit shouldBe true
@ -87,7 +87,7 @@ main {
val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16)
val filepath = outputDir.resolve("$filenameBase.p8")
filepath.toFile().writeText(sourceText)
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target(), emptyList())
val (program, options, importedfiles) = parseMainModule(filepath, errors, C64Target(), emptyList())
program.toplevelModule.name shouldBe filenameBase
withClue("all imports other than the test source must have been internal resources library files") {

View File

@ -19,10 +19,10 @@ class TestLaunchEmu: FunSpec({
<ASMSYMBOLS>
</ASMSYMBOLS>
<BSS>
</BSS>
<VARIABLES>
</VARIABLES>
<VARIABLESNOINIT>
</VARIABLESNOINIT>
<VARIABLESWITHINIT>
</VARIABLESWITHINIT>
<MEMORYMAPPEDVARIABLES>
</MEMORYMAPPEDVARIABLES>

View File

@ -15,7 +15,6 @@ import prog8.code.core.Position
import prog8.code.core.SourceCode
import prog8.code.core.ZeropageWish
import prog8.code.target.C64Target
import prog8.compiler.printProgram
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
@ -28,7 +27,7 @@ class TestMemory: FunSpec({
fun wrapWithProgram(statements: List<Statement>): Program {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), statements.toMutableList(), false, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements.toMutableList(), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
program.addModule(module)
return program
@ -113,7 +112,7 @@ class TestMemory: FunSpec({
}
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@ -147,12 +146,11 @@ class TestMemory: FunSpec({
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
wrapWithProgram(listOf(assign))
printProgram(target.definingModule.program)
target.isIOAddress(c64target.machine) shouldBe true
}
test("regular variable not in mapped IO ram on C64") {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, null, Position.DUMMY)
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@ -164,7 +162,7 @@ class TestMemory: FunSpec({
test("memory mapped variable not in mapped IO ram on C64") {
val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@ -176,7 +174,7 @@ class TestMemory: FunSpec({
test("memory mapped variable in mapped IO ram on C64") {
val address = 0xd020u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@ -187,7 +185,7 @@ class TestMemory: FunSpec({
}
test("array not in mapped IO ram") {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, null, Position.DUMMY)
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@ -200,7 +198,7 @@ class TestMemory: FunSpec({
test("memory mapped array not in mapped IO ram") {
val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@ -213,7 +211,7 @@ class TestMemory: FunSpec({
test("memory mapped array in mapped IO ram") {
val address = 0xd800u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)

View File

@ -14,7 +14,6 @@ import prog8.code.core.Position
import prog8.code.core.toHex
import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5
import prog8.compiler.printProgram
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@ -199,9 +198,8 @@ class TestNumbers: FunSpec({
}
"""
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val statements = result.program.entrypoint.statements
val statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 8
printProgram(result.program)
(statements[1] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 32768.0, Position.DUMMY)
(statements[3] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 65535.0, Position.DUMMY)
(statements[5] as Assignment).value shouldBe NumericLiteral(DataType.UBYTE, 255.0, Position.DUMMY)

View File

@ -16,7 +16,6 @@ import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.compiler.printProgram
import prog8tests.helpers.*
@ -32,10 +31,10 @@ class TestOptimization: FunSpec({
}
"""
val result = compileText(C64Target(), true, sourcecode)!!
val toplevelModule = result.program.toplevelModule
val toplevelModule = result.compilerAst.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements.single() as Subroutine
result.program.entrypoint shouldBeSameInstanceAs startSub
result.compilerAst.entrypoint shouldBeSameInstanceAs startSub
withClue("only start sub should remain") {
startSub.name shouldBe "start"
}
@ -57,11 +56,11 @@ class TestOptimization: FunSpec({
}
"""
val result = compileText(C64Target(), true, sourcecode)!!
val toplevelModule = result.program.toplevelModule
val toplevelModule = result.compilerAst.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements[0] as Subroutine
val emptySub = mainBlock.statements[1] as Subroutine
result.program.entrypoint shouldBeSameInstanceAs startSub
result.compilerAst.entrypoint shouldBeSameInstanceAs startSub
startSub.name shouldBe "start"
emptySub.name shouldBe "empty"
withClue("compiler has inserted return in empty subroutines") {
@ -131,7 +130,7 @@ class TestOptimization: FunSpec({
// cx16.r5s -= 1899
// cx16.r7s = llw
// cx16.r7s += 99
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 14
val addR0value = (stmts[5] as Assignment).value
@ -183,7 +182,7 @@ class TestOptimization: FunSpec({
// cx16.r4s = llw
// cx16.r4s *= 90
// cx16.r4s /= 5
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 13
val mulR0Value = (stmts[3] as Assignment).value
@ -225,7 +224,7 @@ class TestOptimization: FunSpec({
}
"""
val result = compileText(C64Target(), false, sourcecode)!!
val mainsub = result.program.entrypoint
val mainsub = result.compilerAst.entrypoint
mainsub.statements.size shouldBe 10
val declTest = mainsub.statements[0] as VarDecl
val declX1 = mainsub.statements[1] as VarDecl
@ -265,8 +264,7 @@ class TestOptimization: FunSpec({
}
"""
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
printProgram(result.program)
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 8
val value1 = (stmts[4] as Assignment).value as BinaryExpression
@ -278,39 +276,6 @@ class TestOptimization: FunSpec({
value3.operator shouldBe "&"
}
test("intermediate assignment steps generated for typecasted expression") {
val src = """
main {
sub start() {
ubyte r
ubyte @shared bb = (sgn(r)*2 + 100) as ubyte
}
}
"""
val result = compileText(C64Target(), true, src, writeAssembly = true)!!
/* turned into:
ubyte r
r = 0
ubyte bb
prog8_lib.retval_interm_b = sgn(r)
prog8_lib.retval_interm_b <<= 1
prog8_lib.retval_interm_b += 100
bb = prog8_lib.retval_interm_b
return
*/
val st = result.program.entrypoint.statements
st.size shouldBe 8
st.last() shouldBe instanceOf<Return>()
var assign = st[3] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[4] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[5] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","tempvar_b")
assign = st[6] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("bb")
}
test("asmgen correctly deals with float typecasting in augmented assignment") {
val src="""
%import floats
@ -324,7 +289,7 @@ class TestOptimization: FunSpec({
}
"""
val result = compileText(C64Target(), optimize=false, src, writeAssembly = false)!!
val assignFF = result.program.entrypoint.statements.last() as Assignment
val assignFF = result.compilerAst.entrypoint.statements.last() as Assignment
assignFF.isAugmentable shouldBe true
assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignFF.value as BinaryExpression
@ -350,8 +315,8 @@ class TestOptimization: FunSpec({
}
"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
result.program.entrypoint.statements.size shouldBe 7
val alldecls = result.program.entrypoint.allDefinedSymbols.toList()
result.compilerAst.entrypoint.statements.size shouldBe 7
val alldecls = result.compilerAst.entrypoint.allDefinedSymbols.toList()
alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar_only_written", "usedvar")
}
@ -373,11 +338,11 @@ class TestOptimization: FunSpec({
}
}"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
result.program.entrypoint.statements.size shouldBe 3
val ifstmt = result.program.entrypoint.statements[0] as IfElse
result.compilerAst.entrypoint.statements.size shouldBe 3
val ifstmt = result.compilerAst.entrypoint.statements[0] as IfElse
ifstmt.truepart.statements.size shouldBe 1
(ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
val func2 = result.program.entrypoint.statements[2] as Subroutine
val func2 = result.compilerAst.entrypoint.statements[2] as Subroutine
func2.statements.size shouldBe 1
(func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
}
@ -401,7 +366,6 @@ class TestOptimization: FunSpec({
}
}"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
printProgram(result.program)
/* expected:
ubyte z1
z1 = 10
@ -418,7 +382,7 @@ class TestOptimization: FunSpec({
z6 = z1
z6 -= 5
*/
val statements = result.program.entrypoint.statements
val statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 14
val z1decl = statements[0] as VarDecl
val z1init = statements[1] as Assignment
@ -469,7 +433,7 @@ class TestOptimization: FunSpec({
"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
val assign=stmts.last() as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
@ -486,7 +450,7 @@ class TestOptimization: FunSpec({
}
"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
val assign=stmts.last() as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
@ -505,7 +469,7 @@ class TestOptimization: FunSpec({
}
"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 10
stmts.filterIsInstance<VarDecl>().size shouldBe 5
stmts.filterIsInstance<Assignment>().size shouldBe 5
@ -554,8 +518,8 @@ class TestOptimization: FunSpec({
}
}"""
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
result.program.entrypoint.statements.size shouldBe 11
result.program.entrypoint.statements.last() shouldBe instanceOf<Return>()
result.compilerAst.entrypoint.statements.size shouldBe 11
result.compilerAst.entrypoint.statements.last() shouldBe instanceOf<Return>()
}
test("keep the value initializer assignment if the next one depends on it") {
@ -582,7 +546,7 @@ class TestOptimization: FunSpec({
xx = abs(xx)
xx += 6
*/
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 8
stmts.filterIsInstance<VarDecl>().size shouldBe 3
stmts.filterIsInstance<Assignment>().size shouldBe 5
@ -611,7 +575,7 @@ class TestOptimization: FunSpec({
yy = 0
xx += 10
*/
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7
stmts.filterIsInstance<VarDecl>().size shouldBe 2
stmts.filterIsInstance<Assignment>().size shouldBe 5
@ -638,7 +602,6 @@ class TestOptimization: FunSpec({
}
}"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
printProgram(result.program)
/*
expected result:
ubyte[] auto_heap_var = [1,4,99,3]
@ -649,7 +612,7 @@ class TestOptimization: FunSpec({
if source in auto_heap_var
thingy++
*/
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
val ifStmt = stmts[5] as IfElse
val containment = ifStmt.condition as ContainmentCheck
@ -679,8 +642,7 @@ class TestOptimization: FunSpec({
}
}"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
printProgram(result.program)
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
@ -698,8 +660,7 @@ class TestOptimization: FunSpec({
}
}"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
printProgram(result.program)
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
@ -717,7 +678,7 @@ class TestOptimization: FunSpec({
}
}"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
@ -734,7 +695,7 @@ class TestOptimization: FunSpec({
}
}"""
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 3
}
@ -756,15 +717,15 @@ main {
}
}"""
var result = compileText(Cx16Target(), true, srcX16, writeAssembly = true)!!
var statements = result.program.entrypoint.statements
var statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 9
(statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
(statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 0x9fff
val srcC64="""
main {
@ -783,14 +744,26 @@ main {
}
}"""
result = compileText(C64Target(), true, srcC64, writeAssembly = true)!!
statements = result.program.entrypoint.statements
statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 9
(statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
(statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
}
test("no crash on sorting unused array") {
val text="""
main {
ubyte[5] cards = [ 14, 6, 29, 16, 3 ]
sub start() {
sort(cards)
}
}"""
compileText(C64Target(), true, text, writeAssembly = false) shouldNotBe null
}
})

View File

@ -27,9 +27,9 @@ class TestScoping: FunSpec({
"""
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val module = result.program.toplevelModule
val module = result.compilerAst.toplevelModule
module.parent shouldBe instanceOf<GlobalNamespace>()
module.program shouldBeSameInstanceAs result.program
module.program shouldBeSameInstanceAs result.compilerAst
module.parent.parent shouldBe instanceOf<ParentSentinel>()
}
@ -46,7 +46,7 @@ class TestScoping: FunSpec({
"""
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val module = result.program.toplevelModule
val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
@ -120,7 +120,7 @@ class TestScoping: FunSpec({
"""
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
val module = result.program.toplevelModule
val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val labels = start.statements.filterIsInstance<Label>()

View File

@ -60,7 +60,7 @@ class TestSubroutines: FunSpec({
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val module = result.program.toplevelModule
val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
@ -116,7 +116,7 @@ class TestSubroutines: FunSpec({
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val module = result.program.toplevelModule
val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
@ -178,7 +178,7 @@ class TestSubroutines: FunSpec({
"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val module = result.program.toplevelModule
val module = result.compilerAst.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
@ -284,7 +284,7 @@ class TestSubroutines: FunSpec({
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.last() shouldBe instanceOf<Subroutine>()
stmts.dropLast(1).last() shouldBe instanceOf<Return>() // this prevents the fallthrough

View File

@ -4,19 +4,26 @@ import io.kotest.assertions.fail
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SourceCode
import prog8.code.core.ZeropageWish
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
class TestSymbolTable: FunSpec({
test("empty symboltable") {
val st = SymbolTable()
st.scopedName shouldBe ""
st.name shouldBe ""
val astNode = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val st = SymbolTable(astNode)
st.name shouldBe "test"
st.type shouldBe StNodeType.GLOBAL
st.children shouldBe mutableMapOf()
st.position shouldBe Position.DUMMY
st.astNode shouldBeSameInstanceAs astNode
st.astNode.position shouldBe Position.DUMMY
}
test("symboltable flatten") {
@ -33,9 +40,9 @@ class TestSymbolTable: FunSpec({
st.lookupUnscoped("undefined") shouldBe null
st.lookup("undefined") shouldBe null
st.lookup("undefined.undefined") shouldBe null
var default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, Position.DUMMY) }
var default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, PtIdentifier("default", DataType.BYTE, Position.DUMMY)) }
default.name shouldBe "default"
default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, Position.DUMMY) }
default = st.lookupUnscopedOrElse("undefined") { StNode("default", StNodeType.LABEL, PtIdentifier("default", DataType.BYTE, Position.DUMMY)) }
default.name shouldBe "default"
val msbFunc = st.lookupUnscopedOrElse("msb") { fail("msb must be found") }
@ -52,7 +59,7 @@ class TestSymbolTable: FunSpec({
sub1.name shouldBe "sub1"
sub1.scopedName shouldBe "block1.sub1"
sub1.type shouldBe StNodeType.SUBROUTINE
sub1.children.size shouldBe 2
sub1.children.size shouldBe 4
val v1 = sub1.lookupUnscopedOrElse("v1") { fail("v1 must be found") } as StStaticVariable
v1.type shouldBe StNodeType.STATICVAR
@ -67,35 +74,84 @@ class TestSymbolTable: FunSpec({
subsub.lookupUnscoped("blockc") shouldBe null
subsub.lookupUnscoped("label") shouldNotBe null
}
test("symboltable collections") {
val st= makeSt()
st.allVariables.size shouldBe 4
st.allMemMappedVariables.single().scopedName shouldBe "block1.sub1.v3"
st.allMemorySlabs.single().scopedName shouldBe "block1.sub1.slab1"
}
})
private fun makeSt(): SymbolTable {
val st = SymbolTable()
val block1 = StNode("block1", StNodeType.BLOCK, Position.DUMMY)
val sub11 = StNode("sub1", StNodeType.SUBROUTINE, Position.DUMMY)
val sub12 = StNode("sub2", StNodeType.SUBROUTINE, Position.DUMMY)
// first build the AST
val astProgram = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val astBlock1 = PtBlock("block1", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
val astConstant1 = PtConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY)
val astConstant2 = PtConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY)
astBlock1.add(astConstant1)
astBlock1.add(astConstant2)
val astSub1 = PtSub("sub1", emptyList(), null, Position.DUMMY)
val astSub2 = PtSub("sub2", emptyList(), null, Position.DUMMY)
val astSub1v1 = PtVariable("v1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY)
val astSub1v2 = PtVariable("v2", DataType.BYTE, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
val astSub1v3 = PtVariable("v3", DataType.FLOAT, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
val astSub1v4 = PtVariable("slab1", DataType.UWORD, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
val astSub2v1 = PtVariable("v1", DataType.BYTE, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
val astSub2v2 = PtVariable("v2", DataType.BYTE, ZeropageWish.DONTCARE,null, null, Position.DUMMY)
astSub1.add(astSub1v1)
astSub1.add(astSub1v2)
astSub1.add(astSub1v3)
astSub1.add(astSub1v4)
astSub2.add(astSub2v2)
astSub2.add(astSub2v2)
astBlock1.add(astSub1)
astBlock1.add(astSub2)
val astBfunc = PtIdentifier("msb", DataType.UBYTE, Position.DUMMY)
astBlock1.add(astBfunc)
val astBlock2 = PtBlock("block2", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
val astSub21 = PtSub("sub1", emptyList(), null, Position.DUMMY)
val astSub22 = PtSub("sub2", emptyList(), null, Position.DUMMY)
val astSub221 = PtSub("subsub", emptyList(), null, Position.DUMMY)
val astLabel = PtLabel("label", Position.DUMMY)
astSub221.add(astLabel)
astSub22.add(astSub221)
astBlock2.add(astSub21)
astBlock2.add(astSub22)
astProgram.add(astBlock1)
astProgram.add(astBlock2)
// now hook up the SymbolTable on that AST
val st = SymbolTable(astProgram)
val block1 = StNode("block1", StNodeType.BLOCK, astBlock1)
val sub11 = StNode("sub1", StNodeType.SUBROUTINE, astSub1)
val sub12 = StNode("sub2", StNodeType.SUBROUTINE, astSub2)
block1.add(sub11)
block1.add(sub12)
block1.add(StConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY))
block1.add(StConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY))
sub11.add(StStaticVariable("v1", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub11.add(StStaticVariable("v2", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub12.add(StStaticVariable("v1", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
sub12.add(StStaticVariable("v2", DataType.BYTE, true, null, null, null, null, ZeropageWish.DONTCARE, Position.DUMMY))
val block2 = StNode("block2", StNodeType.BLOCK, Position.DUMMY)
val sub21 = StNode("sub1", StNodeType.SUBROUTINE, Position.DUMMY)
val sub22 = StNode("sub2", StNodeType.SUBROUTINE, Position.DUMMY)
block1.add(StConstant("c1", DataType.UWORD, 12345.0, astConstant1))
block1.add(StConstant("blockc", DataType.UWORD, 999.0, astConstant2))
sub11.add(StStaticVariable("v1", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, astSub1v1))
sub11.add(StStaticVariable("v2", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, astSub1v2))
sub11.add(StMemVar("v3", DataType.FLOAT, 12345u, null, astSub1v3))
sub11.add(StMemorySlab("slab1", 200u, 64u, astSub1v4))
sub12.add(StStaticVariable("v1", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, astSub2v1))
sub12.add(StStaticVariable("v2", DataType.BYTE, null, null, null, null, ZeropageWish.DONTCARE, astSub2v2))
val block2 = StNode("block2", StNodeType.BLOCK, astBlock2)
val sub21 = StNode("sub1", StNodeType.SUBROUTINE, astSub21)
val sub22 = StNode("sub2", StNodeType.SUBROUTINE, astSub22)
block2.add(sub21)
block2.add(sub22)
val sub221 = StNode("subsub", StNodeType.SUBROUTINE, Position.DUMMY)
sub221.add(StNode("label", StNodeType.LABEL, Position.DUMMY))
val sub221 = StNode("subsub", StNodeType.SUBROUTINE, astSub221)
sub221.add(StNode("label", StNodeType.LABEL, astLabel))
sub22.add(sub221)
val builtinfunc = StNode("msb", StNodeType.BUILTINFUNC, Position.DUMMY)
val builtinfunc = StNode("msb", StNodeType.BUILTINFUNC, astBfunc)
st.add(block1)
st.add(block2)
st.add(builtinfunc)
return st
}
}

View File

@ -49,7 +49,7 @@ class TestTypecasts: FunSpec({
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 4
val expr = (stmts[3] as Assignment).value as BinaryExpression
expr.operator shouldBe "and"
@ -57,7 +57,7 @@ class TestTypecasts: FunSpec({
(expr.left as IdentifierReference).nameInSource shouldBe listOf("bb2") // no cast
val result2 = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts2 = result2.program.entrypoint.statements
val stmts2 = result2.compilerAst.entrypoint.statements
stmts2.size shouldBe 6
val expr2 = (stmts2[4] as Assignment).value as BinaryExpression
expr2.operator shouldBe "&"
@ -86,7 +86,7 @@ main {
}
}"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
/*
ubyte ub1
ub1 = 1
@ -120,6 +120,42 @@ main {
right3.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
}
test("simple logical with bool no typecast") {
val text="""
main {
bool bb
sub start() {
bb = bb and 123
}
}"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 2
val assignValue = (stmts[0] as Assignment).value as BinaryExpression
assignValue.left shouldBe instanceOf<IdentifierReference>()
assignValue.operator shouldBe "&"
(assignValue.right as NumericLiteral).number shouldBe 1.0
}
test("simple logical with byte instead of bool ok with typecasting") {
val text="""
main {
ubyte ubb
sub start() {
ubb = ubb and 123
}
}"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 2
val assignValue = (stmts[0] as Assignment).value as BinaryExpression
assignValue.left shouldBe instanceOf<BinaryExpression>() // as a result of the cast to boolean
assignValue.operator shouldBe "&"
(assignValue.right as NumericLiteral).number shouldBe 1.0
}
test("logical with byte instead of bool") {
val text="""
%import textio
@ -329,7 +365,7 @@ main {
}
"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBeGreaterThan 10
}
@ -542,7 +578,7 @@ main {
}
"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBeGreaterThan 10
}
@ -561,7 +597,7 @@ main {
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
val arraydecl = stmts[0] as VarDecl
arraydecl.datatype shouldBe DataType.ARRAY_BOOL
@ -595,7 +631,7 @@ main {
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 9
val fcall1 = ((stmts[6] as Assignment).value as IFunctionCall)
fcall1.args[0] shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
@ -628,7 +664,7 @@ main {
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBeGreaterThan 10
}
@ -647,7 +683,7 @@ main {
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 3
}
@ -670,7 +706,7 @@ main {
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 8
val arg1 = (stmts[2] as IFunctionCall).args.single()
val arg2 = (stmts[3] as IFunctionCall).args.single()
@ -707,7 +743,7 @@ main {
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
result.program.entrypoint.statements.size shouldBe 15
result.compilerAst.entrypoint.statements.size shouldBe 15
}
test("invalid typecasts of numbers") {
@ -827,7 +863,7 @@ main {
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val statements = result.program.entrypoint.statements
val statements = result.compilerAst.entrypoint.statements
statements.size shouldBeGreaterThan 10
}
@ -854,7 +890,7 @@ main {
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val statements = result.program.entrypoint.statements
val statements = result.compilerAst.entrypoint.statements
statements.size shouldBeGreaterThan 10
}

View File

@ -71,26 +71,26 @@ class TestC64Zeropage: FunSpec({
compTarget = c64target, loadAddress = 999u
))
var result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
var result = zp.allocate("", DataType.UBYTE, null, null, errors)
result.onFailure { fail(it.toString()) }
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
result = zp.allocate("", DataType.UBYTE, null, null, errors)
result.onFailure { fail(it.toString()) }
result = zp.allocate(listOf("varname"), DataType.UBYTE, null, null, errors)
result = zp.allocate("varname", DataType.UBYTE, null, null, errors)
result.onFailure { fail(it.toString()) }
shouldThrow<IllegalArgumentException> { zp.allocate(listOf("varname"), DataType.UBYTE,null, null, errors) }
result = zp.allocate(listOf("varname2"), DataType.UBYTE, null, null, errors)
shouldThrow<IllegalArgumentException> { zp.allocate("varname", DataType.UBYTE,null, null, errors) }
result = zp.allocate("varname2", DataType.UBYTE, null, null, errors)
result.onFailure { fail(it.toString()) }
}
test("testZpFloatEnable") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, errors)
var result = zp.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "should be allocation error due to disabled floats" }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target, 999u))
result = zp2.allocate(emptyList(), DataType.FLOAT, null, null, errors)
result = zp2.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "should be allocation error due to disabled ZP use" }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u))
zp3.allocate(emptyList(), DataType.FLOAT, null, null, errors)
zp3.allocate("", DataType.FLOAT, null, null, errors)
}
test("testZpModesWithFloats") {
@ -112,7 +112,7 @@ class TestC64Zeropage: FunSpec({
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target, 999u))
println(zp.free)
zp.availableBytes() shouldBe 0
val result = zp.allocate(emptyList(), DataType.BYTE, null, null, errors)
val result = zp.allocate("", DataType.BYTE, null, null, errors)
result.expectError { "expected error due to disabled ZP use" }
}
@ -125,9 +125,9 @@ class TestC64Zeropage: FunSpec({
zp3.availableBytes() shouldBe 97
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
zp4.availableBytes() shouldBe 207
zp4.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
zp4.allocate("test", DataType.UBYTE, null, null, errors)
zp4.availableBytes() shouldBe 206
zp4.allocate(listOf("test2"), DataType.UBYTE, null, null, errors)
zp4.allocate("test2", DataType.UBYTE, null, null, errors)
zp4.availableBytes() shouldBe 205
}
@ -166,19 +166,19 @@ class TestC64Zeropage: FunSpec({
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, errors)
var result = zp.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "expect allocation error: in regular zp there aren't 5 sequential bytes free" }
for (i in 0 until zp.availableBytes()) {
val alloc = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
val alloc = zp.allocate("", DataType.UBYTE, null, null, errors)
alloc.getOrElse { throw it }
}
zp.availableBytes() shouldBe 0
zp.hasByteAvailable() shouldBe false
zp.hasWordAvailable() shouldBe false
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
result = zp.allocate("", DataType.UBYTE, null, null, errors)
result.expectError { "expected allocation error" }
result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
result = zp.allocate("", DataType.UWORD, null, null, errors)
result.expectError { "expected allocation error" }
}
@ -187,47 +187,47 @@ class TestC64Zeropage: FunSpec({
zp.availableBytes() shouldBe 207
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
var result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
var result = zp.allocate("", DataType.UWORD, null, null, errors)
val loc = result.getOrElse { throw it } .address
loc shouldBeGreaterThan 3u
loc shouldNotBeIn zp.free
val num = zp.availableBytes() / 2
for(i in 0..num-3) {
zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
zp.allocate("", DataType.UWORD, null, null, errors)
}
zp.availableBytes() shouldBe 5
// can't allocate because no more sequential bytes, only fragmented
result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
result = zp.allocate("", DataType.UWORD, null, null, errors)
result.expectError { "should give allocation error" }
for(i in 0..4) {
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
zp.allocate("", DataType.UBYTE, null, null, errors)
}
zp.availableBytes() shouldBe 0
zp.hasByteAvailable() shouldBe false
zp.hasWordAvailable() shouldBe false
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
result = zp.allocate("", DataType.UBYTE, null, null, errors)
result.expectError { "should give allocation error" }
}
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp.availableBytes() shouldBe 18
zp.allocate(emptyList(), DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
zp.availableBytes() shouldBe 0
}
@ -258,9 +258,9 @@ class TestCx16Zeropage: FunSpec({
zp2.availableBytes() shouldBe 175
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
zp3.availableBytes() shouldBe 216
zp3.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
zp3.allocate("test", DataType.UBYTE, null, null, errors)
zp3.availableBytes() shouldBe 215
zp3.allocate(listOf("test2"), DataType.UBYTE, null, null, errors)
zp3.allocate("test2", DataType.UBYTE, null, null, errors)
zp3.availableBytes() shouldBe 214
}
@ -276,12 +276,12 @@ class TestCx16Zeropage: FunSpec({
test("preallocated zp vars") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
zp1.allocatedVariables[listOf("test")] shouldBe null
zp1.allocatedVariables[listOf("cx16", "r0")] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r15")] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r0L")] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r15L")] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r0sH")] shouldNotBe null
zp1.allocatedVariables[listOf("cx16", "r15sH")] shouldNotBe null
zp1.allocatedVariables["test"] shouldBe null
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
zp1.allocatedVariables["cx16.r0L"] shouldNotBe null
zp1.allocatedVariables["cx16.r15L"] shouldNotBe null
zp1.allocatedVariables["cx16.r0sH"] shouldNotBe null
zp1.allocatedVariables["cx16.r15sH"] shouldNotBe null
}
})

View File

@ -8,7 +8,6 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.compiler.astprocessing.IntermediateAstMaker
import prog8.compiler.astprocessing.SymbolTableMaker
import prog8tests.helpers.compileText
class TestIntermediateAst: FunSpec({
@ -38,9 +37,8 @@ class TestIntermediateAst: FunSpec({
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
val result = compileText(target, false, text, writeAssembly = false)!!
val st = SymbolTableMaker(result.program, options).make()
val ast = IntermediateAstMaker(result.program, st, options).transform()
ast.name shouldBe result.program.name
val ast = IntermediateAstMaker(result.compilerAst, options).transform()
ast.name shouldBe result.compilerAst.name
ast.allBlocks().any() shouldBe true
val entry = ast.entrypoint() ?: fail("no main.start() found")
entry.children.size shouldBe 5
@ -59,18 +57,16 @@ class TestIntermediateAst: FunSpec({
ccdecl.name shouldBe "cc"
ccdecl.scopedName shouldBe "main.start.cc"
ccdecl.type shouldBe DataType.UBYTE
val arraydecl = entry.children[1] as PtVariable
val arraydecl = entry.children[1] as IPtVariable
arraydecl.name shouldBe "array"
arraydecl.type shouldBe DataType.ARRAY_UB
val containmentCast = (entry.children[3] as PtAssignment).value as PtTypeCast
containmentCast.type shouldBe DataType.UBYTE
val containment = containmentCast.value as PtContainmentCheck
val containment = (entry.children[3] as PtAssignment).value as PtContainmentCheck
(containment.element as PtNumber).number shouldBe 11.0
val fcall = (entry.children[4] as PtAssignment).value as PtFunctionCall
fcall.void shouldBe false
fcall.type shouldBe DataType.UBYTE
ast.print()
printAst(ast, ::println)
}
})

Some files were not shown because too many files have changed in this diff Show More