diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 932f8b2f0..236d91837 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1269,7 +1269,7 @@ internal class AstChecker(private val program: Program, val arrayspec = ArrayIndex.forArray(array) checkValueTypeAndRangeArray(array.type.getOrUndef(), arrayspec, array) } else { - errors.err("undefined array type (multiple element types?)", array.position) + errors.err("undefined array type (multiple or incompatible element types?)", array.position) } if(array.parent is VarDecl) { @@ -1277,6 +1277,17 @@ internal class AstChecker(private val program: Program, errors.err("initialization value contains non-constant elements", array.value[0].position) } + val elementDt = (array.parent as VarDecl).datatype.elementType() + if(elementDt.isPointer) { + // all elements in the initializer array should be of the same element type + array.value.forEach { + val valueDt = it.inferType(program).getOrUndef() + if(!valueDt.isUnsignedWord && valueDt != elementDt) { + errors.err("struct initializer element has invalid type, expected $elementDt or uword but got $valueDt", it.position) + } + } + } + } else if(array.parent is ForLoop) { if (!array.value.all { it.constValue(program) != null }) errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position) @@ -2449,7 +2460,7 @@ internal class AstChecker(private val program: Program, else if (targetDatatype.isPointer) { if(sourceDatatype.isPointer) { if(!(sourceDatatype isAssignableTo targetDatatype)) - errors.err("cannot assign different pointer type, expected $targetDatatype got $sourceDatatype", position) + errors.err("cannot assign different pointer type, expected $targetDatatype or uword but got $sourceDatatype", position) } else if(sourceDatatype.isString && targetDatatype.sub?.isByte==true) { // assigning a string to a byte pointer is allowed. } else if(!sourceDatatype.isUnsignedWord && !sourceDatatype.isStructInstance) diff --git a/compiler/test/TestPointers.kt b/compiler/test/TestPointers.kt index 762b86031..adc873ab9 100644 --- a/compiler/test/TestPointers.kt +++ b/compiler/test/TestPointers.kt @@ -1845,10 +1845,13 @@ main { }""" val errors=ErrorReporterForTests() compileText(C64Target(), false, src, outputDir, errors=errors) shouldBe null - errors.errors.size shouldBe 2 - errors.errors[0] shouldContain "wrong pointer type in array" - errors.errors[1] shouldContain "wrong pointer type in array" - // TODO check that they belong to onlynodes and node + errors.errors.size shouldBe 6 + errors.errors[0] shouldContain "11:30: initialization value for pointer array" + errors.errors[1] shouldContain "11:30: undefined array type" // a bit redundant but can't be helped + errors.errors[2] shouldContain "13:13: struct initializer element has invalid type" + errors.errors[3] shouldContain "16:28: invalid assignment value" + errors.errors[4] shouldContain "16:28: undefined array type" + errors.errors[5] shouldContain "21:23: cannot assign different pointer type" } test("local and global struct pointer qualified name lookups") { @@ -2075,8 +2078,8 @@ main { val errors = ErrorReporterForTests() compileText(VMTarget(), false, src, outputDir, errors = errors, writeAssembly = false) shouldBe null errors.errors.size shouldBe 2 - errors.errors[0] shouldContain "7:24: cannot assign different pointer type, expected ^^uword got ^^ubyte" - errors.errors[1] shouldContain "10:24: cannot assign different pointer type, expected ^^ubyte got ^^uword" + errors.errors[0] shouldContain "7:24: cannot assign different pointer type, expected ^^uword or uword but got ^^ubyte" + errors.errors[1] shouldContain "10:24: cannot assign different pointer type, expected ^^ubyte or uword but got ^^uword" } test("passing nosplit array of structpointers to a subroutine in various forms should be param type ptr to struct") { diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 92d59d1fd..6cc5a99ce 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -1841,7 +1841,13 @@ class StaticStructInitializer(var structname: IdentifierReference, override fun referencesIdentifier(nameInSource: List): Boolean = structname.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)} - override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.UWORD) + override fun inferType(program: Program): InferredTypes.InferredType { + val struct = structname.targetStructDecl() + return if(struct==null) + InferredTypes.unknown() + else + InferredTypes.knownFor(DataType.pointer(struct)) + } } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index f49654199..ebc26a15e 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -58,7 +58,6 @@ and for example the below code omits line 5:: STRUCTS and TYPED POINTERS -------------------------- -- fix type checks for wrong pointer types in pointer array initalizer and assignment - fix the pointers/hashtable.p8 example - fix code size regressions (if any left) - optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0 @@ -73,6 +72,7 @@ STRUCTS and TYPED POINTERS - support for typed function pointers? (&routine could be typed by default as well then) - support @nosplit pointer arrays? - support pointer to pointer? +- the type of struct initializer arrays should not be uword[] but ^^struct[] - really fixing the pointer dereferencing issues (cursed hybrid beween IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator" - (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field) diff --git a/examples/pointers/hashtable.p8 b/examples/pointers/hashtable.p8 new file mode 100644 index 000000000..67a59c3c9 --- /dev/null +++ b/examples/pointers/hashtable.p8 @@ -0,0 +1,247 @@ +; Doubly Linked List with Hash Table Cache +; Demonstrates advanced pointer usage with a combination of doubly linked lists and hash tables +; for efficient data storage and retrieval + + +; TODO fix this AI generated code + + +%import math +%import strings +%import textio +%zeropage basicsafe + +main { + sub start() { + txt.print("Doubly Linked List with Hash Table Cache Demo\n") + txt.print("============================================\n\n") + + ; Initialize the data structure + cache.init() + + ; Add some sample data + txt.print("Adding sample data...\n") + cache.add("Alice", "Engineer", 30) + cache.add("Bob", "Designer", 25) + cache.add("Charlie", "Manager", 35) + cache.add("Diana", "Developer", 28) + cache.add("Eve", "Analyst", 32) + + txt.print("\nForward traversal:\n") + cache.print_forward() + + txt.print("\nBackward traversal:\n") + cache.print_backward() + + txt.print("\nSearching for specific entries:\n") + txt.print("Looking for 'Charlie': ") + ^^cache.Entry entry = cache.find("Charlie") + if entry!=0 { + txt.print(entry.name) + txt.print(" - ") + txt.print(entry.job) + txt.print(" (") + txt.print_ub(entry.age) + txt.print(" years old)\n") + } else { + txt.print("Not found\n") + } + + txt.print("Looking for 'Frank': ") + entry = cache.find("Frank") + if entry!=0 { + txt.print("Found\n") + } else { + txt.print("Not found\n") + } + + txt.print("\nRemoving 'Bob'...\n") + void cache.remove("Bob") + + txt.print("Forward traversal after removal:\n") + cache.print_forward() + + txt.print("\nDemonstrating hash collision handling...\n") + ; Add entries that will likely collide in the hash table + cache.add("A", "First", 20) + cache.add("B", "Second", 21) + cache.add("C", "Third", 22) + + txt.print("Added entries with potential hash collisions.\n") + txt.print("Forward traversal:\n") + cache.print_forward() + } +} + +cache { + struct Entry { + ^^Entry next ; Next entry in the list + ^^Entry prev ; Previous entry in the list + ^^Entry hash_next ; Next entry in the hash bucket + str name ; Name (key) + str job ; Job description + ubyte age ; Age + } + + const ubyte HASH_TABLE_SIZE = 16 + ^^Entry[HASH_TABLE_SIZE] hash_table ; Hash table for fast lookups + ^^Entry head = 0 ; Head of the doubly linked list + ^^Entry tail = 0 ; Tail of the doubly linked list + uword count = 0 ; Number of entries + + sub init() { + ; Initialize hash table buckets to null + ubyte i + for i in 0 to HASH_TABLE_SIZE-1 + hash_table[i] = 0 + } + + sub hash(str key) -> ubyte { + ; Simple hash function + ubyte hash_value = 0 + ubyte i = 0 + while key[i] != 0 { + hash_value = hash_value * 31 + key[i] + i++ + } + return hash_value % HASH_TABLE_SIZE + } + + sub add(str name, str job, ubyte age) { + ; Create new entry + ^^Entry new_entry = arena.alloc(sizeof(Entry)) + ubyte name_len = strings.length(name) + ubyte job_len = strings.length(job) + ^^ubyte name_copy = arena.alloc(name_len + 1) + ^^ubyte job_copy = arena.alloc(job_len + 1) + void strings.copy(name, name_copy) + void strings.copy(job, job_copy) + + new_entry.name = name_copy + new_entry.job = job_copy + new_entry.age = age + new_entry.next = 0 + new_entry.prev = 0 + new_entry.hash_next = 0 + + ; Add to the end of the doubly linked list + if head == 0 { + ; First entry + head = new_entry + tail = new_entry + } else { + ; Add to the end + tail.next = new_entry + new_entry.prev = tail + tail = new_entry + } + + ; Add to hash table + ubyte bucket = hash(name) + new_entry.hash_next = hash_table[bucket] + hash_table[bucket] = new_entry + + count++ + txt.print("Added: ") + txt.print(name) + txt.print("\n") + } + + sub find(str name) -> ^^Entry { + ; Find entry using hash table for O(1) average case + ubyte bucket = hash(name) + ^^Entry current = hash_table[bucket] + + while current != 0 { + if strings.compare(current.name, name) == 0 + return current + current = current.hash_next + } + + return 0 ; Not found + } + + sub remove(str name) -> bool { + ; Find the entry + ^^Entry to_remove = find(name) + if to_remove == 0 + return false ; Not found + + ; Remove from doubly linked list + if to_remove.prev != 0 + to_remove.prev.next = to_remove.next + else + head = to_remove.next ; Was the head + + if to_remove.next != 0 + to_remove.next.prev = to_remove.prev + else + tail = to_remove.prev ; Was the tail + + ; Remove from hash table + ubyte bucket = hash(name) + if hash_table[bucket] == to_remove { + hash_table[bucket] = to_remove.hash_next + } else { + ^^Entry current = hash_table[bucket] + while current.hash_next != 0 { + if current.hash_next == to_remove { + current.hash_next = to_remove.hash_next + break + } + current = current.hash_next + } + } + + count-- + txt.print("Removed: ") + txt.print(name) + txt.print("\n") + return true + } + + sub print_forward() { + ^^Entry current = head + while current != 0 { + txt.print("- ") + txt.print(current.name) + txt.print(" (") + txt.print(current.job) + txt.print(", ") + txt.print_ub(current.age) + txt.print(")\n") + current = current.next + } + txt.print("Total entries: ") + txt.print_uw(count) + txt.print("\n") + } + + sub print_backward() { + ^^Entry current = tail + while current != 0 { + txt.print("- ") + txt.print(current.name) + txt.print(" (") + txt.print(current.job) + txt.print(", ") + txt.print_ub(current.age) + txt.print(")\n") + current = current.prev + } + txt.print("Total entries: ") + txt.print_uw(count) + txt.print("\n") + } +} + +arena { + ; Simple arena allocator + uword buffer = memory("arena", 8000, 0) + uword next = buffer + + sub alloc(ubyte size) -> uword { + defer next += size + return next + } +} diff --git a/examples/test.p8 b/examples/test.p8 index d7d7a33a2..366784a5f 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -16,14 +16,9 @@ main { ^^Node[] @shared nodeswithtype = [ ^^Node: [1,"one", 1000, true, 1.111], ^^Node: [], - ^^Foobar: [] ; TODO fix so that type error ] - ^^Node derp = ^^Foobar: [] ; TODO fix so that type error - ^^Node derp2 = ^^Node: [] - - ^^ubyte bptr = derp2 as ^^ubyte - bptr = derp as ^^ubyte + ^^Node derp2 = ^^Foobar: [] ^^Node[] @shared nodeswithout = [ [2,"two", 2000, false, 2.222],