struct finished

This commit is contained in:
Irmen de Jong 2019-07-12 19:01:36 +02:00
parent 7500c6efd0
commit 3e5deda46c
14 changed files with 197 additions and 129 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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))
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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)")

View File

@ -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)
}
}
}
}

View File

@ -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!!)

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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
---------

View File

@ -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
^^^^

View File

@ -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++
}
}
}

View File

@ -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
; }
}