# m2compiler-V3 grammar Two adjacent grammars live in `compiler/src`: | file | scope | |------|-------| | `M2.atg` | the modern m2compiler-V3 subset (locked step-1 rules) | | `TopSpeed-V3-M2.atg` | the **complete historical TopSpeed/Dos Modula-2 V3 dialect**, reconstructed from the real JPI/Clarion V3 corpus | This page documents `TopSpeed-V3-M2.atg` (the deliverable of the "complete V3 grammar" work item). The bare EBNF below is its `PRODUCTIONS` section with the semantic actions (`(. .)`) and attributes (`<...>`) stripped; terminals in quotes are literals, lowercase names are tokens (ident, integer, real, string). ## Validation status - Validated against **all 662 `.mod`/`.def` files** of the original TS-V3 distribution (`/home/eric/bin/Dos/M2/TS-V3`: EXAMPLES, MODBASE, REP16, SRC, INCLUDE, GRAPHIX, SAVE). **0 syntax errors**; the only rows left are semantic `not a type` messages (parse-clean), e.g. GWINC2M/GRAPH/WINDOW/DEF. - Coco/R (`CR`) reports **25 benign LL(1) advisories** ("X is the start & successor of a deletable structure", plus a few "start of several alternatives"); attribute and undefined-nonterminal checks are clean. - Scratch compiler regression: **35 passed, 0 failed**. ## Dialect points the corpus proves (vs. the modern subset) - identifiers carry `_` and `@` (Graph._VGA, ct__@Fssssi) - `&` = AND, `~` = NOT, `<>` = `#` (all accepted) - literal suffixes `H` (hex), `C` (char), `B` (octal), plus `0x`/`0X` hex forms - `::=` constant alias form (GCoord::=INTEGER) - anchored type subranges (SHORTINT[-1..1]) - qualified type/designator idents (Client.ClientRec, p^.Left, S1[sp1]) - full composite types: ARRAY (fixed + open), RECORD with variants, SET OF (range/base/enum forms), POINTER TO, PROCEDURE types - statements: WITH, IN, IS, set constructors `{}`, shifts `>>`, parameterless procedure-call statements - Clarion-form classes with `CLASS ... IMPLEMENTATION` and two-parent CLAUSE ## Conditional-compile fences (scanner) The scanner treats `(*%F flag*) ... (*%E*)` regions as **disabled in the default (flags-off) build**: the region body (declarations, whole procedures, even a full `VAR ... BEGIN ... END` alternate module body) is skipped as a comment, nesting level-matched (nested `(*%T/E*)` markers balance). `(*%T flag*)` markers are whitespace: the region content is real code and is compiled (thread/`_fdata`-variant shipping, e.g. LIB.MOD's `IsOfClass` Far/Near WHILE pairs where only the `%F` twin is dropped). Standalone `(*%E*)` is an ordinary empty comment. This behaviour is a **manual patch to the generated `M2S.mod` `Comment()` routine** and must be re-applied after every `CR` regeneration (see the note below). It is required to parse the shipped corpus: DEMO/OMAIN/COMPDEMO doc banners, WINDOW.MOD's OS/2 alternate body, LIB.MOD's variant blocks. ## Regeneration notes 1. `CR -m TopSpeed-V3-M2.atg` produces `M2S/M2P/M2(.mod/.def)`. 2. `CR` garbles `M2S.def`; restore it from `compiler/src/M2S.def` (the pristine copy in this repo). 3. Re-apply the `%F`-skip fence patch to `M2S.mod` (the `Comment()` routine; keep `(*`/`%` out of patch comments - gm2 nested comment parsing). 4. `Neither :the: M2S.def` nor `M2S.mod` carry v3-specific tokens; the token tables are regenerated from the `.atg`. ## Bare EBNF (PRODUCTIONS) ```ebnf M2 = Unit "." . Unit = DefUnit | ImplUnit | ProgModule . DefUnit = "DEFINITION" "MODULE" GetIdent ";" { Import } { ConstBlock | TypeBlock | VarBlock | ProcHeading ";" } "END" GetIdent . ImplUnit = "IMPLEMENTATION" "MODULE" GetIdent ";" { Import } DeclSeq [ "BEGIN" [ StatSeq ] ] "END" GetIdent . ProgModule = "MODULE" GetIdent [ Priority ] ";" { Import } DeclSeq [ "BEGIN" [ StatSeq ] ] "END" GetIdent . DeclSeq = { ConstBlock | TypeBlock | VarBlock | [ "INLINE" ] ProcDecl ";" | NestedModule ";" | ClassItem ";" | "LABEL" LabelList ";" } . LabelList = GetIdent { "," GetIdent } . NestedModule = "MODULE" GetIdent [ Priority ] ";" { Import } [ Export ] DeclSeq [ "BEGIN" [ StatSeq ] ] "END" GetIdent . Priority = "[" integer "]" . Export = "EXPORT" [ "QUALIFIED" ] GetIdent { "," GetIdent } ";" . Import = "FROM" GetIdent "IMPORT" ImpList ";" | "IMPORT" ImpList ";" . ImpList = GetIdent { "," GetIdent } . TypeBlock = "TYPE" { TypeItem ";" | ClassItem ";" } . TypeItem = GetIdent ( "=" Type | ) . Type = TypeIdent | [ IntegerTypeSubrange ] "[" Expr ".." Expr "]" [ "OF" Type ] | "(" EnumList ")" (* enumeration *) | "ARRAY" [ "[" Bound { "," Bound } "]" ] "OF" Type | "RECORD" FieldListSequence "END" | "SET" "OF" Type | "POINTER" "TO" Type | "PROCEDURE" [ "(" [ ProcTypeParam { "," ProcTypeParam } ] ")" ] . ProcTypeParam = [ "VAR" ] Type . Bound = integer | GetIdent | subrange | enum . FieldListSequence = { FieldList } . FieldList = [ IdentList ":" Type ] | "CASE" Ident [ ":" Type ] "OF" VariantList "END" | ";" . VariantList = [ "|" ] Variant { "|" Variant } [ "ELSE" FieldListSequence ] . Variant = VariantLabels ":" FieldListSequence . ClassItem = "CLASS" ( "IMPLEMENTATION" ClassImplRest | ClassRest ) . ClassRest = GetIdent [ Parents ] ";" { ClassField ";" } { MethodHeading ";" ( "FORWARD" ";" | "BEGIN" [ StatSeq ] "END" GetIdent ";" | ) } [ "BEGIN" [ StatSeq ] ] "END" GetIdent . Parents = "(" GetIdent [ "CLAUSE" GetIdent ] { "," GetIdent } ")" . ClassField = GetIdent ( "=" GetIdent | { "," GetIdent } ":" Type ) . MethodHeading = [ "VIRTUAL" ] ProcHeading . ClassImplRest = GetIdent ";" { MethodImpl ";" } [ "BEGIN" [ StatSeq ] ] "END" GetIdent . ConstBlock = "CONST" { ConstDecl ";" } . ConstDecl = GetIdent ( "=" Expr | "::=" Expr ) . VarBlock = "VAR" { VarDecl ";" | "[" Expr ":" Expr "]" : ";" } . VarDecl = VarIdents ":" Type . VarIdents = GetIdent [ "[" Bound ":" Bound "]" ] { "," GetIdent [ "[" Bound ":" Bound "]" ] } . (* incl. mapped-array nil slots *) ParIdents = GetIdent { "," GetIdent } . ProcHeading = "PROCEDURE" GetIdent [ FormalParams ] [ ":" Type ] [ "IN" GetIdent ] . FormalParams = "(" [ ParIdents ":" Type { ";" ParIdents ":" Type } ] ")" . ProcDecl = "INLINE" ProcHeading ";" | ProcHeading ";" ( "FORWARD" | Block ) | "PROCEDURE" GetIdent [ FormalParams ] ";" "IN" GetIdent . Block = DeclSeq [ "BEGIN" [ StatSeq ] ] "END" GetIdent . StatSeq = Statement { ";" [ Statement ] } . Statement = DesignStat | "IF" Expr "THEN" [ StatSeq ] { "ELSIF" Expr "THEN" [ StatSeq ] } [ "ELSE" [ StatSeq ] ] "END" | "WHILE" Expr "DO" [ StatSeq ] "END" | "REPEAT" [ StatSeq ] "UNTIL" Expr | "LOOP" [ StatSeq ] "END" | "FOR" GetIdent ":=" Expr "TO" Expr [ "BY" Expr ] "DO" [ StatSeq ] "END" | "CASE" Expr "OF" [ "|" ] CaseAlt { "|" CaseAlt } [ "ELSE" [ StatSeq ] ] "END" | "WITH" Designator [ "," Designator ] "DO" [ StatSeq ] "END" | "RETURN" [ Expr ] | "GOTO" GetIdent | "EXIT" . DesignStat = Designator ( ":=" Expr | "(" [ ActualParams ] ")" | ) . CaseAlt = CaseLabel { "," CaseLabel } ":" [ StatSeq ] . CaseLabel = Expr [ ".." Expr ] . ActualParams = Expr { "," Expr } . Expr = SimExpr [ Rel SimExpr ] . Rel = "=" | "#" | "<>" | "<" | "<=" | ">" | ">=" | "IN" | "IS" . SimExpr = [ "+" | "-" ] Term { ( "+" | "-" | "OR" ) [ Term ] } . Term = Fact { ( "*" | "/" | "DIV" | "MOD" | "AND" | "&" | "<<" | ">>" ) Fact } . Fact = integer | real | string | "{" [ SetItems ] "}" | "[" Expr ":" Expr [ TypeIdent2 ] "]" MapTail (* mapped array *) | Design2 | "(" Expr ")" | "NOT" Fact | "~" Fact . MapTail = { "." GetIdent | "^" | "[" Expr "]" | "(" [ ActualParams ] ")" } . Design2 = Designator { "." GetIdent | "^" | "[" ExprList "]" | "(" [ ActualParams ] ")" } . Designator = TypeIdent | GetIdent . TypeIdent2 = GetIdent { "." GetIdent } . SetItems = SetItem { "," SetItem } . SetItem = Expr [ ".." Expr ] . GetIdent = ident . ``` Semantic markers: constructs the backend cannot lower yet parse fully and end with SemError(230) (`not supported yet`), following the parse-now/ lower-later discipline of `M2.atg`. Semantic codes reused from the V1/V2/Test2 family: 200 duplicate, 201 undeclared, 202 module-name mismatch, 210 bad assignment, 211 bad arithmetic, 212 boolean operand, 213 incompatible comparison, 214 boolean-cond required, 220 FOR bounds, 221 not a type name, 222 set mismatch, 230 not supported yet, 231 opaque outside definition, 232 bad RETURN.