building syntax support for ptr[x].field

attempting to do this by making '.' an expression operator
This commit is contained in:
Irmen de Jong
2025-04-28 03:56:08 +02:00
parent 37da3e2170
commit c96e4b40d4
11 changed files with 130 additions and 21 deletions

View File

@@ -62,6 +62,12 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(identifier: IdentifierReference) { override fun visit(identifier: IdentifierReference) {
val parentExpr = identifier.parent as? BinaryExpression
if(parentExpr?.operator=="^^") {
return // identifiers will be checked over at the BinaryExpression itself
}
if(identifier.nameInSource.any { it.startsWith('_') }) { if(identifier.nameInSource.any { it.startsWith('_') }) {
errors.err("identifiers cannot start with an underscore", identifier.position) errors.err("identifiers cannot start with an underscore", identifier.position)
} }
@@ -1228,6 +1234,39 @@ internal class AstChecker(private val program: Program,
override fun visit(expr: BinaryExpression) { override fun visit(expr: BinaryExpression) {
super.visit(expr) super.visit(expr)
if(expr.operator==".")
throw FatalAstException("temporary operator '.' should have been replaced by '^^' for 'walking the chain' at ${expr.position}")
if(expr.operator=="^^") {
val leftIdentfier = expr.left as? IdentifierReference
val leftIndexer = expr.left as? ArrayIndexedExpression
val rightIdentifier = expr.right as? IdentifierReference
val rightIndexer = expr.right as? ArrayIndexedExpression
if(rightIdentifier!=null) {
val struct: StructDecl? =
if (leftIdentfier != null) {
// PTR.FIELD
leftIdentfier.targetVarDecl()?.datatype?.subType as? StructDecl
} else if(leftIndexer!=null) {
// ARRAY[x].NAME --> maybe it's a pointer dereference
leftIndexer.arrayvar.targetVarDecl()?.datatype?.subType as? StructDecl
}
else null
if (struct != null) {
val field = struct.getFieldType(rightIdentifier.nameInSource.joinToString("."))
if (field == null)
errors.err("no such field '${rightIdentifier.nameInSource.joinToString(".")}' in struct '${struct.name}'", expr.position)
} else
errors.err("cannot find struct type", expr.position)
} else if(rightIndexer!=null) {
TODO("something.field[y] at ${expr.position}")
// TODO I don't think we can evaluate this because it could end up in as a struct instance, which we don't support yet... rewrite or just give an error?
} else
throw FatalAstException("expected identifier or arrayindexer after ^^ operator at ${expr.position})")
return
}
checkLongType(expr) checkLongType(expr)
val leftIDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)

View File

@@ -132,7 +132,7 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
checker2.visit(this) checker2.visit(this)
if(errors.noErrors()) { if(errors.noErrors()) {
val lit2decl = LiteralsToAutoVars(this, errors) val lit2decl = LiteralsToAutoVarsAndRecombineIdentifiers(this, errors)
lit2decl.visit(this) lit2decl.visit(this)
while(errors.noErrors() && lit2decl.applyModifications()>0) while(errors.noErrors() && lit2decl.applyModifications()>0)
lit2decl.visit(this) lit2decl.visit(this)

View File

@@ -25,6 +25,7 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - flatten chained assignments // - flatten chained assignments
// - remove alias nodes // - remove alias nodes
// - replace implicit pointer dereference chains (a.b.c.d) with explicit ones (a^^.b^^.c^^.d) // - replace implicit pointer dereference chains (a.b.c.d) with explicit ones (a^^.b^^.c^^.d)
// - replace binary expression with "." operator with one that has a "^^" operator, to signify pointer scope traversal
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> { override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(alias, parent as IStatementContainer)) return listOf(IAstModification.Remove(alias, parent as IStatementContainer))
@@ -267,6 +268,10 @@ _after:
return listOf(IAstModification.ReplaceNode(expr, squareCall, parent)) return listOf(IAstModification.ReplaceNode(expr, squareCall, parent))
} }
if(expr.operator==".") {
expr.operator = "^^"
}
return noModifications return noModifications
} }

View File

@@ -12,7 +12,7 @@ import prog8.code.ast.PtContainmentCheck
import prog8.code.core.IErrorReporter import prog8.code.core.IErrorReporter
internal class LiteralsToAutoVars(private val program: Program, private val errors: IErrorReporter) : AstWalker() { internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> { override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) { if(string.parent !is VarDecl && string.parent !is WhenChoice) {
@@ -160,4 +160,22 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
// } // }
return noModifications return noModifications
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".") {
val leftIdent = expr.left as? IdentifierReference
val rightIndex = expr.right as? ArrayIndexedExpression
if (leftIdent != null && rightIndex != null) {
// maybe recombine IDENTIFIER . ARRAY[IDX] --> COMBINEDIDENTIFIER[IDX]
val leftTarget = leftIdent.targetStatement(null)
if(leftTarget==null || leftTarget !is StructDecl) {
val combinedName = leftIdent.nameInSource + rightIndex.arrayvar.nameInSource
val combined = IdentifierReference(combinedName, leftIdent.position)
val indexer = ArrayIndexedExpression(combined, rightIndex.indexer, leftIdent.position)
return listOf(IAstModification.ReplaceNode(expr, indexer, parent))
}
}
}
return noModifications
}
} }

View File

@@ -665,10 +665,17 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
private fun transform(srcExpr: BinaryExpression): PtBinaryExpression { private fun transform(srcExpr: BinaryExpression): PtBinaryExpression {
val type = srcExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") } val type = srcExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val expr = PtBinaryExpression(srcExpr.operator, type, srcExpr.position) if(srcExpr.operator=="^^") {
expr.add(transformExpression(srcExpr.left)) if(srcExpr.left is ArrayIndexedExpression) {
expr.add(transformExpression(srcExpr.right)) TODO("ptr[x].field dereference, field type=$type at ${srcExpr.position}")
return expr } else
TODO("??? deref something ??? at ${srcExpr.position}")
} else {
val expr = PtBinaryExpression(srcExpr.operator, type, srcExpr.position)
expr.add(transformExpression(srcExpr.left))
expr.add(transformExpression(srcExpr.right))
return expr
}
} }
private fun transform(srcCheck: ContainmentCheck): PtExpression { private fun transform(srcCheck: ContainmentCheck): PtExpression {

View File

@@ -92,14 +92,17 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
} }
override fun visit(expr: BinaryExpression) { override fun visit(expr: BinaryExpression) {
output("(") val isValue = expr.parent is Assignment
if(!isValue) output("(")
expr.left.accept(this) expr.left.accept(this)
if(expr.operator.any { it.isLetter() }) if(expr.operator.any { it.isLetter() })
output(" ${expr.operator} ") output(" ${expr.operator} ")
else if(expr.operator=="^^")
output(".")
else else
output(expr.operator) output(expr.operator)
expr.right.accept(this) expr.right.accept(this)
output(")") if(!isValue) output(")")
} }
override fun visit(directive: Directive) { override fun visit(directive: Directive) {

View File

@@ -231,6 +231,36 @@ class BinaryExpression(
"<=", ">=", "<=", ">=",
"==", "!=" -> InferredTypes.knownFor(BaseDataType.BOOL) "==", "!=" -> InferredTypes.knownFor(BaseDataType.BOOL)
"<<", ">>" -> leftDt "<<", ">>" -> leftDt
"." -> InferredTypes.unknown() // intermediate operator, will be replaced with '^^' after recombining scoped identifiers
"^^" -> {
val leftIdentfier = left as? IdentifierReference
val leftIndexer = left as? ArrayIndexedExpression
val rightIdentifier = right as? IdentifierReference
val rightIndexer = right as? ArrayIndexedExpression
if(rightIdentifier!=null) {
val struct: StructDecl? =
if (leftIdentfier != null) {
// PTR . FIELD
leftIdentfier.targetVarDecl()?.datatype?.subType as? StructDecl
} else if(leftIndexer!=null) {
// ARRAY[x].NAME --> maybe it's a pointer dereference
leftIndexer.arrayvar.targetVarDecl()?.datatype?.subType as? StructDecl
}
else null
if (struct != null) {
val field = struct.getFieldType(rightIdentifier.nameInSource.joinToString("."))
if (field != null)
InferredTypes.knownFor(field)
else
InferredTypes.unknown()
} else
InferredTypes.unknown()
} else if(rightIndexer!=null) {
TODO("something.field[x] at ${right.position}")
// TODO I don't think we can evaluate this type because it could end up in as a struct instance, which we don't support yet... rewrite or just give an error?
} else
InferredTypes.unknown()
}
else -> throw FatalAstException("resulting datatype check for invalid operator $operator") else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
} }
} }
@@ -357,8 +387,10 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
target.datatype.isString || target.datatype.isUnsignedWord -> InferredTypes.knownFor(BaseDataType.UBYTE) target.datatype.isString || target.datatype.isUnsignedWord -> InferredTypes.knownFor(BaseDataType.UBYTE)
target.datatype.isArray -> InferredTypes.knownFor(target.datatype.elementType()) target.datatype.isArray -> InferredTypes.knownFor(target.datatype.elementType())
target.datatype.isPointer -> { target.datatype.isPointer -> {
if(target.datatype.subType!=null) if(target.datatype.subTypeFromAntlr!=null)
TODO("indexing on pointer to struct would yield the struct type itself, this is not yet supported (only pointers) at $position") InferredTypes.unknown()
else if(target.datatype.subType!=null)
InferredTypes.unknown() // TODO("indexing on pointer to struct would yield the struct type itself, this is not yet supported (only pointers) at $position")
else else
InferredTypes.knownFor(target.datatype.sub!!) InferredTypes.knownFor(target.datatype.sub!!)
} }

