improve docs about recursion

This commit is contained in:
Irmen de Jong
2025-07-29 21:57:24 +02:00
parent 1c9c5aeef7
commit 4ccd7f9f3a
8 changed files with 71 additions and 16 deletions
@@ -691,11 +691,10 @@ internal class AstChecker(private val program: Program,
// unfortunately the AST regarding pointer dereferencing is a bit of a mess, and we cannot do precise type checking on elements inside such expressions yet.
if(assignment.value.inferType(program).isUnknown) {
val binexpr = assignment.value as? BinaryExpression
if(binexpr?.operator == ".") {
errors.err("invalid pointer dereference (can't determine type)", assignment.value.position)
if (binexpr?.operator != ".") {
if(assignment.value !is PtrDereference && assignment.target.multi==null)
errors.err("invalid assignment value", assignment.value.position)
}
else if(assignment.value !is PtrDereference && assignment.target.multi==null)
errors.err("invalid assignment value", assignment.value.position)
}
super.visit(assignment)
@@ -1396,6 +1395,16 @@ internal class AstChecker(private val program: Program,
)
}
}
} else if(leftDt.isStructInstance) {
val struct = leftDt.getOrUndef().subType as StructDecl
if (rightIdentifier.nameInSource.size == 1) {
val fieldDt = struct.getFieldType(rightIdentifier.nameInSource.single())
if (fieldDt == null)
errors.err(
"no such field '${rightIdentifier.nameInSource.single()}' in struct '${struct.scopedNameString}'",
rightIdentifier.position
)
}
} else
errors.err("cannot find struct type", expr.left.position)
}
@@ -2108,7 +2117,21 @@ internal class AstChecker(private val program: Program,
if((deref.parent as? BinaryExpression)?.operator==".") {
throw FatalAstException("binexpr with '.' operator should have been converted into PtrDereference or something else ${deref.position}")
}
if(deref.inferType(program).isUnknown)
if(deref.chain.size>1) {
val field = deref.chain.last()
val structname = deref.chain.dropLast(1)
val variable = deref.definingScope.lookup(structname) as? VarDecl
if (variable != null) {
if(variable.datatype.isStructInstance || variable.datatype.isPointer) {
val struct = variable.datatype.subType!! as StructDecl
if(struct.getFieldType(field)==null) {
errors.err("no such field '$field' in struct '${struct.name}'", deref.position)
return
}
}
}
}
if (deref.inferType(program).isUnknown)
errors.err("unable to determine type of dereferenced pointer expression", deref.position)
}
@@ -206,8 +206,10 @@ class AstPreprocessor(val program: Program,
val node = decl.definingScope.lookup(antlrTypeName)
if(node==null) {
errors.err("cannot find struct type ${antlrTypeName.joinToString(".")}", decl.position)
} else {
decl.datatype.setActualSubType(node as StructDecl)
} else if(node is StructDecl) {
decl.datatype.setActualSubType(node)
} else if(antlrTypeName.size==1 && antlrTypeName[0] in program.builtinFunctions.names) {
errors.err("builtin function can only be called, not used as a type name", decl.position)
}
}
+22 -1
View File
@@ -598,6 +598,28 @@ main {
err[0] shouldContain("15:16: incompatible value type, can only assign uword or correct pointer")
}
test("unknown field") {
val src="""
main {
sub start() {
struct Node {
ubyte weight
}
^^Node nodes
nodes^^.zzz1 = 99
cx16.r0L = nodes^^.zzz2
cx16.r0L = nodes[2].zzz3
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors=errors, writeAssembly = false) shouldBe null
val err = errors.errors
err.size shouldBe 3
err[0] shouldContain("no such field 'zzz1'")
err[1] shouldContain("no such field 'zzz2'")
err[2] shouldContain("no such field 'zzz3'")
}
class Struct(override val scopedNameString: String) : ISubType {
override fun memsize(sizer: IMemSizer): Int {
@@ -975,7 +997,6 @@ main {
compileText(VMTarget(), true, src, outputDir) shouldNotBe null
}
test("local struct var deref type") {
val src="""
main {
+1 -1
View File
@@ -64,7 +64,7 @@ Subroutines
-----------
- Subroutines can be nested. Inner subroutines can directly access variables from their parent.
- Subroutine parameters are just local variables in the subroutine. (you can access them directly as such via their scoped name, if you want)
- There is no call stack for subroutine arguments: subroutine parameters are overwritten when called again. Thus recursion is not easily possible, but you can do it with manual stack manipulations.
- There is no call stack for subroutine arguments: subroutine parameters are overwritten when called again. Thus recursion is not easily possible, but you can still do it with manual stack handling.
There are a couple of example programs that show how to solve this in different ways, among which are fractal-tree.p8, maze.p8 and queens.p8
- There is no function overloading (except for a couple of builtin functions).
- Subroutines can return multiple return values, and you can multi-assign those in a single statement.
+8 -3
View File
@@ -1185,9 +1185,14 @@ Otherwise the compiler will warn you about discarding the result of the call.
.. caution::
Note that due to the way parameters are processed by the compiler,
subroutines are *non-reentrant*. This means you cannot create recursive calls.
If you do need a recursive algorithm, you'll have to hand code it in embedded assembly for now,
or rewrite it into an iterative algorithm.
subroutines are *non-reentrant*. This means you cannot create *recursive calls* (routines calling themselves)
without doing some manual work to save and restore the variables that need to retain their value between calls.
If you really need a recursive algorithm, there are a few options:
- hand code it in embedded assembly
- rewrite it as an iterative algorithm if possible
- use manual stack handling of the variables that need to retain their values, perhaps using a manual stack or using `push()` and `pop()`
Also, subroutines used in the main program should not be used from an IRQ handler. This is because
the subroutine may be interrupted, and will then call itself from the IRQ handler. Results are
then undefined because the variables will get overwritten.
+1 -1
View File
@@ -45,7 +45,7 @@ So the syntax for declaring typed pointers looks like this:
So for example; ``^^word[100] values`` declares values to be an array of 100 pointers to words.
Note that an array of pointers (regardless of the type they point to) is always a @split word array at this time.
(this is the most efficient way to access the pointers, and they need to be copied to zeropage first to
be able to use them anyway. It also allows for arrays of up to 256 pointers instead of 128.)
be able to use them anyway. It also allows for arrays of up to 256 pointers instead of 128.)
It is not possible to define pointers to *arrays*; ``^^(type[])`` is invalid syntax.
+2
View File
@@ -139,6 +139,8 @@ Calling a subroutine requires three steps:
#. calling the subroutine
#. preparing the return value (if any) and returning that from the call.
*There is no stack handling involved: Prog8 doesn't have call stack frames.*
Regular subroutines
^^^^^^^^^^^^^^^^^^^
+5 -3
View File
@@ -1,11 +1,13 @@
TODO
====
make more use of ISubType interface itself rather than casting it to StructDecl all the time
fix ^^Node nodes / cx16.r0L = nodes[2].weight
don't write pointer types into P8IR files, just write uword as the type? (actually breaks the VARIABLESWITHINIT now that zp vars get initialized to 0 again; all the pointer examples won't compile anymore)
fix countries[2]^^ = 0 compiler crash
fix ^^Node nodes / cx16.r0L = nodes[2].weight
fix passing array of structptrs to subroutine , arg type mismatches
disallow ^^str entirely??
disallow ^^str
STRUCTS and TYPED POINTERS
@@ -54,7 +56,7 @@ STRUCTS and TYPED POINTERS
- DONE: @(ptr) complains that ptr is not uword when ptr is ^^ubyte (should be allowed)
- DONE: pointer[0] should be replaced with @(pointer) if pointer is ^^ubyte, so these are now all identical: ptr[0], ptr^^, @(ptr) if ptr is ^^ubyte
- DONE: STR should be asssignment compatible with UBYTE^^ but local scoped STR should still be accessed directly using LDA str,Y instead of through the pointer, like arrays.
- DONE: replace ^^str by ^^ubyte
- DONE: disallow ^^str
- DONE: allow return ubyte/uword when pointer type is expected as return value type
- DONE: fix _msb/_lsb storage of the split-words pointer-arrays
- DONE: what about static initialization of an array of struct pointers? -> impossible right now because the pointer values are not constants.