explanations.md 9.3 KB

Mini Modula-2 Compiler — Explanations

1. Goal

The project is a simplified Modula-2-like compiler implemented in GNU Modula-2.

The language is intentionally smaller than full Modula-2:

  • no procedures or functions
  • INTEGER and CHAR types
  • variables and constants as supported by the compiler
  • basic statements and expressions
  • symbol table
  • type checking
  • code generation

The objective is to have a real compiler pipeline rather than only a parser.

2. Compiler architecture

The compiler follows this pipeline:

MiniM2 source
    |
    v
+-----------+
|   Lexer   |
+-----------+
    |
    v
+-----------+
|  Parser   |
+-----------+
    |
    v
+----------------+
| Symbol table   |
| Type checking  |
+----------------+
    |
    v
+----------------+
| Code generator |
+----------------+
    |
    v
Generated GNU Modula-2
    |
    v
+-----------+
|    gm2    |
+-----------+
    |
    v
Native executable

The compiler itself is written in GNU Modula-2.

3. Why GNU Modula-2 is used as the backend

Coco/R has traditionally provided compiler-generator targets such as C#, Java and C++, but GNU Modula-2 is not a standard Coco/R target.

Instead of trying to make Coco/R generate GNU Modula-2 code, the compiler can be implemented directly in GNU Modula-2.

The first native backend is source-to-source:

  1. Parse the MiniM2 program.
  2. Build and check its symbols and types.
  3. Generate valid GNU Modula-2.
  4. Invoke gm2.
  5. Let GNU Modula-2 produce the final native executable.

This is already a genuine compiler architecture: the front-end performs lexical analysis, parsing and semantic checking, while GNU Modula-2 acts as the native backend.

4. GNU Modula-2

GNU Modula-2 is provided by GCC.

Typical compilation is:

gm2 -g hello.mod

GNU Modula-2 uses .mod files for implementation/program modules and .def files for definition modules.

The generated program can therefore be compiled by the GNU Modula-2 compiler.

5. Project layout

The generated project is:

mini-modula2-real-compiler/
├── MiniM2C.mod
├── Makefile
├── README.md
└── examples/
    ├── hello.mod
    └── bad.mod

MiniM2C.mod contains the compiler implementation.

examples/hello.mod is a valid example program.

examples/bad.mod is intended to demonstrate semantic/type errors.

6. Building

From the project directory:

make

The compiler executable is intended to be:

minim2c

A MiniM2 source file can then be compiled with:

./minim2c examples/hello.mod

The compiler generates a GNU Modula-2 source file:

examples/hello.mod.gen.mod

and then invokes gm2 to build the native executable.

The exact executable name can depend on the generated module and GNU Modula-2 toolchain.

7. Running the example

The intended workflow is:

cd mini-modula2-real-compiler
make
./minim2c examples/hello.mod
./mini-program

If the generated executable has a different name, use the executable name reported by the compiler or inspect the generated build output.

8. Lexer

The lexer converts source characters into tokens.

Typical token categories include:

  • identifiers
  • integer literals
  • character literals
  • operators
  • punctuation
  • keywords

Examples of keywords include:

MODULE
BEGIN
END
VAR
CONST
INTEGER
CHAR
READ
WRITE
IF
THEN
ELSE
WHILE
DO

The lexer is responsible for recognizing these tokens before parsing.

9. Parser

The parser consumes the token stream and verifies that the program follows the MiniM2 grammar.

A simplified structure is:

Module
    = MODULE identifier ;
      declarations
      BEGIN
        statements
      END identifier .

Expressions are parsed according to precedence.

For example:

a + b * c

must be interpreted as:

a + (b * c)

rather than:

(a + b) * c

10. Symbol table

The symbol table records declared identifiers.

For each identifier the compiler can keep information such as:

name
kind
type

For example:

x : INTEGER
letter : CHAR

When an identifier is used, the compiler looks it up in the symbol table.

This allows the compiler to detect errors such as:

unknown variable
duplicate declaration

11. Type checking

The semantic phase checks that expressions and statements use compatible types.

Examples:

INTEGER + INTEGER

is valid.

But:

INTEGER + CHAR

is rejected by the type checker.

