Skip to content
Merged
31 changes: 27 additions & 4 deletions BUGS-TO-REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -798,15 +798,38 @@ Then, on that 477-file export:
nothing (the `import` entry above), as it does for any folder under `Packages`;
- the standalone script packs it, into a 4,220,723-byte project, against 2,055 bytes for the
same export with `Packages` removed. The project now embeds its own copy of the four
compiler packages. It compiles with no errors; which copy the IDE then uses was not
measured.
compiler packages. It compiles with no errors;
- the IDE's own **New Project → Import from folder...** does the same, into a
4,222,833-byte project, against 4,207 bytes from the export with `Packages` removed.

**The embedded copy is dead, and every later export writes it back.** Measured on the
IDE's import (round 10):

1. Export a project with the default references into an empty folder `E`.
2. In `E\Packages\VBA\Sources\Math.twin`, add `Public Function ProbeEmbeddedMarker() As
Long` before `End Module`, and a call to it in one of the project's own modules.
3. **Import from folder...** on `E`: TB5079, *Unrecognized symbol 'ProbeEmbeddedMarker'*.
The compiler uses its own VBA package, not the copy the project now holds.
4. Save the project, and export it again: the Debug Console reports
`[EXPORT] COMPLETED (139 folders, 954 files)`, against `(72 folders, 479 files)` before,
and the exported `Math.twin` holds the marker. The export writes both copies to the same
paths, the embedded one last.

So a project kept in Git through *Export After Save* and rebuilt from a clone keeps committing
the package source of the IDE that first exported it, while it compiles against the current
IDE's. Expected: a `.twinproj` the IDE saves holds no compiler packages, so **Import from
folder** could skip them, or the export could write the IDE's own copy rather than the
project's.

**What does not reproduce it:** the command line's own `export`, which writes what the
project file holds.
project file holds; and **Import from folder** on the export with the compiler packages'
folders removed, which gives the 4,207-byte project, compiles, and exports 479 files.

**Found by** checking round 9's UC-62 answer, which sets up *Export After Save* into a Git
repository and rebuilds the project from a fresh clone with the tB executable. The export was
round 8's, written by the IDE's `exportProjectTo()` over DevTools.
round 8's, written by the IDE's `exportProjectTo()` over DevTools. The dead copy was measured
following round 10's UC-66, the fresh clone, with `root.loadProjectFromFolder()` --- what the
dialog calls after its folder picker --- and `root.saveProjectAs()` over DevTools.

---

Expand Down
469 changes: 469 additions & 0 deletions builder/REVIEW-USECASES-16969e5.md

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions builder/vendor/just-the-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ patcher in [`offline.mjs`](../../offline.mjs) is AST-based and survives
cosmetic upstream edits inside the patched function bodies, but the
copy-button retirement above is structural and has to be re-applied.

**The search's typo fallback is capped at an edit distance of 2.** When a
query's words match nothing, upstream searches again with an edit distance
taken from the length of the *whole query*, `Math.round(Math.sqrt(input.length
/ 2 - 1))`, and applies it to every word. lunr's fuzzy expansion grows
exponentially with that distance. Measured in headless Chromium against the
built site: three Windows API names the site does not index, 49 characters and
distance 5, froze the page for 5.1 s and took the JS heap to 1.6 GB; four, 70
characters and distance 6, never answered within 60 s. Capped, both answer in
about 10 ms. A query of 15 characters or fewer is unaffected, since the formula
gives at most 2 there. [`eval/site_search.mjs`](../../../eval/site_search.mjs)
replicates the query logic and carries the same cap; it is where the defect
was found, when re-measuring an evaluator's search ran the replica out of
memory.

## Licence

just-the-docs is MIT-licensed, and `LICENSE.txt` beside this file is the
Expand Down Expand Up @@ -185,9 +199,10 @@ Bumping the just-the-docs version is a deliberate operation. Procedure:
by the axe scan, and the three focus rings by nothing at all, since axe
checks that a control is reachable and named, not that its ring is visible.

