Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 19:39 10 Jan 2026 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : PicoMite V6.02.00 release candidates - Structured types

     Page 3 of 6    
Author Message
PhenixRising
Guru

Joined: 07/11/2023
Location: United Kingdom
Posts: 1688
Posted: 08:15pm 06 Jan 2026
Copy link to clipboard 
Print this post

  Mixtel90 said  @damos
I'm (stubbornly 'cos I'm like that) sticking with my "no SQL" stance. I don't think it's feasible on the Pico as you haven't got any user RAM at runtime other than what's available in the variables area and that's limited. The entire database has to be on either A: (for speed) or B: (for size). Consequently the most efficient way to access it is to use a "world" random access file on a SD card and several index files on A:. You could interface this system to structs, probably, using routines. You can read and update the index files very fast, rewriting the records of "world" to its SD card as and when necessary.


 

Broken record alert:

Android + OLIBasic for the HMI

SQL is part of Android
Just as easy as MMBasic
Many remote control options (TeamViewer, etc.)


Amazing graphics handling

Battery backup

WiFi, Bluetooth, etc.

Capacitive touchscreen

Keyboard/mouse support

Cameras, all kinds of sensors

Dirt cheap  

Ubiquitous
 
damos
Regular Member

Joined: 15/04/2016
Location: Australia
Posts: 81
Posted: 10:40pm 06 Jan 2026
Copy link to clipboard 
Print this post

My first thought was to use MAUI because I can use it on Windows or Android (or even iOS for those fruit people, or Linux for the people on this forum).

I have found bluetooth serial to MAUI is quite simple through a HC06 and it works very well on Windows and Android. I can use Pico and all the other embedded options for the hardware (I have done the RFID sensors in Arduino because they have a library which just works).

Peter has added this nice stuff with the structs which will make every project better and it is getting close to the people where everything can be done on the Pico itself.

While I also use many databases >10GB, the SQLite stuff I have used for Android is really tiny and the files are tiny at around 50kB for 3 files with not much in the database. The data size will be largely the same even if I use text files but realistically you will probably only have dozens to hundreds of rows and it will fit on the build in flash drive. It is really all about the convenience of SQL for finding and sorting.

The struct feature doesn't quite make a database yet. It is in-memory which really limits the size and the only collection type is array which means that every search needs to be implemented in specialized code. If we had an array find where you can search the array for a struct where the specified property matches a condition it would be close.
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10838
Posted: 11:04pm 06 Jan 2026
Copy link to clipboard 
Print this post

Volhout
Done more research and this is crap code
ctrl.poll.single is being called with driver$ set to "" and then it relies on ON ERROR SKIP to not error on the line "call driver$,ctrlOPEN"
For some reason the on error skip is not being actioned on the 5th time the function is called with a blank value in driver$ but bluntly this is just rubbish
 
lizby
Guru

Joined: 17/05/2016
Location: United States
Posts: 3546
Posted: 03:54am 07 Jan 2026
Copy link to clipboard 
Print this post

Suppose I have 500 records of the following struct in a file:
Type JobRec
 LinkID     As INTEGER
 LastName   As STRING LENGTH 15
 Age        As INTEGER
 Occupation As STRING LENGTH 20
 Salary     As INTEGER
End Type
DIM job as JobRec


I figure the length is 8+(15+1)+8+(20+1)+8 is 61. [Wrong, see below] How do I read, say, the 100th record in the file into job with random access?

Note if I run a program listing the first 10 records using this struct and then do PRINT len(job), I get 10. What does that signify?

Is there a way to retrieve the length of a structure without counting?

Hmmm. I see that I disregarded alignment. Recalculating, maybe 64.

Anyway, if I run this:
Option EXPLICIT
Option DEFAULT NONE

Type JobRec
 LinkID     As INTEGER
 LastName   As STRING LENGTH 15
 Age        As INTEGER
 Occupation As STRING LENGTH 20
 Salary     As INTEGER
End Type

Dim job As JobRec
Dim i As INTEGER

Print "LinkID | Last Name       | Age | Occupation           | Salary  "
Print "-------+-----------------+-----+----------------------+---------"

Open "jobs.dbt" For INPUT As #1

