From 63e2a45f09e98b2688b0c8a8d6cd005c21a9850b Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 20 Oct 2026 01:40:02 +0200 Subject: [PATCH] Require explicit via for linked-list for loops --- .../astprocessing/LinkedForLoopChecks.kt | 43 +++++++++- compiler/test/TestCompilerOnExamples.kt | 2 +- docs/source/libraries.rst | 80 ++++++++++--------- examples/pointers/sortedlist.p8 | 3 +- 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/compiler/src/prog8/compiler/astprocessing/LinkedForLoopChecks.kt b/compiler/src/prog8/compiler/astprocessing/LinkedForLoopChecks.kt index 813b178b4..02deae71c 100644 --- a/compiler/src/prog8/compiler/astprocessing/LinkedForLoopChecks.kt +++ b/compiler/src/prog8/compiler/astprocessing/LinkedForLoopChecks.kt @@ -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 +} + + diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 718e6d2f3..d09a91952 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -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), diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 5876374c9..f77eff249 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -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 ` and :source:`amiga lists source code ` 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 diff --git a/examples/pointers/sortedlist.p8 b/examples/pointers/sortedlist.p8 index c44a1f865..12492a0f4 100644 --- a/examples/pointers/sortedlist.p8 +++ b/examples/pointers/sortedlist.p8 @@ -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)