Assignments are also checked.

For example:

VAR
    x : INTEGER;
    c : CHAR;

Then:

x := 10;

is valid, while:

x := c;

should produce a type error.

12. Code generation

The current backend generates GNU Modula-2 source.

For example, an internal MiniM2 construct such as:

x := 10;

can be emitted as GNU Modula-2:

x := 10;

The generated source is then compiled by gm2.

This approach makes the compiler relatively small while still producing native executables.

13. Important backend limitation

The first implementation is deliberately simple.

In particular, the initial WRITE implementation supports only simple operands such as an identifier or literal rather than every possible expression.

For example, this may be supported:

WRITE(x);

while something such as:

WRITE(x + 1);

requires an expression-aware WRITE code-generation path.

That is one of the natural next improvements.

14. Error handling

The compiler should report lexical, syntactic and semantic errors.

Examples:

unexpected token
expected identifier
undeclared identifier
duplicate declaration
type mismatch

A useful future improvement is to preserve exact source line and column information for every token and report errors like:

example.mod:12:7: type mismatch

15. Why this is a real compiler

A compiler does not have to directly emit machine instructions itself.

A common architecture is:

source
  -> front-end
  -> intermediate representation / generated source
  -> backend compiler
  -> object code
  -> executable

In this project, GNU Modula-2 is the backend.

The compiler therefore performs real compilation work:

  • lexical analysis
  • syntax analysis
  • symbol resolution
  • semantic/type checking
  • code generation
  • native compilation

16. Future evolution

A stronger version can evolve toward:

AST

Instead of immediately generating code while parsing, build an Abstract Syntax Tree:

Module
 ├── Declarations
 └── Statements
      ├── Assignment
      ├── If
      ├── While
      └── Write

Intermediate representation

The AST can then be converted into an IR.

For example:

LOAD_CONST 10
STORE x

or:

LOAD x
LOAD 1
ADD
STORE x

Better code generation

The IR can then be translated to:

  • GNU Modula-2
  • C
  • LLVM IR
  • assembly
  • another native backend

More language features

Possible additions include:

  • BOOLEAN
  • arrays
  • records
  • FOR
  • richer IF
  • richer WHILE
  • modules and definition modules
  • procedures
  • functions
  • parameters
  • standard library support

17. Relation to the Theia extension

The compiler and the Theia Modula-2 extension can eventually be integrated.

Theia can provide:

  • syntax highlighting
  • indentation
  • keyword uppercasing
  • snippets
  • diagnostics
  • build commands
  • compiler invocation
  • error navigation

The compiler can provide:

  • parsing
  • semantic analysis
  • type checking
  • code generation
  • executable generation

A future language-server integration could expose compiler diagnostics directly in the editor.

18. Automatic keyword uppercasing

The Theia extension has a separate editor feature for automatic Modula-2 keyword uppercasing.

The required behavior is:

Only the keyword currently being typed is converted to uppercase.

It should not uppercase unrelated existing text in the document.

For example, when typing:

mod

the editor can turn the current keyword into:

MOD

without modifying other words.

19. Snippets

The Theia extension can also provide Modula-2 snippets.

Typical snippets can accelerate constructs such as:

MODULE ...;
BEGIN
...
END ... .

or declarations and control structures.

Snippets are normally triggered through the editor's completion/snippet mechanism.

20. Verification status

The project was generated as a GNU Modula-2 compiler prototype.

It should not be described as production-ready without compiling and testing it with an installed GNU Modula-2 (gm2) toolchain.

The development environment used to prepare the project did not have gm2 available, so the generated compiler was not actually built and executed there.

Before treating it as a finished compiler, install GNU Modula-2 and run:

gm2 --version
make
./minim2c examples/hello.mod

Then test both successful programs and intentionally invalid programs.

21. Recommended next step

The most useful next improvement is to make the compiler robust enough to compile its complete test suite under GNU Modula-2.

After that, the compiler can be upgraded from the simple source-to-source backend to:

Lexer
  -> Parser
  -> AST
  -> Symbol table
  -> Type checker
  -> IR
  -> Code generator
  -> Native executable

This provides a cleaner foundation for adding the full set of desired Modula-2 features.