This note records the design discussions from 2026-10-25 so the
questions do not have to be re-derived later. It covers TBITSIZE,
address arithmetic, set ordering, complex types, pragmas, UTF-8 string
libraries, collections, the TopSpeed B-tree Toolkit, communications,
pyserial, ModBase, network-library design, terminal escape libraries,
PIM versus ISO coroutines, external assembly, and the dynamic-set
design. It is not a commitment to implement everything.
State at the time of writing: suite 310/310, one-shot fixpoint OK
(3,845,439 bytes), per-object fixpoint OK (11 objects), corpus
521/612 compile-OK, 0 crashes, 64 LANG and 27 LIB.
TBITSIZETBITSIZE is a GNU Modula-2 SYSTEM procedure function:
PROCEDURE TBITSIZE (<type>) : CARDINAL;
It returns the minimum number of bits needed to represent a type,
not its normal storage size in bytes. It is mainly useful for packed
records/bitfields. A BOOLEAN may be one bit logically but occupy a
word in an unpacked record.
M2WIDESET).SIZE/TSIZE report storage size in bytes.TBITSIZE(BYTE) = 8 works in CONST;It is a small-to-medium language gap, not a runtime feature.
ADDRESS arithmeticISO SYSTEM defines:
PROCEDURE ADDADR (addr: ADDRESS; offset: CARDINAL): ADDRESS;
PROCEDURE SUBADR (addr: ADDRESS; offset: CARDINAL): ADDRESS;
PROCEDURE DIFADR (addr1, addr2: ADDRESS): INTEGER;
PROCEDURE MAKEADR (high: <type>; ...): ADDRESS;
V3 currently supports:
ADRADDRESS/pointer assignmentADDRESS/pointer equalityVAL/CAST for compatible scalar representationsV3 does not support:
ADDADR/SUBADR/DIFADR/MAKEADRADDRESS + offset, ADDRESS - offset, ADDRESS - ADDRESSPOINTER TO valuesADDRESS is an opaque pointer-like value in V3's type system. Raw
pointer arithmetic is not part of the PIM/ISO base language and would
weaken V3's separation between typed pointers and addresses.
Add the ISO SYSTEM functions first, not general pointer arithmetic:
ADDADR/SUBADR;DIFADR returning the byte difference (range-checked for INTEGER);MAKEADR;For external C/assembly interaction, a C helper remains the pragmatic escape hatch.
PIM/ISO set operations are:
=, inequality #, membership IN;+, difference -, intersection *, symmetric difference /.There is no standard <, <=, >, >= ordering for sets. GNU
Modula-2 adds inclusion ordering:
a <= b (* a is a subset of b *)
a < b (* a is a proper subset of b *)
a >= b (* a is a superset of b *)
a > b (* a is a proper superset of b *)
V3 currently rejects these; corpus case largeset5 remains a LANG
failure.
Fixed/inline sets:
a <= b iff (a AND NOT b) == 0 for every word
a < b iff a <= b AND a # b
Dynamic sorted-range sets:
a <= b iff every range of a is covered by a range of b
a < b iff a <= b AND a # b
Compiler work:
SymTab.RelCheck/OrdCheck: accept set ordering;Lower: branch fixed/dynamic set ordering;QbeGen: subset/superset comparisons;largeset5 would likely move from LANG to compile-OK.This is a safe gm2 compatibility extension, not standard PIM/ISO.
Low-level SYSTEM primitives:
TYPE PROCESS;
PROCEDURE NEWPROCESS (body: PROC; workspace: ADDRESS;
size: CARDINAL; VAR p: PROCESS);
PROCEDURE TRANSFER (VAR from: PROCESS; to: PROCESS);
PROCEDURE IOTRANSFER (VAR from, to: PROCESS;
interruptNo: CARDINAL);
IOTRANSFER uses an explicit interrupt number.V3 implements this with ucontext; IOTRANSFER currently degrades to a
normal transfer because there is no interrupt subsystem.
COROUTINESDEFINITION MODULE COROUTINES;
TYPE
COROUTINE;
INTERRUPTSOURCE = CARDINAL;
PROTECTION = [UnassignedPriority .. 7];
PROCEDURE NEWCOROUTINE (procBody: PROC; workspace: ADDRESS;
size: CARDINAL; VAR cr: COROUTINE;
[initProtection: PROTECTION]);
PROCEDURE TRANSFER (VAR from: COROUTINE; to: COROUTINE);
PROCEDURE IOTRANSFER (VAR from: COROUTINE; to: COROUTINE);
PROCEDURE ATTACH (source: INTERRUPTSOURCE);
PROCEDURE DETACH (source: INTERRUPTSOURCE);
PROCEDURE IsATTACHED (source: INTERRUPTSOURCE): BOOLEAN;
PROCEDURE HANDLER (source: INTERRUPTSOURCE): COROUTINE;
PROCEDURE CURRENT (): COROUTINE;
PROCEDURE LISTEN (p: PROTECTION);
PROCEDURE PROT (): PROTECTION;
PROCEDURE TurnInterrupts (to: PROTECTION): PROTECTION;
PROCEDURE ListenLoop;
END COROUTINES.
Main differences:
| Area | PIM | ISO |
|---|---|---|
| Context type | PROCESS |
COROUTINE |
| Creation | NEWPROCESS |
NEWCOROUTINE + optional protection |
| Switch | TRANSFER |
TRANSFER |
| Interrupt transfer | IOTRANSFER(..., interruptNo) |
IOTRANSFER(from,to) with attached source |
| Current context | none | CURRENT |
| Interrupt registration | none | ATTACH/DETACH/IsATTACHED/HANDLER |
| Protection/priority | none | PROTECTION, LISTEN, PROT, TurnInterrupts |
| Event loop | user-written | ListenLoop |
V3 has PIM coroutines but no ISO COROUTINES module. A pure
coroutine subset (NEWCOROUTINE, TRANSFER, CURRENT, workspace
management) could map onto the existing ucontext layer. The real
interrupt/protection parts need an OS signal/event layer.
ASMExternal assembly works today through EXTERNAL and the normal linker;
no compiler support for inline ASM is required.
DEFINITION MODULE AsmOps;
PROCEDURE Add (a, b : CARDINAL) : CARDINAL;
END AsmOps.
IMPLEMENTATION MODULE AsmOps;
PROCEDURE Add (a, b : CARDINAL) : CARDINAL;
EXTERNAL;
END AsmOps.
.text
.globl Add
.type Add,@function
Add:
movl %edi, %eax
addl %esi, %eax
ret
cc -c asm_add.s -o asm_add.o
M2 -c AsmOps.def AsmOps.mod
qbe -o AsmOps.s AsmOps.ssa
cc -c AsmOps.s -o AsmOps.o
cc Prog.o AsmOps.o asm_add.o runtime/syslib/shim.c -o Prog -lm
For M2make --separate, add the assembly object with --extra
asm_add.o or link manually.
amd64_sysv.INTEGER/CARDINAL are 32-bit; LONGINT is 64-bit.l values.VAR parameters receive addresses.ARRAY OF CHAR, are passed as descriptor
addresses, not raw C arrays.For anything beyond scalar arguments, use a small C wrapper around the assembly routine so the V3-facing signature is C-compatible.
This is the recommended route for low-level code; inline ASM is
unnecessary.
SET OF CARDINAL / INTEGER — chosen designV3 now uses a sorted linked list of disjoint inclusive ranges:
set = pointer to
{ lo : CARDINAL; hi : CARDINAL; next : pointer }
This was chosen over a full 2^32-bit bit-vector (512 MiB per set value) and over a linked list of individual bits (too much per-value overhead). The range-list representation is:
INTEGER values map with VAL(CARDINAL, x) XOR 0x80000000.Implemented operations include empty/range literals, INCL/EXCL/IN,
assignment, equality, union/intersection/difference and symmetric
difference. Bounded sets such as SET OF [0..100000] keep the inline
bit-vector form.
SET CONST is rejected (loud 230), because no static
range-list layout exists yet;malloc; assignment frees old lists but there is no GC or
program-exit cleanup;PACKEDSET remains capped at 32 bits.Future work: runtime-initialised dynamic constants, deep composite copy, ownership/lifetime rules, and optionally a hash/paged bitset if range lists become too fragmented.
V3 currently has no complex arithmetic. COMPLEX32, COMPLEX64 and
COMPLEX128 are declaration-compatible aliases of REAL (currently 8
bytes each), so they are placeholders, not real complex types. Their
names imply two 4-, 8- and 16-byte components (8, 16 and 32 bytes
respectively), so the current aliases are also the wrong size. The
comparison table correctly marks COMPLEX/LONGCOMPLEX as unsupported.
ISO Modula-2 treats COMPLEX/LONGCOMPLEX as predefined types.
The language is expected to provide:
+, -, *, /);CMPLX constructor;RE/IM accessors;Transcendental functions live in the separate ComplexMath and
LongComplexMath library modules. The ISO architecture is therefore:
compiler-known COMPLEX/LONGCOMPLEX types
+ operator lowering / builtins
+ ComplexMath / LongComplexMath libraries
A separate library can provide complex-number functions, but it cannot
by itself provide natural Modula-2 syntax such as a + b or CMPLX.
Operator overloading does not exist in Modula-2, so a library-only design
is necessarily a nonstandard utility API.
Classes are a poor fit for complex numbers:
a.Add(b) instead of a + b;CLASS is Clarion-form);RE/IM syntax.Use a value record or a compiler-known type, not a class.
V3 has ISO 10514-2 generics, so a generic implementation could share code between real and long-real variants:
GENERIC MODULE Complex(T);
TYPE Value = RECORD re, im : T END;
...
END Complex.
That is useful for implementation sharing, but generics still do not
provide operators, literals or ISO source compatibility. Since V3's
REAL and LONGREAL are both 8 bytes today, two small concrete modules
are also acceptable and simpler for separate compilation.
For practical complex math without an ISO-conformance claim:
DEFINITION MODULE Complex;
TYPE
T = RECORD
re, im : REAL
END;
PROCEDURE Add (a, b : T) : T;
PROCEDURE Sub (a, b : T) : T;
PROCEDURE Mul (a, b : T) : T;
PROCEDURE Div (a, b : T) : T;
PROCEDURE Conj (z : T) : T;
PROCEDURE Abs (z : T) : REAL;
PROCEDURE Arg (z : T) : REAL;
END Complex.
Advantages: value semantics, no allocation, simple ABI, easy to test.
Limitations: call syntax only; no operator or literal support; does not
satisfy ISO COMPLEX.
Do not call this type COMPLEX or reuse the existing COMPLEX*
aliases, because that would falsely suggest ISO support.
Implement COMPLEX/LONGCOMPLEX as compiler-known value types, with
arithmetic lowered to runtime helpers:
COMPLEX = two REAL values
a + b -> __m2_complex_add(a, b)
a * b -> __m2_complex_mul(a, b)
CMPLX -> compiler builtin
RE/IM -> compiler builtins
Then implement ComplexMath and LongComplexMath as libraries on top.
This avoids requiring QBE to understand a native complex type while still
providing ISO syntax and semantics.
Work items:
COMPLEX is 16 bytes with the current 8-byte
REAL; COMPLEX32 cannot be 8 bytes until a real 4-byte SHORTREAL
exists);CMPLX, RE, IM and constant expressions;abs, arg, conj, sqrt, exp, ln,
trigonometric functions and polarToComplex;For C interop, do not assume the C99 _Complex ABI without checking;
prefer small C wrappers where necessary.
Complex library first, with no classes and no fake COMPLEX names.COMPLEX32/64/128 REAL aliases so programs cannot silently get
wrong-size, non-complex values.V3 has no general pragma facility. It currently parses and ignores two
gm2 declaration pragmas (__BUILTIN__ and __ATTRIBUTE__
__BUILTIN__ (( name ))) after PROCEDURE, and its TopSpeed sidecar
lexes (*# ... *) as an ordinary comment. The local EXCEPTIONS.def
therefore omits <* noreturn *>.
There are two separate design questions:
The recommendation is to separate them: use the portable, scoped model as the native design, and add a narrow TopSpeed compatibility mode.
TopSpeed Modula-2 pragmas are a special form of comment:
(*# save *)
(*# check(range => on, index => off, overflow => off) *)
PROCEDURE P (...);
(*# restore *)
They are stateful compiler directives:
save / restore maintain a stack of compiler settings;They are grouped into pragma classes:
check(...) — range, index, overflow, NIL, stack checks;call(...) — calling convention, register parameters, interrupt
handlers, open-array passing;data(...) — segments, packing, volatile, enum size, stack/heap size;optimize(...) — CSE, register allocation, peephole, CPU choice;module(...) — initialization code, implementation-object rules;name(...) — external symbol naming;debug(...) — debug information and procedure tracing;option(...) — language extensions such as bitwise operations on
cardinals;save / restore — compiler-option stack;link, project, define — project-system integration.Older $ compiler directives are also accepted by TopSpeed for
compatibility.
Strengths
(*# ... *).Problems for V3
reg_param, iopl, memory models, overlays, CPU and
coprocessor selection. These do not map to V3's amd64/QBE target.(*# ... *) is a comment to other compilers, ignoring it
silently changes checks, layout or ABI without any diagnostic.TopSpeed pragmas are therefore useful as a compatibility adapter, not as V3's native pragma model.
The portable proposal (used by Modula-2 R10/BSK) uses <* ... *>:
PROCEDURE Reboot <*NORETURN*>;
TYPE Header = RECORD <*PACKED*>
...
END;
VAR Port <*VOLATILE*> : BYTE;
It defines:
<*v3.SomeOption=...*>;PRESETS / UNSET for external pragma settings instead of a
TopSpeed-style global save / restore;DEPRECATED, FFI, FFIDENT, INLINE and
PRIVATETO, plus ENCODING where Unicode is supported.The full proposal is much larger: ABI, ADDR, ALIGN, PACKED,
PADBITS, LEADBITS, VOLATILE, NORETURN, PURE, WEAK, IN,
OUT, SINGLEASSIGN, ARC, GC, OWNER, BORROWER, RETAIN,
RELEASE, MSG, TELL, TICKET and others. R10 itself omits some of
these (RANGE, RELEASE, RETAIN).
Strengths
<* ... *> aligns with ISO-style/gm2 pragma syntax.Problems
PURE/WEAK/DETM need effect checking, IN/OUT need
parameter-write analysis, NORETURN needs control-flow checks and
SINGLEASSIGN needs data-flow checks.ARC, GC, OWNER, BORROWER,
RETAIN, RELEASE; RESIDENT, CONTIGUOUS and LOWLATENCY are
mostly OS/runtime hints; ENCODING needs scanner/Unicode work.ADDR, ALIGN, PACKED, PADBITS and LEADBITS require real
layout support.| Aspect | TopSpeed V3 | Benjamin / portable |
|---|---|---|
| Status | Vendor dialect | Proposed portable specification, used by R10/BSK |
| Delimiters | (*# ... *) |
<* ... *> |
| Form | class(name => value, ...), save, restore |
PRAGMA, PRAGMA=expr, clause sequences |
| Scope | Stateful from point forward | Attached to explicit syntactic scopes |
| ABI/layout | Many 16-bit-specific directives | Portable ABI/layout annotations |
| Unknown pragmas | Often ignored or comment-like | Standard versus implementation-defined, diagnosable |
| Separate compilation | Needs external project-state discipline | Naturally attachable to symbols and object stamps |
| V3 fit | Compatibility adapter | Native pragma design |
TopSpeed is not a standard; the <* ... *> syntax is the better
long-term direction because it is already used by ISO-style/gm2
compilers and by the portable proposal.
Use <* ... *> with explicit scope attachment as the native pragma
syntax. Do not make (*# ... *) the default; handle it only in the
TopSpeed sidecar or behind an explicit compatibility flag.
Phase 1 — infrastructure and low-risk semantics
<* ... *> and adjacent blocks;DEPRECATED;NORETURN;NOINLINE;INLINE as advisory, with a warning if not honoured;MSG / TICKET diagnostics if desired;PRIVATETO.NORETURN is especially valuable immediately: it lets V3 consume
standard ISO/gm2 definition files without stripping pragmas.
Phase 2 — layout and ABI
PACKED;ALIGN / MEMALIGN;FFI / FFIDENT;ADDR only once absolute variables and address arithmetic are
designed;PADBITS / LEADBITS only after packed-record layout exists.Every ABI/layout pragma must participate in:
M2make --separate.Otherwise two objects can disagree about record layout or symbol names and still link.
Phase 3 — contracts
PURE, WEAK, DETM;IN / OUT;SINGLEASSIGN.These should not be accepted as no-ops. If V3 cannot verify them, it should reject them or emit a clear "accepted but not enforced" warning. Silently pretending to enforce a promise is worse than not supporting it.
Do not implement initially
ARC, GC, OWNER, BORROWER, RETAIN, RELEASE;RESIDENT, CONTIGUOUS, LOWLATENCY;PRESETS / pragma-file machinery;ENCODING.These need runtime support V3 does not have, or are platform hints with little value on the current target.
Keep TopSpeed pragmas out of the default V3 grammar. Handle them in the
existing TopSpeed-V3-M2.atg sidecar, or add a
--topspeed-pragmas mode to the main compiler.
Map only the meaningful subset:
check(range/index/overflow/nil/stack);data(packed/volatile);module(init_code);call(c_conv);save / restore.Reject or warn about:
near_call, far, segments, seg_name, ds_*;reg_param, reg_saved, reg_return;interrupt, iopl;cpu, copro;TopSpeed stateful pragmas can be translated by keeping an option stack during parsing and snapshotting the current state onto each declaration. That is an adapter, not the native model.
v3.* pragma should be a warning or informational message.Otherwise V3 will compile code with different checks or ABI and call it success.
trijezdci/M2-Pragmas wiki, especially Pragma Specification,
Pragma Placement and Pragma Scopes.V3 has good language-level Unicode support but only a thin library layer. The architecture is "UTF-8 at the edges, codepoints internally", not "UTF-8 strings everywhere".
UCHAR — 32-bit code point.UString — codepoint-based literals and ARRAY OF UCHAR values.U'...' / U"..." literals with strict RFC 3629 decoding at
compile time (error 234 on malformed input).UCHR, CHR8, UORD.UString whole-string assignment to a fixed ARRAY OF UCHAR
(count-clamped, zero-codepoint terminated).+ concatenation for UString literals and single-codepoint
operands, plus LEN/LENGTH/HIGH for literals.runtime/syslib/Utf8 — scalar codec:
Decode (one sequence from a byte array to UCHAR and next) and
Encode (one UCHAR to bytes). MaxUTF8Length = 4,
MaxCodePoint = 1114111.lib/iso/TextIO — UTF-8 output:
UWriteChar, UWriteString, UWriteLn.utf8_prog, utext_prog, t_ustrcat, t_ustrassign,
t_unicode, plus malformed-literal rejection tests.Utf8 lives in runtime/syslib and is always shared. TextIO is in
lib/iso, so its U* output helpers are ISO-dialect library API; other
dialects can import Utf8 manually.
| Area | Current state |
|---|---|
| UTF-8 codec | One codepoint at a time only |
| Whole-string validation | Manual Decode loop only |
| UTF-8 input | None |
| UTF-8 output | TextIO.U* only |
| UTF-8 length/index/slice | None |
| UString library | None |
| UString comparison/search | None |
| Dynamic UString | None |
| Unicode case conversion | None |
| Unicode character classes | CharClass is ASCII-only |
| Normalization / collation / grapheme clusters | None |
| Locale/bidi support | None |
lib/common/Strings is byte-oriented:
Length counts bytes, not codepoints;Slice, Pos, Insert, Replace and Delete use byte positions;Capitalize is explicitly ASCII;lib/common/CharClass is ASCII-only; there is no UCHAR version and
no Unicode category lookup.
Input is byte/line oriented. SysIO, STextIO, IOChan, Files and
StreamFile have no UTF-8 decoding or validation policy.
A U"..." literal is a descriptor { count, codepoints... }. A
declared ARRAY OF UCHAR is a fixed-length array. As a result:
LEN/HIGH on a fixed array refer to declared bounds, not the number
of codepoints actually assigned;TextIO.UWriteString writes 0 .. HIGH(u), not just through a zero
terminator;UString concatenation is bounded and transient. The shim uses a static
4096-codepoint buffer, silently truncates beyond that, and the result is
valid only until the next m2ustrcat call. Single-codepoint conversion
uses a small rotating pool (16 descriptors). This is adequate for
literal expressions, not for a general string runtime.
Utf8.Encode documents that it returns 0 for a non-scalar value, but
its implementation does not reject the surrogate range D800..DFFF.
Those values are currently encoded as three-byte UTF-8 sequences, which
is invalid UTF-8. Add the surrogate check and a regression test.
Keep byte strings and codepoint strings as separate layers. Do not make
Strings Unicode-aware in place; existing code expects bytes.
1. UTF-8 byte-string module
For interoperability with files, sockets, C APIs and existing V3
ARRAY OF CHAR strings:
ValidateLength (codepoint count)ByteLengthIndex (codepoint index to byte offset)Next / PrevSliceCompareFindToUStringFromUStringThis is the most practical "UTF-8 string" library for V3 because CHAR
arrays are already the byte-oriented representation.
2. UString codepoint module
For operations over ARRAY OF UCHAR:
First resolve the length model: either consistently use a zero terminator, or introduce an explicit descriptor with separate length and capacity. Do not mix both.
3. Input/output
Add UTF-8 input alongside output:
UReadChar / UReadString (or equivalent);UCHAR/UString values.4. Unicode properties and case
This is a later layer:
These need generated Unicode tables, not hand-written ranges.
Utf8.Encode surrogate rejection and add a test.Strings/CharClass byte/ASCII semantics and add separate
Unicode modules rather than changing existing behaviour.V3 has no collections library. What exists is only infrastructure:
lib/common/Storage — ALLOCATE, DEALLOCATE, REALLOCATElib/gm2/SysStorage — raw allocationlib/gm2/DynamicStrings — heap stringscompiler/tests/showcase23lib.def — DList(T)compiler/tests/showcase24stack.def — GStack(T)Those test modules are proof of concept, not production libraries: they
have no Done/Clear, no ownership policy, and use one NEW per node.
The MyWork/Strings/DataStructures FIFO/LIFO/dynamic-array files are
incomplete and are not part of V3.
The real toolkit is installed with the TS-V3 tree. Useful references:
$HOME/bin/Dos/M2/TS-V3/INCLUDE/BTREE.DEF
$HOME/bin/Dos/M2/TS-V3/SRC/BTREE.MOD
$HOME/bin/Dos/M2/TS-V3/DOC/BTREE.DOC
$HOME/bin/Dos/M2/TS-V3/Manuals/B-Tree toolkit.odt
It is a disk-based B-tree/ISAM toolkit, not an in-memory collections library. Its architecture is:
FHandle)IHandle) in that physical fileAdd/DeleteCompareFunction and KeyFunctionFind/Search (exact and greater-or-equal), Next/Prev/Reset
cursorsAdd, Delete, Change, Open, OpenData, OpenIndex, Close,
Flush, LastError, LastFErrorPack/APACK2The current definition also includes AccessMode values such as
FixSize, Size16, Size32, NoDealoc and Compress.
The source is 16-bit DOS/OS/2 code: inline x86 assembly, segment
arithmetic, TopSpeed pragmas, FIOx, CoreMem, and raw
ADDRESS/ARRAY OF BYTE interfaces. It is a design reference, not a
portable V3 implementation.
Find, Search, Next, Prev, Reset) rather
than exposing pages.These map naturally to V3 generics:
Compare : PROCEDURE(a, b : K) : INTEGER;
Hash : PROCEDURE(k : K) : CARDINAL;
Equal : PROCEDURE(a, b : K) : BOOLEAN;
ADDRESS/ARRAY OF BYTE public interfaces: provide typed
wrappers.SHARE/FIOx locking: use V3 file APIs and POSIX locking later.Keep in-memory collections and the persistent B-tree separate.
In-memory containers
Vector(T) dynamic array
Deque(T) FIFO / double-ended queue
Stack(T) LIFO
HashMap(K,V) hash table with Hash/Equal callbacks
BTreeMap(K,V) optional ordered map/set with Compare
Vector is the foundation. Stack and Deque should reuse it or a
ring-buffer implementation. Avoid one allocation per element unless
stable references are required.
HashMap should use open addressing with tombstones or separate
chaining, a load factor around 0.7, and explicit resize/rehash. Add a
StringMap(V) convenience wrapper with owned string keys.
BTreeMap is the in-memory ordered structure: comparator callback,
first/last/next/prev, range scan, optional duplicate keys, no pager.
Persistent B-tree
BTreeFile — disk-based index, toolkit-inspired
This is a separate library, not part of Vector/HashMap. It needs:
The TopSpeed toolkit is the checklist for this stage.
Init/Done/Clear.Storage.REALLOCATE exists, but arbitrary pointer arithmetic does
not. TopSpeed uses AddAddr/IncAddr/DecAddr assembly helpers;
V3 cannot rely on that.Vector(T) needs one of:
Suggested error model:
TYPE Status = (Ok, Empty, NotFound, InvalidIndex,
OutOfMemory, DuplicateKey);
If enum handling is awkward in a given context, use integer status codes
or BOOLEAN plus LastError.
Vector(T)Deque(T) / Stack(T)HashMap(K,V)StringMap(V)BTreeMap(K,V)BTreeFile based on the TopSpeed architectureThe TopSpeed B-tree Toolkit should not delay steps 1–3.
These are three different things and should not be merged into one design:
All TopSpeed/ModBase source is commercial historical code; use it as a design reference, not copyable source. pyserial is BSD-3-Clause.
V3 has no serial, socket, terminal-emulation, scripting or database library. What exists:
lib/iso/IOChan, StreamFile, Files for byte/line channels;lib/common/SysIO and runtime/syslib/SysShim for stdio/file I/O;EXTERNAL and the C shim for libc access;Storage/SysStorage.References:
$HOME/bin/Dos/M2/TS-V3/SRC/RS232.MOD
$HOME/bin/Dos/M2/TS-V3/SRC/XMODEM.MOD
$HOME/bin/Dos/M2/TS-V3/SRC/YMODEM.MOD
$HOME/bin/Dos/M2/TS-V3/SRC/WXMODEM.MOD
$HOME/bin/Dos/M2/TS-V3/SRC/KERMIT.MOD
$HOME/bin/Dos/M2/TS-V3/SRC/TERM.MOD
$HOME/bin/Dos/M2/TS-V3/SRC/SCRIPTCO.MOD
$HOME/bin/Dos/M2/TS-V3/SRC/SCRIPTPR.MOD
$HOME/bin/Dos/M2/TS-V3/INCLUDE/RS232.DEF
$HOME/bin/Dos/M2/TS-V3/INCLUDE/FTSupport.DEF
$HOME/bin/Dos/M2/TS-V3/Manuals/Modula2-Communication-toolkit.odt
The low-level RS232 driver:
SerialRead timeout in tenths of a second;SerialWrite copies into the TX ring;Limitations:
SelectPort disables the previous handler;InitPort/Install have no error result;Install/UnInstall);The protocol layer:
FTSupport global state and FBuff;The protocol implementations are useful as protocol state-machine
references, but they call RS232 and FIO directly and drive status
windows through CommUtil/Window. They are not transport-agnostic.
Only one transfer can be active at a time.
The toolkit also includes:
SingleStep execution;WAITFOR, WHEN, macros, logging, file-transfer commands.The script VM is a useful design for an application-level automation engine, not a general V3 library requirement.
pyserial 3.5 is installed at:
/usr/lib/python3/dist-packages/serial
Its architecture:
SerialBase plus platform backends;termios, fcntl and select/poll;socket://, rfc2217://, loop://, spy://;list_ports and sysfs metadata;Useful API semantics:
in_waiting / out_waiting;cancel_read / cancel_write;flush waits for transmission to drain;send_break;flock;BOTHER, low-latency mode, RS485 on Linux;Comparison:
| Area | TopSpeed | pyserial |
|---|---|---|
| Target | DOS/OS-2 PC UART | POSIX/Windows OS tty |
| Serial layer | direct UART + IRQ | termios + OS driver + poll |
| Buffering | application ring buffers | kernel buffers + waiting counts |
| Timeouts | tenths, busy wait | read/write/inter-byte, monotonic |
| Cancellation | none | read/write cancellation |
| Multiple ports | one active singleton | multiple instances |
| Discovery | COM1–COM4 | device paths, URLs, sysfs |
| Advanced | hooks | RS485, custom baud, low latency, exclusive |
| Protocols | X/Y/WX/Kermit | none in core |
Recommendation: model V3’s serial layer on pyserial’s feature set, not on TopSpeed’s hardware driver. Keep TopSpeed’s protocol descriptions and state machines as references for optional file-transfer modules.
Core:
Serial POSIX tty transport
Transport read/write/flush/poll/cancel/close interface
Socket optional TCP transport
Loopback deterministic test transport
Protocols and application layers:
XModem / YModem / WXModem optional
Kermit only if required
Terminal optional VT100/ANSI emulation
Script optional application-level VM
The protocol modules must depend only on Transport, never directly on
Serial.
A C shim should implement the POSIX details:
m2serial_open
m2serial_configure
m2serial_read
m2serial_write
m2serial_poll
m2serial_flush
m2serial_set_modem
m2serial_send_break
m2serial_close
Use status codes, not fatal errors. Support:
in_waiting/out_waiting;A blocking read with timeout is sufficient initially. A reader coroutine or event loop can be added later; avoid binding the API to threads.
References:
$HOME/bin/Dos/M2/TS-V3/MODBASE/MODBASE3.DEF
$HOME/bin/Dos/M2/TS-V3/MODBASE/DBINDXES.DEF
$HOME/bin/Dos/M2/TS-V3/MODBASE/DBFIELDS.DEF
$HOME/bin/Dos/M2/TS-V3/MODBASE/LOCKS.DEF
$HOME/bin/Dos/M2/TS-V3/MODBASE/HASH.DEF
$HOME/bin/Dos/M2/TS-V3/Manuals/Modbase 3.odt
ModBase is a dBase III-compatible database engine:
.DBF data files, .NDX indexes, .DBT memo files;Core modules:
ModBase3 data file, record buffer, fields, memo, locks
DBIndxes B-tree index, cursors, build/rebuild, auto update
DBFields typed field access (date, logical, numeric, string)
DBRelate relational lookup through an index
Locks file/range locking and retry
DBIndxes uses 512-byte B-tree nodes, a configurable in-memory node
buffer, a 128-byte key limit, and a maximum depth of 24. Hash is an
opaque byte-key table from Repertoire, not part of the core dBase
engine.
Good ideas to borrow:
DBFile/DBIndex handles;GoTop, GoBottom, Next, Prev);Hash API shape for a generic hash table.Why not port it:
HandleIO, StringIO, LowLevel,
VStorage, ErrorManager, SmartScreen, FAPI) and DOS/OS-2
services;Recommendation:
BTreeFile;Serial POSIX transport with pyserial-inspired semantics.Transport abstraction plus Loopback tests.Socket transport.V3 currently has no socket/network support. lib/iso/IOChan is a
file/stdio-style channel API with no addresses, timeouts, non-blocking
mode or datagrams, so it can be a convenience bridge but not the primary
network API.
The network library should be layered:
Application
HTTP / WebSocket / custom protocols
|
TLS (optional)
|
Transport (connected byte stream or datagram)
|
Net (addresses, DNS, TCP/UDP sockets, listeners)
|
C shim / OS sockets
Transport is shared with the communications work:
Protocol modules should depend only on Transport, never directly on
Socket.
Use opaque handles; do not expose file descriptors or sockaddr.
DEFINITION MODULE Net;
TYPE
Family = (IPv4, IPv6);
Kind = (Stream, Datagram);
ShutdownMode = (StopReading, StopWriting, StopBoth);
Status = (Ok, WouldBlock, Timeout, Closed, Refused,
Unreachable, BadAddress, NotFound,
PermissionDenied, OutOfResources,
NetworkError, UnknownError);
Socket;
Address;
PROCEDURE Resolve(host : ARRAY OF CHAR; port : CARDINAL;
family : Family; kind : Kind;
VAR addr : Address) : Status;
PROCEDURE Listen(addr : Address; backlog : CARDINAL;
VAR listener : Socket) : Status;
PROCEDURE Accept(listener : Socket;
VAR conn : Socket;
VAR peer : Address) : Status;
PROCEDURE Connect(addr : Address; timeoutMS : CARDINAL;
VAR conn : Socket) : Status;
PROCEDURE Read(conn : Socket; VAR buf : ARRAY OF BYTE;
max, timeoutMS : CARDINAL;
VAR n : CARDINAL) : Status;
PROCEDURE Write(conn : Socket; VAR buf : ARRAY OF BYTE;
len, timeoutMS : CARDINAL;
VAR n : CARDINAL) : Status;
PROCEDURE WriteAll(conn : Socket; VAR buf : ARRAY OF BYTE;
len, timeoutMS : CARDINAL;
VAR written : CARDINAL) : Status;
PROCEDURE Shutdown(conn : Socket; mode : ShutdownMode);
PROCEDURE Close(VAR s : Socket);
PROCEDURE LocalAddress(s : Socket; VAR a : Address) : Status;
PROCEDURE PeerAddress(s : Socket; VAR a : Address) : Status;
PROCEDURE AddressToString(a : Address; VAR s : ARRAY OF CHAR);
PROCEDURE LastError(s : Socket) : CARDINAL;
Rules:
Socket and Address are opaque.ARRAY OF BYTE is the binary buffer type; text helpers wrap it.Read and Write may return short counts.WriteAll loops over partial writes with a timeout.WouldBlock is a normal result in non-blocking mode.Closed means orderly shutdown or peer close.LastError returns the OS error code for diagnostics.Do not model addresses as a single CARDINAL IPv4 value.
Use an opaque Address supporting:
Use getaddrinfo, not gethostbyname. A resolver may return several
addresses; Connect should be able to try them in order.
For listening sockets:
AI_PASSIVE for wildcard binds;IPV6_V6ONLY;SO_REUSEADDR for restart;A useful security default: servers bind to loopback unless the caller explicitly asks for a public interface.
Initial limitation: getaddrinfo is blocking and has no timeout. An
asynchronous resolver can be added later.
TCP:
Listen, Accept, Connect;Shutdown;SO_KEEPALIVE and TCP_NODELAY;UDP:
SendTo / RecvFrom;Keep TCP and UDP wrappers separate publicly even if they share a core socket module.
Do not require threads.
Phase 1:
poll in the C shim;Phase 2:
PollSet abstraction:TYPE
Event = SET OF (Readable, Writable, Error);
PROCEDURE Init(VAR p : PollSet);
PROCEDURE Add(VAR p : PollSet; s : Socket; events : Event);
PROCEDURE Remove(VAR p : PollSet; s : Socket);
PROCEDURE Wait(VAR p : PollSet; timeoutMS : CARDINAL;
VAR ready : CARDINAL) : Status;
PROCEDURE Events(p : PollSet; s : Socket) : Event;
Phase 3:
PollSet with PIM coroutines or an event loop;Use poll first; epoll is an optimization, not a public API
requirement.
Use status codes as the default; fatal errors and uncaught exceptions are not the normal path.
The shim must:
EINTR;EINPROGRESS for non-blocking connect;EAGAIN/EWOULDBLOCK to WouldBlock;SIGPIPE with MSG_NOSIGNAL or an ignored signal;Do not implement TLS. Use OpenSSL or mbedTLS through a C shim.
TLS module:
Tls.Connect(transport, hostname, ...)
Tls.Read
Tls.Write
Tls.Close
Defaults:
HTTP is later:
Content-Length;WebSocket is later still.
Start with POSIX/Linux:
socket, bind, listen, accept, connect,
send, recv, poll, getaddrinfo, setsockopt, shutdown,
close;sockaddr_storage, addrinfo, fd_set and pollfd behind the
shim.Later add a Windows Winsock2 backend behind the same API.
Do not expose sockaddr, fd_set, pollfd or raw file descriptors in
Modula-2.
runtime/syslib/net.c POSIX C shim
lib/common/Net.def/.mod addresses, DNS, sockets, status
lib/common/Transport.def/.mod connected byte transport
lib/common/Poll.def/.mod readiness/event API
lib/common/Loopback.def/.mod deterministic tests
lib/common/Tls.def/.mod optional, OpenSSL/mbedTLS
lib/common/Http.def/.mod optional, later
Possible names:
Net — socket/address layerTcp / Udp — protocol-specific wrappersPoll — event readinessTransport — shared with serialTls — optional security layerHttp — optional application protocolUse loopback and ephemeral ports only:
127.0.0.1 and ::1;SIGPIPE / write-after-close;SO_REUSEADDR;No external network is required for the suite.
Net status/address API.Poll and non-blocking sockets.Transport integration with serial/loopback.Do not start with HTTP or TLS. Do not expose raw sockets or
sockaddr. Do not require threads. Reuse the Transport abstraction
so serial, TCP, loopback and TLS share one protocol-facing interface.
The existing MyWork/VT100 repository contains four useful terminal
output libraries:
VT100 strict DEC VT100 escape sequences
VT52 strict DEC VT52 escape sequences
Ansi modern ANSI/xterm superset with colours and modes
Regis ReGIS vector graphics commands
There are 27 tests covering the output sequences, plus a SplitV1
helper used by the CPR (cursor-position report) test.
These modules are escape-sequence emitters. They generate output
such as ESC[2J, ESC[3;7H, and ReGIS commands. They do not parse
incoming terminal data or maintain a screen buffer.
That is the right scope for an output library. A terminal emulator — needed to render incoming VT100/ANSI data in the communications stack — is a separate parser/screen model and should not be confused with these modules.
The modules were written for gm2 -fiso and currently depend on:
STextIO
NumberIO
ASCII
IO
termios
FIO
V3 has STextIO and NumberIO, but the other dependencies are either
missing or not desirable in a portable library:
IO and termios are used only by Unbuffered/Buffered (raw
input for CPR replies);FIO.StdIn is used only for the same raw-input support;ASCII.esc can be replaced locally;NumberIO is PIM-oriented and should not be pulled into a common
library just to emit decimal CSI parameters.The current emitters also write directly to stdout through STextIO,
which prevents using them with a serial or TCP transport. That is the
main architectural change needed.
Add a small output sink:
DEFINITION MODULE TermOut;
TYPE Output = PROCEDURE(s : ARRAY OF CHAR);
PROCEDURE SetOutput(out : Output);
PROCEDURE WriteString(s : ARRAY OF CHAR);
PROCEDURE WriteChar(c : CHAR);
PROCEDURE WriteCard(n : CARDINAL); (* decimal, no padding *)
END TermOut.
Default output can remain STextIO, but it can be redirected to:
Transport;Transport;Then the terminal modules call TermOut.WriteString, TermOut.WriteChar
and TermOut.WriteCard, not STextIO/NumberIO directly.
Move raw-mode input into a separate optional module:
Tty — termios save/restore, raw mode, non-blocking reads
VT100.Unbuffered/Buffered and the equivalents in VT52, Ansi and
Regis should be removed from the emitters and replaced by Tty
operations. This keeps the emitters pure and portable.
lib/common/TermOut.def/.mod output sink + decimal/ESC helpers
lib/common/VT100.def/.mod strict VT100 emitter
lib/common/VT52.def/.mod strict VT52 emitter
lib/common/Ansi.def/.mod ANSI/xterm emitter
lib/common/Regis.def/.mod ReGIS emitter (optional)
lib/common/Tty.def/.mod optional POSIX raw-mode input
lib/common is the simplest location because it is included by all
dialects. A dedicated lib/term folder is possible but would require
build-selection changes.
Regis is specialist vector graphics; include it but mark it optional.
SplitV1 is test-only and should not become a core library module.
Convert the existing tests to headless byte tests:
VAR buf : ARRAY [0..1023] OF CHAR;
TermOut.SetOutput(Capture);
VT100.CursorPosition(3, 7);
Check(buf, "ESC[3;7H");
Benefits:
od byte checks become assertions;Cover at least:
The CPR/live-terminal test needs a pty or a real terminal. Keep it as an optional integration test, not part of the default headless suite.
TermOut makes the emitters usable with the communications design:
Transport;Transport;TermOut points at the transport writer;This gives a clean split:
terminal emitters -> TermOut -> Transport
terminal emulator <- Transport <- incoming bytes
The MyWork/VT100 repository currently has no explicit license file.
Before vendoring the files into V3:
The reference documents in docs/ are independent of the code.
TermOut.VT100, VT52 and Ansi to TermOut.Unbuffered/Buffered from the emitters.Tty separately.Regis.No compiler changes are required.
TBITSIZE (small, standard-facing).ADDADR/SUBADR/DIFADR (small, ISO SYSTEM).largeset5).COMPLEX* aliases.<* ... *> parser/IR plus a low-risk semantic
subset; later add the TopSpeed adapter and ABI/layout pragmas.Utf8.Encode, add byte-string
and UString modules, then UTF-8 input.Vector, Deque/Stack, HashMap, then optional
BTreeMap; persistent BTreeFile last.TermOut, VT100, VT52, Ansi, optional
Regis; Tty and headless tests.Serial/Transport first; protocols and terminal
emulation optional.Net/Transport, TCP/UDP, Poll, then optional TLS/HTTP.COROUTINES subset, then interrupt/protection layer.SET CONST and deep composite copies.External assembly needs no compiler work; document it as the supported low-level escape hatch.