Skip to content

UltimateBasic 64 1.5.5

Choose a tag to compare

@zstarczali zstarczali released this 26 Aug 20:12
· 100 commits to main since this release
4d93336

Ultimate Basic v1.5.5 – Release Notes

2026-08-26 – Magic Desk CRT export

  • Added .crt output support: when the output filename ends in .crt, the
    compiler now writes a Commodore 64 cartridge image instead of a raw PRG.
  • The cartridge uses the same banked Magic Desk type-19 layout as
    VisualAssembler: 64-byte header, bank 0 boot loader, and the compiled PRG
    payload copied into RAM on startup.
  • Existing .prg and .d64 output paths are unchanged.
  • Added integration tests covering the CRT header, bank layout, payload copy,
    and boot stub.

2026-08-18 – Struct types (type ... endtype)

  • New user-defined struct types with type NAME ... endtype blocks. Fields
    are declared inside as var name: int, var name: word, or var name: float.
    Element size is the sum of field widths (no padding).
    type TEntity
    var x: int
    var y: int
    var hp: word # 1 + 1 + 2 = 4 bytes per element
    endtype
  • Array-of-struct declarations look like regular arrays:
    var enemies: TEntity = array(N) ; allocates N * elem_size bytes at $C000+
  • Field access on both sides of assignment:
    enemies[0].x = 5 ; constant idx → STA absolute
    enemies[i].hp = enemies[i].hp + 1 ; variable idx → shift-add mul + (ptr),Y
  • Constant-index writes/reads fold to bare LDA/STA absolute. Variable-index
    access emits a small shift-and-add multiplier for idx * elem_size (uses
    ASL A chains for power-of-two sizes, or a shift-add sequence for arbitrary
    sizes via two scratch zero-page bytes), then indexed (ptr),Y access.
  • Word and float fields write both bytes (little-endian): STA addr / STA addr+1
    for constant idx, STA (ptr),Y ; INY ; STA (ptr),Y for variable idx.
  • Float fields use Q8.8 fixed-point format (hi byte = integer, lo byte =
    fraction × 256). Reading a float field into a float variable works via
    gen_word_assign; printing a float struct field uses print_fixed ("N.DD").
  • The parser resolves arr[i].field into Stmt::StructSet and Expr::StructGet
    nodes that carry (field_offset, field_kind, elem_size) — codegen is
    self-contained and needs no type table lookup.
  • Not yet implemented in this release: struct-typed sub/fn parameters,
    default field values (parsed for compatibility with the proposal but not
    initialized at allocation time), nested struct fields, multi-dimensional
    struct arrays, string fields. Errors reported: struct-typed var without
    array(N) initializer, and unknown-field access.
  • Added examples/type_demo.ub and CPU-run integration tests for constant
    and variable index, int, word, and float field kinds, and the unknown-field
    error.

2026-08-18 – Count-down for-loop fix

  • for / loop with a negative constant step (e.g. for i = 20 to 0 step -2)
    used to compile but never enter the body, and — for values that wrap past 0 —
    loop forever. The exit test was unsigned-only, so counting down from 20 to 0
    kept re-entering after the 254 (= -2 mod 256) wrap-around.
  • The compiler now inspects the sign of a constant step at code generation:
    • positive step or default +1 → BCC/BEQ to body, unchanged (exit when var > to)
    • negative constant step → BCS to body (exit when var < to), plus a
      post-increment BCS loop_top ; JMP exit that catches the ADC underflow
      (carry clear) when var wraps below 0. This is what keeps
      for i = N to 0 step -k finite.
  • A default-step for/loop where from > to (e.g. loop i = 10 to 1, no
    step) used to silently skip the body — 0 iterations, no diagnostic. It is
    now a compile-time error:
    "for-loop: from (10) > to (1) with default step +1 loops 0 times
    — use 'step -1' to count down"
  • Non-constant step (variable or expression) is still treated as positive at
    compile time. Use a while loop when you need count-down with a runtime step.
  • Added examples/countdown_demo.ub (working forms) and
    examples/countdown_errors_demo.ub (the compile-time error form).
  • Added integration tests: a codegen check that count-down loops emit BCS
    for the body branch, a check that count-up loops still emit BCC + BEQ,
    should_panic coverage of the from>to default-step error, and CPU-run tests
    that execute the loop and verify it terminates with the expected iteration count.

2026-08-18 – --explicit CLI flag

  • Added the --explicit build flag:
    ub build myprog.ub --explicit
    With the flag, every var, sub-parameter, and fn-parameter declaration
    must carry a :type annotation. Untyped declarations become compile-time
    errors:
    line 12: 'explicit' mode: 'var loose' has no type
    — use 'var loose: int|word|float|string'
    line 14: 'explicit' mode: parameter 'a' has no type
    — use 'a: int|word|float|string'
  • Array declarations (array(N), array_word(N)) and const NAME = value
    are unaffected — their type is fixed by the declaration form.
  • The flag is CLI-only; no source-level directive keyword. Existing programs
    compile unchanged without the flag. Add it to a Makefile / build script to
    enforce typed style project-wide.
  • Added examples/explicit_demo.ub (annotated code that builds under the flag)
    and examples/explicit_errors_demo.ub (loose code that builds without the
    flag but reports errors with it).
  • Added integration tests covering the typed-ok case, untyped var / sub-param /
    fn-param errors, and the without-flag regression.