|
Forum Index : Microcontroller and PC projects : MMbasic RP2350 V6.01.00EXP with user-defined structures
| Author | Message | ||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10808 |
V6.01.00EXP6 MMBasic_Structures_Manual.pdf PicoMiteV6.01.00EXP6.zip Huge changes: Fixes bug in passing an element of an structure array to a function - see test below Many Math and most drawing commands are now structure enabled - see manual. Attempting to pass a structure array to a non enabled command will now give a sensible error. Change to FIND function and SORT, EXTRACT, and INSERT commands. These now take the structure and element as a single parameter was: structure,element now: structure.element e.g. STRUCT SORT mystruct().name$ ' StructParamTest.bas - Test passing structure array elements to subroutines ' Tests fix for: "Array dimensions" error when passing struct array element to sub ' ' Bug scenario: ' Dim people(2) As TPerson ' TPerson.Print people(1) ' <- This caused "Array dimensions" error inside sub ' ' The fix ensures that when passing a single array element (like people(1)), ' the parameter is treated as a single struct, not as an array. Option Base 1 Option Explicit Type TPerson phone As integer surname As string Length 8 name As string Length 7 padding As string Length 2 End Type Dim people(2) As TPerson = (1234,"Mather","Peter","", 5678,"Graham","Geoff","") Dim pt As TPerson Dim test_pass As INTEGER = 0 Dim test_fail As INTEGER = 0 Print "================================================" Print "Structure Array Element Parameter Test Suite" Print "================================================" ' TEST 1: Pass single struct variable to sub Print "TEST 1: Pass single struct to sub" pt = people(1) TPerson.Print pt Print " (If no error, PASS)" test_pass = test_pass + 1 ' TEST 2: Pass array element to sub (THE BUG FIX TEST) Print "TEST 2: Pass array element people(1) to sub" TPerson.Print people(1) Print " (If no 'Array dimensions' error, PASS)" test_pass = test_pass + 1 ' TEST 3: Pass second array element Print "TEST 3: Pass array element people(2) to sub" TPerson.Print people(2) Print " PASS" test_pass = test_pass + 1 ' TEST 4: Modify through sub parameter Print "TEST 4: Modify struct through sub parameter" Dim before_phone% before_phone% = people(1).phone TPerson.Modify people(1), 9999 If people(1).phone = 9999 Then Print " PASS: phone changed from "; before_phone%; " to "; people(1).phone test_pass = test_pass + 1 Else Print " FAIL: phone still "; people(1).phone test_fail = test_fail + 1 EndIf people(1).phone = before_phone% ' Restore ' TEST 5: Pass whole array to sub expecting array Print "TEST 5: Pass whole array people() to sub" TPerson.PrintAll people() Print " PASS" test_pass = test_pass + 1 ' TEST 6: Copy inside sub (was working but verify) Print "TEST 6: Copy struct inside sub" TPerson.CopyTest people(1) Print " PASS" test_pass = test_pass + 1 ' TEST 7: Local struct assigned from parameter Print "TEST 7: Local struct assigned from parameter" TPerson.LocalCopy people(2) Print " PASS" test_pass = test_pass + 1 Print "================================================" Print "TEST SUMMARY" Print "================================================" Print "Passed: "; test_pass Print "Failed: "; test_fail If test_fail = 0 Then Print "*** ALL TESTS PASSED ***" Else Print "*** SOME TESTS FAILED ***" EndIf End ' Sub that expects a single struct (not an array) Sub TPerson.Print(me As TPerson) Print " In TPerson.Print - me.name: "; me.name Struct Print me Local p As TPerson p = me Struct Print p End Sub ' Sub that modifies a struct parameter Sub TPerson.Modify(me As TPerson, newPhone%) me.phone = newPhone% End Sub ' Sub that expects an array of structs Sub TPerson.PrintAll(arr() As TPerson) Local i% Print " Printing all "; Bound(arr()); " elements:" For i% = 1 To Bound(arr()) Print " ["; i%; "] "; arr(i%).name; " "; arr(i%).surname Next i% End Sub ' Sub that does STRUCT PRINT on parameter Sub TPerson.CopyTest(me As TPerson) Print " Testing STRUCT PRINT me inside sub:" Struct Print me End Sub ' Sub that assigns parameter to local Sub TPerson.LocalCopy(me As TPerson) Local localCopy As TPerson localCopy = me Print " Copied to local: "; localCopy.name; " "; localCopy.surname Struct Print localCopy End Sub |
||||
| JanVolk Senior Member Joined: 28/01/2023 Location: NetherlandsPosts: 281 |
With all versions V6.01.00 RP2040 and RP2350, minifm_RC2.bas works correctly, but with all subsequent versions up to V6.01.00EXP5, it no longer works. Has anyone else noticed this? The options list doesn't show any additional settings, and only minifm_RC2.bas is in memory. 42946 minifm_RC2.bas [1176] FList$(RQt)=".." Error: String too long Happy New Year and a healthy and enjoyable hobby year to all. Kind regards, Jan. |
||||
TassyJim![]() Guru Joined: 07/08/2011 Location: AustraliaPosts: 6425 |
EXP6 RP2040 Tried to create a STATIC variable FUNCTION thisreading(p AS INTEGER) AS room ' p = pin number for DHT22 LOCAL FLOAT tmpr, hmid STATIC n IF demo_mode THEN IF n = 0 THEN RESTORE dummydata n = (n+1) MOD 144 READ tmpr ,hmid ELSE DEVICE HUMID p, tmpr ,hmid ENDIF thisreading.t = tmpr thisreading.h = hmid END FUNCTION Gives error: [23] Static n Error : Unknown structure member Using a global variable instead works OK. Jim VK7JH MMedit |
||||
| bfwolf Senior Member Joined: 03/01/2025 Location: GermanyPosts: 129 |
Wow !!! "The child is growing up.." Thanks! ![]() Sorry again for my "quick and dirty" test program.. But it seems to have become clear in the end, and it was helpful after all? --- Monty Python: "And now to something completely different.." I'm a bit hesitant to ask... But I have another idea... ![]() New additional Struct-Subfunction.. typeName$ = Struct(TYPEOF typeVarInstance$) Edit: or perhaps better.. typeName$ = Struct(TYPEOF typeVarInstance) Something "evil" with it comes to my mind... ![]() A little hint: It would have something to do with 'EXECUTE command$' or 'CALL(userfunname$,[userfunparameters,..])' And with the subnames in my previous test program, which all started with a 'typeName.' - Do you already have an idea what I'm thinking about? ![]() ..But of course, bugfixing has priority.. ![]() Edited 2026-01-02 07:16 by bfwolf |
||||
| Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 5585 |
Confirm that the problem still exists in EXP6 for RP2040 nonVGA nonUSB. Volhout PicomiteVGA PETSCII ROBOTS |
||||
| karlelch Guru Joined: 30/10/2014 Location: GermanyPosts: 310 |
Static Integer n? |
||||
TassyJim![]() Guru Joined: 07/08/2011 Location: AustraliaPosts: 6425 |
The issue with minifm.bas can be reduced to passing an array to a sub. ' OPTION EXPLICIT OPTION DEFAULT INTEGER OPTION BASE 0 DIM flist$(100) LENGTH 63 getflist "b:","aaa",flist$(),1 END '============================ SUB GetFList(Disk$,Folder$,FList$(),SIDE) LOCAL INTEGER RQt,DQt,FQt,i,FS 'Drive Disk$: Chdir Folder$ FList$(RQt)=".." 'TMP$=Dir$("*",DIR) END SUB RUN [14] FList$(RQt)=".." Error : String too long Jim Edited 2026-01-02 08:14 by TassyJim VK7JH MMedit |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10808 |
Please give me a complete test program demonstrating the STATIC bug. Is it just a poor error message? I've fixed the ".." bug. Will be in the next version Edited 2026-01-02 08:47 by matherp |
||||
| bfwolf Senior Member Joined: 03/01/2025 Location: GermanyPosts: 129 |
I think karlelch wrote the solution above: https://www.thebackshed.com/forum/ViewTopic.php?TID=18519&P=6#248994 It should be perhaps be 'STATIC integer|float|string varName' Or should 'STATIC varName' be allowed? Pehaps rather 'STATIC n%' or 'STATIC n!' or 'STATIC s$' ? ..don't know.. ![]() Edit: Just read about the syntax for DIM which should be the same.. If I understood right, 'STATIC varName' might be allowed? Type would then be the default-type.. Perhaps it depends on 'OPTION EXPLICIT" if a type must be given? Edited 2026-01-02 08:53 by bfwolf Footnote added 2026-01-02 08:55 by bfwolf @Peter: You were faster than me.. |
||||
| aFox Senior Member Joined: 28/02/2023 Location: GermanyPosts: 109 |
Hi JSON files are a readable posibility to im- and export structure variables. Gregor |
||||
TassyJim![]() Guru Joined: 07/08/2011 Location: AustraliaPosts: 6425 |
Static bug: OPTION EXPLICIT OPTION DEFAULT INTEGER OPTION BASE 0 TYPE room t AS FLOAT h AS FLOAT END TYPE DIM readings(144) AS room 'dim n readings(144)=thisreading() struct PRINT readings(144) FUNCTION thisreading() AS room LOCAL FLOAT tmpr, hmid STATIC n thisreading.t = 20 'tmpr thisreading.h = 40 'hmid END FUNCTION [17] Static n Error : Unknown structure member Jim VK7JH MMedit |
||||
| LeoNicolas Guru Joined: 07/10/2020 Location: CanadaPosts: 555 |
It not only uses more memory and more variable slots, having more indirections reduce the overall game performance. |
||||
| karlelch Guru Joined: 30/10/2014 Location: GermanyPosts: 310 |
I did not find anything explicit about this in the manual, but it would be great if one could pass a structure member individually to a sub (see example below). Or should one also better use extract before? Option Base 0 Option explicit Option Default Integer Type TTest a As Integer b As Integer End Type Sub MyTest p Print p End Sub Dim test As TTest test.a = 123 MyTest test.a End Causes: Error : Incompatible type: test.a |
||||
| karlelch Guru Joined: 30/10/2014 Location: GermanyPosts: 310 |
In the MMBasic Structures User Manual, shouldn't it be "Using with PEEK(VARADDR ...) to access memory" instead of "Using with PEEK(VARTBL to access memory"? |
||||
| Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 5585 |
Constants for this purpose eat up valuable variable slots. Just learning, so I am curious if they don't require space when in structures ??? Somewhere the structure is defined, so somewhere the memory is reserved for the structure definition. Maybe that is more compact ? Volhout It not only uses more memory and more variable slots, having more indirections reduce the overall game performance. As soon as I find the time I plan to test just that. I would love to be convinced otherways, but at the moment I guess structures are just a conversion layer to underlying same memory map, and mechanism, that became possible with the introduction of REDIM in 6.01.00rc. So better readability of code, more familiar for people used to other languages. But under the hood, the same. Volhout Edited 2026-01-02 18:26 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
| ville56 Guru Joined: 08/06/2022 Location: AustriaPosts: 370 |
I think ------------------------- Option explicit Sub MyTest p (and no datatype) ------------------------- doesn't go together .... and yes, errormessages are not always to the point, but wuth a little reading the own code, I always succeeded . If the interprter throws an error, usually there IS a reason for it.Forgot to mention that option explicit and option default in the same program to me is a contradiction and option explicit wins.... Edited 2026-01-02 19:00 by ville56 73 de OE1HGA, Gerald |
||||
| phil99 Guru Joined: 11/02/2018 Location: AustraliaPosts: 2913 |
OPTION EXPLICIT only requires variables to be declared. It doesn't require the type to be specified so OPTION DEFAULT is still relevant if you don't want FLOAT to be the default. |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10808 |
I've fixed all reported bugs in structure interaction with subroutines including the issue raised by karlech which was a bug. I'm just starting some more comprehensive testing. In the meantime here is a technical description about how structures are implemented which may answer some of the questions raised (e.g. variable usage) ## Overview MMBasic structures (user-defined types) are implemented using a **single vartbl entry per structure variable**, regardless of how many members the structure contains. Structure members do NOT create individual entries in `g_vartbl`. Instead, member metadata is stored in a separate **structure type definition table** (`g_structtbl`), and member access is resolved at runtime by calculating byte offsets into a contiguous memory block. ## Architecture ### Data Structures #### 1. Structure Type Definition (`s_structdef`) Defined in [MMBasic.h](MMBasic.h#L288-L294): ```c typedef struct s_structdef { unsigned char name[MAXVARLEN]; // Structure type name (e.g., "POINT") int num_members; // Number of members in this type struct s_structmember members[MAX_STRUCT_MEMBERS]; // Member definitions int total_size; // Total size in bytes (with alignment) } structdef_val; ``` #### 2. Structure Member Definition (`s_structmember`) Defined in [MMBasic.h](MMBasic.h#L280-L286): ```c typedef struct s_structmember { unsigned char name[MAXVARLEN]; // Member name (e.g., "X", "Y") unsigned char type; // T_NBR, T_STR, T_INT, or T_STRUCT (nested) unsigned char size; // String max length, or nested struct type index int offset; // Byte offset within structure short dims[MAXDIM]; // Array dimensions (0 = not an array) } structmember_val; ``` #### 3. Variable Table Entry (`s_vartbl`) Structure variables use the standard variable table entry: ```c typedef struct s_vartbl { unsigned char name[MAXVARLEN]; // Variable name (e.g., "PT", "POINTS") unsigned char type; // T_STRUCT | T_IMPLIED (and possibly T_PTR) unsigned char level; // Scope level (0 = global, >0 = local) unsigned char size; // ** STRUCT TYPE INDEX ** (not string size) unsigned char namelen; // Flags (NAMELEN_EXPLICIT, NAMELEN_STATIC) int/short dims[MAXDIM]; // Array dimensions (for struct arrays) union u_val { MMFLOAT f; long long int i; MMFLOAT *fa; long long int *ia; unsigned char *s; // ** POINTER TO STRUCT DATA BLOCK ** } val; } vartbl_val; ``` ### Global Tables | Table | Purpose | Size | |-------|---------|------| | `g_structtbl[MAX_STRUCT_TYPES]` | Array of pointers to structure type definitions | 32 pointers | | `g_structcnt` | Count of defined structure types | int | | `g_vartbl[]` | Variable table (structures use ONE entry each) | MAXVARS entries | Configuration constants from [configuration.h](configuration.h#L297-L299): - `MAX_STRUCT_TYPES` = 32 (maximum distinct TYPE definitions) - `MAX_STRUCT_MEMBERS` = 16 (maximum members per structure) - `MAX_STRUCT_NEST_DEPTH` = 8 (maximum nesting depth) ## Memory Layout ### Single Structure Variable For a structure definition: ```basic TYPE point x AS FLOAT y AS FLOAT END TYPE DIM pt AS point ``` Creates: 1. **One entry in `g_structtbl`** (type definition, allocated once) 2. **One entry in `g_vartbl`** for variable `pt` 3. **One contiguous memory block** (16 bytes) pointed to by `g_vartbl[idx].val.s` Memory layout for `pt`: ``` Offset Content Size 0 x (FLOAT) 8 bytes 8 y (FLOAT) 8 bytes --- Total 16 bytes ``` ### Structure Array ```basic DIM points(100) AS point ``` Creates: 1. **One entry in `g_vartbl`** for array `points` 2. **One contiguous memory block** (16 × 101 = 1616 bytes, accounting for OPTION BASE) Memory layout for `points(0)` through `points(100)`: ``` Offset Content 0 points(0).x, points(0).y (16 bytes) 16 points(1).x, points(1).y (16 bytes) 32 points(2).x, points(2).y (16 bytes) ... 1600 points(100).x, points(100).y (16 bytes) ``` ## How vartbl Fields Are Used for Structures | Field | Usage for Structures | |-------|---------------------| | `name[]` | Variable name (e.g., "PT", "POINTS") | | `type` | `T_STRUCT \| T_IMPLIED` (may include `T_PTR` for STATIC) | | `level` | Scope level (0 = global) | | `size` | **Structure type index** into `g_structtbl[]` | | `namelen` | Flags: `NAMELEN_STATIC` for static variables | | `dims[]` | Array dimensions (0 for simple struct, >0 for arrays) | | `val.s` | **Pointer to allocated struct data** | ### Key Insight: `size` Field Repurposing For regular variables, `size` holds string length. For structures: - `size` holds the **index into `g_structtbl`** that defines this struct's type - Retrieved via: `int struct_type = (int)g_vartbl[idx].size;` - Then access type definition: `g_structtbl[struct_type]` ## Structure Member Access Resolution When code accesses `pt.x` or `points(5).y`: ### Step 1: Parse Variable Name In `findvar()` ([MMBasic.c](MMBasic.c#L3378-L3420)): - Detect dot in name: `unsigned char *dot = strchr(name, '.')` - Split into base name ("pt") and member path ("x") ### Step 2: Find Base Variable Using `FindStructBase()` ([MMBasic.c](MMBasic.c#L3202-L3260)): - Hash-based lookup in local then global variable space - Verify `type & T_STRUCT` - Return struct type index from `size` field ### Step 3: Resolve Member Path Using `ResolveStructMember()` ([MMBasic.c](MMBasic.c#L2944-L3195)): - Look up member in `g_structtbl[type_idx]->members[]` - Use `FindStructMember()` for name matching - Calculate byte offset from `member.offset` - Handle nested structs recursively - Handle array indexing for member arrays ### Step 4: Return Pointer - Final pointer = `base_ptr + total_offset + array_offset` - Set `g_StructMemberType` for type checking - Set `g_StructMemberOffset` and `g_StructMemberSize` for STRUCT operations ## Structure Definition Processing Structure types are parsed during `PrepareProgram()` (program preparation phase), not at runtime. ### Parsing Flow ([MMBasic.c](MMBasic.c#L650-L810)) 1. **Detect TYPE token** in token stream 2. **Parse type name** and check for duplicates 3. **Allocate `s_structdef`** in memory 4. **Parse members** until END TYPE: - Call `ParseStructMember()` ([Commands.c](Commands.c#L5891-L6070)) - Calculate aligned offsets - Support INTEGER, FLOAT, STRING LENGTH n, nested types 5. **Finalize type** with padding for alignment 6. **Increment `g_structcnt`** ### Member Offset Calculation From [Commands.c](Commands.c#L6024-L6032): ```c // Align integers, floats, and nested structures to 8-byte boundary offset = sd->total_size; if ((type == T_INT || type == T_NBR || type == T_STRUCT) && (offset % 8) != 0) { offset = ((offset / 8) + 1) * 8; } ``` ## Variable Creation (DIM AS structtype) When `DIM pt AS point` executes ([Commands.c](Commands.c#L4660-L4890)): 1. **Detect AS keyword** and structure type name 2. **Look up type** in `g_structtbl` → get type index 3. **Call `findvar()`** with `V_DIM_VAR` 4. **Allocate memory** for struct data (size from `g_structtbl[idx]->total_size`) 5. **Set vartbl fields**: - `type = T_STRUCT | T_IMPLIED` - `size = struct_type_index` - `val.s = allocated_memory_pointer` ### For Arrays Memory allocated = `total_size × num_elements` Where `num_elements` = product of (dim[i] + 1 - OPTION BASE) for all dimensions. ## STATIC Structure Variables STATIC structures create TWO vartbl entries: 1. **Global entry** with mangled name (`"funcname\x1evarname"`) - Holds actual struct data - Persists across function calls 2. **Local entry** with original name - `type |= T_PTR` (pointer flag) - `val.s` points to global entry's data - Destroyed when function exits The `\x1e` (ASCII Record Separator) in the name prevents conflicts with structure member syntax (which uses `.`). ## Example: Complete Memory Trace ```basic TYPE room temp AS FLOAT name AS STRING LENGTH 20 END TYPE FUNCTION thisreading() AS room STATIC integer n ' Creates global "thisreading\x1en" STATIC data AS room ' Creates global "thisreading\x1edata" n = n + 1 data.temp = 22.5 data.name = "Kitchen" thisreading = data END FUNCTION ``` ### g_structtbl[0] (room type): ``` name = "ROOM" num_members = 2 members[0] = { name="TEMP", type=T_NBR, offset=0, size=8 } members[1] = { name="NAME", type=T_STR, offset=8, size=20 } total_size = 29 (8 + 21, rounded up for alignment) ``` ### g_vartbl entries when function runs: | Index | Name | Type | Size | val.s | |-------|------|------|------|-------| | G1 | "THISREADING\x1EN" | T_INT | 8 | (integer value) | | G2 | "THISREADING\x1EDATA" | T_STRUCT | 0 | → 32-byte block | | L1 | "N" | T_INT\|T_PTR | 8 | → G1's value | | L2 | "DATA" | T_STRUCT\|T_PTR | 0 | → G2's data block | ## Summary: Key Design Decisions 1. **One vartbl entry per variable** - Members are NOT individual variables 2. **Type definitions separate from instances** - `g_structtbl` holds metadata 3. **Contiguous memory blocks** - Efficient for arrays and memory operations 4. **Offset-based member access** - Calculated at runtime, no pointer chasing 5. **Alignment enforced** - 8-byte boundary for numeric types 6. **Nested structures supported** - Via recursive type references 7. **STATIC uses mangled names** - With `\x1e` separator to avoid dot conflicts Edited 2026-01-02 19:58 by matherp |
||||
| twofingers Guru Joined: 02/06/2014 Location: GermanyPosts: 1715 |
Thanks Peter, I really appreciate getting such a comprehensive overview! It will certainly be very useful for many here! Perhaps this (and other internal details, e.g., "Calltable, etc.") could be included in a separate appendix to the manual? Just a suggestion. Kind regards Michael Other interesting topics would be, for example, an explanation of how each storage type is used, how much buffer memory MP3 playback (WAV, FLAC) requires, etc. These are things I often miss, and others apparently don't. ![]() Edited 2026-01-02 21:41 by twofingers causality ≠ correlation ≠ coincidence |
||||
| Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 5585 |
Thanks Peter ! I assume the structure and member definitions find place in the heap. Correct ? Volhout PicomiteVGA PETSCII ROBOTS |
||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2026 |