Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Arrays inside Types?
#31
@justsomeguy
I changed 10 to 5
Code: (Select All)

_Title "simple_test"

$Console:Only
_Dest _Console
Option _Explicit

Type mytype
    m( 5) As Long
End Type

Dim x(5) As mytype
Dim As Long i, j
For j = 0 To 5
    For i = 0 To 5
        x(j).m(i) = i
    Next
Next
For j = 0 To 5
    For i = 0 To 5
        Print x(j).m(i)
    Next
Next
result
0
1
2
3
4
5
0
1
2
3
4
5
0
1
2
3
4
5
0
1
2
3
4
5
0
1
2
3
4
5
0
1
2
3
4
5

Petr, so far it seems to work Big Grin
Reply
#32
(03-09-2026, 08:45 PM)Jack Wrote: Petr
this simple test run OK
Code: (Select All)

_Title "simple_test"

$Console:Only
_Dest _Console
Option _Explicit

Type mytype
    m( 10) As Long
End Type

Dim x As mytype
Dim As Long i

For i = 0 To 10
    x.m(i) = i
Next

For i = 0 To 10
    Print x.m(i)
Next

I am going to modify my decfloat stuff which uses arrays in udts a lot, so I can determine the performance and stability of your QB64, the modifications will take quite a bit of time but as soon as it's done and run some tests I will post the results here

This sure dont work in v4.3.

So have you guys tried anything above 10 because remember QB64 doesn't need to Dim anything  10 and under ?
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
Reply
#33
@bplus
Huh
I am using the modified version of QB64pe by Petr see https://qb64phoenix.com/forum/showthread...9#pid40429

@Petr
I finally got my decfloat stuff to the point that it works
calculating Pi to 10000 digits using the Brent-Salamin Formula took 0.6 seconds, 2 times faster than the offset method
<edit> 10000 digits of Pi using the Chudnovsky binary split method took 0.26 seconds
with the offset method it took 0.64 seconds
Reply
#34
Oh wow! Petr has modified QB64pe for UDT arrays! This is so unbelievable I didn't understand!

Holy cow! wow !

+1 each! Nice job Petr! and Jack for testing and making noise for me to notice Smile

I honestly thought I'd never the day we get arrays in UDT's in QB64pe.
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
Reply
#35
Exclamation  Idea  Here is a ZIP file that you can use to further improve this version of the IDE so that it starts supporting 1D +  (2D and more) nested arrays as well. There is one limitation though — the size of the 2D ++ arrays must be fixed (a numeric value or a constant), because a dynamic version would require a much larger change to the internals of QB64PE (that takes more time and testing).


Attached Files
.zip   qb64pe_arrays_in_type_multidim_source_update.zip (Size: 184.16 KB / Downloads: 9)
.txt   about.txt (Size: 16.86 KB / Downloads: 11)


Reply
#36
Well this is cool! I look forward to a change that might include dynamic arrays. Good job, Petr.
The noticing will continue
Reply
#37
thanks Petr  Big Grin
Reply
#38
@Petr
may I ask that you write detailed article on the steps that you took to achieve this feat ?
before time takes its toll on the memory Wink
Reply
#39
@Jack Of course!  Big Grin

For first version (1D):

Comparison of QB64PE Source Codes — 1D Array Support inside TYPE
Summary

This modification extends the QB64PE compiler to support one-dimensional arrays as members of TYPE structures. The default version of QB64PE only allows scalar members within a TYPE. The modified version enables syntax such as  

TYPE MyType
    values(10) AS INTEGER
    items(1 TO 5) AS STRING * 20
END TYPE

This change is not merely a parser adjustment; it simultaneously impacts: Parsing of TYPE members. Metadata of UDT (User-Defined Type) elements. Calculation of TYPE size and layout. Generation of access to members via obj.member(i).Initialization, clearing, freeing, and copying of UDTs containing arrays.

The QB64PE bootstrap build chain. Manual changes were implemented in three primary source files:

source/qb64pe.bas
source/utilities/type.bas
source/utilities/type.bi

