struct literals

This commit is contained in:
Irmen de Jong 2019-07-16 00:08:28 +02:00
parent 17be722e2b
commit 61af72b906
14 changed files with 237 additions and 131 deletions

View File

@ -6,6 +6,10 @@ import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
// TODO sealed classes instead??
interface Node {
val position: Position
var parent: Node // will be linked correctly later (late init)

View File

@ -443,6 +443,10 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
// the ConstantFolder takes care of that and converts the type if needed.
ReferenceLiteralValue(DataType.ARRAY_UB, array = array, position = litval.toPosition())
}
litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() }
StructLiteralValue(values, litval.toPosition())
}
else -> throw FatalAstException("invalid parsed literal")
}
}
@ -518,6 +522,9 @@ private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
private fun prog8Parser.ArrayliteralContext.toAst() : Array<IExpression> =
expression().map { it.toAst() }.toTypedArray()
private fun prog8Parser.StructliteralContext.toAst() : Array<IExpression> =
expression().map { it.toAst() }.toTypedArray()
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
val condition = expression().toAst()

View File

@ -420,6 +420,26 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
}
}
class StructLiteralValue(var values: List<IExpression>,
override val position: Position): IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent=parent
values.forEach { it.linkParents(this) }
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
override fun inferType(program: Program) = DataType.STRUCT
override fun toString(): String {
return "struct{ ${values.joinToString(", ")} }"
}
}
class ReferenceLiteralValue(val type: DataType, // only reference types allowed here
val str: String? = null,
val array: Array<IExpression>? = null,
@ -428,9 +448,7 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun referencesIdentifiers(vararg name: String): Boolean {
return array?.any { it.referencesIdentifiers(*name) } ?: false
}
override fun referencesIdentifiers(vararg name: String) = array?.any { it.referencesIdentifiers(*name) } ?: false
val isString = type in StringDatatypes
val isArray = type in ArrayDatatypes
@ -443,8 +461,6 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
if(str==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
in ArrayDatatypes ->
if(array==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
// DataType.STRUCT ->
// if(struct==null && heapId==null) throw FatalAstException("literal value missing structvalue/heapId")
else -> throw FatalAstException("invalid type $type")
}
if(array==null && str==null && heapId==null)

View File

@ -526,6 +526,29 @@ internal class AstChecker(private val program: Program,
is NumericLiteralValue -> {
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
}
is StructLiteralValue -> {
if(decl.datatype==DataType.STRUCT) {
val struct = decl.struct!!
val structLv = decl.value as StructLiteralValue
if(struct.numberOfElements != structLv.values.size) {
checkResult.add(ExpressionError("struct value has incorrect number of elements", structLv.position))
return
}
for(value in structLv.values.zip(struct.statements)) {
val memberdecl = value.second as VarDecl
val constValue = value.first.constValue(program)
if(constValue==null) {
checkResult.add(ExpressionError("struct literal value for field '${memberdecl.name}' should consist of compile-time constants", value.first.position))
return
}
val memberDt = memberdecl.datatype
if(!checkValueTypeAndRange(memberDt, constValue)) {
checkResult.add(ExpressionError("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position))
return
}
}
}
}
else -> {
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!.javaClass.simpleName}")
super.visit(decl)
@ -1245,21 +1268,12 @@ internal class AstChecker(private val program: Program,
DataType.STR -> sourceDatatype== DataType.STR
DataType.STR_S -> sourceDatatype== DataType.STR_S
DataType.STRUCT -> {
// for now we've decided you cannot assign struct by-value.
// but you can however assign an array to it of the correct size
if(sourceDatatype in ArrayDatatypes) {
val identifier = sourceValue as IdentifierReference
val sourceArraySize = identifier.targetVarDecl(program.namespace)!!.arraysize?.size()
if(sourceDatatype==DataType.STRUCT) {
val structLv = sourceValue as StructLiteralValue
val numValues = structLv.values.size
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!!
return targetstruct.numberOfElements == sourceArraySize
return targetstruct.numberOfElements == numValues
}
// if(sourceDatatype==DataType.STRUCT) {
// val sourcename = (sourceValue as IdentifierReference).nameInSource
// val vd1 = program.namespace.lookup(sourcename, target) as? VarDecl
// val targetname = target.identifier!!.nameInSource
// val vd2 = program.namespace.lookup(targetname, target) as? VarDecl
// return vd1?.struct == vd2?.struct
// }
false
}
else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position))

View File

@ -225,4 +225,9 @@ interface IAstModifyingVisitor {
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
return structDecl
}
fun visit(structLv: StructLiteralValue): IExpression {
structLv.values = structLv.values.map { it.accept(this) }
return structLv
}
}

View File

@ -173,4 +173,8 @@ interface IAstVisitor {
fun visit(structDecl: StructDecl) {
structDecl.statements.forEach { it.accept(this) }
}
fun visit(structLv: StructLiteralValue) {
structLv.values.forEach { it.accept(this) }
}
}

View File

@ -9,47 +9,41 @@ import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
fun flattenStructAssignment(structAssignment: Assignment, program: Program): List<Assignment> {
fun flattenStructAssignmentFromIdentifier(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 ReferenceLiteralValue).array!!
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
when {
structAssignment.value is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
// 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
}
}
}
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
structAssignment.value is StructLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
}
@ -215,52 +209,59 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
override fun visit(assignment: Assignment): IStatement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
// 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)
val valuetype = assg.value.inferType(program)
val targettype = assg.target.inferType(program, assg)
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)
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
assg.value.linkParents(assg)
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
// struct assignments will be flattened
// struct assignments will be flattened (if it's not a struct literal)
if(valuetype==DataType.STRUCT && targettype==DataType.STRUCT) {
val assignments = flattenStructAssignment(assignment, program)
if(assg.value is StructLiteralValue)
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
if(assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
return assignment
return assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assignment.position)
scope.linkParents(assignment.parent)
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
return scope
}
}
if(assignment.aug_op!=null) {
// transform augmented assignment into normal assignment so we have one case less to deal with later
if(assg.aug_op!=null) {
// transform augmented assg into normal assg so we have one case less to deal with later
val newTarget: IExpression =
when {
assignment.target.register != null -> RegisterExpr(assignment.target.register!!, assignment.target.position)
assignment.target.identifier != null -> assignment.target.identifier!!
assignment.target.arrayindexed != null -> assignment.target.arrayindexed!!
assignment.target.memoryAddress != null -> DirectMemoryRead(assignment.target.memoryAddress!!.addressExpression, assignment.value.position)
else -> throw FatalAstException("strange assignment")
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
assg.target.identifier != null -> assg.target.identifier!!
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
else -> throw FatalAstException("strange assg")
}
val expression = BinaryExpression(newTarget, assignment.aug_op.substringBeforeLast('='), assignment.value, assignment.position)
expression.linkParents(assignment.parent)
val convertedAssignment = Assignment(assignment.target, null, expression, assignment.position)
convertedAssignment.linkParents(assignment.parent)
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
expression.linkParents(assg.parent)
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
convertedAssignment.linkParents(assg.parent)
return super.visit(convertedAssignment)
}
return super.visit(assignment)
return assg
}
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
@ -319,28 +320,6 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
}
private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> {
val sequence= mutableListOf(first)
var trailing: IStatement? = null
while(stmtIter.hasNext()) {
val next = stmtIter.next()
if(next is Assignment) {
val constValue = next.value.constValue(program)
if(constValue==null) {
trailing = next
break
}
sequence.add(next)
}
else {
trailing=next
break
}
}
val sorted = sequence.sortedWith(compareBy({it.value.inferType(program)}, {it.target.shortString(true)}))
return Pair(sorted, trailing)
}
override fun visit(typecast: TypecastExpression): IExpression {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
@ -397,4 +376,42 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
super.visit(memwrite)
}
override fun visit(structLv: StructLiteralValue): IExpression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
val decl = litval.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
} else {
val assign = litval.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
}
}
}
return litval
}
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
structLv.values = struct.statements.zip(structLv.values).map {
val memberDt = (it.first as VarDecl).datatype
val valueDt = it.second.inferType(program)
if (valueDt != memberDt)
TypecastExpression(it.second, memberDt, true, it.second.position)
else
it.second
}
}
}

