TMod-c — Complete Syntax (EBNF)
Status: Living formal grammar (single source of
truth for syntax productions).
Last updated: 2 September 2026
Companion: Language definition prose is in language-report.md.
Not here: Implementation status, changelogs, design
essays — see CURRENT.md and
language-report.md.
Notation is EBNF as used in the productions below. Keywords are case-insensitive; identifiers are case-sensitive (see productions and the Language Report). An identifier is at most 255 characters (Language Report §2.2).
(*
* TMod-c — complete formal EBNF (single source of truth for productions)
*
* Builtin types (lowercase reserved type-identifiers):
* bool, byte, char, integer, cardinal, real, string
*)
tmodc-unit = program-unit | module-unit ;
(**** Top-level units **********************************************)
program-unit = "PROGRAM" module-name formal-parameters EOS
{ import-stmt }
declaration-sequence
block ;
module-unit = "MODULE" module-name formal-parameters EOS
{ import-stmt }
declaration-sequence
block ;
module-name = identifier ;
(**** Imports ******************************************************)
import-stmt = "IMPORT" [ import-item { "," import-item } ] "FROM" import-source EOS ;
import-source = string-literal | qualident ;
import-item = [ type-identifier "::" ] identifier ;
(**** Top Level Declarations *************************************************)
declaration-sequence = { type-decl
| const-decl
| let-decl
| var-decl
| method-decl
| extern-type-decl
| extern-var-decl
| extern-proc-decl
| extern-func-decl } ;
type-decl = [ "EXPORT" ] type-def ;
const-decl = [ "EXPORT" ] const-def ;
let-decl = [ "EXPORT" ] let-def ;
var-decl = [ "EXPORT" ] [ "EXTERN" ] var-def (* top level. *)
| [ "STATIC" ] var-def ; (* block level *)
(* Multi-item definitions *)
type-def = "TYPE" type-item { "," type-item } EOS ;
const-def = "CONST" const-item { "," const-item } EOS ;
let-def = "LET" let-item { "," let-item } EOS ; (* may be used anywhere *)
var-def = "VAR" var-item { "," var-item } EOS ;
type-item = identifier "=" type-expression
| identifier "FORWARD" ;
const-item = identifier [ ":" type-specifier ] "=" const-expression ;
let-item = identifier [ ":" type-specifier ] "=" ( initializer | expression ) ;
var-item = identifier [ ":" type-specifier ] [ "=" ( initializer | expression ) ] ;
initializer = "{" [ expression { "," expression } ] "}" ;
(**** Types ********************************************************)
(* Full type expressions used on right-hand side of TYPE declarations *)
type-expression = type-specifier
| type-name
| struct-type
| union-type
| enum-type
| set-type (* Planned: SET OF enum; see language-report §5 *)
| method-type
| opaque-type-expression
| qualident "<" type-specifier { "," type-specifier } ">" ; (* future generics *)
opaque-type-expression = "OPAQUE"
| "^" "OPAQUE"
| "POINTER" "TO" "OPAQUE" ;
(* Used for VAR / LET / CONST / formals / fields — no OPAQUE here.
At most one pointer constructor (^ or POINTER TO) per specifier.
Further levels: name the inner type, or ^T[] — not ^^T.
ARRAY OF may nest; the element type may itself include one ^.
Language Report §5.3. *)
type-specifier = [ "CONST" ] type-name [ array-suffix ]
| [ "CONST" ] ( "^" | "POINTER" "TO" ) type-name [ array-suffix ]
| [ "CONST" ] ( "^" | "POINTER" "TO" ) "ARRAY" [ array-suffix ] "OF" type-specifier
| [ "CONST" ] "ARRAY" [ array-suffix ] "OF" type-specifier
| [ "CONST" ] set-type ; (* Planned: SET OF enum; no array of sets in v1 *)
array-suffix = "[" [ const-expression ] "]" (* fixed size *)
| "[" "]" ; (* open array T[] *)
type-name = type-identifier { type-identifier } (* "int", "long long", etc. *)
| width-type ;
width-type = ( "integer" | "cardinal" | "real" ) decimal-integer ;
(* Exact width. Closed N: integer/cardinal 8|16|32|64; real 32|64.
Decimal digits only (no 0x, no '_', no leading zeros).
Not a rebindable name; typical use is TYPE RHS or a type-specifier.
Same production in .mh type-spec (fields, formals, results); mutually
exclusive with extra C words ("long long"). *)
type-identifier = identifier ;
(* lowercase builtins: bool, byte, char, integer, cardinal, real, string. *)
struct-type = [ "PACKED" ] "STRUCT" [ "EXTENDS" type-identifier ] [ "OF" ] [ EOS ]
{ field-decl EOS }
"END" ;
union-type = "UNION" [ "OF" ] [ EOS ]
{ field-decl EOS }
"END" ;
enum-type = "ENUM" [ "OF" ] [ EOS ]
enum-decl { "," enum-decl } [ "," ] [ EOS ]
"END" ;
enum-decl = identifier [ "=" const-expression ] ;
(* Planned (not parsed yet). E is an enum type; members in 0 .. 63. *)
set-type = "SET" "OF" type-identifier ;
method-type = ( "PROCEDURE" | "FUNCTION" ) formal-parameters ;
(* Field of STRUCT or UNION. type-specifier only (named types for nesting). *)
field-decl = identifier ":" type-specifier [ "=" const-expression ] ;
return-type = [ "CONST" ] [ "^" | "POINTER" "TO" ] type-identifier ;
(* Placeholder for full template/generic definition support *)
template-decl = "TEMPLATE" "<" template-parameter { "," template-parameter } ">"
( type-decl | struct-decl | method-prototype ) ;
template-parameter = type-identifier [ ":" type-specifier ] ; (* T: Type or just T *)
(**** Methods ******************************************************)
method-decl = [ "EXPORT" ] [ "EXTERN" ] method-prototype ;
(* Foreign symbols — see "Modules, imports, exports, and foreign symbols" *)
extern-type-decl = "EXTERN" "TYPE" identifier [ "=" type-expression ] EOS ;
extern-var-decl = "EXTERN" "VAR" var-item { "," var-item } EOS ;
extern-proc-decl = "EXTERN" [ "EXPORT" ] "PROCEDURE" identifier formal-parameters EOS ;
extern-func-decl = "EXTERN" [ "EXPORT" ] "FUNCTION" identifier formal-parameters EOS ;
method-prototype = [ "RECURSIVE" ] ( "PROCEDURE" | "FUNCTION" )
qualified-method-name formal-parameters [ "FORWARD" ] EOS ;
qualified-method-name = [ type-identifier "::" ] identifier ;
method-def = method-prototype
block;
formal-parameters = "(" [ parameter-def { "," parameter-def } ] ")" [ ":" return-type ] ;
parameter-def = [ "VAR" | "REF" | "CONST" ] identifier ":" type-specifier ;
(**** Blocks & Statements ******************************************)
block =
"BEGIN"
{ require-clause }
statement-sequence
[ return-statement ]
{ ensure-clause }
"END" [ end-tag ] ;
end-tag = identifier [ "::" identifier ] ;
(* Design by Contract clauses *)
require-clause = "REQUIRE" expression EOS ;
ensure-clause = "ENSURE" expression EOS ;
invariant-clause = "INVARIANT" expression EOS ; (* allowed only in loop bodies *)
statement-sequence = definition-sequence { statement EOS } [ return-statement ] ;
definition-sequence = { const-def | let-def | var-def } ;
(****** Statements ****************************************************)
statement =
block
| if-then-statement
| switch-statement
| while-statement
| repeat-statement
| for-statement
| loop-statement
| assignment-stmt
| call-stmt
| break-statement
| continue-statement
| expr-statement
| defer-stmt
| debug-stmt
| assert-stmt
| let-stmt
| inc-dec-stmt ;
let-stmt = let-def ;
if-then-statement =
"IF" expression "THEN" statement-sequence
{ "ELSIF" expression "THEN" statement-sequence }
[ "ELSE" statement-sequence ]
"END" ;
switch-statement =
"SWITCH" expression "OF"
{ "CASE" case-labels ":" statement-sequence }
"ELSE" ":" statement-sequence
"END" ;
case-labels = const-expression { "," const-expression } ;
while-statement =
"WHILE" expression "DO"
[ "BOUND" const-expression ]
{ invariant-clause }
statement-sequence
"END" ;
repeat-statement =
"REPEAT"
[ "BOUND" const-expression ]
{ invariant-clause }
statement-sequence
"UNTIL" expression ;
for-statement =
"FOR" identifier ":=" expression ( "TO" | "DOWNTO" ) expression [ "BY" const-expression ] "DO"
{ invariant-clause }
statement-sequence
"END" ;
loop-statement =
"LOOP"
[ "BOUND" const-expression ]
{ invariant-clause }
statement-sequence
"END" ;
assignment-stmt = designator ( ":=" | compound-assignment ) expression ;
compound-assignment
= "*=" | "/=" | "%=" | "+=" | "-=" | "<<=" | ">>=" | "&=" | "|=" ;
call-stmt = ( qualified-method-name | designator ) "(" [ argument-list ] ")"
{ "." identifier "(" [ argument-list ] ")" } ;
expr-statement = "(" expression ")" ; (* explicit discard of results *)
return-statement = "RETURN" [ expression ] ; (* expression required in FUNCTION *)
break-statement = "BREAK" ;
continue-statement = "CONTINUE" ;
inc-dec-stmt = "INC" "(" designator [ "," expression ] ")"
| "DEC" "(" designator [ "," expression ] ")" ;
defer-stmt = "DEFER" statement ;
debug-stmt = "DEBUG" statement ;
assert-stmt = "ASSERT" expression [ "," assert-msg ]
assert-msg = const-expression ;
(************** Designators ****************************)
designator = qualident { selector } ;
selector = "." identifier (* field access; call => instance method sugar *)
| "[" expression "]" (* array/index *)
| "^" ; (* Pascal-style dereference *)
(*** INC / DEC - both statement and expression forms ***)
inc-dec-expr = "INC" "(" designator [ "," expression ] ")"
| "DEC" "(" designator [ "," expression ] ")" ;
qualident = identifier { "." identifier } ;
identifier = (letter | "_") , { letter | digit | "_" } ;
(* Length: at most 255 characters (Language Report §2.2). Not encoded in the production. *)
(************** Expressions ****************************)
(* Expression precedence chain - right-associative where appropriate *)
expression = conditional-expression;
conditional-expression = logical-or-expression
| logical-or-expression "?" expression ":" conditional-expression ;
logical-or-expression = logical-and-expression { "OR" logical-and-expression } ;
logical-and-expression = equality-expression { "AND" equality-expression } ;
equality-expression = relational-expression { equality-op relational-expression } ;
equality-op = "==" | "!=" | "<>" ; (* "<>" is the same as "!=" *)
relational-expression = shift-expression { relational-op shift-expression } ;
relational-op = "<" | ">" | "<=" | ">=" | "IN" ; (* IN: Planned SET OF membership; not a type guard *)
shift-expression = additive-expression { shift-op additive-expression } ;
shift-op = "<<" | ">>" ;
additive-expression = multiplicative-expression { additive-op multiplicative-expression } ;
additive-op = "+" | "-" | "|" | "XOR";
multiplicative-expression = power-expression { mult-op power-expression } ;
mult-op = "*" | "/" | "%" | "&" | "DIV" | "MOD" ;
power-expression = unary-expression
| unary-expression "**" power-expression; (* right-associative *)
unary-expression = inc-dec-expr
| cast-expression
| prefix-op unary-expression ;
cast-expression = "CAST" "(" type-specifier "," expression ")"
| postfix-expression "AS" type-specifier (* postfix cast *)
| postfix-expression ;
prefix-op = "NOT" | "!" | "~" | "+" | "-" | "^" | "@" ;
postfix-expression = primary-expression
{ "[" expression "]"
| "(" [ argument-list ] ")" (* free call, or field+call => instance method *)
| "." identifier (* field; next "(" may form instance call *)
| "^"
} ;
instance-method-call = "." identifier "(" [ argument-list ] ")" ;
type-qualified-call = type-identifier "::" identifier "(" [ argument-list ] ")" ;
argument-list = expression { "," expression } ;
primary-expression = identifier
| literal
| "(" expression ")"
| array-literal (* { expr {, expr} } *)
| designator
| range-expression
| len-expression
| type-qualified-call ;
range-expression = additive-expression ".." additive-expression ; (* inclusive *)
array-literal = "{" [ expression { "," expression } ] [","] "}" ;
(* Also the Planned set constructor (SET OF enum). Semantic: set vs array by type. *)
literal = integer-literal | real-literal | char-literal | string-literal
| "TRUE" | "FALSE" | "NIL" ;
integer-literal = decimal-integer | hexadecimal-integer | octal-integer | binary-integer ;
decimal-integer = digit { [ seperator ] digit } ;*)
hexadecimal-integer = "0x" hex-digit { [ seperator ] hex-digit } ;
octal-integer = "0o" octal-digit { [ seperator ] octal-digit } ;
binary-integer = "0b" binary-digit { [ seperator ] binary-digit } ;
hex-digit = digit | "A".."F" | "a".."f" ;
octal-digit = "0".."7" ;
binary-digit = "0" | "1" ;
seperator = "_" ; (* optional thousands/grouping seperator *)
real-literal = decimal-part [ "." decimal-part ] [ exponent-part ] ;
decimal-part = digit { [ seperator ] digit } ;
exponent-part = ("E" | "e") [ "+" | "-" ] digit { [ seperator ] digit } ;
char-literal = "'" ( character | escape-sequence ) "'" ;
(* string-literal type is builtin string; see "Builtin type: string" *)
string-literal = '"' { character except '"' | escape-sequence } '"' ;
letter = "a".."z" | "A".."Z" ;
digit = "0".."9" ;
escape-sequence = "\\" | "\'" | "\"" | "\?" | "\a" | "\b" | "\f" | "\n"
| "\r" | "\t" | "\v" | "\x" hex-digit hex-digit
| "\" octal-digit [ octal-digit [ octal-digit ] ] ;
character = (* any printable char except " or \ or control chars *) ;
const-expression = expression ; (* Expressions that can be computed at compile time. *)
sizeof-expession = "SIZEOF" "(" ( type-specifier | designator ) ")" ;
len-expression = "LEN" "(" designator ")" ;
(******* Keywords *******************************************************************)
(* Keywords are case-insensitive: "IF", "if", "If", "iF" are recognized as the same *)
(* Identifiers are case-sensitive: "MyVar", "myvar", "MYVAR" are distinct. *)
keyword = "ALIAS" | "AND" | "ARRAY" | "AS" | "ASSERT" | "BEGIN" | "BOUND" | BREAK" |
"BY" | "CASE" | "CAST" | "CONST" | "CONTINUE" | "DEBUG" | "DEC" | "DEFAULT" |
"DEFER" | "DIV" | "DO" | "DOWNTO" | "ELSE" | "ELSIF" | "END" | "ENSURE" |
"ENUM" | "EXPORT" | "EXTENDS" | "EXTERN"| "FALSE" | "FOR" | "FORWARD" | "FROM" |
"FUNCTION" | "HEADER" | "IF" | "IMPORT" | "IN" | "INC" | "INVARIANT" | "LEN" |
"LET" | "LOOP" | "MOD" | "MODULE" | "NIL" | "NOT" | "OF" | "OPAQUE" | "OR" |
"PACKED" | "POINTER" | "PROCEDURE" | "PROGRAM" | "RECORD" | "RECURSIVE" | "REF" |
"REPEAT" | "REQUIRE" | "RETURN" | "SET" | "SIZEOF" | "STATIC" | "STRUCT" | "SWITCH" |
"THEN" | "TO" | "TRUE" | "TYPE" | "UNION" | "UNTIL" | "VAR" | "WHILE" | "XOR" |
"TEMPLATE" ;
(**** End-of-Statement ****************************)
(* Layout convention (for authors):
- Prefer one statement per line.
- Multiple statements on one line require ';'.
- Indentation is NOT significant.
*)
EOS = ";" | newline-when-eos ;
newline-when-eos = NEWLINE (* unless any of:
(1) Explicit continuation: '\' is the last non-whitespace before NEWLINE
(lexer suppresses the line break).
(2) Bracketed expression: inside (), [], {} delimiter pairs in expressions.
(3) Trailing continuator: the last non-comment token on the line cannot
complete the current phrase. Includes:
:= , . :: + - * / ^ @ ( [ {
and or xor = <> <= >= < >
keywords that grammatically require a following phrase
(if, elsif, while, repeat, for, return, require, ensure, then, else, …).
(4) Anchor-not-yet-closed: statement forms with a fixed closing marker
before EOS — notably import-stmt before FROM import-source.
Comma-separated declaration lists (VAR, CONST, LET, TYPE) also continue
on the next line when the previous line ended with ','.
';' always ends a statement (except inside strings and comments).
import-stmt EOS placement: IMPORT … FROM import-source EOS
(EOS is after the import-source operand, not after each import-item). *)
(******* Comments *****************************************************)
comment = { single-line-comment | c-style-comment | pascal-style-comment } ;
single-line-comment = "//" ... newline
c-style-comment = "/*" ... "*/"
pascal-style-comment = "(*" ... { pascal-style-comment } ... "*)"
(***************************************************************************)
End of formal syntax. For informal rules and semantics, see the TMod-c Language Report.