For i = 1 To 10
 If Eof(#1) Then Exit For
 Struct LOAD #1, job

 ' Format: LinkID (4 digits right-aligned)
 Print Right$("    " + Str$(job.LinkID), 4) + " | ";

 ' Format: Name (15 chars left-aligned)
 Print Left$(job.LastName + Space$(15), 15) + " | ";

 ' Format: Age (3 chars right-aligned)
 Print Right$("   " + Str$(job.Age), 3) + " | ";

 ' Format: Occupation (20 chars left-aligned)
 Print Left$(job.Occupation + Space$(20), 20) + " | ";

 ' Format: Salary (8 chars right-aligned)
 Print Right$("        " + Str$(job.Salary), 8)
Next i

Close #1
> run

I get this
LinkID | Last Name       | Age | Occupation           | Salary
-------+-----------------+-----+----------------------+---------
  1 | Butt            |  63 | Analyst              |   116000
  2 | Darakjy         |  61 | Analyst              |    64000
  3 | Venere          |  63 | Analyst              |   106000
  4 | Paprocki        |  37 | Engineer             |    48000
  5 | Foller          |  31 | Analyst              |   100000
  6 | Morasca         |  30 | Teacher              |    36000
  7 | Tollner         |  65 | Engineer             |    87000
  8 | Dilliard        |  18 | Sales                |    63000
  9 | Wieser          |  31 | Teacher              |   124000
 10 | Marrier         |  37 | Sales                |   104000


But with this:
Option EXPLICIT
Option DEFAULT NONE

Type JobRec
 LinkID     As INTEGER
 LastName   As STRING LENGTH 15
 Age        As INTEGER
 Occupation As STRING LENGTH 20
 Salary     As INTEGER
End Type

Dim job As JobRec
Dim i As INTEGER

Print "LinkID | Last Name       | Age | Occupation           | Salary  "
Print "-------+-----------------+-----+----------------------+---------"

Open "jobs.dbt" For random As #1
Struct LOAD #1, job
PrintRec

Seek #1,64*2
Struct LOAD #1, job
PrintRec

Close #1
End

Sub printrec
 ' Format: LinkID (4 digits right-aligned)
 Print Right$("    " + Str$(job.LinkID), 4) + " | ";

 ' Format: Name (15 chars left-aligned)
 Print Left$(job.LastName + Space$(15), 15) + " | ";

 ' Format: Age (3 chars right-aligned)
 Print Right$("   " + Str$(job.Age), 3) + " | ";

 ' Format: Occupation (20 chars left-aligned)
 Print Left$(job.Occupation + Space$(20), 20) + " | ";

 ' Format: Salary (8 chars right-aligned)
 Print Right$("        " + Str$(job.Salary), 8)

End Sub

I get this (even the first record doesn't print correctly):
LinkID | Last Name       | Age | Occupation           | Salary
-------+-----------------+-----+----------------------+---------
  0 |                 |   0 |                      |        0
768 |                 | 160 |                      | 27136000


Here's the file zipped.

jobs.zip

~
Edited 2026-01-07 14:30 by lizby
PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed
 
TassyJim

Guru

Joined: 07/08/2011
Location: Australia
Posts: 6426
Posted: 05:16am 07 Jan 2026
Copy link to clipboard 
Print this post

Seek #1,64*2+1

first byte is 1 so add 1 to the calculated offset

Jim
VK7JH
MMedit
 
toml_12953
Guru

Joined: 13/02/2015
Location: United States
Posts: 540
Posted: 05:17am 07 Jan 2026
Copy link to clipboard 
Print this post

  lizby said  Suppose I have 500 records of the following struct in a file:
Type JobRec
 LinkID     As INTEGER
 LastName   As STRING LENGTH 15
 Age        As INTEGER
 Occupation As STRING LENGTH 20
 Salary     As INTEGER
End Type
DIM job as JobRec


I figure the length is 8+(15+1)+8+(20+1)+8 is 61. [Wrong, see below] How do I read, say, the 100th record in the file into job with random access?



You don't have to calculate manually, you can find the length of your structure by:

Struct(SIZEOF "JobRec")


In your case, this is 64 bytes.
 
toml_12953
Guru

Joined: 13/02/2015
Location: United States
Posts: 540
Posted: 05:26am 07 Jan 2026
Copy link to clipboard 
Print this post

  lizby said    
Format: LinkID (4 digits right-aligned)
 Print Right$("    " + Str$(job.LinkID), 4) + " | ";



No need for the leader bytes. This will do the same thing:

Print Str$(job.LinkID, 4) + " | ";


The STR$ function automatically pads numbers with spaces on the left to create the size you specify and right-justify your numbers.
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5602
Posted: 08:05am 07 Jan 2026
Copy link to clipboard 
Print this post

  matherp said  Volhout
Done more research and this is crap code
ctrl.poll.single is being called with driver$ set to "" and then it relies on ON ERROR SKIP to not error on the line "call driver$,ctrlOPEN"
For some reason the on error skip is not being actioned on the 5th time the function is called with a blank value in driver$ but bluntly this is just rubbish


Interesting. Thanks for diving so deep into it. I might not have been able to grasp to complexity of the code. Good decision I did not decide to start altering it.
The intersting question however is: why does ON ERROR SKIP work 4 times, but not the 5'th time (in the same situation) ? Is this a nesting issue ? Maybe it points to an issue in 6.02.00 (regardless how good the code is).

Volhout

P.S.This code has not been changed since 5.08.00 (release of Game*Mite). Although there may be better ways to do this today, MMBasic was not that mature in 5.07.xx when the code was written. This may have been the way to do things back then.
PicomiteVGA PETSCII ROBOTS
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10838
Posted: 08:22am 07 Jan 2026
Copy link to clipboard 
Print this post

  Quote  The intersting question however is: why does ON ERROR SKIP work 4 times, but not the 5'th time (in the same situation) ? Is this a nesting issue ?

That is what I need to solve (it isn't always the fifth), but I also want to test a very simple workaround for the program for you. Are there any other programs that don't run or just this one?

UPDATE
This change fixes the bug in terms of the program running. I don't know what it is supposed to do so can't test further
Function ctrl.poll_single%(driver$, mask%)
On Error Ignore
If driver$<>"" Then Call driver$, ctrl.OPEN
If MM.Errno = 0 Then
 Local key%, t% = Timer + 5
 Do
  If driver$<>"" Then Call driver$, key%
  ctrl.poll_single% = key% And mask%
  If ctrl.poll_single% Then
   Do While key% : Pause 5 : Call driver$, key% : Loop
   Exit Do
  EndIf
 Loop While Timer < t%
 If driver$<>"" Then Call driver$, ctrl.SOFT_CLOSE
EndIf
On Error Abort
End Function



UPDATE to the UPDATE
found and fixed. Both the DIM bug and this one will be fixed in RC2
Edited 2026-01-07 18:50 by matherp
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5602
Posted: 08:44am 07 Jan 2026
Copy link to clipboard 
Print this post

There is still a long list of programs to test, but the (historically) most critical ones have been tested, and run. Appart from Lazer-Cycle I have not seen problems (except for Kong, but Kong was not in release state yet. It is in a development phase. And it challenged heap (I recall).

I could focus on Kong first (maybe it points to something ?), and then follow up on the other programs. One that I need to re-create a test setup for is the thermal camera (the parts are scattered around my lab). I know that one pushed the boundaries of heap also (so many arrays with floating point variables, and MATH) but was final, on both Game*Mite as well as VGA.

Volhout
Edited 2026-01-07 18:46 by Volhout
PicomiteVGA PETSCII ROBOTS
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10838
Posted: 08:51am 07 Jan 2026
Copy link to clipboard 
Print this post

See previous updated post. Note: my fix is still recommended as erroring and recovering is very inefficient
Edited 2026-01-07 19:25 by matherp
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10838
Posted: 09:29am 07 Jan 2026
Copy link to clipboard 
Print this post

V6.02.00RC2

PicoMiteV6.02.00RC2.zip

Fixes bug introduced the cause an error when the DIM command used a function in the dimension calculatation
Fixes bug that can occur when ON ERROR IGNORE is used in a program also running MMbasic interrupts

For info, this was the bug fix process for ON ERROR IGNORE. The DIM bug was much harder.

ME:
“The implementation of structures has somehow affected the operation of ON ERROR IGNORE. This subroutine should never give an error when driver$ is invalid but it does at undefined intervals which suggests some something in structure parsing is changing the status of OptionErrorSkip from -1. Investigate this and fix it. Note the names with . characters are not structures just normal variables but this doesn't seem to be the issue.
Function ctrl.poll_single%(driver$, mask%)
On Error Ignore
Call driver$, ctrl.OPEN
If MM.Errno = 0 Then
 Local key%, t% = Timer + 5
 Do
  Call driver$, key%
  ctrl.poll_single% = key% And mask%
  If ctrl.poll_single% Then
   Do While key% : Pause 5 : Call driver$, key% : Loop
   Exit Do
  EndIf
 Loop While Timer < t%
 Call driver$, ctrl.SOFT_CLOSE
EndIf
On Error Abort
End Function



CLAUDE OPUS 4.5:
Now let me look at structure-related code that might be affecting OptionErrorSkip. Let me search for structure parsing:
Let me look at the findvar function to understand how variables with dots are handled:
Now let me look at how struct member access is resolved, particularly in the FindStructBase and ResolveStructMember functions, and see if they may be modifying OptionErrorSkip:
Now, let me search for ResolveStructMember:
These functions don't seem to modify OptionErrorSkip. Let me look at the call flow when a variable with a dot is accessed, and check specifically where evaluate() is called:
Now, let me look specifically for the code where errors are thrown or handled in evaluate():
MMBasic.c
I see! At line 433-434, after each command, OptionErrorSkip is decremented if it's in the range 0 < OptionErrorSkip < 100000. When ON ERROR IGNORE is set, it sets OptionErrorSkip = -1 which should NOT be decremented.
Now let me search for places where OptionErrorSkip could be being modified in the structure-related code or elsewhere:
Now let me check MM_Misc.c at the lines that save and restore OptionErrorSkip:
This is interesting! At lines 8953-8957, when an interrupt occurs, the code saves OptionErrorSkip if it's > 0, otherwise sets SaveOptionErrorSkip = 0, and then sets OptionErrorSkip = 0.
The bug is here: When OptionErrorSkip = -1 (ON ERROR IGNORE), the condition if (OptionErrorSkip > 0) is false, so SaveOptionErrorSkip is set to 0. Then OptionErrorSkip is set to 0, losing the IGNORE state.
When returning from the interrupt (line 1896-1897), because SaveOptionErrorSkip was saved as 0, the restore code doesn't do anything, and OptionErrorSkip remains 0.
Let me verify this is the issue by looking at the full context:
MM_Misc.c
Now I understand the bug:
1. When ON ERROR IGNORE is set, OptionErrorSkip = -1
2. When an interrupt occurs, the save code checks if (OptionErrorSkip > 0) which is false for -1
3. So SaveOptionErrorSkip is set to 0
4. Then OptionErrorSkip is set to 0
5. When returning from interrupt, if (SaveOptionErrorSkip > 0) is false (it's 0), so OptionErrorSkip is not restored
6. Result: OptionErrorSkip is now 0 instead of -1, meaning ON ERROR IGNORE is lost
The fix should handle the case where OptionErrorSkip is negative (IGNORE mode). I need to change both the save and restore logic:
Edited
MM_Misc.c+3-1
Let me verify the changes look correct:
The fix is complete. Here's a summary of the bug and fix:
Bug Analysis
The issue was in the interrupt handling code in MM_Misc.c. When ON ERROR IGNORE is active, OptionErrorSkip is set to -1.
The problem:
1. When an interrupt fires (line 8953), the code saved OptionErrorSkip only if > 0, missing the -1 (IGNORE) case
2. For IGNORE mode, SaveOptionErrorSkip was set to 0
3. OptionErrorSkip was then reset to 0 during interrupt processing
4. On IRETURN (line 1896), the restore code only handled > 0, so IGNORE mode was never restored
Result: Any interrupt occurring while ON ERROR IGNORE was active would silently disable error suppression, causing sporadic unexpected errors.
Fix Applied
1. Save logic (line 8955): Changed if (OptionErrorSkip > 0) to if (OptionErrorSkip != 0) to also save negative values like -1
2. Restore logic (line 1898-1899): Added else if (SaveOptionErrorSkip < 0) branch to restore ON ERROR IGNORE state
Edited 2026-01-07 20:05 by matherp
 
bfwolf
Senior Member

Joined: 03/01/2025
Location: Germany
Posts: 131
Posted: 10:49am 07 Jan 2026
Copy link to clipboard 
Print this post

  toml_12953 said  
  lizby said  Suppose I have 500 records of the following struct in a file:
Type JobRec
 LinkID     As INTEGER
 LastName   As STRING LENGTH 15
 Age        As INTEGER
 Occupation As STRING LENGTH 20
 Salary     As INTEGER
End Type
DIM job as JobRec


I figure the length is 8+(15+1)+8+(20+1)+8 is 61. [Wrong, see below] How do I read, say, the 100th record in the file into job with random access?



You don't have to calculate manually, you can find the length of your structure by:

Struct(SIZEOF "JobRec")


In your case, this is 64 bytes.


I would recommend always using the `Struct(SIZEOF "typeName")` function, as this also takes any necessary padding bytes into account. Peter has written quite a bit about this in the Struct manual: The reason is alignment.

Calculating the lengths manually is very tedious and prone to errors!

If you really need to know them, add a `Print Struct(SIZEOF "typeName")` statement to your program, which you can then comment out when no longer needed.
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5602
Posted: 11:56am 07 Jan 2026
Copy link to clipboard 
Print this post

@Peter,

is this real ? Claude fixed the bug ?
Nice !!!

Volhout
PicomiteVGA PETSCII ROBOTS
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10838
Posted: 12:10pm 07 Jan 2026
Copy link to clipboard 
Print this post

  Quote  is this real ?

Absolutely. and this is the dialog for the DIM bug. Note how I stopped it processing and steered it to find the solution.
ME:
User: The parsing of structure in cmd_dim has broken something. Analyse and fix
numpoll%=22
> Dim CTRLS_TO_POLL$(Max(1, num_poll% - 1))
Error : Unexpected text: )
CLAUDE:
GitHub Copilot: Searched for text `void cmd_dim`, 1 result

Searched for regex `cmd_dim\(void\)\s*\{`, no results

Searched for text `cmd_dim(void)`, 2 results

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4721-4721), lines 4721 to 4900

Let me continue reading the function to understand the structure parsing:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4900-4900), lines 4900 to 5100

Now I need to look at the `getcsargs` function as it seems to be the one parsing the arguments:

Searched for text `getcsargs`, 20 results

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.h#225-225), lines 225 to 260

Now let me look at the `MakeCommaSeparatedArgs` function which is used for RP2350:

Searched for text `MakeCommaSeparatedArgs`, 20 results

Searched for text `void MakeCommaSeparatedArgs`, 1 result

Searched for regex `MakeCommaSeparatedArgs\(unsigned char`, 1 result

Let me search for the implementation:

Searched for text `MakeCommaSeparatedArgs(unsigned char **tp`, 1 result

Searched for files matching `**/MMBasic.c`, 1 match

Searched for text `MakeCommaSeparatedArgs` (`**/MMBasic.c`), 1 result

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4211-4211), lines 4211 to 4400

Now I see the issue. The `MakeCommaSeparatedArgs` function should be properly handling nested parentheses. Let me check the `getclosebracket` function to understand how brackets are matched:

Searched for text `getclosebracket`, 20 results

Searched for regex `getclosebracket\(` (`**/MMBasic.c`), 3 results

Let me find the getclosebracket function implementation:

Searched for regex `unsigned char \*getclosebracket`, 1 result

User: This was working functionality before the implementation of structures. The bug must be in the structure parsing in the DIM command

GitHub Copilot: Let me look more closely at the `cmd_dim` function to find the structure-related parsing that was added:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4721-4721), lines 4721 to 4820