View File

@ -225,7 +225,7 @@ class VarDecl(val type: VarDeclType,
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, true, position)
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position)
if(parent!=null)
decl.linkParents(parent)
return decl
@ -234,7 +234,7 @@ class VarDecl(val type: VarDeclType,
fun flattenStructMembers(): MutableList<IStatement> {
val result = struct!!.statements.withIndex().map {
val member = it.value as VarDecl
val initvalue = if(value!=null) (value as ReferenceLiteralValue).array!![it.index] else null
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null
VarDecl(
VarDeclType.VAR,
member.datatype,

View File

@ -1,16 +1,13 @@
package prog8.compiler
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.IFunctionCall
import prog8.ast.IStatement
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
var scopelevel = 0
fun indent(s: String) = " ".repeat(scopelevel) + s
@ -261,30 +258,42 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
refLiteral.isString -> output("\"${escape(refLiteral.str!!)}\"")
refLiteral.isArray -> {
if(refLiteral.array!=null) {
var counter = 0
output("[")
scopelevel++
for (v in refLiteral.array) {
v.accept(this)
if (v !== refLiteral.array.last())
output(", ")
counter++
if(counter > 16) {
outputln("")
outputi("")
counter=0
}
}
scopelevel--
output("]")
outputListMembers(refLiteral.array.asSequence(), '[', ']')
}
}
}
}
private fun outputListMembers(array: Sequence<IExpression>, openchar: Char, closechar: Char) {
var counter = 0
output(openchar.toString())
scopelevel++
for (v in array) {
v.accept(this)
if (v !== array.last())
output(", ")
counter++
if (counter > 16) {
outputln("")
outputi("")
counter = 0
}
}
scopelevel--
output(closechar.toString())
}
override fun visit(assignment: Assignment) {
if(assignment is VariableInitializationAssignment) {
val targetVar = assignment.target.identifier?.targetVarDecl(program.namespace)
if(targetVar?.struct != null) {
// skip STRUCT init assignments
return
}
}
assignment.target.accept(this)
if(assignment.aug_op!=null)
if (assignment.aug_op != null)
output(" ${assignment.aug_op} ")
else
output(" = ")
@ -432,6 +441,11 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
whenChoice.statements.accept(this)
outputln("")
}
override fun visit(structLv: StructLiteralValue) {
outputListMembers(structLv.values.asSequence(), '{', '}')
}
override fun visit(nopStatement: NopStatement) {
output("; NOP @ ${nopStatement.position} $nopStatement")
}

View File

@ -4,7 +4,6 @@ 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
@ -593,6 +592,7 @@ internal class Compiler(private val program: Program) {
is TypecastExpression -> translate(expr)
is DirectMemoryRead -> translate(expr)
is AddressOf -> translate(expr)
is StructLiteralValue -> throw CompilerException("a struct Lv should have been flattened as assignments")
else -> {
val lv = expr.constValue(program) ?: throw CompilerException("constant expression required, not $expr")
when(lv.type) {
@ -1404,6 +1404,26 @@ internal class Compiler(private val program: Program) {
private fun translate(stmt: Assignment) {
prog.line(stmt.position)
if(stmt.value is StructLiteralValue) {
// flatten into individual struct member assignments
val identifier = stmt.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val sourcevalues = (stmt.value as StructLiteralValue).values
val assignments = struct.statements.zip(sourcevalues).map { member ->
val decl = member.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val idref = IdentifierReference(listOf(mangled), stmt.position)
val assign = Assignment(AssignTarget(null, idref, null, null, stmt.position),
null, member.second, member.second.position)
assign.linkParents(stmt)
assign
}
assignments.forEach { translate(it) }
return
}
translate(stmt.value)
val valueDt = stmt.value.inferType(program)
@ -1448,11 +1468,6 @@ internal class Compiler(private val program: Program) {
else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
}
}
DataType.STRUCT -> {
// Assume the value is an array. Flatten the struct assignment into memberwise assignments.
flattenStructAssignment(stmt, program).forEach { translate(it) }
return
}
in StringDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
in ArrayDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
else -> throw CompilerException("weird/unknown targetdt")

View File

@ -17,7 +17,6 @@ import prog8.parser.importModule
import prog8.parser.moduleName
import java.io.File
import java.io.PrintStream
import java.lang.Exception
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
@ -146,7 +145,7 @@ fun compileProgram(filepath: Path,
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print)
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}

View File

@ -261,7 +261,7 @@ private fun builtinLen(args: List<IExpression>, position: Position, program: Pro
return NumericLiteralValue.optimalInteger(arraySize, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetStatement(program.namespace) as VarDecl
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
return when(target.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {

View File

@ -12,11 +12,18 @@
}
sub start() {
Color rgb1
Color rgb1 = {1,2,3.44}
Color rgb2
rgb2 = {22233, 33, 1.1} ; @todo implicit type conversion
c64scr.print_b(rgb1.green)
c64.CHROUT('\n')
c64scr.print_b(rgb2.green)
c64.CHROUT('\n')
rgb1=rgb2
c64scr.print_b(rgb1.green)
c64.CHROUT('\n')
}
}

View File

@ -214,7 +214,9 @@ wordsuffix : '.w' ;
booleanliteral : 'true' | 'false' ;
arrayliteral : '[' EOL? expression (',' EOL? expression)* EOL? ']' ; // you can split the array list over several lines
arrayliteral : '[' EOL? expression (',' EOL? expression)* EOL? ']' ; // you can split the values over several lines
structliteral : '{' EOL? expression (',' EOL? expression)* EOL? '}' ; // you can split the values over several lines
stringliteral : STRING ;
@ -222,6 +224,7 @@ charliteral : SINGLECHAR ;
floatliteral : FLOAT_NUMBER ;
literalvalue :
integerliteral
| booleanliteral
@ -229,6 +232,7 @@ literalvalue :
| stringliteral
| charliteral
| floatliteral
| structliteral
;
inlineasm : '%asm' INLINEASMBLOCK;