Skip to content

N. Wirth's Project Oberon RISC compiler ported to Go.

License

Notifications You must be signed in to change notification settings

fzipp/oberon-compiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

oberon-compiler

This is a port of the Project Oberon compiler for RISC-5 (not to be confused with RISC-V) from Oberon to Go. The compiled binaries can be run in a Project Oberon RISC emulator like Peter de Wachter's oberon-risc-emu or on my port of it to Go — or on real Project Oberon FPGA hardware, of course.

The source code of the original compiler by Niklaus Wirth can be found on the website linked above.

Installation

$ go install github.com/fzipp/oberon-compiler/cmd/oc@latest

Usage

oc [-s] modfile...

Flags:
    -s  Overwrites existing symbol file on changes.

Example 1: Compiling the Oberon core modules

Download the source code of the Project Oberon core modules from Wirth's homepage (the *.Mod files in the first row).

Compile the inner core modules:

$ oc Kernel.Mod FileDir.Mod Files.Mod Modules.Mod

Compile the outer core modules:

$ oc Input.Mod Display.Mod Viewers.Mod Fonts.Mod Texts.Mod Oberon.Mod MenuViewers.Mod TextFrames.Mod System.Mod Edit.Mod

The compilation result is a RISC object file (.rsc) and a symbol file (.smb) for each module.

Example 2: Hello World

This requires the compiled core modules from the previous example, specifically the symbol files Texts.smb and Oberon.smb, because the example code will import these two modules.

Create a source file named Hello.Mod containing the following code:

MODULE Hello;

IMPORT Texts, Oberon;

VAR W: Texts.Writer;

PROCEDURE World*;
BEGIN
  Texts.WriteString(W, "hello, world");
  Texts.WriteLn(W);
  Texts.Append(Oberon.Log, W.buf);
END World;

BEGIN
  Texts.OpenWriter(W);
END Hello.

Compile it with the Oberon compiler oc:

$ oc Hello.Mod
OR Compiler  8.3.2020; ported to Go
  compiling Hello new symbol file 33 40 E4DFD669

This results in two new files:

Hello.rsc Hello.smb

One is the RISC object file (.rsc), and the other is the symbol file (.smb).

Motivation

My motivation was the same as @arnobastenhof's motivation to port a subset of Wirth's reference implementation for the Oberon-07 language to C:

"It was written primarily for self-educational purposes as a kind of intensive code reading exercise."

Having a compiler available outside the target system also turned out to be useful in practice. One can develop code in a familiar environment before transferring it to the target system.

Porting Notes

  • I resisted the temptation to remove limits like the maximum length of strings or identifiers in order to keep source code written for this compiler compatible with the original compiler. These restrictions can also have an educational value as Hanspeter Mössenböck points out in Compiler Construction - The Art of Niklaus Wirth.
  • REPEAT...UNTIL x; was translated to Go as for {...; if x { break } }. I did not rewrite these loops as loops with the condition at the start (which would be idiomatic Go) in order to preserve the original control flow.
  • I kept the "single return" style of Oberon and did not translate it to the "early return" style that is idiomatic in Go.
  • I kept the short names, but I changed the capitalization of many variables and struct fields consistently to "camel case". I had to slightly adjust the names of some functions and variables that would otherwise clash with Go keywords, specifically import and type.
  • I introduced the types ors.Sym, orb.Form and orb.Class, and prefixed the names of their enumeration-like constants, as it is customary in Go. The original code uses raw integer types for them, but I found the additional type safety helpful.
  • The functionality is implemented in Go as methods on types (ors.Scanner, orp.Parser, orb.Base, org.Generator), not as free functions in "flat" packages with package scoped variables. This is a deviation from the original implementation, but it allows the creation of multiple compiler instances, for example to compile multiple modules in parallel with goroutines.
  • I used a map for ors.keyTab instead of an array-based lookup table, and a slice for orp.Parser.pbsList instead of a linked list.
  • I extracted two instances of duplicated code fragments in the scanner as two new methods: ors.Scanner.hexDigit and ors.Scanner.decimalInteger.
  • Non-compilation errors like I/O errors are propagated as panics, with orp.Compile acting as the boundary where they are recovered and transformed back into a regular error return value.
  • DIV and MOD in Oberon are defined differently than / and % in Go (floored division vs. truncated division). In Oberon (-15) DIV 4 = -4 and (-15) MOD 4 = 1, whereas in Go (-15) / 4 == -3 and (-15) % 4 == -3. The original code frequently uses DIV and MOD for bit shifting and masking. The translated code uses bitwise operators such as <<, >> and & instead where appropriate.

License

This project is free and open source software licensed under the Project Oberon License.