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) - constant folding in expressions (compile-time evaluation)
- conditional branches - conditional branches
- when statement to provide a 'jump table' alternative to if/elseif chains - 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 - automatic type conversions
- floating point operations (uses the C64 Basic ROM routines for this) - 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 - 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 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) val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) { return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> 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. // - the name of a symbol somewhere else starting from the root of the namespace.
// check struct first // 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 mangledname = mangledStructMemberName(scopedName[0], scopedName[1])
val vardecl = localContext.definingScope().getLabelOrVariable(mangledname) val vardecl = localContext.definingScope().getLabelOrVariable(mangledname)
if(vardecl!=null) 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 var resultingAssignment = assignment
resultingAssignment = processAssignmentTarget(resultingAssignment, assignment.target) resultingAssignment = processAssignmentTarget(resultingAssignment, assignment.target)
return super.visit(resultingAssignment) return super.visit(resultingAssignment)
@ -1275,8 +1286,8 @@ internal class AstChecker(private val program: Program,
else { else {
if(decl.zeropage) if(decl.zeropage)
checkResult.add(SyntaxError("struct can not contain zeropage members", decl.position)) checkResult.add(SyntaxError("struct can not contain zeropage members", decl.position))
if(decl.datatype==DataType.STRUCT) if(decl.datatype !in NumericDatatypes)
checkResult.add(SyntaxError("structs can not be nested", decl.position)) 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) if(decl.structHasBeenFlattened)
return decl // don't do this multiple times 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 decls: MutableList<IStatement> = decl.struct!!.statements.withIndex().map {
val member = it.value as VarDecl val member = it.value as VarDecl
val initvalue = if(decl.value!=null) (decl.value as LiteralValue).arrayvalue!![it.index] else null 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) 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 kotlin.comparisons.nullsLast
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.DataType import prog8.ast.base.*
import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions 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 { internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
// Reorders the statements in a way the compiler needs. // Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set. // - 'main' block must be the very first statement UNLESS it has an address set.
@ -174,13 +218,29 @@ 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 // see if a typecast is needed to convert the value's type into the proper target type
val valuetype = assignment.value.inferType(program) val valuetype = assignment.value.inferType(program)
val targettype = assignment.target.inferType(program, assignment) val targettype = assignment.target.inferType(program, assignment)
if(targettype!=null && valuetype!=null && valuetype!=targettype) { if(targettype!=null && valuetype!=null) {
if(valuetype isAssignableTo targettype) { if(valuetype!=targettype) {
if (valuetype isAssignableTo targettype) {
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position) assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
assignment.value.linkParents(assignment) assignment.value.linkParents(assignment)
} }
// if they're not assignable, we'll get a proper error later from the AstChecker // 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
}
}
return super.visit(assignment) return super.visit(assignment)
} }

View File

@ -4,6 +4,7 @@ import prog8.ast.*
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.base.RegisterOrPair.* import prog8.ast.base.RegisterOrPair.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.flattenStructAssignment
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.intermediate.IntermediateProgram import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.Opcode
@ -1464,26 +1465,6 @@ internal class Compiler(private val program: Program) {
popValueIntoTarget(stmt.target, datatype) 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) { private fun pushHeapVarAddress(value: IExpression, removeLastOpcode: Boolean) {
when (value) { when (value) {
is LiteralValue -> throw CompilerException("can only push address of string or array (value on the heap)") 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 package prog8.optimizer
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel import prog8.ast.base.ParentSentinel
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.base.initvarsSubName 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 // make sure autogenerated vardecls are in the used symbols
addNodeAndParentScopes(decl) addNodeAndParentScopes(decl)
} }
if(decl.datatype==DataType.STRUCT)
addNodeAndParentScopes(decl)
super.visit(decl) super.visit(decl)
} }
@ -196,6 +201,7 @@ class CallGraph(private val program: Program): IAstVisitor {
if (matches2 != null) { if (matches2 != null) {
val target= matches2.groups[2]?.value val target= matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) { if (target != null && (target[0].isLetter() || target[0] == '_')) {
if(target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context) val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) { if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node) subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
@ -206,4 +212,5 @@ class CallGraph(private val program: Program): IAstVisitor {
} }
} }
} }
}
} }

View File

@ -40,9 +40,11 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
when (decl.type) { when (decl.type) {
// we can assume the value in the vardecl already has been converted into a constant LiteralValue here. // we can assume the value in the vardecl already has been converted into a constant LiteralValue here.
VarDeclType.VAR -> { VarDeclType.VAR -> {
if(decl.datatype!=DataType.STRUCT) {
val value = RuntimeValue.from(decl.value as LiteralValue, heap) val value = RuntimeValue.from(decl.value as LiteralValue, heap)
runtimeVariables.define(decl.definingScope(), decl.name, value) runtimeVariables.define(decl.definingScope(), decl.name, value)
} }
}
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as LiteralValue).asIntegerValue!!) 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. 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 Special types: const and memory-mapped
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -234,7 +234,7 @@ Various examples::
byte[5] values = 255 ; initialize with five 255 bytes byte[5] values = 255 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
Color rgb = [1,255,0] ; a struct variable
Data types Data types
@ -358,6 +358,22 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
string[4] ; the fifth character (=byte) in the string 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 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? 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 Misc
^^^^ ^^^^

View File

@ -5,20 +5,24 @@
const uword width = 40 const uword width = 40
const uword height = 25 const uword height = 25
sub start() { struct Ball {
uword anglex uword anglex
uword angley uword angley
ubyte color ubyte color
}
sub start() {
Ball ball
while true { while true {
ubyte x = msb(sin8u(msb(anglex)) as uword * width) ubyte x = msb(sin8u(msb(ball.anglex)) as uword * width)
ubyte y = msb(cos8u(msb(angley)) as uword * height) ubyte y = msb(cos8u(msb(ball.angley)) as uword * height)
c64scr.setcc(x, y, 81, color) c64scr.setcc(x, y, 81, ball.color)
anglex+=800 ball.anglex+=800
angley+=947 ball.angley+=947
color++ ball.color++
} }
} }
} }

View File

@ -7,68 +7,18 @@
sub start() { sub start() {
uword derp =44 Color subcol
ubyte[] v = [22,33,44]
Color foreground = [1,2,3] A=msb(subcol.red)
c64scr.print_ub(foreground.red) for ubyte i in 10 to 20 {
c64.CHROUT(':') ;A=subcol.red
c64scr.print_ub(foreground.green) ;A=blocklevelcolor.green
c64.CHROUT(':')
c64scr.print_ub(foreground.blue)
c64.CHROUT('\n')
;subcol.blue = Y
Color background ;blocklevelcolor.green=Y
Color cursor = [255,255,255] A=msb(subcol.red)
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()
return
} }
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 { struct Color {
@ -77,9 +27,4 @@
ubyte blue 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
; }
} }