Now let me look at the `skipvar` function and `CheckIfTypeSpecified` to understand the flow:

Searched for text `CheckIfTypeSpecified`, 20 results

Searched for text `unsigned char *CheckIfTypeSpecified` (`**/MMBasic.c`), no results

Searched for regex `CheckIfTypeSpecified\(unsigned`, 2 results

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4634-4634), lines 4634 to 4720

Now I see the issue! The `CheckIfTypeSpecified` function is checking for structure types before falling back to the default type. When parsing `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`, it's checking if `Max` is a structure type name. Since `Max` is not a structure type, it should work... but let me trace through more carefully.

The issue is in the `CheckIfTypeSpecified` function. It's being called on the cmdline which includes the variable name and dimensions. Let me look at what's happening:

1. `getcsargs` is called to split up the arguments
2. `argv[i]` is `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`

The problem might be in how `MakeCommaSeparatedArgs` handles this - it might be splitting on the comma inside the `Max()` function!

Let me check `MakeCommaSeparatedArgs` more closely:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4211-4211), lines 4211 to 4310

I see the issue now! In `MakeCommaSeparatedArgs`, when checking for opening bracket or function token:

```c
if (*tp == '(' || (tokentype(*tp) & T_FUN))
```

It calls `getclosebracket(tp)` to find the matching closing bracket. But if `tp` points to a function token (like `Max`), `getclosebracket` expects to start at `(`, not at the function token itself.

Let me find the `getclosebracket` implementation:

Searched for regex `unsigned char \*.*getclosebracket` (`**/*.c`), no results

Searched for text `getclosebracket(unsigned char *p)`, 1 result

Searched for regex `not_in_flash.*getclosebracket`, 1 result

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#5747-5747), lines 5747 to 5810

Now I see the problem! In `getclosebracket`, it starts counting from `i = 0` and counts `(` or function tokens as `i++`. But if you call it when `*p` is a function token (like `Max`), it will:
1. Start with `i = 0`
2. See a function token, increment `i` to 1
3. Move to the next character (the `(`)
4. See `(`, increment `i` to 2
5. Continue until it finds TWO `)` characters

