mirror of
https://github.com/irmen/prog8.git
synced 2024-12-26 14:29:35 +00:00
struct finished
This commit is contained in:
parent
7500c6efd0
commit
3e5deda46c
@ -22,6 +22,7 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
- constant folding in expressions (compile-time evaluation)
|
||||
- conditional branches
|
||||
- when statement to provide a 'jump table' alternative to if/elseif chains
|
||||
- structs to group together sets of variables and manipulate them at once
|
||||
- automatic type conversions
|
||||
- floating point operations (uses the C64 Basic ROM routines for this)
|
||||
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
|
@ -71,6 +71,21 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
return builtinPlaceholder
|
||||
}
|
||||
|
||||
if(scopedName.size>1) {
|
||||
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
|
||||
// try the struct first.
|
||||
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
|
||||
val struct = thing?.struct
|
||||
if (struct != null) {
|
||||
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
|
||||
// return ref to the mangled name variable
|
||||
val mangled = mangledStructMemberName(thing.name, scopedName.last())
|
||||
val mangledVar = thing.definingScope().getLabelOrVariable(mangled)
|
||||
return mangledVar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val stmt = localContext.definingModule().lookup(scopedName, localContext)
|
||||
return when (stmt) {
|
||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
||||
|
@ -137,7 +137,7 @@ interface INameScope {
|
||||
// - the name of a symbol somewhere else starting from the root of the namespace.
|
||||
|
||||
// check struct first
|
||||
if(scopedName.size==2) { // TODO support for referencing structs in other scopes
|
||||
if(scopedName.size==2) { // TODO support for referencing structs in other scopes . see GlobalNamespace?
|
||||
val mangledname = mangledStructMemberName(scopedName[0], scopedName[1])
|
||||
val vardecl = localContext.definingScope().getLabelOrVariable(mangledname)
|
||||
if(vardecl!=null)
|
||||
|
@ -350,6 +350,17 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val sourceIdent = assignment.value as? IdentifierReference
|
||||
val targetIdent = assignment.target.identifier
|
||||
if(sourceIdent!=null && targetIdent!=null) {
|
||||
val sourceVar = sourceIdent.targetVarDecl(program.namespace)
|
||||
val targetVar = targetIdent.targetVarDecl(program.namespace)
|
||||
if(sourceVar?.struct!=null && targetVar?.struct!=null) {
|
||||
if(sourceVar.struct!==targetVar.struct)
|
||||
checkResult.add(ExpressionError("assignment of different struct types", assignment.position))
|
||||
}
|
||||
}
|
||||
|
||||
var resultingAssignment = assignment
|
||||
resultingAssignment = processAssignmentTarget(resultingAssignment, assignment.target)
|
||||
return super.visit(resultingAssignment)
|
||||
@ -1275,8 +1286,8 @@ internal class AstChecker(private val program: Program,
|
||||
else {
|
||||
if(decl.zeropage)
|
||||
checkResult.add(SyntaxError("struct can not contain zeropage members", decl.position))
|
||||
if(decl.datatype==DataType.STRUCT)
|
||||
checkResult.add(SyntaxError("structs can not be nested", decl.position))
|
||||
if(decl.datatype !in NumericDatatypes)
|
||||
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,9 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
if(decl.structHasBeenFlattened)
|
||||
return decl // don't do this multiple times
|
||||
|
||||
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
|
||||
return decl // a non-numeric member, not supported. proper error is given by AstChecker later
|
||||
|
||||
val decls: MutableList<IStatement> = decl.struct!!.statements.withIndex().map {
|
||||
val member = it.value as VarDecl
|
||||
val initvalue = if(decl.value!=null) (decl.value as LiteralValue).arrayvalue!![it.index] else null
|
||||
@ -245,4 +248,14 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
return super.visit(addressOf)
|
||||
}
|
||||
|
||||
override fun visit(structDecl: StructDecl): IStatement {
|
||||
for(member in structDecl.statements){
|
||||
val decl = member as? VarDecl
|
||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
|
||||
}
|
||||
|
||||
return super.visit(structDecl)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,14 +2,58 @@ package prog8.ast.processing
|
||||
|
||||
import kotlin.comparisons.nullsLast
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.initvarsSubName
|
||||
import prog8.ast.base.printWarning
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
fun flattenStructAssignment(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
if(!sourceVar.isArray && sourceVar.struct==null)
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
if(sourceVar.isArray) {
|
||||
val sourceArray = (sourceVar.value as LiteralValue).arrayvalue!!
|
||||
return struct.statements.zip(sourceArray).map { member ->
|
||||
val decl = member.first as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, decl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, member.second, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
else {
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
}
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
@ -174,12 +218,28 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
// see if a typecast is needed to convert the value's type into the proper target type
|
||||
val valuetype = assignment.value.inferType(program)
|
||||
val targettype = assignment.target.inferType(program, assignment)
|
||||
if(targettype!=null && valuetype!=null && valuetype!=targettype) {
|
||||
if(valuetype isAssignableTo targettype) {
|
||||
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
if(targettype!=null && valuetype!=null) {
|
||||
if(valuetype!=targettype) {
|
||||
if (valuetype isAssignableTo targettype) {
|
||||
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
}
|
||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
||||
}
|
||||
}
|
||||
|
||||
// struct assignments will be flattened
|
||||
if(valuetype==DataType.STRUCT && targettype==DataType.STRUCT) {
|
||||
val assignments = flattenStructAssignment(assignment, program)
|
||||
if(assignments.isEmpty()) {
|
||||
// something went wrong (probably incompatible struct types)
|
||||
// we'll get an error later from the AstChecker
|
||||
return assignment
|
||||
} else {
|
||||
val scope = AnonymousScope(assignments.toMutableList(), assignment.position)
|
||||
scope.linkParents(assignment.parent)
|
||||
return scope
|
||||
}
|
||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
||||
}
|
||||
|
||||
return super.visit(assignment)
|
||||
|
@ -4,6 +4,7 @@ import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.RegisterOrPair.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.flattenStructAssignment
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.intermediate.IntermediateProgram
|
||||
import prog8.compiler.intermediate.Opcode
|
||||
@ -1464,26 +1465,6 @@ internal class Compiler(private val program: Program) {
|
||||
popValueIntoTarget(stmt.target, datatype)
|
||||
}
|
||||
|
||||
private fun flattenStructAssignment(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
if(!sourceVar.isArray)
|
||||
throw CompilerException("can only assign arrays to structs")
|
||||
val sourceArray = (sourceVar.value as LiteralValue).arrayvalue!!
|
||||
return struct.statements.zip(sourceArray).map { member ->
|
||||
val decl = member.first as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, decl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, member.second, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
|
||||
private fun pushHeapVarAddress(value: IExpression, removeLastOpcode: Boolean) {
|
||||
when (value) {
|
||||
is LiteralValue -> throw CompilerException("can only push address of string or array (value on the heap)")
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.base.initvarsSubName
|
||||
@ -122,6 +123,10 @@ class CallGraph(private val program: Program): IAstVisitor {
|
||||
// make sure autogenerated vardecls are in the used symbols
|
||||
addNodeAndParentScopes(decl)
|
||||
}
|
||||
|
||||
if(decl.datatype==DataType.STRUCT)
|
||||
addNodeAndParentScopes(decl)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@ -196,10 +201,12 @@ class CallGraph(private val program: Program): IAstVisitor {
|
||||
if (matches2 != null) {
|
||||
val target= matches2.groups[2]?.value
|
||||
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
if(target.contains('.')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,8 +40,10 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
|
||||
when (decl.type) {
|
||||
// we can assume the value in the vardecl already has been converted into a constant LiteralValue here.
|
||||
VarDeclType.VAR -> {
|
||||
val value = RuntimeValue.from(decl.value as LiteralValue, heap)
|
||||
runtimeVariables.define(decl.definingScope(), decl.name, value)
|
||||
if(decl.datatype!=DataType.STRUCT) {
|
||||
val value = RuntimeValue.from(decl.value as LiteralValue, heap)
|
||||
runtimeVariables.define(decl.definingScope(), decl.name, value)
|
||||
}
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as LiteralValue).asIntegerValue!!)
|
||||
|
@ -273,6 +273,38 @@ you have to use the ``str_s`` variants of the string type identifier.
|
||||
The same is true for arrays by the way.
|
||||
|
||||
|
||||
Structs
|
||||
^^^^^^^
|
||||
|
||||
A struct is a group of one or more other variables.
|
||||
This allows you to reuse the definition and manipulate it as a whole.
|
||||
Individual variables in the struct are accessed as you would expect, just
|
||||
use a scoped name to refer to them: ``structvariable.membername``.
|
||||
|
||||
Structs are a bit limited in Prog8: you can only use numerical variables
|
||||
as member of a struct, so strings and arrays and other structs can not be part of a struct.
|
||||
Also, it is not possible to use a struct itself inside an array.
|
||||
|
||||
Structs are mainly syntactic sugar for repeated groups of vardecls
|
||||
and assignments that belong together.
|
||||
|
||||
To create a variable of a struct type you need to define the struct itself,
|
||||
and then create a variable with it::
|
||||
|
||||
struct Color {
|
||||
ubyte red
|
||||
ubyte green
|
||||
ubyte blue
|
||||
}
|
||||
|
||||
Color rgb = [255,122,0]
|
||||
Color another ; the init value is optional, like arrays
|
||||
|
||||
another = rgb ; assign all of the values of rgb to another
|
||||
another.blue = 255 ; set a single member
|
||||
|
||||
|
||||
|
||||
Special types: const and memory-mapped
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -234,7 +234,7 @@ Various examples::
|
||||
byte[5] values = 255 ; initialize with five 255 bytes
|
||||
|
||||
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
||||
|
||||
Color rgb = [1,255,0] ; a struct variable
|
||||
|
||||
|
||||
Data types
|
||||
@ -358,6 +358,22 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
|
||||
string[4] ; the fifth character (=byte) in the string
|
||||
|
||||
|
||||
Struct
|
||||
^^^^^^
|
||||
|
||||
A *struct* has to be defined to specify what its member variables are.
|
||||
There are one or more members::
|
||||
|
||||
struct <structname> {
|
||||
<vardecl>
|
||||
[ <vardecl> ...]
|
||||
}
|
||||
|
||||
You can only use numerical variables as member of a struct, so strings and arrays
|
||||
and other structs can not be part of a struct. Vice versa, a struct can not occur in an array.
|
||||
|
||||
After defining a struct you can use the name of the struct as a data type to declare variables with.
|
||||
|
||||
|
||||
Operators
|
||||
---------
|
||||
|
@ -52,25 +52,6 @@ Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly
|
||||
without having to to index into the stack?
|
||||
|
||||
|
||||
structs?
|
||||
^^^^^^^^
|
||||
|
||||
A user defined struct type would be nice to group a bunch
|
||||
of values together (and use it multiple times). Something like::
|
||||
|
||||
struct Point {
|
||||
ubyte color
|
||||
word[] vec = [0,0,0]
|
||||
}
|
||||
|
||||
Point p1
|
||||
Point p2
|
||||
Point p3
|
||||
|
||||
p1.color = 3
|
||||
p1.vec[2] = 2
|
||||
|
||||
|
||||
Misc
|
||||
^^^^
|
||||
|
||||
|
@ -5,20 +5,24 @@
|
||||
const uword width = 40
|
||||
const uword height = 25
|
||||
|
||||
sub start() {
|
||||
|
||||
struct Ball {
|
||||
uword anglex
|
||||
uword angley
|
||||
ubyte color
|
||||
}
|
||||
|
||||
sub start() {
|
||||
|
||||
Ball ball
|
||||
|
||||
while true {
|
||||
ubyte x = msb(sin8u(msb(anglex)) as uword * width)
|
||||
ubyte y = msb(cos8u(msb(angley)) as uword * height)
|
||||
c64scr.setcc(x, y, 81, color)
|
||||
ubyte x = msb(sin8u(msb(ball.anglex)) as uword * width)
|
||||
ubyte y = msb(cos8u(msb(ball.angley)) as uword * height)
|
||||
c64scr.setcc(x, y, 81, ball.color)
|
||||
|
||||
anglex+=800
|
||||
angley+=947
|
||||
color++
|
||||
ball.anglex+=800
|
||||
ball.angley+=947
|
||||
ball.color++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,79 +7,24 @@
|
||||
|
||||
sub start() {
|
||||
|
||||
uword derp =44
|
||||
ubyte[] v = [22,33,44]
|
||||
|
||||
Color foreground = [1,2,3]
|
||||
c64scr.print_ub(foreground.red)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(foreground.green)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(foreground.blue)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
|
||||
Color background
|
||||
Color cursor = [255,255,255]
|
||||
|
||||
foreground.red=99
|
||||
background.blue=foreground.red
|
||||
|
||||
cursor = [1,2,3] ; assign all members at once
|
||||
cursor = v
|
||||
cursor = foreground ; @todo memberwise assignment
|
||||
|
||||
c64scr.print_ub(foreground.red)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(foreground.green)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(foreground.blue)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print_ub(background.red)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(background.green)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(background.blue)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print_ub(cursor.red)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(cursor.green)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(cursor.blue)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
foo()
|
||||
foo()
|
||||
foo()
|
||||
foo()
|
||||
foo()
|
||||
foo()
|
||||
foo()
|
||||
Color subcol
|
||||
|
||||
A=msb(subcol.red)
|
||||
for ubyte i in 10 to 20 {
|
||||
;A=subcol.red
|
||||
;A=blocklevelcolor.green
|
||||
|
||||
;subcol.blue = Y
|
||||
;blocklevelcolor.green=Y
|
||||
A=msb(subcol.red)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
sub foo() {
|
||||
Color localcolor
|
||||
localcolor.red++
|
||||
c64scr.print_ub(localcolor.red)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(localcolor.green)
|
||||
c64.CHROUT(':')
|
||||
c64scr.print_ub(localcolor.blue)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
struct Color {
|
||||
ubyte red
|
||||
ubyte green
|
||||
ubyte blue
|
||||
}
|
||||
|
||||
; @todo structs as sub args. After strings and arrays as sub-args.
|
||||
; sub foo(Color arg) -> ubyte {
|
||||
; return arg.red+arg.green+arg.blue
|
||||
; }
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user