View File

@@ -28,6 +28,7 @@ STRUCTS and TYPED POINTERS
- DONE (for basic types only): allow array syntax on pointers too: ptr[2] means ptr+sizeof()*2, ptr[0] just means ptr^^ . - DONE (for basic types only): allow array syntax on pointers too: ptr[2] means ptr+sizeof()*2, ptr[0] just means ptr^^ .
- allow array syntax on pointers to structs too, but what type will ptr[2] have? And it will require ptr[2].field to work as well now. Actually that will be the only thing to work for now. - allow array syntax on pointers to structs too, but what type will ptr[2] have? And it will require ptr[2].field to work as well now. Actually that will be the only thing to work for now.
- pointer arithmetic should follow C: ptr=ptr+10 adds 10*sizeof() instead of just 10. - pointer arithmetic should follow C: ptr=ptr+10 adds 10*sizeof() instead of just 10.
- can we get rid of the '.' -> '^^' operator rewrite?
- add unit tests for all changes - add unit tests for all changes
- arrays of structs? No -> Just an array of uword pointers to said structs. Can even be @split as the only representation form because that's the default for word arrays. - arrays of structs? No -> Just an array of uword pointers to said structs. Can even be @split as the only representation form because that's the default for word arrays.
- static initialization of structs may be allowed only at block scope and then behaves like arrays; it won't reset to the original value when program is restarted, so beware. Syntax = TBD - static initialization of structs may be allowed only at block scope and then behaves like arrays; it won't reset to the original value when program is restarted, so beware. Syntax = TBD