This means when `getclosebracket` is called on a function token, it incorrectly counts the function token itself as an opening bracket and then ALSO counts the actual `(` - so it ends up expecting two closing brackets instead of one!

The issue is that the `getclosebracket` function was modified to handle function tokens, but this breaks when called from `MakeCommaSeparatedArgs` which detects function tokens AND opening brackets.

Let me check the old behavior - let's look at the `makeargs` function (non-rp2350) to see how it handles this:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4400-4400), lines 4400 to 4550

Both `makeargs` and `MakeCommaSeparatedArgs` use the same pattern and call `getclosebracket`. The bug is in `getclosebracket` - when it's called on a pointer that points to a function token (like `Max`), it counts that token as an opening bracket. But then the actual `(` that follows is ALSO counted as an opening bracket.

The fix should be in `getclosebracket`. When it encounters a function token, it shouldn't count it as an opening bracket because the `(` that follows will be counted separately.

Let me look at what version of `getclosebracket` was there before. The problem is likely that someone added `|| (tokentype(*p) & T_FUN)` to getclosebracket to handle function tokens, but this creates double-counting.

The fix should be: when `getclosebracket` sees a function token, it should just advance past it without incrementing the counter, since the `(` that follows will be counted.

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#5745-5745), lines 5745 to 5790

Now I understand the bug completely. When `getclosebracket` is called on a function token like `Max`:

1. `i = 0`
2. It sees `Max` token (T_FUN), increments `i` to 1, advances `p`
3. It sees `(`, increments `i` to 2, advances `p`
4. It sees `1`, just advances
5. It sees `,`, just advances
6. It sees `num_poll%`, just advances
7. It sees `-`, just advances
8. It sees `1`, just advances
9. It sees `)`, decrements `i` to 1, advances `p`
10. It keeps going looking for ANOTHER `)` since `i` is still 1!

The fix is that function tokens should NOT be counted - only the `(` should be counted. Function tokens are followed by `(` which will be counted.

Using "Replace String in File"


```

```
The fix separates the handling of function tokens from bracket counting. Now:
- Function tokens (`T_FUN`) are simply skipped (advance past without counting)
- Only actual `(` characters increment the bracket counter
- Only actual `)` characters decrement the bracket counter

This prevents the double-counting issue where both the function token AND its following `(` were being counted as opening brackets.

Made changes.
ME:
User: This is the implementation of getclosebracket before structures were implemented. Check that your change will operate exactly the same as the original code in the error example just posted
// scans text looking for the matching closing bracket
// it will handle nested strings, brackets and functions
// it expects to be called pointing at the opening bracket or a function token
unsigned char __not_in_flash_func (*getclosebracket)(unsigned char *p)
{
   int i = 0;

   do
   {
       if (*p == 0)
           error("Expected closing bracket");

       // Handle quoted strings - skip them entirely
       if (*p == '\"')
       {
           p++;
           while (*p != '\"')
           {
               if (*p == 0)
                   error("Expected closing bracket");
               p++;
           }
           p++;
           continue;
       }

       // Check for closing bracket
       if (*p == ')')
           i--;
       // Check for opening bracket or function token
       else if (*p == '(' || (tokentype(*p) & T_FUN))
           i++;

       p++;
   } while (i);

   return p - 1;
}
// check that there is no excess text following an element
// will skip spaces and abort if a zero char is not found

