Require explicit via for linked-list for loops

This commit is contained in:
Irmen de Jong committed 2026-10-20 01:42:39 +02:00
1 parent 6076b01f6d
commit 63e2a45f09
4 files changed
+84 -44

No files matched your search

@@ -14,16 +14,18 @@ import prog8.code.core.Position
internal fun checkLinkedForLoop(
forLoop: ForLoop,
nodeType: DataType,
nodeTypeIn: DataType,
program: Program,
errors: IErrorReporter
): Boolean {
val nodeType = resolveDt(nodeTypeIn, forLoop.definingScope, program)
if (!nodeType.isPointer) {
errors.err("linked-list for loop requires a pointer loop variable", forLoop.position)
return false
}
val iterableType = forLoop.iterable.inferType(program).getOrUndef()
val iterableTypeRaw = forLoop.iterable.inferType(program).getOrUndef()
val iterableType = resolveDt(iterableTypeRaw, forLoop.definingScope, program)
if (!iterableType.isPointer) {
errors.err("linked-list for loop start must be a pointer", forLoop.position)
return false
@@ -98,3 +100,40 @@ internal fun resolveDeferredType(
}
private fun resolveDt(dt: DataType, scope: INameScope, program: Program): DataType {
val antlrName = dt.subTypeFromAntlr ?: return dt
// Try the scope first, then search all modules/blocks for a struct with that name.
var resolved = scope.lookup(antlrName) as? ISubType
if (resolved == null && antlrName.size == 1) {
for (module in program.modules) {
val block = findStructInScope(module, antlrName[0])
if (block != null) {
resolved = block
break
}
}
}
return if (resolved != null) {
when {
dt.isPointer -> DataType.pointer(resolved)
dt.isStructInstance -> DataType.structInstance(resolved)
else -> dt
}
} else {
dt
}
}
private fun findStructInScope(scope: INameScope, name: String): ISubType? {
val direct = scope.lookup(name) as? ISubType
if (direct != null) return direct
for (stmt in scope.statements) {
if (stmt is INameScope) {
val found = findStructInScope(stmt, name)
if (found != null) return found
}
}
return null
}
+1 -1
View File
@@ -296,7 +296,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
ExampleSizes("pointers/animalgame", c64SizeOptimized=1965, c64SizeUnoptimized=2521, cx16SizeOptimized=1991, cx16SizeUnoptimized=2801),
ExampleSizes("pointers/binarytree", c64SizeOptimized=2672, c64SizeUnoptimized=2735, cx16SizeOptimized=2175, cx16SizeUnoptimized=2487),
ExampleSizes("pointers/hashtable", c64SizeOptimized=3388, c64SizeUnoptimized=4207, cx16SizeOptimized=2911, cx16SizeUnoptimized=3782),
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1361, c64SizeUnoptimized=3149, cx16SizeOptimized=1291, cx16SizeUnoptimized=3358),
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1353, c64SizeUnoptimized=3137, cx16SizeOptimized=1291, cx16SizeUnoptimized=3358),
ExampleSizes("pointers/sorting", c64SizeOptimized=1776, c64SizeUnoptimized=2523, cx16SizeOptimized=1788, cx16SizeUnoptimized=2653),
),
listOf(false, true),
+41 -39
View File
@@ -1193,45 +1193,6 @@ to the header::
}
}
.. _linked-list-iteration:
Linked-list iteration
~~~~~~~~~~~~~~~~~~~~~
Prog8 can desugar linked-list ``for`` loops into ordinary ``while`` loops.
The explicit form works for arbitrary node layouts and link field names::
main {
^^MyList mylist = [] ; zero-initialized header
^^MyNode n1 = [0, 0, 42]
^^MyNode n2 = [0, 0, 99]
lists.init(mylist)
lists.add_tail(mylist, n1)
lists.add_tail(mylist, n2)
; forward iteration over an Exec/MinList
for node in mylist.Head via .Succ until &mylist.Tail {
txt.print_ub(node.value)
txt.nl()
}
; backward iteration
for node in mylist.TailPred via .Pred until &mylist.Head {
txt.print_ub(node.value)
txt.nl()
}
}
For simple null-terminated lists, ``until`` may be omitted and defaults to ``0``::
for node in head via .next {
; use node
}
Exec-compatible lists use uppercase field names (``Succ``, ``Pred``, ``Head``,
``Tail``), so use the explicit ``via`` form for them. Linked-list iteration always
requires this explicit field selection, regardless of the target or field names.
User-defined node structs work the same way as long as the link pointers are the first fields at
offset 0; add ``Type``/``Pri``/``Name`` after them if you need ``enqueue`` or
``find_name`` (the ``exec.Node``/``FullNode`` layout).
@@ -1245,6 +1206,47 @@ suitably aligned (even address on 6502, long-aligned on m68k).
Read the :source:`lists source code <compiler/res/prog8lib/lists.p8>` and
:source:`amiga lists source code <compiler/res/prog8lib/shared_amiga_lists.p8>` for details.
.. _linked-list-iteration:
Linked-list iteration
~~~~~~~~~~~~~~~~~~~~~
Prog8 has some convenience syntax to iterate through a linked-list via a ``for`` loop.
The for loop's ``via .field`` clause selects which pointer field in the node struct to follow
from one node to the next (usually called 'next' or 'succ'). The ``until`` clause
gives the sentinel value where iteration stops (when 0, it can be omitted)::
; iteration over an Amiga Exec list.
main {
^^MyList mylist = []
^^MyNode n1 = [0, 0, 42]
^^MyNode n2 = [0, 0, 99]
lists.init(mylist)
lists.add_tail(mylist, n1)
lists.add_tail(mylist, n2)
; forward iteration
for node in mylist.Head via .Succ until &mylist.Tail {
txt.print_ub(node.value)
txt.nl()
}
; backward iteration
for node in mylist.TailPred via .Pred until &mylist.Head {
txt.print_ub(node.value)
txt.nl()
}
}
For simple null-terminated lists, ``until`` may be omitted and then the stop value defaults to ``0``::
for node in head via .next {
; use node
}
math
^^^^
.. index:: pair: Libraries; math
+1 -2
View File
@@ -31,8 +31,7 @@ main {
sub printlist() {
ubyte count = 0
^^slist.Node head = slist.head
for ^^slist.Node n in head via .next {
for ^^slist.Node n in slist.head via .next {
txt.print_uw(n.size)
txt.chrout(':')
txt.chrout(n.letter)