ACE BASIC - Structs RTG and More
Another round of updates
The previous posts covered ACE BASIC up to v2.8 -- closures, MUI, linked lists, and CubicIDE integration. Version 2.9 is out now with struct enhancements, new string functions, RTG graphics, tail-call optimization, an HTTP client, double-precision floats, and SAGA audio.
Struct Enhancements
Structs in ACE have been fairly basic until now -- flat collections of scalar and string fields. Version 2.9 changes that. Structs can now contain typed arrays, embed other structs, hold typed pointers to structs, reference their own type, and contain arrays of structs. This makes it possible to model real data structures without falling back to raw PEEK/POKE arithmetic.
Typed array members
Previously, only STRING could use the SIZE keyword to declare a fixed-size buffer inside a struct. Now any base type works:
STRUCT Packet
BYTE header SIZE 4
LONGINT values SIZE 10
SHORTINT flags SIZE 8
SINGLE coords SIZE 3
STRING name SIZE 32
END STRUCT
Each array member reserves the appropriate amount of space inline in the struct. Access is through indexed notation:
DECLARE STRUCT Packet p
p->values(0) = 100&
p->values(1) = 200&
p->coords(0) = 1.5
p->coords(2) = 3.5
FOR i% = 0 TO 3
p->header(i%) = 65 + i%
NEXT i%
The index can be a constant, a variable, or an expression. The compiler generates the correct element size multiplication and offset calculation automatically.
Nested structs
Structs can now embed other structs as members. The -> operator chains through the nesting:
STRUCT Vec2
LONGINT x
LONGINT y
END STRUCT
STRUCT Rect
Vec2 topLeft
Vec2 bottomRight
END STRUCT
DECLARE STRUCT Rect r
r->topLeft->x = 10&
r->topLeft->y = 20&
r->bottomRight->x = 100&
r->bottomRight->y = 200&
This also works with deeper nesting. A struct that embeds a struct that embeds another struct gives you three levels of -> chaining. The compiler resolves the offsets at compile time, so there is no runtime cost for the nesting.
Typed struct pointers
A struct member can be declared as a pointer to a specific struct type. This tells the compiler what type lives at the other end, so you can chain -> through the pointer:
STRUCT Inner
LONGINT x
LONGINT y
END STRUCT
STRUCT Outer
Inner *ptr
LONGINT z
END STRUCT
DECLARE STRUCT Outer o
o->ptr = ALLOC(SIZEOF(Inner))
o->ptr->x = 42&
o->ptr->y = 99&
Without typed pointers, you would have to store a plain ADDRESS, cast it manually, and use PEEK/POKE. With typed pointers, the compiler knows the layout and does the offset math for you.
Self-referential structs and struct arrays
A struct can contain a pointer to its own type, which is the classic building block for linked lists and trees:
STRUCT Node
STRING name
Node *next
END STRUCT
And you can embed a fixed-size array of structs inside another struct:
STRUCT Vec2
LONGINT x
LONGINT y
END STRUCT
STRUCT Polygon
Vec2 pts SIZE 5
LONGINT count
END STRUCT
DECLARE STRUCT Polygon poly
poly->pts(0)->x = 10&
poly->pts(0)->y = 20&
poly->pts(2)->x = 50&
The syntax poly->pts(i)->field combines struct array indexing with field access in a single expression. This is probably the most complex access pattern ACE supports now, and it works with variable indices and in loops.
New String Functions
Version 2.9 adds eleven new string functions. Here is a quick overview:
| Function | Description |
|---|---|
TRIM$ / LTRIM$ / RTRIM$ | Strip leading/trailing whitespace |
STARTSWITH / ENDSWITH | Test prefix or suffix (returns boolean) |
RINSTR | Search for substring from the right |
REPLACE$ | Replace all occurrences of a substring |
REVERSE$ | Reverse a string |
REPEAT$ | Repeat a string N times |
LPAD$ / RPAD$ | Pad to a given width |
FMT$ | sprintf-style formatting |
MID$ (statement) | In-place modification of a substring |
The one I find most useful is FMT$. It works like C's sprintf but returns a BASIC string:
msg$ = FMT$("%s has %d items", "list", 42)
'..result: "list has 42 items"
hex$ = FMT$("addr: %08x", 255)
'..result: "addr: 000000FF"
It supports %s, %d, %x, %c, and %% with up to eight arguments. This is much cleaner than concatenating strings with STR$ calls.
P96/RTG Screen Support
Until now, ACE only supported planar Amiga screens -- OCS, ECS, and AGA modes that use bitplane graphics. Version 2.9 adds Picasso96 retargetable graphics with a new screen mode 13. This gives you chunky (linear) framebuffers with 8, 15, 16, 24, or 32-bit color depth. It works on any P96-compatible hardware.
Opening a P96 screen is straightforward:
SCREEN 1, 800, 600, 8, 13
Mode 13 tells ACE to use P96 instead of the native chipset. The depth parameter sets the color depth in bits. For 8-bit screens, you get a 256-color palette just like AGA, but the framebuffer is chunky instead of planar. For HiColor and TrueColor depths, there is a new COLOR r,g,b syntax for direct RGB drawing.
All the standard drawing commands -- LINE, CIRCLE, PRINT, LOCATE -- work on P96 screens because Picasso96 patches the graphics library. But you can also write directly to the framebuffer via POKE for maximum speed:
' Get the chunky framebuffer address
bitmapAddr& = SCREEN(4)
frameAddr& = PEEKL(bitmapAddr& + 8)
' Write a pixel at (x, y) on an 8-bit screen
POKE frameAddr& + CLNG(y%) * CLNG(800) + CLNG(x%), colorIndex%
This opens up possibilities for software rendering, chunky-to-chunky blitting, and effects that would be painful on planar screens.
The following example combines direct framebuffer writes (the rainbow gradient) with OS drawing primitives (lines, rectangles, circles) on the same chunky buffer:
' P96 Chunky Screen - 800x600, 256 colors
SCREEN 1, 800, 600, 8, 13
WINDOW 1,"",(0,0)-(799,599),0,1
' Rainbow palette
FOR i% = 0 TO 253
' ... compute r%, g%, b% for rainbow gradient ...
PALETTE i%, r%/255, g%/255, b%/255
NEXT
' Direct framebuffer fill with rainbow bars
bitmapAddr& = SCREEN(4)
frameAddr& = PEEKL(bitmapAddr& + 8)
FOR y% = 0 TO 599
i% = (y% * 254) / 600
FOR x% = 0 TO 799
POKE frameAddr& + CLNG(y%) * 800& + CLNG(x%), i%
NEXT
NEXT
' OS drawing on top: lines, circles, filled shapes
COLOR 255, 0
LINE (20,80)-(380,80)
CIRCLE (200,180),80
CIRCLE (200,180),60,,,,f
LINE (400,80)-(600,180),200,bf
Tail-Call Optimization
Recursive functions in ACE use the system stack. Each call pushes a frame, and if the recursion is deep enough, you run out of stack and crash. Version 2.9 adds automatic tail-call optimization (TCO) for self-recursive SUBs with numeric parameters. When the compiler detects that a recursive call is the last thing a SUB does, it replaces the call with a jump back to the top of the SUB, reusing the same stack frame.
Enable it with OPTION O+:
OPTION O+
SUB LONGINT Gcd(a&, b&)
IF b& = 0 THEN
Gcd = a&
ELSE
Gcd = Gcd(b&, a& MOD b&)
END IF
END SUB
' This would overflow the stack without TCO
ASSERT Gcd(1000000, 3) = 1
Without TCO, Gcd(1000000, 3) requires around 333,000 recursive calls and would need roughly 48 KB of stack space -- well beyond the default. With TCO, it uses about 24 bytes regardless of depth.
The optimization works through the peephole optimizer. After the compiler generates the recursive JSR instruction, the peephole pass recognizes the pattern (restore frame, JSR to self, return) and replaces it with parameter shuffling and a BRA to the function entry. No parser changes were needed.
TCO only applies when the recursive call is in tail position -- there must be no computation after the call. The accumulator pattern is the standard way to write tail-recursive functions:
OPTION O+
SUB LONGINT Factorial(n&, acc&)
IF n& <= 1 THEN
Factorial = acc&
ELSE
Factorial = Factorial(n& - 1, n& * acc&)
END IF
END SUB
result& = Factorial(12, 1) '..479001600
HTTP Client
Version 2.9 ships an HTTP client submodule that provides networking from ACE BASIC. It supports HTTP and HTTPS (via AmiSSL), chunked transfer encoding, and streaming responses. The implementation is split into three submodules: tcpclient.b for raw TCP sockets, amissl.b for TLS, and httpclient.b for the HTTP protocol layer.
The API is struct-based. You declare connection and request/response structs, then call the appropriate functions:
#include <submods/httpclient.h>
DECLARE STRUCT TcpConn conn
DECLARE STRUCT HttpRequest req
DECLARE STRUCT HttpResponse resp
LONGINT status
' High-level: one-call HTTP HEAD
status = HttpHead(req, resp, conn, "http://www.google.com/")
PRINT "Status:"; status '..prints 200
For more control, there is a low-level API that lets you open a connection, send a request, read headers, and read the body in chunks:
rc = HttpOpen(req, conn, "www.example.com", 80, 0)
rc = HttpSendRequest(req, conn, "GET", "/")
status = HttpReadStatus(conn, resp)
bytes = HttpReadBody(conn, resp, buffer, bufferSize)
HttpClose(conn)
This is the first time ACE BASIC has any kind of network access built in. Fetching data from a web API, downloading files, or even posting data to a server is now possible from BASIC.
Brief Mentions
A few more additions worth noting:
DP-Float submodule: Double-precision floating-point math via the
mathieeedoubbasandmathieeedoubtranslibraries. ACE's nativeSINGLEtype uses Motorola Fast Floating Point (32-bit). The DP-Float submodule gives you 64-bit IEEE doubles with 32 functions covering arithmetic, trigonometry, hyperbolic functions, exponents, and string conversion. Originally written by David Benn (the creator of ACE), now integrated as an external submodule.SAGA Sound submodule: 16-bit audio playback on Vampire V4 hardware using the SAGA chipset. Supports 16 channels, 8 or 16-bit samples, stereo volume control, and sample rates up to 56 kHz.
Turtle graphics moved to submodule: The 13 built-in turtle commands (
FORWARD,BACK,TURNRIGHT, etc.) have been removed from the compiler and runtime. They now live in theturtle.bsubmodule. Existing programs just need to add#include <submods/turtle.h>and link the submodule. This keeps the compiler smaller and makes the turtle library easier to maintain independently.Buffered File I/O: The runtime functions behind
LINE INPUT #,INPUT #, andINPUT$now use bulkRead+Seekcalls instead of reading one character at a time. This gives roughly 12x throughput improvement for file reading operations.CubicIDE plugin improvements: The plugin now uses
regeditfor proper preset and filetype registration. The autocase dictionary, syntax highlighting, and quickinfo have been updated to cover all current keywords and functions.
Conclusion
Version 2.9 makes ACE BASIC significantly more capable for systems programming. Structs now support the kind of nesting and composition you need for working with OS data structures and building your own. RTG support opens up high-resolution, high-color graphics beyond the Amiga chipset. Tail-call optimization makes recursive algorithms practical. And the HTTP client brings network access to ACE BASIC for the first time.
The project lives on GitHub. Bug reports and feature requests are welcome.
-
[ACE BASIC - Structs RTG and More]
16-02-2026 -
[Developing with AI - Understanding the Context]
13-02-2026 -
[ACE BASIC - Closures MUI and More]
10-02-2026 -
[ACE BASIC - GadTools and More]
31-01-2026 -
[ACE BASIC - AGA Screen Support]
27-01-2026 -
[Polymorphism and Multimethods]
02-03-2023 -
[Global Day of CodeRetreat - recap]
07-11-2022 -
[House automation tooling - Part 4 - Finalized]
01-11-2022 -
[House automation tooling - Part 3 - London-School and Double-Loop]
02-07-2022 -
[Modern Programming]
14-05-2022 -
[House automation tooling - Part 2 - Getting Serial]
21-03-2022 -
[House automation tooling - Part 1 - CL on MacOSX Tiger]
07-03-2022 -
[Common Lisp - Oldie but goldie]
18-12-2021 -
[Functional Programming in (Common) Lisp]
29-05-2021 -
[Patterns - Builder-make our own]
13-03-2021 -
[Patterns - Builder]
24-02-2021 -
[Patterns - Abstract-Factory]
07-02-2021 -
[Lazy-sequences - part 2]
13-01-2021 -
[Lazy-sequences]
07-01-2021 -
[Thoughts about agile software development]
17-11-2020 -
[Test-driven Web application development with Common Lisp]
04-10-2020 -
[Wicket UI in the cluster - the alternative]
09-07-2020 -
[TDD - Mars Rover Kata Outside-in in Common Lisp]
03-05-2020 -
[MVC Web Application with Elixir]
16-02-2020 -
[Creating a HTML domain language in Elixir with macros]
15-02-2020 -
[TDD - Game of Life in Common Lisp]
01-07-2019 -
[TDD - classicist vs. London Style]
27-06-2019 -
[Wicket UI in the cluster - reflection]
10-05-2019 -
[Wicket UI in the Cluster - know how and lessons learned]
29-04-2019 -
[TDD - Mars Rover Kata classicist in Scala]
23-04-2019 -
[Burning your own Amiga ROMs (EPROMs)]
26-01-2019 -
[TDD - Game of Life in Clojure and Emacs]
05-01-2019 -
[TDD - Outside-in with Wicket and Scala-part 2]
24-12-2018 -
[TDD - Outside-in with Wicket and Scala-part 1]
04-12-2018 -
[Floating Point library in m68k Assembler on Amiga]
09-08-2018 -
[Cloning Compact Flash (CF) card for Amiga]
25-12-2017 -
[Writing tests is not the same as writing tests]
08-12-2017 -
[Dependency Injection in Objective-C... sort of]
20-01-2011