grammar.md 8.6 KB

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)

  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.