CLAUDE:
GitHub Copilot: Let me trace through the original code for `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:

In MMBasic, function tokens (T_FUN) include their opening parenthesis as part of the token. So `Max(` is a single token. This means:

**Original getclosebracket starting at the first `(` (array dimension):**
- `i=0`, sees `(`, `i=1`, advance
- sees `Max(` T_FUN token (includes the `(`), `i=2`, advance
- sees `1`, advance
- sees `,`, advance
- sees `num_poll%`, advance
- sees `-`, advance  
- sees `1`, advance
- sees `)` (closes Max), `i=1`, advance
- sees `)` (closes array dim), `i=0`, advance
- Loop exits correctly!

**My modified code:**
- `i=0`, sees `(`, `i=1`, advance
- sees `Max(` T_FUN token, **skips without counting**, advance
- sees `1`, advance
- ...
- sees `)` (closes Max), `i=0`, advance ← **exits too early!**

My fix was wrong! Since T_FUN tokens include the opening parenthesis, they SHOULD be counted. Let me revert to the original:

Using "Replace String in File"


```

```
Good, I've reverted `getclosebracket` to the original. The bug must be elsewhere in the structure-related changes to `cmd_dim`. Let me look more carefully at `CheckIfTypeSpecified` since it's called early in `cmd_dim`:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4634-4634), lines 4634 to 4685

Now let me look at `FindStructType`:

Searched for text `int FindStructType`, 2 results

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#6330-6330), lines 6330 to 6370

Now I see the issue! In `CheckIfTypeSpecified`, when `STRUCTENABLED` is defined, it calls `FindStructType(p)` for ANY input that doesn't match INTEGER/STRING/FLOAT. For the DIM command parsing `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:

1. `CheckIfTypeSpecified` is called with `p` pointing to `CTRLS_TO_POLL$...`
2. It checks for INTEGER, STRING, FLOAT - no match
3. It calls `FindStructType(p)` which tries to match `CTRLS_TO_POLL` against structure names
4. It returns -1 (not found)
5. Since `AllowDefaultType` is true, it sets `tp = p` and `*type = DefaultType`

That seems fine. Let me trace through `cmd_dim` more carefully:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4721-4721), lines 4721 to 4760

Wait - after `getcsargs`, the code loops through `argv[]` with `i += 2`, expecting odd `argc`. This is correct for comma-separated args where each variable and comma are separate entries.

Let me check what `skipvar` does after `argv[i]`:

Searched for text `unsigned char *skipvar`, 1 result

Searched for regex `not_in_flash.*skipvar`, 1 result

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#5533-5533), lines 5533 to 5630

I see `skipvar` now handles struct member access with the `STRUCTENABLED` code. Let me continue reading:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#5620-5620), lines 5620 to 5700

Wait, I see an important detail. In `skipvar`, when it handles array indices, it uses:

```c
if (*p == '(' || (tokentype(*p) & T_FUN))
   i++;
```

This is the same bracket-counting logic. The problem might be in the struct member parsing section. Let me check the flow again for `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:

Actually wait - the error message is "Unexpected text: )". This error comes from `checkend`. Let me trace where this is called in cmd_dim:

Searched for text `checkend` (`**/Commands.c`), 15 results

There's no `checkend` in cmd_dim. Let me search in other files:

Searched for text `Unexpected text`, 4 results

The "Unexpected text: $" message (which shows the actual text) is from `checkend` or another similar function. Let me look at the findvar code path since that's where the variable is actually created:

Searched for text `void *findvar`, 1 result

Searched for regex `void \*.*findvar\(unsigned char`, 1 result

Searched for regex `not_in_flash.*\*findvar`, 1 result

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#3438-3438), lines 3438 to 3600

Let me see if there's code that might be calling `checkend` after `findvar`:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4000-4000), lines 4000 to 4100

I found it! Look at line 4051-4052:

```c
else if (!(*p == ',' || *p == 0 || tokenfunction(*p) == op_equal || tokenfunction(*p) == op_invalid))
   error("Unexpected text: $", p);
```

This code is looking after the LENGTH keyword parsing (or lack thereof) for string arrays. It's checking if there's unexpected text. The issue is that after processing the array dimensions, the pointer `p` should be pointing at the end, but something is going wrong.

Let me look at the code just above this:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4025-4025), lines 4025 to 4060

Found it! This is the bug. Look at lines 4038-4046:

```c
do
{
   if (*p == '(')
       i++;
   if (tokentype(*p) & T_FUN)
       i++;
   if (*p == ')')
       i--;
   p++;
} while (i);
```

This code is trying to skip over the array dimensions for string arrays. It uses the same pattern of counting `(` and T_FUN as openers and `)` as closers. But here's the problem:

For `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:
1. `*p == '('` → true, `i = 1`
2. advance to `Max` token, `tokentype(*p) & T_FUN` → true, `i = 2`
3. This `Max` token IS the `Max(` combined, so advance puts us at `1`
4. `1` → neither condition, advance
5. `,` → neither, advance
6. ... etc ...
7. First `)` → `i = 1`
8. Second `)` → `i = 0`
9. Loop exits

Wait, but this seems correct? Actually wait - step 3 is wrong. After processing the T_FUN token `Max(`, we advance and the next char is `(` from the tokenized `Max(`... no wait, T_FUN tokens include the `(`.

Let me think again. The issue is that when we see the `(` from the array dimension, we increment `i`. Then we see the `Max(` function token - but this token ALREADY includes the opening paren. So we're double-counting the function's bracket.

Actually no - this code first checks `*p == '('`, then checks `tokentype(*p) & T_FUN`. So:
1. `(` - array dimension open, `i=1`
2. `Max(` token - NOT `(` (it's a function token), but IS T_FUN, so `i=2`

The function token `Max(` already contains the `(`, so the counter is now at 2 when it should only be at 2 (1 for array, 1 for Max's paren).

Wait, that's actually correct then... Let me trace through more carefully what the stream looks like after tokenization.

Actually the problem is that when the loop finishes, `p` should point AFTER the closing `)`. But wait - there are TWO closing parens: one for `Max()` and one for the array dimension. Let me trace:

For `(Max(1, num_poll% - 1))` (the array dimension part including both parens):

If T_FUN includes the `(`:
1. See `(` → i=1, advance
2. See `Max(` token (T_FUN) → i=2, advance  
3. See `1` → neither, advance
4. See `,` → neither, advance
5. See stuff → advance
6. See `)` (closes Max) → i=1, advance
7. See `)` (closes array) → i=0, advance
8. Loop exits

This seems correct! But wait - step 2 advances past the ENTIRE `Max(` token. After advancing from a T_FUN token, where are we? At `1`. So we never see a second `(` at all.

Hmm, let me check what happens in `getcsargs`/`MakeCommaSeparatedArgs`. Perhaps the problem is there. When `MakeCommaSeparatedArgs` processes `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:

1. Copies `CTRLS_TO_POLL$`
2. Sees `(`, calls `getclosebracket(tp)` starting at `(`
3. In getclosebracket:
  - See `(` → i=1
  - See `Max(` T_FUN → i=2
  - See `1` → nothing
  - See `,` → nothing ← WAIT! This comma is inside the parens!
  - ...
  - See `)` → i=1
  - See `)` → i=0
  - Return
4. Back in MakeCommaSeparatedArgs, copy everything from `(` to last `)` inclusive

Actually I wonder if the issue is that after `getcsargs` splits the line, the `argv[i]` string is being re-parsed incorrectly.

Let me check what `getcsargs` gives for `Dim CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:

After tokenization and `getcsargs`, we should have:
- `argc = 1`
- `argv[0] = "CTRLS_TO_POLL$(Max(1, num_poll% - 1))"`

Since there's only one variable (no comma outside parens), argc should be 1.

Then in cmd_dim:
```c
p = skipvar(argv[i], false); // point to after the variable
```

After `skipvar`, `p` should point to the null terminator (since the entire string is the variable).

Actually wait - when `argv[i]` is processed by `getcsargs`, is the closing `)` included in the copied argument?

Let me re-read `MakeCommaSeparatedArgs`:

```c
if (*tp == '(' || (tokentype(*tp) & T_FUN))
{
   int x;
   x = (getclosebracket(tp) - tp) + 1;
   memcpy(op, tp, x);
   op += x;
   tp += x;
   continue;
}
```

So when it sees `(`, it finds the close bracket and copies EVERYTHING including the close bracket. Then it continues to the next character.

After the `)`, the next character should be null (end of line). So the entire expression should be in argv[0].

Let me check if the problem is in how `findvar` handles the expression. In `findvar`, there's a call to `getargs`:

```c
getargs(&p, MAXDIM * 2, (unsigned char *)"(,");
```

This parses the dimensions. For `(Max(1, num_poll% - 1))`:
- The `(,` delimiters mean: expect opening `(`, split on `,`
- It should find `Max(1, num_poll% - 1)` as the dimension expression

Wait, but `getargs` is different from `getcsargs`. Let me check `getargs`:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4430-4430), lines 4430 to 4500

OK so `getargs` (makeargs) also uses `getclosebracket`. Let me focus on the actual error location. The error "Unexpected text: )" comes from findvar at line 4051-4052.

Let me look at that code path again. In findvar, after parsing the array dimensions with `getargs`, the code then checks for the LENGTH keyword for string arrays. The loop at lines 4038-4046 is trying to skip the parentheses:

```c
if (*p == '(')
{
   do
   {
       if (*p == '(')
           i++;
       if (tokentype(*p) & T_FUN)
           i++;
       if (*p == ')')
           i--;
       p++;
   } while (i);
}
```

But wait - at this point, `getargs` has ALREADY consumed the `(...)` and `p` should be pointing AFTER the closing paren. Let me check...

Actually no - `getargs` doesn't modify the original pointer in the same way. Let me re-read how `getargs` is called in findvar:

```c
getargs(&p, MAXDIM * 2, (unsigned char *)"(,");
```

The `&p` means `p` will be updated by getargs. Looking at the makeargs function... it uses a local `tp` and doesn't modify `*p` directly at the end. Actually let me check the macro:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.h#210-210), lines 210 to 230

The `getargs` macro calls `makeargs(x, ...)` where `x` is a pointer to pointer. Looking at the makeargs signature:

```c
void MIPS16 __not_in_flash_func(makeargs)(unsigned char **p, int maxargs, unsigned char *argbuf, unsigned char *argv[], int *argc, unsigned char *delim)
```

The first param is `unsigned char **p` - a pointer to a pointer. So yes, `makeargs` could potentially update `*p`. But looking at the function body, it uses `tp = *p` and doesn't seem to update `*p` at the end.

Let me search for where `*p` is updated in makeargs:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4310-4310), lines 4310 to 4400

I need to find where `*p` is updated at the end of makeargs:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4455-4455), lines 4455 to 4480

I notice that `makeargs` does NOT update `*p` at the end! It uses a local `tp` but never writes back to `*p`. So after `getargs`, `p` in `findvar` still points to the opening `(` of the array dimensions.

Now I understand the issue better. After the `getargs` call, `p` still points at `(`. Then the code at line 4037-4046 tries to skip over the parentheses by doing its own counting loop.

For `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`, after `skipvar` has parsed up to the `$`, and after `getargs` has parsed the dimensions, `p` points to `(Max(1, num_poll% - 1))`.

Now the loop:
```c
do
{
   if (*p == '(')    // First: yes, i=1
       i++;
   if (tokentype(*p) & T_FUN)  // First: no (it's '(')
       i++;
   if (*p == ')')    // First: no
       i--;
   p++;              // Now p points to Max token
} while (i);          // i=1, continue

// Second iteration:
// p points to Max token
if (*p == '(')    // No, it's a token not '('
   i++;
if (tokentype(*p) & T_FUN)  // YES! Max is T_FUN, i=2
   i++;
if (*p == ')')    // No
   i--;
p++;              // p advances ONE byte (past the token byte), NOT to after the '(' that's part of the token

// This is the bug! After a T_FUN token, we only advance 1 byte but the token includes '('
```

The problem is that when we see a T_FUN token, we increment `i` (correctly treating it as containing an opening paren), but then we only advance `p` by 1. But function tokens are multi-byte tokens! After a function token comes its opening `(` which should NOT be counted again (since T_FUN already counts it).

Wait, let me reconsider. In the tokenized form, is `Max(` a single byte token, or is it the token byte followed by the `(` character?

Looking at how `getclosebracket` works and the original code working - it seems like the tokenizer converts `Max(` into a single token byte, and that's why counting T_FUN as one opener works.

But in the loop at line 4037-4046, after we process that token byte and increment i, we do `p++` which moves to the NEXT byte. If the next byte happens to be `(` (stored after the token), we'd count it again!

Let me check what happens. If `Max(` is tokenized as:
- Option A: Single byte token that represents `Max(`
- Option B: Token byte for `Max` followed by literal `(` character

I think Option A is correct based on how `getclosebracket` works. But then in the loop, after seeing the token byte and doing `p++`, we'd be at the first argument `1`, not at `(`. So the bug might not be here.

Actually, wait - the problem might be simpler. Let me check if this loop was even modified for structures:

Searched for regex `if \*p == '\(' copy everything|skip over the dimension values`, 1 result

Let me check git history or any recent changes. Actually, since the user said this was working before structures were implemented, let me look for any code in findvar related to structures that might affect `p`:

Searched for text `STRUCTENABLED` (`**/MMBasic.c`), 20 results

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#3515-3515), lines 3515 to 3580

I see there's struct member checking code in findvar at line 3515-3558. This code looks for a `.` in the variable name. Let me see if this might be affecting the flow incorrectly.

But for `CTRLS_TO_POLL$`, there's no `.` in the name, so this shouldn't trigger. The name extracted would be `CTRLS_TO_POLL` (without the `$` which is handled separately).

Let me think about this differently. The user said "The parsing of structure in cmd_dim has broken something". Let me look more closely at the structure parsing in cmd_dim - specifically at `CheckIfTypeSpecified`:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4634-4634), lines 4634 to 4685

Wait! I see the issue now!

In `CheckIfTypeSpecified`, when STRUCTENABLED is defined:
1. It first checks for INTEGER, STRING, FLOAT - these use `checkstring` which returns a pointer PAST the keyword
2. If none match, it calls `FindStructType(p)`
3. If that returns -1 (not a struct), it sets `tp = p` and returns `tp`

But in the non-STRUCTENABLED version:
```c
else
{
   if (!AllowDefaultType)
       error("Variable type");
   tp = p;
   *type = DefaultType;
}
```

It just sets `tp = p`. Same thing. So that's not the problem.

Let me trace through cmd_dim again:

```c
p = CheckIfTypeSpecified(cmdline, &type, true); // check for DIM FLOAT A, B, ...
ImpliedType = type;
{
   getcsargs(&p, (MAX_ARG_COUNT * 2) - 1);
```

So `p` is set to the return value of `CheckIfTypeSpecified`. For `Dim CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:
- `cmdline` points to `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`
- `CheckIfTypeSpecified` checks if this starts with INTEGER/STRING/FLOAT - NO
- With STRUCTENABLED, it calls `FindStructType(p)` which looks at `CTRLS_TO_POLL` and returns -1 (not a struct)
- It sets `tp = p`, returns `p` (unchanged)

So `p` still points to `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`.

Then `getcsargs(&p, ...)` is called. Let me check what happens:

Continue to iterate?
[object Object]

Made changes.

User: @agent Continue: "Continue to iterate?"

GitHub Copilot: Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.h#566-566), lines 566 to 566

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.h#225-225), lines 225 to 250

I see! For RP2350, `getcsargs` calls `MakeCommaSeparatedArgs`. For non-RP2350, it calls `makeargs` with `","` as delimiter.

Let me re-examine `MakeCommaSeparatedArgs` more carefully:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4211-4211), lines 4211 to 4315

Now I see the issue clearly. Look at line 4279-4287:

```c
// if an opening bracket '(' copy everything until we hit the matching closing bracket
// this includes special characters such as , and keeps track of any nested brackets
if (*tp == '(' || (tokentype(*tp) & T_FUN))
{
   int x;
   x = (getclosebracket(tp) - tp) + 1;
   memcpy(op, tp, x);
   op += x;
   tp += x;
   continue;
}
```

When processing `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:
1. First, it copies `CTRLS_TO_POLL$` character by character via the "anything else" case
2. Then it encounters `(` and calls `getclosebracket(tp)` where tp points to `(`

Now in `getclosebracket`, starting at `(`:
- `i=0`, see `(` → `i=1`
- see `Max(` token (T_FUN) → `i=2`
- see `1` → nothing
- see `,` → nothing
- see stuff → nothing
- see `)` (Max's close) → `i=1`
- see `)` (array's close) → `i=0`
- return pointer to last `)`

This should be correct! getclosebracket returns pointer to the SECOND `)`, which is the array's close paren. Then:
- `x = (pointer to second ')') - (pointer to '(') + 1` = length including both parens
- Copy all that
- Advance both op and tp past the whole thing

So `MakeCommaSeparatedArgs` should produce `argv[0] = "CTRLS_TO_POLL$(Max(1, num_poll% - 1))"`.

Now, back in `cmd_dim`, after `getcsargs`:
```c
p = skipvar(argv[i], false); // point to after the variable
```

`skipvar` should parse `CTRLS_TO_POLL$(Max(1, num_poll% - 1))` and return a pointer to the null terminator (end of the string).

But wait - let me look at skipvar again. The struct-related code might be the problem:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#5595-5595), lines 5595 to 5665

In the STRUCTENABLED section of `skipvar`, after processing the base variable and its array indices, it looks for `.` to handle member access. For `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`, there's no `.`, so this loop should just break immediately and return `p`.

Let me check the earlier part of skipvar that handles the array indices:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#5560-5560), lines 5560 to 5600

OK so `skipvar` handles array indices correctly using the same pattern. After this, `p` should point past the closing `)`.

For `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`:
1. Parse name: `CTRLS_TO_POLL`
2. Parse suffix: `$`
3. See `(`, enter array handling
4. Start with `i=1`, advance past `(`
5. See `Max(` token (T_FUN), `i=2`
6. See `1`, nothing
7. See `,`, nothing
8. See stuff, nothing  
9. See `)`, `i=1`
10. See `)`, `i=0`, break
11. `p++` to step over closing bracket
12. Return `p` pointing past last `)`

So skipvar returns pointing at the null terminator (end of argv[0]).

Let me continue tracing cmd_dim:

```c
p = skipvar(argv[i], false); // point to after the variable
while (!(*p == 0 || *p == tokenAS || *p == (unsigned char)'\'' || *p == tokenEQUAL))
   p++; // skip over a LENGTH keyword if there and see if we can find "AS"
```

After skipvar, `p` points to `\0` (end of string). The while loop condition checks if `*p == 0` - YES, so the loop doesn't execute.

```c
chSave = *p;  // chSave = 0
chPosit = p;
*p = 0; // no change, already 0
```

Then it checks `if (chSave == tokenAS)` - NO (chSave is 0).

```c
strcpy((char *)VarName, (char *)argv[i]);  // VarName = "CTRLS_TO_POLL$(Max(1, num_poll% - 1))"
v = findvar(VarName, type | V_NOFIND_NULL); // check if the variable exists
```

Now `findvar` is called with `VarName = "CTRLS_TO_POLL$(Max(1, num_poll% - 1))"`.

In findvar, it parses the name, sees `$` suffix (string type), then sees `(` and calls `getargs` to parse dimensions.

`getargs` is called with delim `"(,"`. Let me trace through what happens:

The string to parse is `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`. But wait - in findvar, `p` has already advanced past the name and suffix. Let me check:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#3460-3460), lines 3460 to 3510

So after parsing the name `CTRLS_TO_POLL` and the `$` suffix, `p` points to `(Max(1, num_poll% - 1))`.

Then the struct member check happens (but no `.` in name, so it falls through).

Then at line ~3560:
```c
if (*p == '(')
{
   ...
   getargs(&p, MAXDIM * 2, (unsigned char *)"(,");
```

`getargs` with delim `"(,"` expects the list to start with `(` (because first char of delim is `(`). It will:
1. See `(`, expect closing bracket, advance past it
2. Split on `,`

So for `(Max(1, num_poll% - 1))`:
- Skip opening `(`
- Now at `Max(1, num_poll% - 1))`
- Process until we hit `)` or `,`
- See `Max(` token (T_FUN), copy until closing bracket via getclosebracket

Wait, let me check makeargs again for how it handles this:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4360-4360), lines 4360 to 4445

Now I see it. At line 4432-4439:

```c
if (*tp == '(' || ((tokentype(*tp) & T_FUN) && !expect_cmd))
{
   int x;
   x = (getclosebracket(tp) - tp) + 1;
   memcpy(op, tp, x);
   op += x;
   tp += x;
   continue;
}
```

When processing `Max(1, num_poll% - 1))` (after the outer `(` was skipped):
- `*tp` is the `Max(` token (T_FUN)
- The condition `*tp == '(' || ((tokentype(*tp) & T_FUN) && !expect_cmd)` is TRUE
- `getclosebracket(tp)` is called starting at the `Max(` token

Now in `getclosebracket` starting at `Max(` token:
- `i=0`, see `Max(` T_FUN → `i=1`
- see `1` → nothing
- see `,` → nothing
- see stuff → nothing
- see `)` → `i=0`, return pointing at this `)`

So `getclosebracket` returns pointing at the FIRST `)` (Max's close). Then:
- `x = (pointer to first ')') - (pointer to Max) + 1`
- Copy `Max(1, num_poll% - 1)`
- Advance tp to point AFTER the first `)`

Now `tp` points to `)` (the SECOND one, the array's close)!

The main loop continues:
- `expect_bracket == true` and `*tp == ')'` → break!

So `makeargs` exits with just `Max(1, num_poll% - 1)` copied as the dimension.

But then `tp` points to `)`. The outer loop has broken. Looking at line 4464-4467:
```c
if (expect_bracket && *tp != ')')
   SyntaxError();
```

Wait, `*tp == ')'` so this doesn't error.

After makeargs returns, where does this leave things? The argv would contain the dimension expression `Max(1, num_poll% - 1)` properly. But `p` in findvar wasn't updated (makeargs doesn't update `*p`).

Actually wait - let me re-read. After the dimension is parsed, the code continues. Let me find where the error actually occurs:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4025-4025), lines 4025 to 4060

This is in `findvar` when `action & V_DIM_VAR` (which it is, from cmd_dim). For string arrays, it tries to skip the parentheses to look for LENGTH.

At this point, `p` still points to `(Max(1, num_poll% - 1))` (because getargs doesn't update `*p`).

The loop:
```c
do
{
   if (*p == '(')           // First: YES, i=1
       i++;
   if (tokentype(*p) & T_FUN)  // First: NO ('(' is not T_FUN)
       i++;
   if (*p == ')')           // First: NO
       i--;
   p++;                     // Now p points to Max( token
} while (i);                 // i=1, continue

// Second iteration:
if (*p == '(')               // NO (it's a token, not '(')
   i++;
if (tokentype(*p) & T_FUN)   // YES! Max( is T_FUN, i=2
   i++;
if (*p == ')')               // NO
   i--;
p++;                         // p moves 1 byte past the token byte
// i=2, continue

// Third iteration:
// p now points to '1' (first arg to Max)
if (*p == '(')               // NO
   i++;
if (tokentype(*p) & T_FUN)   // NO
   i++;
if (*p == ')')               // NO
   i--;
p++;                         // p moves to ','
// i=2, continue

// And so on until we hit ')':
// First ')' -> i=1
// Second ')' -> i=0, loop exits
```

After this loop, `p` points one past the second `)`, which should be `\0`.

Then:
```c
skipspace(p);
if ((s = checkstring(p, (unsigned char *)"LENGTH")) != NULL)
   size = getint(s, 1, MAXSTRLEN);
else if (!(*p == ',' || *p == 0 || tokenfunction(*p) == op_equal || tokenfunction(*p) == op_invalid))
   error("Unexpected text: $", p);
```

If `p` points to `\0`, then `*p == 0` is true, so the condition `!(*p == ',' || *p == 0 || ...)` is false, and no error is thrown.

So this path seems OK...

Let me think about what else could be wrong. The error message is "Unexpected text: )". This means `p` points to `)` when that check happens.

What if the issue is in how the T_FUN token is handled in the skip loop? Let me think about what a tokenized function looks like.

Actually, I wonder if the issue is that T_FUN tokens are multi-byte. When we do `p++` after seeing a T_FUN token, we only advance ONE byte, but the token might be longer!

Let me check how tokens are structured. Function tokens might be: token_byte followed by function index, then `(`. Or they might be single-byte tokens where the `(` is implicit.

Searched for text `T_FUN`, 30 results

Let me look at the tokenizer to understand how function tokens are structured:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#2260-2260), lines 2260 to 2300

I see! Look at lines 2271-2280. When processing a T_FUN token:
1. `p1 = p + 1` - p1 points after the token byte
2. `p = getclosebracket(p)` - this finds the closing bracket starting from the token
3.
I stop the analysis at this point
ME:

User: You are going down a rabbit hole. This code worked before structures were implmented. The RP2040 uses getargs and the rp2350 uses getcsargs. Both show exactly the same issue. The problem may be in findvar where the possibility of a structure is being checked and it is consuming characters that it shouldn't if one isn't found
CLAUDE:
GitHub Copilot: Let me look at the structure checking code in findvar:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#3515-3515), lines 3515 to 3565

This code looks at `name` for a `.`, not `p`. Let me look for where `p` might be incorrectly modified. Let me search for other STRUCTENABLED sections in findvar:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#3450-3450), lines 3450 to 3520

I see - after extracting the name and type suffix, `p` points to whatever comes after. For `CTRLS_TO_POLL$(Max(...))`, after the name parsing loop and the `$` check, `p` points to `(Max(...)`.

Wait - let me look more carefully at the name parsing. The loop is:
```c
do
{
   u = mytoupper(*p++);
   ...
} while (isnamechar(*p));
```

So it copies chars while they're name chars. For `CTRLS_TO_POLL$`:
- Copies `C`, `T`, `R`, `L`, `S`, `_`, `T`, `O`, `_`, `P`, `O`, `L`, `L`
- Stops when `*p` is `$` (not a namechar)

Then `$` is checked and `p++` advances past it. So `p` points to `(`.

Now let me check if there's any STRUCTENABLED code that might be consuming the `(`:

Searched for text `g_StructArg` (`**/MMBasic.c`), 15 results

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4100-4100), lines 4100 to 4145

Let me look at the CheckIfTypeSpecified function in Commands.c again - this is called in cmd_dim before getcsargs:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4721-4721), lines 4721 to 4740

So `p = CheckIfTypeSpecified(cmdline, &type, true)` is called first. For `Dim CTRLS_TO_POLL$(Max(1, num_poll% - 1))`, let me trace:

- `cmdline` points to `CTRLS_TO_POLL$(Max(1, num_poll% - 1))`
- `CheckIfTypeSpecified` checks for INTEGER/STRING/FLOAT - no match
- With STRUCTENABLED, it calls `FindStructType(p)` where p = `CTRLS_TO_POLL$(Max...)`

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#6325-6325), lines 6325 to 6360

`FindStructType` just reads the name, it doesn't modify the input pointer. Let me look at `CheckIfTypeSpecified` again:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/Commands.c#4634-4634), lines 4634 to 4685

I see it now! In `CheckIfTypeSpecified`, when STRUCTENABLED:

1. It calls `FindStructType(p)` - this returns -1 (no struct named CTRLS_TO_POLL)
2. Since `structidx < 0`, it goes to the else branch:
  ```c
  else
  {
      if (!AllowDefaultType)
          error("Variable type");
      tp = p;
      *type = DefaultType;
  }
  ```
3. It sets `tp = p` and returns `tp`

This looks correct - it returns `p` unchanged. But wait - look at line 4652:
```c
skipspace(p); // Skip any leading whitespace before type name
```

This modifies `p`! If there's a space after DIM, this would skip it. But for `DIM CTRLS_TO_POLL$...`, after the space is skipped, p points to `CTRLS_TO_POLL$...` which is correct.

Hmm, let me think about this differently. The user said the problem is in findvar where structure checking consumes characters it shouldn't.

Let me look at the struct member access code more carefully in findvar - specifically where it modifies `p`:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#3515-3515), lines 3515 to 3560

This code looks for `.` in the `name` variable, not in `p`. For `CTRLS_TO_POLL`, there's no `.`, so `dot` is NULL and this block is skipped.

Wait - but what if the issue is elsewhere? Let me search for all places in findvar where `p` is modified in STRUCTENABLED blocks:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#4160-4160), lines 4160 to 4200

This code doesn't modify `p`. Let me look for any code path that might accidentally consume from `p` when it shouldn't.

Actually, let me search for where `p` is modified anywhere in the area of the findvar function after structure code was added:

Searched for regex `p\+\+|p\s*=\s*|p\s*\+=` (`**/MMBasic.c`), 100 results

This is too noisy. Let me focus on findvar specifically and look for where the structure parsing might consume the wrong data:

Read [](file:///d%3A/Dropbox/PicoMite/PicoMite/MMBasic.c#3560-3560), lines 3560 to 3750

Found it! Lines 3600-3614:

```c
           // After getargs, advance p past the (indices) - makeargs doesn't update *p
           // This is needed for struct array member access like points(4).x
           if (*p == '(')
           {
               unsigned char *ptemp = p + 1;
               int depth = 1;
               while (depth > 0 && *ptemp)
               {
                   if (*ptemp == '(')
                       depth++;
                   else if (*ptemp == ')')
                       depth--;
                   ptemp++;
               }
               p = ptemp; // Now points past the closing )
           }
```

This code was added for struct array member access, but it has a bug - it only counts literal `(` and `)` characters, not function tokens (T_FUN). So for `(Max(1, num_poll% - 1))`:

1. Starts at `(`
2. `ptemp` starts at `M` (the Max token)
3. Loop: `Max` token - not `(` or `)`, advance
4. Continue until it hits the first `)` (closing Max's paren)
5. `depth--` → `depth = 0`
6. Loop exits
7. `p` now points AFTER the first `)`, but before the second `)`!

Now `p` points to `)` (the array's closing paren), which later causes the "Unexpected text: )" error!

The fix is to account for T_FUN tokens in this loop, just like the other bracket-counting code does:

Using "Replace String in File"


```

```
The fix adds `|| (tokentype(*ptemp) & T_FUN)` to the condition that increments `depth`. This ensures that function tokens (which include their opening parenthesis as part of the token) are counted as opening brackets, matching the behavior of `getclosebracket` and other bracket-counting code throughout the codebase.

Made changes.
ME:
User: That seems to have fixed that problem .
Edited 2026-01-07 22:15 by matherp
 
mozzie
Senior Member

Joined: 15/06/2020
Location: Australia
Posts: 193
Posted: 01:18pm 07 Jan 2026
Copy link to clipboard 
Print this post

G'day Peter,
Might have found another minor bug to squash:

> option list
PicoMite MMBasic RP2350B V6.02.00RC2
OPTION FLASH SIZE 16777216
OPTION COLOURCODE ON
OPTION PICO OFF
OPTION CPUSPEED (KHz) 200000
OPTION PSRAM PIN GP0
> option reset list
Palm PicoGame*Mite
Pico-ResTouch-LCD-3.5
Pico-ResTouch-LCD-2.8
PICO BACKPACK
RP2040-LCD-1.28
RP2040LCD-0.96
RP2040-GEEK
>

This is the same with RP2350A, RP2040 seems ok but I haven't tested them all.

The onward march of "AI" is incredible, not sure if it is exilarating or terrifying....

Regards,
Lyle.
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10838
Posted: 01:39pm 07 Jan 2026
Copy link to clipboard 
Print this post

What is the bug?

UPDATE
OK I see it now
Edited 2026-01-08 00:01 by matherp
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5602
Posted: 02:46pm 07 Jan 2026
Copy link to clipboard 
Print this post

@Peter,

Kong runs fine with rc2, maybe it's issue was related to the fix you did.

Volhout
PicomiteVGA PETSCII ROBOTS
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10838
Posted: 02:55pm 07 Jan 2026
Copy link to clipboard 
Print this post

  Quote  Kong runs fine with rc2, maybe it's issue was related to the fix you did.

 
mozzie
Senior Member

Joined: 15/06/2020
Location: Australia
Posts: 193
Posted: 02:55pm 07 Jan 2026
Copy link to clipboard 
Print this post

G'day Peter,
Apologies, should have made that clearer.

Also, would you consider the following for a future version of PicoMite:

The ability to change the USB port on the Picomite from Serial device to Keyboard / Mouse protocol device? (or possibly gamepad)

This is in respone to a question from Bill (Turbo46) in another thread but has always been something I think would greatly enhance the usability, the ability to create custom keyboards etc with the power and ease of MMbasic to do the encoding / macros etc.

The idea of a 7" touchscreen keyboard that can be customised endlessly springs to mind, would also be great for people with disabilities etc.

The ability to then plug this into a PicoMite USB variant to control it as well opens many possibilities.

I think this was discussed early in the PicoMite story and has reappeared from time to time.

A further enhancement would be a serial port on the same channel, but I fear I'm getting ahead of myself....

As usual I have no real idea how difficult (or otherwise) this would be.

Regards,
Lyle.
 
     Page 3 of 6    
Print this page
The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026