To ensure these changes are reflected in the resulting compiler, it was necessary to regenerate internal/source/* using the bootstrap chain. Without this step, a standard build via setup_win.cmd would still utilize the original behavior.

Scope of New Support  

The modified version supports the following within a TYPE:
1D arrays of simple numeric types.
1D arrays of STRING (variable length).
1D arrays of STRING * N (fixed length).
1D arrays of nested UDTs.Element access via obj.member(i).

Nested access like obj.member(i).field.Copying of UDTs containing arrays. ERASE and REDIM _PRESERVE.PUT/GET for binary-fixed UDTs. 
Current Limitations: Only 1D arrays are supported. Only constant bounds are allowed. Supports only the member(dim) AS type syntax. 

Currently Unsupported: (first version 1D!)
Multidimensional arrays inside TYPE.
Dynamic arrays inside TYPE.
The AS type member1, member2 syntax when using array members.
Whole-array semantics (treating the member as a standalone array object).

1. File: source/utilities/type.bi 
Main Change: A new shared metadata array was added: REDIM SHARED udtearraybase(1000) AS LONG 
Significance: While the default version used some internal metadata for array elements, it lacked a dedicated mechanism to store the lower bound of an array declared within a TYPE. The new udtearraybase() array stores: The lower index of the array member (e.g., 0 for values(10) or 1 for items(1 TO 5)). This is essential for correct offset calculation during access: obj.items(3). The offset is not calculated solely from the index, but from the expression: $$(index - lower\_bound) \times element\_size$$

2. File: source/utilities/type.bas 
This file contains the logic for UDT handling regarding initialization, clearing, freeing, and copying. This is where the syntax is turned into functional runtime behavior.

2.1 Expansion of increaseUDTArrays The resizing logic for the new udtearraybase() metadata array was added to the internal UDT element table resize routine. This prevents metadata corruption when a large number of elements are defined.
2.2 New Function: udt_array_member_bytes&A helper function was added to calculate the size of a single element of an array member in bytes. Logic: udtesize(element) now contains the total footprint of the array; this function divides that size by the number of elements. Usage: Initializing/freeing variable strings, copying UDTs, and recursive traversal of nested UDT arrays.
2.3 Modification of initialise_udt_varstrings The modified version now checks if a member is an array using udtearrayelements(element) > 0. If true, it iterates through all array elements and: Creates a qbs_new for variable STRING members.Recursively calls initialise_udt_varstrings for nested UDTs.
2.4 Modification of free_udt_varstringsExtended using the same principle to prevent memory leaks, dangling references, or double-free errors when a UDT is destroyed.
2.5 Modification of clear_udt_with_varstringsEnables clearing UDTs that contain array members. Variable strings are cleared element-by-element, nested UDTs are cleared recursively, and fixed-layout blocks are cleared as a single memory block.
2.6 Modification of initialise_array_udt_varstrings Handles cases where the UDT itself is part of an array (e.g., DIM arr(100) AS MyType). It ensures that if MyType contains array members, they are correctly initialized for every instance within the parent array (supporting two layers of nesting).
2.7 Modification of free_array_udt_varstrings Analogous to the above, ensuring that when an array of UDTs is freed, all internal array members (strings or nested UDTs) are also correctly released.
2.8 Modification of copy_full_udt One of the most critical changes. The modified version now distinguishes between:Simple/fixed-layout members → Block copy. Arrays of variable strings → Element-wise copy via qbs_set. Arrays of nested UDTs → Recursive copy_full_udt for each element. This ensures that B = A performs a "deep copy," avoiding shared string references and data corruption. Side Bugfix: A bug was fixed where MainTxtBuf was hardcoded instead of using the buf parameter.
2.9 Modification of dump_udts The debug output now includes udtearraybase(i), allowing developers to see both the element count and the lower bound during a UDT dump.

3. File: source/qb64pe.bas
This is the main compiler source where the most significant changes to the parser and reference generation occurred.
3.1 Implementation of udtearraybase 
The shared metadata array was also added here to provide the compiler with lower-bound information during the build process.
3.2 TYPE Definition Parser 
ExtensionThis is the core of the change. When processing a line within a TYPE ... END TYPE block, the parser now checks for an opening parenthesis ( after the member name. If found, it: Extracts the content between parentheses. Passes it to the new ParseUDTArrayBounds& function. Stores the resulting lower bound and element count in udtearraybase() and udtearrayelements().
3.3 Scaling Member Size to Array FootprintAfter identifying the type, the member's size is multiplied by the number of elements: 

IF udtearrayelements(i2) THEN
    udtesize(i2) = udtesize(i2) * udtearrayelements(i2)
END IF

Without this, the compiler would assume the member only occupies the space of a single element, causing all subsequent member offsets in the structure to be incorrect.

3.4 Limitation to member(dim) AS type SyntaxSupport is explicitly restricted to this syntax. The alternative AS TypeName member1, member2 syntax was not extended for arrays, and the parser will trigger a clear error message if attempted.
3.5 Layout / Preparse Layer Adjustment
Added support for formatting lines like member(1 TO 5) AS LONG to ensure the new syntax survives the preprocessing/layout phase before reaching the actual parser.
3.6 New Function: ParseUDTArrayBounds&Parses the bounds of an array member. It: Validates that dimensions are not empty. Rejects commas (blocking multi-dimensional arrays). Recognizes both upper and lower TO upper formats. Evaluates bounds as constant expressions.
3.7 New Function: UDTArrayIndexExpr$Generates the expression for runtime access. It converts obj.member(index) into an internal expression by: Evaluating the index expression. Subtracting the lower bound. Wrapping the access in array_check(...) if CheckingOn is enabled; otherwise, generating a direct offset.
3.8 UDT Element Lookup via Dot OperatorModified the logic for expressions like myVar.arrayField(index). The parser now calculates the offset as: $$Member\_Offset + ((index - lower\_bound) \times element\_size)$$ This allows the array member to behave as a true array within the structure. Practical ImpactAfter these modifications, QB64PE canBig Grineclare 1D arrays inside TYPE with explicit bounds. Correctly calculate UDT layouts. Access elements via obj.member(i).Handle nested strings and UDT arrays. Perform deep copies, initializations, and clears of complex UDTs. Maintain compatibility with PUT/GET and REDIM _PRESERVE. ConclusionThis update adds genuine 1D array support within TYPE structures to QB64PE—not just syntactically, but semantically and at runtime. By maintaining a fixed UDT layout, the implementation remains stable and does not require intrusive changes to the C-level runtime layer.


SECOND VERSION - 2D ++

# Comparison: Transition from 1D to Multidimensional Arrays in TYPE (QB64PE)

## Summary

These changes extend the previous support for one-dimensional arrays in TYPE to **an arbitrary number of dimensions**. After the modification, it is possible to write:

```basic
TYPE TestType
    Numbers(1 TO 2, 1 TO 3) AS LONG          ' 2D array of integers
    Texts(0 TO 1, 1 TO 2) AS STRING          ' 2D array of variable-length strings
    Kids(1 TO 2, 1 TO 2) AS ChildType        ' 2D array of nested UDTs
END TYPE
```

And access them via: `A.Numbers(2, 3)`, `A.Kids(2, 1).Name`, etc.

Total scope of changes: **approx. +100 lines of new code** across 3 files, plus a rewrite of two key functions.

---

## 1. File `type.bi` — New Metadata (+2 lines)

Two new shared arrays were added:

```basic
REDIM SHARED udtearraydims(1000) AS LONG
REDIM SHARED udtearraydesc(1000) AS STRING
```

### `udtearraydims` — Dimension Count

Stores how many dimensions a given array has. For example, for `M(1 TO 2, 1 TO 3)` the value is `2`. For scalar elements it is `0`. In the 1D version this information did not exist — it was sufficient to check whether `udtearrayelements > 0`.

### `udtearraydesc` — Serialized Dimension Descriptor

A string in the format `lower_bound,element_count;lower_bound,element_count;...` for each dimension.

Example for `M(1 TO 2, 1 TO 3)`:
```
"1,2;1,3"
```
This means: the 1st dimension starts at 1 and has 2 elements, the 2nd dimension starts at 1 and has 3 elements.

This descriptor is key — it contains complete information about the array shape, which is then used by the index linearization at runtime.

**Why this approach?** In the 1D version, two scalars (`udtearraybase` and `udtearrayelements`) were sufficient. For N dimensions, N pairs of values would be needed. Instead of introducing additional 2D arrays (arrays of arrays), the authors opted for a serialized string that is simple to store and parse.

---

## 2. File `type.bas` — Metadata Extensions (+2 lines)

### 2.1 `increaseUDTArrays`

Two `REDIM _PRESERVE` lines added for the new arrays:

```basic
REDIM _PRESERVE udtearraydims(x + 1000) AS LONG
REDIM _PRESERVE udtearraydesc(x + 1000) AS STRING
```

### 2.2 `dump_udts` — Extended Debug Output

The diagnostic dump now also prints `udtearraydims(i)` and `udtearraydesc(i)`, so the complete dimension description of each element is visible in the debug dump.

### 2.3 No Changes to init/free/copy/clear SUBs

**Important design point:** The procedures `initialise_udt_varstrings`, `free_udt_varstrings`, `clear_udt_with_varstrings`, `copy_full_udt` and their array variants **remained unchanged**. The loops `FOR array_i = 0 TO udtearrayelements(element) - 1` work correctly for multidimensional arrays as well, because `udtearrayelements` still contains the **total element count** (the product of all dimensions). Data in memory is stored contiguously, so a linear traversal from 0 to total-1 covers all elements regardless of the number of dimensions.

---

## 3. File `qb64pe.bas` — Main Changes (+99 lines)

### 3.1 Declaration of New Arrays (line ~1350)

Added:
```basic
REDIM SHARED udtearraydims(1000) AS LONG
REDIM SHARED udtearraydesc(1000) AS STRING
```

### 3.2 Initialization of New Arrays During TYPE Parsing (lines ~1999 and ~2130)

At both locations where a new TYPE element is created (both `element AS type` and `AS type element-list` syntax), initialization was added:

```basic
udtearraydims(i2) = 0
udtearraydesc(i2) = ""
```

### 3.3 `ParseUDTArrayBounds` Call — Extended Parameters (line ~2016)

**1D version:**
```basic
IF ParseUDTArrayBounds(expr$, udtearraybase(i2), udtearrayelements(i2)) = 0 THEN GOTO errmes
```

**Multidim version:**
```basic
IF ParseUDTArrayBounds(expr$, udtearraybase(i2), udtearrayelements(i2), udtearraydims(i2), udtearraydesc(i2)) = 0 THEN GOTO errmes
```

The function now accepts two additional parameters: `dimension_count` and `descriptor$`, which it populates upon successful parsing.

---

### 3.4 Completely Rewritten Function `ParseUDTArrayBounds&`

This is the **most significant change**. The function was rewritten from a 1D-specific version to a generalized N-dimensional one.

**Signature — 1D version:**
```basic
FUNCTION ParseUDTArrayBounds& (indexes$, lower_bound AS LONG, element_count AS LONG)
```

**Signature — multidim version:**
```basic
FUNCTION ParseUDTArrayBounds& (indexes$, lower_bound AS LONG, element_count AS LONG,
                                dimension_count AS LONG, descriptor$)
```

**What changed inside:**

The **1D version** searched for the `TO` keyword and treated a comma as an error (reporting "Multiple dimensions not supported"). It parsed a single lower/upper pair.

The **multidim version** implements a loop over comma-separated dimensions:

1. Iterates through the elements of the input expression, tracking parentheses (`b`)
2. When it encounters a comma at level `b = 0` or the end of the expression → extracts the sub-expression for one dimension
3. Within each dimension, searches for `TO` (with nested parenthesis counting via `b2`)
4. Evaluates the lower and upper bounds as constant expressions for each dimension separately
5. Computes the element count for the given dimension (`dim_elements = dim_upper - dim_lower + 1`)
6. Builds the descriptor: `lower,count` separated by semicolons
7. The total `element_count` is the **product** of all dimension sizes (`element_count = element_count * dim_elements`)
8. `lower_bound` is set to the lower bound of the **first** dimension (for backward compatibility)
9. `dimension_count` is incremented with each dimension

**Walkthrough example for `(1 TO 2, 1 TO 3)`:**

| Step | Dimension | lower | upper | elements | descriptor (running) | total count |
|-------|---------------|--------|---------|-------------|-------------------------|---------------|
| 1     | 1st             | 1      | 2        | 2            | `"1,2"`                  |              2 |
| 2     | 2nd            | 1      | 3        | 3            | `"1,2;1,3"`             |              6 |

Result: `element_count = 6`, `dimension_count = 2`, `descriptor$ = "1,2;1,3"`.

---

### 3.5 New Helper Function `ParseNextUDTArrayDescriptorDim&`

A completely new function (~20 lines) that parses one dimension from the descriptor at runtime:

```basic
FUNCTION ParseNextUDTArrayDescriptorDim& (descriptor$, descriptor_position AS LONG,
                                          lower_bound AS LONG, element_count AS LONG)
```

It works as an **iterator** over the descriptor string:
- `descriptor_position` maintains the current position in the string (input/output)
- Finds the next semicolon (dimension separator) or end of string
- Extracts the comma-separated values from the `lower,count` substring
- Returns `lower_bound` and `element_count` for one dimension
- Advances `descriptor_position` past the semicolon for the next call

This function is called repeatedly from `UDTArrayIndexExpr$` — once for each dimension.

---

### 3.6 Completely Rewritten Function `UDTArrayIndexExpr$`

**Signature — 1D version:**
```basic
FUNCTION UDTArrayIndexExpr$ (indexes$, lower_bound AS LONG, element_count AS LONG)
```

**Signature — multidim version:**
```basic
FUNCTION UDTArrayIndexExpr$ (indexes$, dimension_count AS LONG, descriptor$)
```

The **1D version** simply evaluated a single index and generated `(idx - lower)` or `array_check(idx - lower, count)`.

The **multidim version** implements **linearization of an N-dimensional index into a one-dimensional offset** (row-major ordering):

1. Initializes `stride = 1` and `result_expr = ""`
2. Iterates through index expressions separated by commas (tracking parentheses)
3. For each dimension:
  - Evaluates the index expression via `evaluatetotyp$`
  - Reads the dimension metadata from the descriptor via `ParseNextUDTArrayDescriptorDim`
  - Verifies that the number of supplied dimensions matches the declaration
  - **For the 1st dimension:** generates `(idx - lower)` (stride is 1, no multiplication needed)
  - **For subsequent dimensions:** generates `(idx - lower) * stride`
  - With `CheckingOn`, generates `array_check(...)` instead of a plain expression
  - Updates `stride = stride * dim_elements` (for the next dimension)
4. The resulting string is the sum of all dimensional expressions: `(idx1 - lower1) + (idx2 - lower2) * stride2 + ...`
5. Trims the trailing `+` from the assembled string

**Validation:** The function checks that the number of supplied indices (`found_dimensions`) exactly matches the number of dimensions in the declaration (`dimension_count`). On mismatch, it reports "Cannot change the number of elements an array has!".

**Example for accessing `A.Numbers(2, 3)` where Numbers is `(1 TO 2, 1 TO 3)`

| Dimension | idx | lower | elements | stride | Expression |
|--------------|------|--------|-------------|--------|---------------|
| 1st           | 2    | 1       | 2            | 1       | `((2)-1)`   |
| 2nd          | 3    | 1       | 3            | 2       | `((3)-1)*2`|

Total linearized index: `((2)-1) + ((3)-1)*2 = 1 + 4 = 5` → sixth element (0-based).

---

### 3.7 Modification of `udtfindele` — Call with New Parameters (line ~15988)

**1D version:**
```basic
arrindex$ = UDTArrayIndexExpr$(expr$, udtearraybase(E), udtearrayelements(E))
```

**Multidim version:**
```basic
arrindex$ = UDTArrayIndexExpr$(expr$, udtearraydims(E), udtearraydesc(E))
```

Instead of passing a simple lower bound and element count, the dimension count and the full descriptor are now passed.

### 3.8 Minor Parenthesization Changes in the Offset Expression

In `udtfindele`, the parenthesization around `arrindex$` was changed:

**1D:** `"(" + arrindex$ + "*" + arrbytes + ")"`
**Multidim:** `"((" + arrindex$ + ")*" + arrbytes + ")"`

The added outer parentheses around `arrindex$` are necessary because the expression is now a sum of multiple terms (`(idx1-l1) + (idx2-l2)*stride`), and without the parentheses, `*arrbytes` would only multiply the last term of the sum.


Reply
#40
Petr you are the best! Big Grin
10 PRINT "Hola! Smile"
20 GOTO 10
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Arrays as UDTs Pete 26 1,271 02-06-2026, 04:31 AM
Last Post: bplus
  Preserving multi-dim arrays Pete 5 457 12-19-2025, 03:17 PM
Last Post: Dimster
  Array out of passing arrays... Pete 2 428 09-22-2025, 08:53 PM
Last Post: ahenry3068
  Methods in types bobalooie 7 1,600 01-30-2025, 08:00 PM
Last Post: Pete
  C++ types > QB64 types: do we have an equivalent QB64PE page? madscijr 5 1,168 06-01-2024, 03:44 AM
Last Post: grymmjack

Forum Jump:


Users browsing this thread: 2 Guest(s)