TMod-c Change Log
Rebrand (July 2026): Public name TMod-c; legacy Mod-c during transition. Compiler binary target:
tmodc(modcalias retained).
Version 0.26.8.195
- Finish porting sds.c to Tsds.mc
- TMod-c 0.26.8.195: Change ERROR of 'unreachable code after BREAK/CONTINUE/RETURN' to a WARNING
- Update ABuffer in kilo.mc so it uses a preallocated buffer.
- Update header comment of Tsds.mc
- Add another example port of SDSLib 2.0 from https://github.com/antirez/sds
Version 0.26.8.194
.mhalias RHS — optional type-spec aftercomplete/opaqueon alias TYPE:export type integer complete integer 64,export type string complete const ^char. Writermh_append_type_spec; readermh_parse_alias_rhs; importsymbol_type. Old lines without a tail stay valid. Regenerated committed.mhfiles — 0.26.8 stem.- In-tree packs —
tests/fixtures/lp64(64/64/64) andilp32(32/32/32). Lean.h;-Monly. Stand-in fortmodc.lp64/tmodc.ilp32until item 15. LEN(designator)— typeinteger; C((integer)(sizeof(n) / sizeof((n)[0]))). Not a type operand. Interim until language-level array / string length. Teststest_len/test3_len.- #11 build-time defaults deferred — unbound units
stay
cint(int/unsigned int/float/const char *). - Tests green. make test-all /
selfhost / bootstrap /
promote green on gcc,
clang, and tcc. Build
194. Branch
mod-c_0.26. Promoted 0.26.8.194. - Docs this round:
CURRENT.md,changelog.md,README.md, Language Report §3.2 / §5.5 / §10.1 / §10.2,docs/syntax-ebnf.md,docs/grok_report-20260922.md.
Version 0.26.7.192
- Several
.mcinputs — leftover non-options are input files (tmodc *.mc -Candtmodc -C *.mc). Each unit is a fresh arena / parser / source buffer (process_one_unitinsrc/modc.mc). Nothing shared. - Several inputs: bare
-C/-H/-M(auto names from PROGRAM/MODULE) and optional-d. Explicit-C file.c/-H/-M/-bis an error.-C/-H/-Mnever consume a following.mc. Cap 512 (MAX_MC_INPUTS). - Fail-fast —
error_atstillexit(1); generate error or missing file stops the batch (missing file returns 1). - Loop —
while i < mc_input_count(notFOR … TO count - 1) so gcc-Wstrict-overflowis quiet. - Not this slice:
--deps/--print-imports; compiling the import graph; item 15. Language Report / EBNF unchanged. - Promoted 0.26.7.192. Branch
mod-c_0.26. Build 192. - Docs this round:
CURRENT.md,changelog.md,README.md,AGENTS.md(Key Commands),docs/grok_report-20260921.md.
Examples — Kilo tutorial (19 September 2026)
- Done — full snaptoken
Kilo port in
examples/kilo/kilo.mc(started 7 September 2026; author M. Scott Reynolds). Not a compiler version bump. - TYPE-bound
EditorConfig::,ERow::, andABuffer::.E.row[at].updateRow()(C-indexp[i]on^ERow, 0.26.7.191). - Uses
DEFINE,...+stdarg.hFFI,DEFER,RECURSIVE,extern type Termios = struct termios. Extra HLDB entry for TMod-c (.mc/.mh). - Build:
examples/kilo/—make kilo(TMODC=../../bin/tmodc). - Docs this round:
CURRENT.md,changelog.md,README.md,docs/grok_report-20260919.md.
Version 0.26.7.191
- C-index
p[i]on^T— ifpis^T(or a named alias) andTis not an array,p[i]has typeT(C*(p + i)).semantic_type_of_designatorcopy-shells and clearsis_pointerafter the array-element branch. Same strip asp^. - Not a peel — do not set
array_index.auto_deref. Pointer-to-array (^array[N] of T/^Vector3) still yields the array element and C(*(p))[i](0.26.5.178).^char[]argv[i]unchanged. - Methods / fields —
p[i].m()is an ordinaryTreceiver (REFauto-&→Type__m(&p[i])).Type::m(p[i])and(@p[i]).m()work. Unknown fields onp[i]are checked. - Kilo —
E.row[at].updateRow();ERow::andEditor::type-bound procedures (E.open/E.refreshScreen/E.processKeypress). - Tests:
tests/test_ptr_index.mc;tests/test3_ptr_index.mc(p[0].no_such_field). - Not this slice: first formal
^Owneras instance; turningreallocbuffers into realarray[N]types. - Tests green. make test-all /
selfhost / bootstrap /
promote green on gcc,
clang, and tcc. Build
191. Branch
mod-c_0.26. Promoted 0.26.7.191. - Docs this round:
CURRENT.md,changelog.md,README.md, Language Report §5.3 / §6.2 / §8.3,docs/syntax-ebnf.md(designator comment),docs/grok_report-20260918.md.
Version 0.26.7.190
...last-formal pass-through — at least one named formal, then, ...last. FlagNODE_PARAM_LIST.has_ellipsis(not a dummy param). Emit, ...in generated C. Extra call actuals already parsed; without the C ellipsis, gcc errors on too many arguments.- No language stdarg — TMod-c does not type extra
actuals and has no
va_list/va_arg. Consume extras via FFI:import va_list, va_start, va_end from "stdarg.h"plusvprintf/vsnprintf. - PROGRAM / MODULE —
...rejected on unit parameter lists. .mh— signatures may end(string, ...);MhFormal.is_ellipsis; import rebuild setshas_ellipsisand skips aNODE_PARAMfor that slot.- Kilo —
define CTRL_KEY(k) ((k) & 0x1f);editorSetStatusMessage(fmt: string, ...)withvsnprintf;type puchar = ^ucharfor Cunsigned char *. - Docs — Language Report §8.2 (
...) and §5.3 (C specifier order:^unsigned char, notunsigned ^char). EBNFformal-parameters. - Tests:
tests/test_ellipsis.mc;tests/test3_ellipsis.mc(bare...);tests/test3_ellipsis_tail.mc(...not last). - Not this slice: stdarg builtins; printf format
checking;
TYPEOF; vendoring single-file C libs. - Promoted 0.26.7.190 (minor bump: language-surface C
ABI). Branch
mod-c_0.26. - Docs this round:
CURRENT.md,changelog.md,README.md, Language Report §5.3 / §8.2,docs/syntax-ebnf.md,docs/parameter-passing.md,docs/grok_report-20260917-ellipsis.md.
Version 0.26.5.188
DEFINEdeclaration —[EXPORT] DEFINE identifier RestOfLine. Top-level only. Rest of line is C (including\continuations); the compiler does not parse object-like vs function-like. Emit#define+ identifier + rest. Bind the name like an untypedIMPORT(SYM_KIND_IMPORT) socase CTRL_KEY('q'):is a legal TMod-c name (C ICE after preprocess).EXPORT— also the generated.handexport define Name completein the.mh(MH_KIND_DEFINE). ImporterFROM "unit.mh"already#includes the paired.h(no self-include of"thisunit.h").- Unchanged:
#define/#ifpass-through stays unbound.CONSTstaysstatic const(item 19 demoted). Feature-test macros (_GNU_SOURCE) stay Makefile-D. TLexer::capture_line_from— rewind to the identifier and slurp through newline with\continuations.p.lexeris a legalrefactual (no@required).- Tests:
tests/test_define.mc(object-like, function-like, continuation,export define,switchcase);tests/test3_define.mc(definewithout identifier). - Tests green. make clean tmodc test-all selfhost bootstrap
promote green on gcc, clang,
and tcc. Build 188. Branch
mod-c_0.26. Promoted 0.26.5.188. - Docs this round:
CURRENT.md,changelog.md, Language Report §3.2 / §4.1 / §4.2 / §11,docs/syntax-ebnf.md,docs/grok_report-20260914-define.md.
Version 0.26.5.187
DEFERonly if executed (§7.3) — a deferred statement runs only if control passed thatDEFER(C2y / Language Report). Codegen no longer inserts the action onRETURN/BREAK/CONTINUEthat appear before theDEFERin the sameBEGIN/END. Nestedif … then return endthen laterdefer free(buf)(kiloeditorSave) does notfreeon the early return. Fall-through of the block still runs every defer that was reached, LIFO.CodegenContext.stmt_index— cutoff inblock.stmtswhile emitting that block; unwind walkers (codegen_c_emit_defers_to_func/_to_loop) emit only defers with index< stmt_index. Unwind comments are printed only when at least one defer qualifies (no empty/* DEFER cleanup (unwinding to function) */).- Tests:
tests/test_defer.mc— return before defer; return after defer; first defer runs and second skipped;continuebefore defer in aFORbody. - Tests green. make clean tmodc test-all selfhost bootstrap
promote green on gcc, clang,
and tcc. Build 187. Branch
mod-c_0.26. Promoted 0.26.5.187. - Docs this round:
changelog.mdonly. Language Report §7.3 andCURRENT.mdlater.
Version 0.26.5.186
extern type Name = struct [tag]— C struct-tag alias without a shim header.extern type FILEstill emits nothing.extern type termios = struct→typedef struct termios termios;.extern type Foo = struct bar→typedef struct bar Foo;. Notopaque, notFORWARD. Tests:tests/test_extern_struct.mc,tests/fixtures/ctag.h. Example:examples/kilo/kilo.mc.- Char escapes —
'\x1b'(\x+ two hex digits, EBNF); octal\+ 1..3 digits.scan_char_escapeinLexer.mc. Test:tests/test_char_esc.mc. parse_primaryno silentnil— unexpected token iserror_at+ exit.'&' is bitwise AND; use '@' for address-of. Fixes kilo core dump on C-style&ws. Unary&is not@.- Array-literal trailing comma —
{ a, b, }no longer needsparse_exprto returnnil(Lexer.mckeyword table).parse_array_literalbreaks on}after a comma. - Builds 184–185 were intermediate promotes of this arc.
- Tests green. make clean tmodc test-all selfhost bootstrap
promote green on gcc, clang,
and tcc. Build 186. Branch
mod-c_0.26. Promoted 0.26.5.186. - Docs:
CURRENT.md,changelog.md,README.md, Language Report §2.3 / §3.2 / §6.2,docs/syntax-ebnf.md,docs/grok_report-20260908-ffi-parse.md.
Version 0.26.5.183
- Drop C
conston LET (27b) —NODE_LET_DECLis an ordinary C local (codegen_c.c,codegen_header.mc). DeclarationCONSTstill emitsconst(item 19). Type-qualifierconstunchanged. @ofLET/ declarationCONSTillegal —semantic_forbid_at_immutable. Closeslet n; pc := @n; pc^ :=without freezinglet p: Pointthenp.x :=. Read-only@nasconst ^Tdeferred.- Static text: unbound
stringisconst ^char(typedef const char* string). Uselet s: string = "…"(or explicitconst ^char) so gcc does not see a discarded const after LET is no longer Cconst. - Tests:
tests/test_let_field.mc;tests/test3_let_addr.mc;tests/test3_let_addr_var.mc. - Still deferred: item 19
CONSTcodegen;@nasconst ^T. - Tests green. make clean tmodc test-all selfhost bootstrap
promote green on gcc, clang,
and tcc. Build 183. Branch
mod-c_0.26. Promoted 0.26.5.183. - Docs:
CURRENT.md,changelog.md,README.md, Language Report §4.2 / §6.2 / §10.1,docs/parameter-passing.md,docs/grok_report-20260907-let-const-addr.md.
Version 0.26.5.181
LETwrite-forbid Done (§4.2) — TMod-c owns rebind ofLETnames. Baren :=/INC(n)/DEC(n)on aLETis a semantic error (statements andINC/DECas expressions). Through-writes (p.x,pc^,a[i]) stay legal (same body rule asREF/ bare formals).- Three states locked:
VAR= rebind + through;LET/REF/ bare = no rebind, through OK;CONSTformals = deep freeze. DeclarationCONSTstays compile-time, block-head withVAR— not mid-block likeLET. - Not pointer-to-const:
let pc: ^Tmay mutate aVARobject. C still prefixesconston LET locals (wrong for^T; 27b drops it). - Tests:
tests/test3_let_assign.mc,tests/test3_let_inc.mc,tests/test3_let_inc_expr.mc; positivetest_let_rebind. Parkedtests/test3_let_field.mc.keepuntil 27b (-aOK; gccconstmember assign still fails). - Still deferred: drop C
constonNODE_LET_DECL(27b); item 19CONSTcodegen. - Tests green. make clean tmodc test-all selfhost bootstrap
promote green on gcc, clang,
and tcc. Build 181. Branch
mod-c_0.26. Promoted 0.26.5.181. - Docs:
CURRENT.md,changelog.md,README.md, Language Report §4.2 / §8.2,docs/parameter-passing.md,docs/grok_report-20260907-let-write-forbid.md.
Version 0.26.5.180
- Parent-chain instance methods (§5.4.0 / §8.3) —
c.m()/p.m()walkEXTENDSand bindParent::mwhen the receiver isChild/^Child/ further descendant. First instance hit wins (Child shadows Parent). A staticType__nameat that level is an error; the walk does not skip it. call.base_depth—.basehops after peel (0 = method on the receiver type). Receiver upcast uses this flag, notmaybe_upcastonreceiver_expr(would double.base). Argumentmaybe_upcastunchanged (Parent::show(c)by value).- C — callee
Parent__show; valuec.base× N; pointer(*pc).base× N;REF&((*pc).base)(.binds tighter than&). No->. Type::m(p)— peel if the pointee is the owner or an EXTENDS descendant (Parent::show(pc)whenpcis^Child). Still skip@. QualifiedChild::showdoes not walk.- Tests:
tests/test_extends.mc(c.show()/g.show()/pc.show()/Parent::show(pc)/pc.bump/Child::ownYon Child and Grand). - Still deferred: first formal
^Owneras instance. - Tests green. make clean tmodc test-all selfhost bootstrap
promote green on gcc, clang,
and tcc. Build 180. Branch
mod-c_0.26. Promoted 0.26.5.180. - Docs:
CURRENT.md,changelog.md,README.md, Language Report §5.4.0 / §8.3,docs/grok_report-20260904-parent-chain-methods.md.
Version 0.26.5.179
Type::m(p)peel (§8.3) — qualified instance call peels the first actual when it is^Owner(same C asp.m()).Point::print(pp)→Point__print((*pp));REFPoint::scale(pp, n)→Point__scale(&(*pp), n).- Do not peel
@:TLexer::next(@p.lexer)stays&(p.lexer).(*&x)was passing a struct by value (broke selfhostTParser::advance). - Do not peel
pp^: actual is alreadyPoint(one caret wrap). - Placement: after args are resolved; only when
receiver_expr == nil(must not clearpp.getX()’s flag;argcis 0 on that form). Field call-through still clearsauto_deref. - Tests:
tests/test_smoke.mc(Point::print(pp)/Point::scale(pp, 3)/Point::getX(pp)). SelfhostTLexer::next(@p.lexer). - Still deferred then (closed 0.26.5.180):
parent-chain method lookup. Still deferred: first
formal
^Owneras instance. - Tests green. make test-all /
selfhost / bootstrap /
promote green on gcc,
clang, and tcc. Build
179. Branch
mod-c_0.26. Promoted 0.26.5.179. - Docs:
CURRENT.md,changelog.md,README.md, Language Report §8.3,docs/grok_report-20260904-type-m-peel.md.
Version 0.26.5.178
- Oberon
p[i]peel and instance method on^T(§5.3 / §8.3) — remaining auto-deref after field.(176). One implied peel per[]or per instance.method. AST flags only; no inserted caret. Explicitp^[i]/p^.methodunchanged. p[i]— peel iff pointer-to-array (^array[N] of Tor^NamewhereNameis an array typedef). Do not peel^char[]argv[i](array-of-^char). C(*(p))[i].- Anonymous
^array[N] of T—emit_type_specifieremitsT (*name)[N](no longerT name[N]/ param decayT *name). Named^Vector3was alreadyVector3 *p. - Explicit
p^[i]— postfix^emits(*(expr))so C[]does not bind tighter than unary*(*p[i]was*(p[i])). p.methodon^T—call.auto_deref; owner name after the pointer test. First formal still owner value (self: Point/ref self: Point), not^Point. C by-valueType__m((*p));REF/VARauto-&outside the peel (Type__scale(&(*p), n)).Type::m(p)closed in 179. No->.- Tests:
tests/test_array.mc(pv[i]/pv^[i]/pa[i]/argv[0]);tests/test_smoke.mc(pp.getX()/pp.print()/pp.scale(2)/Point::print(pp^)). - Still deferred then (closed 0.26.5.179): auto-peel
of
Type::m(p). Still deferred then (closed 0.26.5.180): parent-chain method lookup. Still deferred: first formal^Owneras instance. - Tests green. make test-all /
selfhost / bootstrap green on
gcc, clang, and tcc.
Build 178. Branch
mod-c_0.26. Promoted 0.26.5.178. - Docs:
CURRENT.md,changelog.md,README.md, Language Report §5.3 / §5.4.0 / §6.2 / §8.3,docs/grok_report-20260904-ptr-index-method.md.
Version 0.26.5.177
- Light refactoring. Include examples/Shapes.mc, an example demonstrating EXTENDS and auto-deref.
Version 0.26.5.176
- Oberon
p.xauto-deref (§5.3) — one implied peel per.when the left side is a pointer (^Tor alias such aspNode = ^Node). Same meaning asp^.x. AST staysNODE_FIELD_ACCESS; no inserted caret node. Explicitp^.xunchanged. - Semantic —
field_access.auto_derefif the use type or itsresolved_typeis a pointer (do not chase alias first; that would drop^Childto namedChild). Field lookup /base_depthalready saw^Childby name. - Codegen —
(*(p)).field, then.base× N. No->.REF/VARformals unchanged (emit_let_identalready loads(*formal)). - Tests:
tests/test_extends.mc—c5.x/c5.y,pp.x,pg.z/pg.x,p.xintakeParentPtrbesidep^.x.tests/test_pointer_var.mc—root.value/p.next.valuebeside^.forms. - Still deferred then (closed 0.26.5.178):
p[i]peel for pointer-to-array (do not peel^char[]argv[i]); instance method on^T. - Tests green. make test-all /
selfhost / bootstrap green on
gcc, clang, and tcc.
Build 176. Branch
mod-c_0.26. Promoted 0.26.5.176. - Docs:
CURRENT.md,changelog.md,README.md, Language Report §5.3 / §5.4.0 / §6.2,docs/grok_report-20260903-auto-deref.md.
Version 0.26.5.175
- EXTENDS polish (optional #4) — three slices; Oberon
p.xauto-deref not in this build. p^.fieldparent fields —semantic_type_of_designatorpeels postfix^andASsoc5^.xgetsbase_depth(C(*c5).base.x). Child fieldsc5^.ywere already fine.- Aggregate
as→.base—g as Parentrecordsupcast_depthfrom inner vs target; codegen skips(Parent)g(invalid C for structs) and emits((g).base × N). - Pointer /
REFupcast —semantic_extends_upcast_depthwalks when both sides are pointers or both are values (mixed still 0).Node.upcast_ptr: value((expr).base × N)soREFactuals are&((c).base); pointer&((*(pc)).base × N). - Tests:
tests/test_extends.mc—c5^.x,g as Parent/g as Child,^Parentinit from^Grand,takeParentPtr/takeParentRef. - Still deferred then: Oberon
p.x/p[i]auto-deref (§5.3); instance method on pointer receiver. (Field.closed in 0.26.5.176.) - Tests green. Build 175. Branch
mod-c_0.26. - Docs:
CURRENT.md,changelog.md,README.md, Language Report §5.4.0 / §6.2,docs/grok_report-20260902-extends-polish.md.
Version 0.26.5.174
- #11 import as the one unit bind — named
.mhimport ofinteger/cardinal/real/stringrefines the prelude (once per unit). Foreign.hand include-only do not.ASalias is not the portable bind. - Codegen skips the default
typedefwhen that name is a listed.mhimport item; the pack.his the C ABI. Qualident is the bind name unlessASis present. .mhwidth spelling — type-spec tailsinteger N/cardinal N/real Non fields, formals, and results. Reader storestype_width/result_width; semantic copies onto importedTType. Old lines without a tail stay legal.- Width token: decimal, no leading zeros, at most two digits (closed
N; avoids
-Wstrict-overflowonn * 10). - Tests:
test_import_rebind+fixtures/lp64;test3_import_rebind_second/test3_import_then_rebind;test2_export_width/test2_import_width;test_import_width+fixtures/wpoint;test3_import_width. - Still deferred (#11): arch packs; build-time
defaults;
.mhalias RHS (export type integer complete integer 64). - Tests green. Build 174. Branch
mod-c_0.26. - 31 August 2026 — one Makefile:
CC ?= gcc;make CC=clang/make CC=tcc(family from theCCstring). Per-familyCFLAGS/DEBUG_FLAGS(no-lasan; tcc has no ASan).make cleanwhen switchingCC.Makefile.clangandMakefile.tccmoved toarchive/.make tarships onlyMakefile. make test-all green on gcc, clang, and tcc. - Docs:
CURRENT.md,changelog.md,README.md,AGENTS.md, Language Report §3.2 / §5.5 / §10.1,docs/syntax-ebnf.md,docs/grok_report-20260828-import-bind.md. Doc filenames:language-report.md,syntax-ebnf.md(hyphens).
Version 0.26.5.173
- Width forms —
integer N/cardinal N/real N(exact C11). Closed N: 8/16/32/64;real32/64. Decimal N only.TType.width; CintN_t/uintN_t/float/double. type integer = integer 64is the Phase A unit bind with a width RHS. Width forms are not rebindable names.- Generated
.howns the C prelude — stdint/stdbool/assert,nil/NIL,byte, portable four unless this unit bound the name. Unexported portableTYPEbinds still appear in the.h. - Generated
.c#include "UnitName.h"; no second prelude.-Cwrites a companion.h. Header always emits the unit prototype. - PROGRAM/MODULE name must match the output stem
(
#include "Name.h"). - Tests:
test_width,test_width_rebind,test3_width,test3_width_real. - Still deferred (#11 at 173): import as the one unit
bind; arch packs;
.mhwidth spelling on fields/formals. (Import-bind and.mhwidths closed in 0.26.5.174.) - make test-all, promote green on gcc, clang, and tcc. Promoted 0.26.5.173.
- Branch
mod-c_0.26; docs:CURRENT.md,changelog.md,README.md, Language Report §5.5 / §10.1 / §11,docs/syntax-ebnf.md,docs/grok_report-20260827-1548.md
Version 0.26.4.172
- Method-type
.mhRHS —export type TFn complete function (integer) : integer(andprocedure). Always(…)on the type line. - Reader:
is_func_type+ signature parse aftercompletewhen the type is notstruct/union. - Semantic: synthetic
NODE_METHOD_TYPEon import (type_decl.method_type := mt). Cross-moduleobj.field(...)call-through; import the method type and the struct. - Policy A: known function result as a statement still requires
(f()). - Tests:
test2_export_fn_type/test2_import_fn_type/test3_import_fn_type;test_import_fn_type+fixtures/fnbox. Field lists from 0.26.3.170. - make test-all, promote green on gcc, clang, and tcc. Promoted 0.26.4.172.
- Branch
mod-c_0.26; docs:CURRENT.md,changelog.md,README.md, Language Report §2.2 / §3.2 / §8.3,docs/grok_report-20260826-2130.md
Version 0.26.3.170
.mhfield lists —export type Name complete struct (f: T { , … });union; optionalextends Parent.opaqueunchanged forEXTERN TYPEandtype T = opaque.- Writer:
mh_type_opaqueno longer treats a struct body as opaque; field tail onDynBuf(same one-line@format 1as 6a signatures). - Reader: whole-file load (parse in place);
MhFieldonMhExport; barecomplete/opaquestill legal. - Semantic: synthetic
NODE_TYPE_DECL+struct_bodyon import; unknown field is an error.EXTENDSparent fields require the parent type imported. - Identifiers ≤ 255 — lexer and
.mhreader;test3_ident_long.mc. - Tests:
test2_export_struct/test2_import_struct/test3_import_struct_field; union and EXTENDS import pair;test_import_struct+fixtures/tpoint. Export.mhstill produced with-M. - Still deferred (closed in 0.26.4): method-type RHS / cross-module field call-through.
- make test-all green. Branch
mod-c_0.26.
Version 0.26.3.168
- Parser self-host closed —
parser.creplaced byparser_common.mc+parser_main.mc. - Shared helpers in
parser_common(TParser/pParser, token/EOS/error_atfamily). Dual exports:advance(p: pParser)(declaration / expression / statement) andTParser::advance(ref p: TParser)(parser_mainonly). parser_mainis the recursive-descent driver.modcincludesparser_common.h+parser_main.h.- Cyclic
.mhimports noted (parser_main ↔︎ declaration / expression / statement). Do not design until after Makefile subdirectory compilation (item 15). Interim: extract a common module. - make test-all, selfhost, bootstrap, promote green on gcc, clang, and tcc. Promoted 0.26.3.168.
- Branch
mod-c_0.26; docs:CURRENT.md,changelog.md,README.md, Language Report §3.2,docs/grok_report-20260824-parser-split.md
Version 0.26.3.167
- Conversion from parser.c to parser_main.mc complete and integrated.
Version 0.26.2.164
- Renamed struct Parser to TParser
Version 0.26.1.161
- Extract advance, check, match, error_at, consume, checkEOS, matchEOS, check_comment, match_comment, check_assignment, skip_empty_statements, skip_layout_breaks, isEOF from parser.c to parser_common.mc.
- Update declaration.mc, statement.mc, expression.mc accordingly.
Version 0.26.1.159
- REQUIRE/ENSURE statements now go through semantic_resolve_expr(...) so REF/VAR parameters are processed.
Version 0.26.1.158
- Unknown type names closed (breaking) — unbound
single-word type is an error
(
semantic_resolve_type_inner). - Still legal: prelude builtins;
IMPORT/EXTERN TYPE;.mhopaques; multi-word C (long long,unsigned int). Bareunsignedis not a type. - Automatic
<stddef.h>dropped — prelude is<stdint.h>/<stdbool.h>/<assert.h>;#define nil ((void *)0)/#define NIL nil. Importsize_t(andFILE,void, …) when used. - Tests:
test3_undef_type.mc; type import sweep onsrc/*.mc, tests, examples. - make test-all, selfhost, bootstrap, promote green on gcc, clang, and tcc. Promoted.
- Branch
mod-c_0.26; docs:CURRENT.md,changelog.md,README.md, Language Report §3.2 / §11
Version 0.26.0.156
- Free-call soft-miss closed (breaking) — unresolved
free
name(...)andType::name(...)are errors (semantic_resolve_call). - Still legal: same-unit
PROC/FUNC; namedIMPORT/EXTERN; 7a module-entry bind; method-typed values; field call-through. - Include-only
IMPORT FROMbinds no names. QuotedFROM "hdr.h"does not parse the header. - C preprocessor is copied to generated C only;
#definedoes not bind TMod-c names. Macros used from.mcbelong in a real.hplusIMPORT. - Not this bump: unknown type names
(
size_t,FILE) still soft-miss; automatic<stddef.h>stays. - Tests:
test3_undef_call.mc,test3_undef_method.mc;test_tmodc.mcrewritten to a realTSTRINGfunction. Compilersrc/*.mcimport sweep. - make test-all, selfhost, bootstrap, promote green on gcc, clang, and tcc. Promoted.
- Branch
mod-c_0.26; docs:CURRENT.md,changelog.md,README.md, Language Report §3.2 / §11,docs/syntax.ebnf.md,docs/grok_report-20260818-1311.md
Version 0.25.4.154
RECURSIVEenforced — direct self-call (resolved_sym.decl == enclosing_func) requiresRECURSIVEon the completing PROC/FUNC (the body).FORWARDtag on the incomplete decl alone is not enough. Mutual recursion not checked.- Compiler
.mcsweep (semantic,ttype,expression, …) so self-host stays legal. - Tests:
tests/test3_recursive.mc; factorial/fib demos already tagged. SIZEOF— type was alreadyinteger; C emit is now((integer)sizeof(...))(codegen_common_sizeof_expr).LENstill not emitted.- Lean generated prelude —
#includeonly<stddef.h>,<stdint.h>,<stdbool.h>,<assert.h>;typedef uint8_t byte;#define nil/NILNULL. No automatic<stdlib.h>(importexit). Tests, examples, andsrc/*.mcupdated. - Design notes (0.26): drop
<stddef.h>and require every non-builtin name on anIMPORT; lengths/indexes stay signedinteger;cardinalfor bits/C unsigned;size_timported. - make test-all, selfhost, bootstrap green on gcc, clang, and tcc. Promoted.
- Branch
mod-c_0.25(stem closed); docs:CURRENT.md,changelog.md, Language Report §8.1 / §10.1–§10.2 / §11,docs/syntax.ebnf.md,docs/grok_report-20260814-1645.md
Version 0.25.3.150
- Item 7a Done — module entry is a real
PROC/FUNCsymbol (same-unit and on.mhimport) - Same-unit:
MODULEname bound after pass 1; synthetic decl sharesprogram_declformals so call-site auto-&applies (ModuleName(obj)needs no@) - Import: any named import from an
.mhalso binds the@moduleunit-entry export (skip if already in scope or no matching export; include-onlyimport from "x.mh"unchanged).PROGRAMnames not bound - Names:
semantic_bind_mh_exportarena-copies bind names beforemh_module_free(fixes dangling constructor names / falseduplicate TType) - Callee ABI: module/program
emit_param_list(..., true)soVAR/REFformals lower toT*(e.g.module Lexer(ref lexer: TLexer, …)) - Tests:
tests/test_module_ref.mc;tests/test_module_entry.mc+fixtures/bumpint.mh(import TBumponly). make test green on gcc, clang, and tcc - Not 7b — no generated startup calls of imported module bodies
- Branch
mod-c_0.25; docs:CURRENT.md(Language Report / grok report later)
Version 0.25.3.149
.mhformal signatures (item 6a) Done — still@format 1/ one line per export- Writer:
( [var|ref|const] type-spec { , … } ) [ : result ]; prefer surface type names (aliases); optional multi-word C types when written that way - Reader:
MhFormal+ result fields; multi-word type-spec (long long,unsigned int); free formals on unload - Import: synthetic
NODE_PROC_DECL/NODE_FUNC_DECLwith formals (arena-copied); existing codegen call-site auto-&works cross-module forVAR/REF - Builds on 0.25.2 method Phase A
(
instance/static,import Type::name,obj.method) - Tests:
tests/test5_mh_method_import_string.mc(modc -a);tests/test4_string.mc+make string-lib/-ltmod_string(manual; not defaultmake test) - Self-host / suite:
make test/test2/test3green; selfhost path loads enriched.mh(e.g.utils.mh) - Deferred: complete struct field lists in
.mh; field call-through across modules; module-entry bind (7a) / startup init (7b) - Branch
mod-c_0.25; docs:CURRENT.md, Language Report §3.2 / §8.2–§8.3,docs/parameter-passing.md,docs/grok_report-20260811-2216.md
Version 0.25.2.146
- Cross-module method Phase A —
.mhinstance/staticOwner;import Type::name;TSymbol.is_method_instance;obj.methodacross modules - Create a small String library for testing with
make string-lib test4_*run tests for the library;test5_*semantic (-a) library import tests- String library targets must be manually run (depend on regenerated
include/*.mh+ archive)
Version 0.25.1.144
- Start conversion of parser.c to parser.mc
- Add
pDynBufto dynbuf.mc - "Mod-c" to "TMod-c" in README.md
- Update html from .md
Version 0.25.1.142
- Callable field call-through Done —
obj.field(args)whenfieldhas a method type (TYPE T = PROCEDURE/FUNCTION …) - Oberon rule: no auto-receiver on field calls; C
lowering
(obj.field)(args)(args as written) - Disambiguation: if both an instance
Type::nameand a method-typed field apply, compile-time error (useType::name(obj, …)) - Values: free call
fp(args)whenfpisVAR/PARAM/LET/CONSTof method type - AST / semantic / codegen:
call.callee_expr; helperssemantic_method_type_node,semantic_field_type_on,semantic_sym_is_method_value; instance sugar still usesreceiver_exprprepend - Tests:
tests/test_callable_field.mc,tests/test3_callable_field_ambig.mc; idiom demotests/test_Math.mc(struct ops +Type::create/ module entry factory; no vtables) - make test-all green on gcc, clang, and tcc; promote verified
- Branch
mod-c_0.25; docs:CURRENT.md,changelog.md
Version 0.25.0.140
- Item 20 Done (breaking) — formal parameter modes VAR / REF / CONST end-to-end
- Semantics: only
VARmay appear as the bare LHS of:=/INC/DEC;CONSTforbids any write through the formal (name, fields, indices);REFallows through-selectors (p.x,a[i]) but not bare-name assign - Codegen 20A:
VAR/REFon non-pointerT→T*; body loads(*p)where needed;REF p: ^Tstays one pointer level;VAR p: ^T→T** - Codegen 20B: call-site auto-
&for by-ref value formals (Swap(x, y)→Swap(&x, &y)); already-@args not double-addressed - Self-host:
arena.mc/semantic.mc/utils.mc(and related) use locals for scratch mutation of non-VARformals - Tests:
test_var_ref,test_var_pointer,test_const_param,test3_var_ref*,test3_const_param*,test3_var_pointer*; make test-all, selfhost, bootstrap, promote green - Known soft spot: named open-array typedefs used as
REF/VARformals (prefer true open-array type-specifiers until alias chase lands) - Branch
mod-c_0.25; docs:CURRENT.md, Language Report §8.2,docs/grok_report-20260806-2042.md
Version 0.24.18.136
- Item #11 Phase A Done — portable unit rebind of
integer,cardinal,real,stringonly (Wirth-family names; not C type names) - One top-level
TYPEbind per name per compilation unit; second bind and rebind of fixed builtins (bool/byte/char, …) are errors - Unbound C defaults:
typedef int integer,unsigned int cardinal,float real,const char* string(rebind skips the default and emits the unit’sTYPEtypedef) - Helpers:
type_is_portable_rebindable; semantic prelude refine;codegen_c_unit_binds_portable - Tests:
tests/test_rebind.mc,tests/test3_rebind_second.mc,tests/test3_rebind_bool.mc; make test-all green - Deferred (#11): width forms
integer N/cardinal N/real N; import as the unit bind; architecture packs; compiler-build default selection - Docs:
CURRENT.md,changelog.md
Version 0.24.17.135
type Name FORWARD— incomplete TYPE in this unit; complete later withtype Name = …(symtab refine, same family as proc postfixFORWARD)- Codegen incomplete tag/typedef and method-type ordering so C accepts formals that name the type before the full layout
- Tests:
tests/test_type_forward.mc,tests/test3_type_forward_dup.mc,tests/test3_type_forward_reforward.mc(as applicable) - Docs:
CURRENT.md,changelog.md
Version 0.24.17.134
- Item 21 Done — type-bound instance
methods:
obj.method(args)→Type__method(obj, args…) - Instance vs static by first formal type (formal
name free): instance if first formal type is the owner; else static /
factory (
Type::only) - Expression + statement postfix chains (e.g.
p.print(),b.topLeft().print()) - Optional
end Type::name(and bareend name) after procedure/function bodies - Factories: e.g.
Point::new(x, y); instance call on static methods is an error - Tests:
tests/test_smoke.mc,tests/test3_smoke.mc; make test-all green on gcc, clang, and tcc - Deferred: callable function-pointer field call-through;
pointer/
VARself; parent-chain method lookup - Docs:
CURRENT.md,changelog.md, Language Report §8.3,docs/grok_report-20260803-2318.md
Version 0.24.16.130
- Self-host: declaration — hand-written
declaration.creplaced bysrc/declaration.mc; generateddeclaration.c(and build-paired header /.mhas applicable) - Conversion cleanup: stale comments removed; most obsolete receiver parse path dropped (type-qualified methods)
- EXTENDS by-value auto-upcast — child/descendant
used as ancestor → C
.base× N (option A);Node.upcast_depth; sites: typed init,:=,RETURN, same-unit call formals; multi-level - Design lock: no hidden type tags / RTTI / vtables
on structs (Modula-2 + C stance); programmer tags optional; no Oberon
ISwithout future opt-in - Deferred polish: pointer/
REFupcast; aggregateas→.base;^Tparent field lookup; richer.mhformals - make test-all green on gcc, clang, and tcc
- Docs:
CURRENT.md,changelog.md, Language Report §5.4.0,docs/syntax.ebnf.md,docs/grok_report-080126-2008.md,docs/project_bible.mdappend
Version 0.24.15.128
- Item 13b Done (Policy A) — bare call as statement
illegal when result type is known
(
semantic_type_of_expr); require(f())or use the value - Unknown result (procedure, untyped foreign import e.g. bare
printf) still allowed as bare call - Explicit discard remains
(expression)/(f())(NODE_PARENstatement) - Tests:
tests/test_discard.mc,tests/test3_discard.mc; make test-all green on gcc, clang, and tcc - Deferred: discard binding
_(item 24); C(void)hygiene optional in codegen - Docs:
CURRENT.md, Language Report §7.2,docs/syntax.ebnf.md
Version 0.24.14.126
- Item 21a Done —
semantic_type_of_expr+ simple init inference when:omitted onVAR/LET/CONST - Typing: literals; designators; paren; cast; sizeof; call returns;
unary
-/not/~/@/^; binary arith / bitwise / logical / compare; real arith - Tests:
tests/test_infer.mc(and suite); preludestringasconst char*where needed for C - Deferred: ternary
? :(and array-literal) typing — TODO 21a-ternary - Unlocks: discard (13b), EXTENDS auto-upcast,
obj.method(21),VARformals (20) - Docs:
CURRENT.mdcalm path step 4 complete
Version 0.24.14.125
- Self-host: Lexer — hand-written
lexer.c/lexer.hreplaced bysrc/Lexer.mc(moduleLexer); generatedLexer.c/Lexer.h/Lexer.mh - Tokenizer is now compiled from TMod-c source in the normal / self-host / bootstrap chain
- Exports:
TokenKindenum,TToken/TLexer,TLexer::init,TLexer::next,TokenKind::string,TToken::print - Keyword table includes correctly spelled
OPAQUE - Docs:
CURRENT.md,docs/grok_report-20260728-2052.md,docs/project_bible.mdappend
Version 0.24.13.122
- Linked-chunk arena — growth never relocates live
slabs (fixes ASan heap-use-after-free when
reallocmoved a full 1 MiB arena under AST/symtab pointers during large-aruns).ArenaChunkchain;callocnew chunks;memseteacharena_allocslice; defaultARENA_MIN_SIZE1 MiB STRUCT EXTENDSoption A (item 13d / calm path step 3) Done — unique field names on chain; synthetic embedParent base;field_access.base_depth→ C.base× depth; flatc.xin source; explicitc.base- Synthetic name
basereserved only on types that useEXTENDS(plain structs may declare a fieldbase) - Semantic helpers: designator typing for field lookup; clash / base-must-be-struct checks
- Tests:
tests/test_extends.mc,test3_extends_clash.mc,test3_extends_base_reserved.mc(plus existingtest2_extends.mc) - Auto-upcast still deferred (after 21a
type_of_expr) - Docs:
CURRENT.md, Language Report §5.4.0,docs/syntax.ebnf.md,docs/grok_report-20260725-2251.md
Version 0.24.12.118
- Bare
UNIONtypes (item 13c / calm path step 2) Done —type T = union … end; lexerUNION;struct_decl.is_union;parse_union_type; semantic (no EXTENDS on union);codegen_common_union_decl→ Cunion - Nested aggregates: define named type first, then use as field type (no anonymous nested union/struct yet)
- Tests:
test_union.mc,test3_union.mc(and opaque suite from 0.24.11) - Promoted; make test-all green on gcc, clang, and tcc
- Docs:
CURRENT.md, Language Report §5.4.1,docs/grok_report-20260723-2250.md
Version 0.24.11.116
- Remove an unnecessary space before ';' production for 'typedef'.
Version 0.24.11.115
OPAQUEtypes (item 13a / calm path step 1) Done —type T = opaque,^opaque,POINTER TO opaqueon TYPE RHS;TType.is_opaque/ parse / codegen (typedef void *Name); bareopaquerejected in type-specifier; tests green
Version 0.24.11.112
- expression.c is now selfhosted with expression.mc
Documentation (21–25 July 2026)
STRUCT EXTENDSdesign then implementation (0.24.13 / 13d) — Oberon record model + C option Aunion-typeEBNF then implementation (0.24.12) — bare C union; not language-level tagged unions- Language SSOT:
language_report.md+syntax.ebnf.md(from 18 July); session reportdocs/grok_report-20260725-2251.md
Documentation (18 July 2026)
- Language SSOT:
docs/language_report.md(Wirth-style Language Report draft);docs/syntax.ebnf.md(complete formal EBNF only) docs/grammar.md— redirect stub; full prior content archived asdocs/grammar_working_notes.md- Design captured in report:
OPAQUE, discard(f()), Oberon auto-deref intent, ownership modes, no mandatory GC,VAR/LETplacement
Version 0.24.10.110
- statement.c to statement.mc, now selfhosted.
Version 0.24.9.106
- Converted symkind.h to symkind.mc and mh_exportkind.h to mh_exportkind.mc
Version 0.24.8.104
- Named
CONSTin array bounds —array[LIMIT] of Tandarray[LIMIT+1] of TwhenLIMITis a compile-time integerCONST TType.size_expr— deferred bound until semantic fold;type_create_array_expr; parser accepts const-expr inarray[…] of TandT[…]semantic_try_eval_const_expr— soft integer fold with optional message out-parameter;semantic_eval_const_expris a thin fatal wrapper (no duplicated logic)CONSTregistration — sethas_const_value/const_valueonly when integer fold succeeds; string/pointerCONSTunchanged (e.g.const c2: ^char = "bye")- Operators — binary
modacceptsTOK_MODandTOK_KEYWORD_MOD - Review fixes — fallback message when hard eval fails without msg; typo in register error string
- Self-host, bootstrap, and promote verified green
Version 0.24.7.101
- ENUM Phase C —
MH_KIND_ENUM;.mhexport enum+ memberexport constlines; cross-module enum import - Tests:
test_mh_enum_export.mc,test2_mh_enum_import.mc,test_mh_enum_import.mc;fixtures/colors.{mh,h} .mhnegative import tests — tier-3test3_*coverage for bad imports- Makefile test CFLAGS (gcc) —
-I$(SRC_DIR) -I$(INCLUDE_DIR) -I$(LIB_DIR);include/fixturessymlink for fixture headers - Design:
WHILE … BYnot planned — usedeferat loop head for iteration step (docs/grammar.md)
Version 0.24.7.100
- ENUM Phase A/B —
parse_enum_type,NODE_ENUM_TYPE/NODE_ENUM_ITEM;semantic_register_enum_type,semantic_eval_const_expr;codegen_common_enum_type(Ctypedef enum) TSymbol.const_valuefor enum member constants; duplicate-member negative test- Experimental
src/tmodc.h— shared C ABI prelude for hand-written C including generated headers - TMod-c rebrand (documentation) —
docs/grammar.md,CURRENT.md,AGENTS.md,changelog.md; archivaldocs/grok_report-20260714-1939.md - Tests:
tests/test2_enum_parse.mc,tests/test_enum.mc,tests/test3_enum_dup.mc - Add
src/tmodc.hfor TMod-c symbol types that can be imported by C files.
Version 0.24.5.97
- Add
skip_layout_breakstoparse_param_list.
Version 0.24.5.96
- Qualident
IMPORT … FROM—import-source = string-literal | qualident - Unquoted qualident → Mod-c module (
.mhsymtab +.h#include); quoted paths unchanged mh_reader:mh_qualident_to_mh_relpath,mh_format_h_include; updated path helperstests/test2_mh_import_qual.mc;docs/grammar.mdupdated
Version 0.24.4.93
- See docs/grok_report-20260711-1911.md
- Converted codegen_header.c to codegen_header.mc (functional parity; minor 0.24.4 bump)
- docs/grammar.md: import phase 1 / phase 2 documented
Version 0.24.3.90
- See docs/grok_report-20260710-1200.md
skip_layout_breaksinparse_unary(expression operands) and insideconsume(required keywords)RETURN: newline after barereturnends statement; multiline values use parentheses- Unreachable code after
RETURN/BREAK/CONTINUEis fatal error (was warning) - Added tests/test_layout.mc, tests/test3_loop.mc
Version 0.24.3.79
- See docs/grok_report-20260709-2338.md
- Lowered
integerandcardinaldown tointandunsigned int - Updated parser so '' isn't required when statement has an explicit
end-of-statement keyword, like
IMPORT...FROM "filename". Import items can be left on one line or spread out with one item on each line. Rules spelled out in docs/grammar.md. - Added tests/test_import_layout.mc
Version 0.24.2.76
- Add counters to tests in Makefile.
- Add negative tests, (test3_*) to Makefle.
- Update many sections of Makefile.
- Update several test .mc files.
- Updated Makefile.clang and Makefile.tcc to match Makefile.
- Build number only updates when 'make version' is executed.
Version 0.24.2
- Add helper to include MODULE name in '.mh' file export list for codegen_mh.mc.
- Update codegen.mc to use corresponding '.mh' files for imports.
- Update the rest of the source files to use the corresponding '.mh' files if there is one.
- Add VERSION, VERSION_BASE, and BUILD_NUMBER to version.h, updated by Makefile everytime a build is done.
- Add changelog.md
- Update version number.