View File

@@ -1,22 +1,25 @@
%import textio
%import floats
%zeropage basicsafe
%option no_sysinit
main { main {
sub start() { sub start() {
; rotatedx[i] = A*shipdata.x[i] + B*shipdata.y[i] + C*shipdata.z[i]
; cx16.VERA_DATA0 = cx16.VERA_DATA0 & gfx_hires.plot.mask4c[cx16.r2L] | cx16.r12L
if cx16.r0[0]!='0' {
cx16.r0++
}
struct Node { struct Node {
bool flag bool flag
^^Node next ^^Node next
} }
^^Node ptr = 2000 ^^Node ptr
txt.print_uw(ptr) bool zz1, zz2
txt.nl() ptr = ptr[10].next
zz2 = ptr[10].next.flag ;; TODO fix
bool derp = ptr[2].flag zz1 = ptr[10].next.next.flag ;; TODO fix
bool derp2 = ptr.next.next[2].flag zz2 = ptr[10].next[2].flag ;; TODO fix
zz1 = ptr[10].next[2].flagz ;; TODO should complain about flagz only
} }
} }

View File

@@ -189,6 +189,7 @@ postincrdecr : assign_target operator = ('++' | '--') ;
expression : expression :
'(' expression ')' '(' expression ')'
| functioncall | functioncall
| left = expression EOL? bop = '.' EOL? right = expression // "scope traversal operator"
| <assoc=right> prefix = ('+'|'-'|'~') expression | <assoc=right> prefix = ('+'|'-'|'~') expression
| left = expression EOL? bop = ('*' | '/' | '%' ) EOL? right = expression | left = expression EOL? bop = ('*' | '/' | '%' ) EOL? right = expression
| left = expression EOL? bop = ('+' | '-' ) EOL? right = expression | left = expression EOL? bop = ('+' | '-' ) EOL? right = expression

View File

@@ -215,7 +215,7 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
init { init {
if(operator in ComparisonOperators + LogicalOperators) if(operator in ComparisonOperators + LogicalOperators)
require(type.isBool) require(type.isBool)
else else if(operator!="^^")
require(!type.isBool) { "no bool allowed for this operator $operator"} require(!type.isBool) { "no bool allowed for this operator $operator"}
} }
} }