5. Re-apply the copy-button patch in `assets/js/just-the-docs.js` (see
above). Diffing against the previous vendored copy via `git diff` is
the easiest way to spot what needs to come back.
5. Re-apply the copy-button patch and the edit-distance cap in
`assets/js/just-the-docs.js` (see above). Diffing against the previous
vendored copy via `git diff` is the easiest way to spot what needs to
come back.

6. Inspect the entry point at `docs/assets/css/just-the-docs-combined.scss`
--- if the upstream `_includes/css/just-the-docs.scss.liquid` Liquid
Expand Down
7 changes: 6 additions & 1 deletion builder/vendor/just-the-docs/assets/js/just-the-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,13 @@ function searchLoaded(index, docs) {
})
if (tokens.length > 0) {
results = index.query(function (query) {
// Patched: capped at 2. Upstream takes the distance from the whole
// query's length and applies it to every word, and lunr's fuzzy
// expansion grows exponentially with it: three unindexed API names
// (49 characters, distance 5) froze the page for 5 s at 1.6 GB, four
// (70, distance 6) for over a minute. See builder/vendor/just-the-docs/README.md.
query.term(tokens, {
editDistance: Math.round(Math.sqrt(input.length / 2 - 1))
editDistance: Math.min(2, Math.round(Math.sqrt(input.length / 2 - 1)))
});
});
}
Expand Down
6 changes: 6 additions & 0 deletions docs/Features/64bit.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ twinBASIC can compile native 64bit executables in addition to 32bit. The syntax

Using the [Fusion](Fusion) feature, it is also possible to use both 32bit and 64bit ActiveX controls in 32bit *and* 64bit projects.

## Building a 64-bit Version

