Adds functionality to the original Min programming language for the Minimal 64x4 Home Computer by Carsten Herting. Based on Carsten's original work.
The primary enhancements to Min in this extended version include:
- Support for various hardware expansion cards made for the Minimal 64x4, notably the multiplication accelerator
- Additions of typed compile-time constants to avoid the use of magic numbers and repeated string literals.
- Support for the
long32-bit signed integer type. - Introduction of explicit type casting to avoid silent value truncations.
- Ability to print to not only the screen, but also the UART connection.
- Sized buffer declarations (
char buf[64],int table[N]) with optional comma-separated initializers. - Compact hex byte blobs for large
chardata tables. - Built-in query functions
len()andsizeof()for inspecting variable element counts and buffer capacity. - Dynamic list operations
append(),pop(), andempty()for using sized buffers as lists.
Extended Min must be built with the BespokeASM assembler rather than the Minimal 64x4 assembler. You can use the compile skill below or directly build Extended Min with this command:
bespokeasm compile -c /path/to/slu4-minimal-64x4.yaml -n -p -t intel_hex extended-min.min64x4The resulting Intel Hex output is then transferred to the Minimal 64x4 via the UART connection using its receive command. Once downloaded to the Minimal 64x4, pay attention to the start and stop address of the downloaded Intel Hex, then save the code to a program file on the Minimal 64x4 with the command save XXXX YYYYY xmin, where XXXX is the start address (typically hex 1000) and YYYY is the stop address (something around hex 3B00).
Alternatively, use the Intel Hex compilation of the most recent release of Extended Min from the releases in this repository on GitHub. Note that the acc variant is intended to be used with the multiplier accelerator card.
This repository includes local Codex skills under skills/.
They are intended to make common Extended Min workflows reusable and portable within this repo.
Current skills:
skills/compile-min-64x4/: compile Minimal 64x4 assembly with a repo-local helperskills/optimize-size/: optimize Extended Min branch/jump layout for size and speed
bespokeasminstalled and available onPATH- either
curlorwgetavailable onPATH
The compile skill fetches the Minimal 64x4 BespokeASM configuration from the BespokeASM GitHub repository into /tmp, so the skills do not depend on host-specific absolute paths.
Read:
Direct script usage:
skills/compile-min-64x4/scripts/compile_min64x4.sh extended-min.min64x4
skills/compile-min-64x4/scripts/compile_min64x4.sh extended-min.min64x4 -- -D USE_ACCELERATORRead:
Typical usage:
cp extended-min.min64x4 /tmp/extended-min.candidate.min64x4
skills/optimize-size/scripts/optimize_dual.sh /tmp/extended-min.candidate.min64x4 candidate
skills/optimize-size/scripts/collect_metrics.sh \
/tmp/extended-min.candidate.min64x4 \
/tmp/optimize-size.candidate.noacc.pretty \
/tmp/optimize-size.candidate.acc.prettyThe optimize-size skill depends only on the repo-local compile skill and the standard host tools listed above.
Any file ending in *.min should be runnable by both Carsten's original Min and this Extended Min, with one exception: if the Min code performs a type change operation (e.g., assign an int value to a char type), Extended Min will throw an error given its new guards against silent value truncation. If that value truncation is something you do want, introduce the explicit cast operation to the code, and make the file an *.xmin type as the original Min does not support type casting.
Any file ending in *.xmin is intended to be run in Extended Min only.
This section is for writing Extended Min programs with file extensions of *.xmin. For interpreter internals and the full grammar, see ARCHITECTURE.md.
Extended Min is line-oriented and indentation-sensitive. Blocks may use either:
- a newline followed by deeper indentation
- a trailing
:before that indented block
Examples:
if x == 10:
print("yes\n")
else:
print("no\n")
def add(int a, int b):
return a + b
Comments start with #.
Multiple simple statements may be written on one line with ;.
Extended Min supports three runtime scalar types:
char: 8-bit unsigned valueint: 16-bit signed valuelong: 32-bit signed value
Examples:
char c = 65
int n = 1234
long big = 70000
Unlike original Min, Extended Min does not silently narrow values across types. Use an explicit cast when narrowing or widening intentionally.
Cast syntax is function-style:
char(expr)int(expr)long(expr)
Examples:
int n = 300
char c = char(n)
long big = long(n)
Extended Min adds compile-time constants declared with :=.
These are resolved during tokenization and do not exist as runtime variables.
Supported constant declaration types:
intcharlong
Examples:
int ScreenBase := 0x0080
long BigValue := 0x12345678
char LetterA := 65
char Banner := "HELLO"
Constant rules:
- a constant must be declared before it is used
charconstants may hold either a numeric byte value or a string-like byte sequence- function-local constants are visible only within that function
- string-like
charconstants are stored internally as length-tracked tokenized text, but when used as runtimecharvalues they behave as null-terminated byte sequences
Normal variable definition:
int score = 0
char name = "MIN"
Bind a variable to a fixed address with @:
char io @ 0x00ff
io = 1
Declare a buffer with a fixed capacity using [N]:
char buf[64]
int table[10]
The size expression N is evaluated at runtime, so variables are allowed:
int sz = 20
char data[sz]
Sized buffers can be combined with an initializer. The buffer capacity is N; the initial element count comes from the initializer:
char header[16] = 0x44, 0x2a, 0x0e
int lookup[8] = 10, 20, 30
For dense binary char data, use a compact hex byte blob. The blob can span physical source lines and is tokenized as raw bytes instead of one integer expression per value:
char data[8] = $(
0xc9, 0x1b, 0x2e, 0x2c,
0x3d, 0x1d, 0x86, 0x30
)
Blob values must be hex bytes (0x00 through 0xff). Whitespace, comments, physical newlines, and commas are allowed inside the blob. After initialization, data is a normal char buffer, so indexing, slicing, len(data), and by-reference calls work normally.
The blob expression itself is always a char byte sequence. To view the same bytes as int or long elements, declare the blob as char and bind a typed variable to its address with @ &name. Multi-byte values are little-endian:
char rawWords = $(
0x34, 0x12, # 0x1234
0x78, 0x56 # 0x5678
)
int words[2] @ &rawWords
print(words[0], " ", words[1], "\n")
char rawLongs = $(
0x78, 0x56, 0x34, 0x12
)
long values[1] @ &rawLongs
The typed view aliases the same memory; it does not copy or repack bytes. Keep the raw char buffer alive for as long as the typed view is used, and make sure the byte count matches the typed element count.
Writing beyond the initial count but within the declared capacity is valid:
char buf[64] = "HI"
buf[10] = 0x41
A "Buffer overflow" error is raised if the initializer has more elements than the declared size.
Sized buffers also work with fixed-address bindings:
char mmio[8] @ 0xFE00
Commas may be used as element separators in any variable initializer, as an alternative to the _ concatenation operator:
char rgb = 0x10, 0x20, 0x30
int coords = 100, 200, 300
char msg = "HEL", "LO"
Commas and _ may be mixed freely:
int data = 1 _ 2, 3 _ 4
In print(), serial(), output(), and function call argument lists, commas remain optional separators between distinct arguments (not concatenation).
Array-like storage can also be allocated by slicing the variable against itself (the original Min idiom):
char text = text[|16]
int nums = nums[|10]
The [N] sized buffer syntax above is the preferred form for new code.
text[0] = "A"
print(text[0|5])
print(nums[i])
print(bigs[0])
Array-style storage works with all three runtime data types:
charintlong
Slice form is:
var[start|end]
where end is exclusive.
len(var) returns the current element count of a variable:
char msg = "Hello"
print(len(msg)) # prints 5
char buf[64] = 1, 2, 3
print(len(buf)) # prints 3 (initialized count)
sizeof(var) returns the buffer capacity (allocated size) of a variable:
char buf[64] = 1, 2, 3
print(sizeof(buf)) # prints 64 (capacity)
print(len(buf)) # prints 3 (current count)
A sized variable is one declared with [N] (e.g., char buf[64]). The capacity (sizeof) is the declared size N, which may be larger than the current element count (len). An unsized variable has no [N] (e.g., char msg = "Hello" or int x = 42). Its capacity equals its element count, so sizeof(var) == len(var).
Sized buffers can be used as dynamic lists with append, pop, and empty:
int stack[32]
empty(stack)
append(stack, 10)
append(stack, 20)
append(stack, 30)
print(len(stack)) # prints 3
int top = pop(stack)
print(top) # prints 30
print(len(stack)) # prints 2
empty(stack)
print(len(stack)) # prints 0
print(sizeof(stack)) # prints 32 (capacity unchanged)
append(var, expr)— writes the value at positionlen(var)and increments the count. Raises "Buffer overflow" if the buffer is full.pop(var)— returns the last element and decrements the count. Raises "Buffer empty" if the count is zero.empty(var)— sets the count to zero without changing the capacity or stored data.
Indexed assignment (var[i] = x) does not update the element count. Only append, pop, empty, and whole-value assignment (var = expr) modify it.
Arithmetic:
+-*/- unary
-
Comparisons:
==!=<<=>>=
Bitwise and logical-style operators:
notandorxor<<>>
String/array concatenation:
_
Examples:
int x = 5 + 3 * 2
if x >= 10:
print("big\n")
char msg = "HEL" _ "LO"
Extended Min supports:
=+=-=
Examples:
count = 10
count += 1
count -= 2
count += -10
Indexed assignment is also supported:
buf[i] = 65
Notes:
- normal indexed and whole-value assignment work with
char,int, andlong - the current fast
+=/-=optimization is implemented forchar,int, andlong
Supported control-flow statements:
ifelifelsewhilebreakreturn
Examples:
while n > 0:
print(n)
print("\n")
n -= 1
if a == b:
print("same\n")
elif a < b:
print("less\n")
else:
print("greater\n")
Function definitions use def.
Parameters are typed, and may optionally be by-reference with &.
Examples:
def add(int a, int b):
return a + b
def clear_byte(char &dst):
dst = 0
Calling a function:
int total = add(10, 20)
Top-level def only:
- functions are defined at top level
- nested function definitions are not supported
Three output functions share the same syntax and argument handling:
print(...)— sends output to the screenserial(...)— sends output to the UART (serial port)output(...)— sends output to both the screen and the UART
Examples:
print("score = ", score, "\n")
serial("debug: ", value, "\n")
output("log: ", msg, "\n")
Behavior:
charpayloads print as strings/byte sequencesintvalues print in decimallongvalues print in decimal
Import another source file:
use "mathlib.xmin"
Call a fixed machine-code address:
call 0xf033
call requires an integer constant address.
int Step := 5
char Hello := "HELLO"
def bump(int n):
n += Step
return n
int value = bump(10)
print(Hello)
print("\n")
print(value)
print("\n")