The [build configuration](../tB/IDE/Project/Toolbar#build-configuration) box on the toolbar chooses between a 32-bit and a 64-bit build: **win32** or **win64**. <kbd>CTRL</kbd> + <kbd>F1</kbd> switches to **win64**, and <kbd>CTRL</kbd> + <kbd>F2</kbd> switches back to **win32**. The IDE remembers the choice for each project.

The two builds write different files. The default [Build Output Path](../tB/IDE/Project/Settings#build-output-path) ends in `${ProjectName}_${Architecture}.${FileExtension}`, and `${Architecture}` is `win32` or `win64`. So a Standard DLL project named `MathGreetLib` builds `Build\MathGreetLib_win32.dll` in one mode and `Build\MathGreetLib_win64.dll` in the other.

## Example Syntax

```tb check_build
Expand Down
2 changes: 1 addition & 1 deletion docs/Features/Fusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ A project-level setting allows you to control where the Fusion host EXE is gener
![tbFusionProjectSettings](Images/569150839-9ffc87ac-250d-40a4-bb47-669b607ad76f.png){:width="800" height="400"}

If left blank (default), the standard build path set in the project settings is used. Unless overriden, the standard build path is:
${SourcePath}\Build${ProjectName}_${Architecture}.${FileExtension}
`${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}`

For Fusion host executables, `${Architecture}` will resolve to:
- `win32host`
Expand Down
60 changes: 60 additions & 0 deletions docs/Features/Language/Delegates.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ permalink: /Features/Language/Delegates

There is native support for calling a function by pointer, by way of `Delegate` syntax. A delegate in twinBASIC is a function pointer type that's compatible with LongPtr. `AddressOf` returns a delegate type, that's also backwards compatible with `LongPtr`.

A delegate is also how a procedure takes another function as an argument, which other languages call a *callback*. See [Passing a function as an argument](#callbacks).

## Basic Usage

The syntax looks like this:
Expand All @@ -26,6 +28,64 @@ Public Function Addition(ByVal A As Long, ByVal B As Long) As Long
End Function
```

## Passing a function as an argument or parameter (callbacks)
{: #callbacks }

A procedure can take a function as an argument and call it. Other languages call this a *callback*. Writing one takes three steps:

1. Declare a delegate type with the signature the function must have.
2. Give the procedure a parameter of that type, and call the parameter as if it were a function.
3. At the call, pass **AddressOf** followed by the name of the function.

**SortNames** below sorts an array of names. The caller chooses the order by passing a comparison function, and **SortNames** calls it through its *isInOrder* parameter:

```tb check_build
' The signature every comparison function must have.
Public Delegate Function Comparer(ByVal a As String, ByVal b As String) As Boolean

' Sorts names() in place. isInOrder returns True if a can stay before b.
Public Sub SortNames(names() As String, ByVal isInOrder As Comparer)
Dim i As Long, j As Long, temp As String
For i = LBound(names) To UBound(names) - 1
For j = LBound(names) To UBound(names) - 1
If Not isInOrder(names(j), names(j + 1)) Then
temp = names(j)
names(j) = names(j + 1)
names(j + 1) = temp
End If
Next j
Next i
End Sub

Public Function ByAlphabet(ByVal a As String, ByVal b As String) As Boolean
Return a <= b
End Function

Public Function ByLength(ByVal a As String, ByVal b As String) As Boolean
Return Len(a) <= Len(b)
End Function

Public Sub SortDemo()
Dim names() As String = Array("Charlie", "Al", "Bob", "Dave", "Ed")
SortNames names, AddressOf ByAlphabet
Debug.Print Join(names, ", ")
SortNames names, AddressOf ByLength
Debug.Print Join(names, ", ")
End Sub
```

**SortDemo** prints:

```text
Al, Bob, Charlie, Dave, Ed
Al, Ed, Bob, Dave, Charlie
```

> [!IMPORTANT]
> The function passed must match the delegate: the same number and types of parameters, the same **ByVal** or **ByRef** on each (a parameter with neither is **ByRef**), and the same return type. A function that does not match causes only a warning, TB0026 *Mismatched delegate type*, and the program still builds. The function then receives its arguments in a form it does not expect: if the delegate's parameter is a **Long** and the function's is an **Integer**, a value of 70000 arrives in the function as 4464. `[EnforceErrors(TB0026)]` on the module that uses **AddressOf** makes the mismatch an error; [Compiler Warnings](../Compiler-IDE/Compiler-Warnings#adjusting-warnings) describes the other ways to do that.

A delegate type can also describe a callback that a Windows API function takes. See [Advanced Usage](#advanced-usage).

## Advanced Usage

The delegate type can also be used in interface/API declarations and as members of a User-defined type. For example, the `ChooseColor` API:
Expand Down
22 changes: 15 additions & 7 deletions docs/Features/Language/Generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,21 @@ The function **Caster** introduces three type variables within its scope:
The body of a generic procedure is compiled once for each type it is used with, as if it had been written out for that type. So the body can use an operator that works on every type it is called with:

```tb check_build
Public Function Max(Of T)(a As T, b As T) As T
If a > b Then
Return a
Else
Return b
End If
End Function
Module MaxDemo
Public Function Max(Of T)(a As T, b As T) As T
If a > b Then
Return a
Else
Return b
End If
End Function

Sub Test()
Debug.Assert Max(3, 7) = 7
Debug.Assert Max(3.5, 2.1) = 3.5
Debug.Assert Max("apple", "banana") = "banana"
End Sub
End Module
```

`Max(3, 7)` returns the **Integer** `7`, `Max(3.5, 2.1)` the **Double** `3.5`, and `Max("apple", "banana")` the **String** `"banana"`. Nothing in the definition says which types *T* may be. A type that the body does not work with is a compile error, and the error is reported in the body, on the line that uses the operator, not at the call that supplied the type. `Max(Of Collection)(c1, c2)` fails with TB5092, *Missing argument 'Index'*, on the line `If a > b Then`: the message is about the argument of **Item**, the **Collection**'s default member.
Expand Down
Loading
Loading