Oberon for microprocessors and controllers.

As explained in the main page, I got entangled in Oberon. And now I want to use the techniques described in Compiler construction (by Niklaus Wirth) to generate an Oberon-0 compiler for small microprocessors and controllers. I am going to use the sources (in real Oberon) as they are described in the afore mentioned book.

The first task is to transform the Oberon sourcecode into Mocka Modula-2 format and syntax. I will mention the steps in this section, possibly divided over several files.

OSS: the Scanner

Below is the source of what used to be the OSS.Mod module. It was in Oberon format, but I changed it into Mocka Modula-2 format. The file is now named OSS.mi. It compiles to an object file! We start out with the DEFINITION MODULE Scanner.md:

DEFINITION MODULE OSS; 			(* NW 19.9.93 / 17.11.94*)

(*     Adapted for Mocka Modula-2 by Jan Verhoeven, Feb 2006.  
       E-mail: fruttenboel@gmail.com      	 	    	       	*)

CONST  IdLen = 16;		(* symbols *)

       times   =  1;    div    =  3; 	mod       =  4; 	and       =  5; 
       plus    =  6;    minus  =  7; 	or 	  =  8; 	eql 	  =  9; 
       neq     = 10;    lss    = 11; 	geq 	  = 12; 	leq 	  = 13; 
       gtr     = 14;    period = 18; 	comma 	  = 19; 	colon 	  = 20; 
       rparen  = 22;    rbrak  = 23; 	of 	  = 25; 	then 	  = 26; 
       do      = 27;    lparen = 29; 	lbrak 	  = 30; 	not 	  = 32; 
       becomes = 33;    number = 34; 	ident 	  = 37; 	semicolon = 38; 
       end     = 40;    else   = 41;    elsif 	  = 42; 	if 	  = 44; 
       while   = 46;    array  = 54; 	record    = 55; 	const 	  = 57; 
       type    = 58;    var    = 59; 	procedure = 60; 
       begin   = 61;    module = 63;

TYPE   Ident = ARRAY [0..IdLen - 1] OF CHAR;

VAR    val     	    	      : LONGINT;
       id		      : Ident;
       error		      : BOOLEAN;
      
PROCEDURE Mark (msg    : ARRAY OF CHAR);

PROCEDURE Get (VAR sym : INTEGER);
  
PROCEDURE Init (Name   : ARRAY OF CHAR);

END OSS.
   

Scanner IMPLEMENTATION MODULE

Below is the source code for the IMPLEMENTATION MODULE OSS.mi which belongs to OSS.md. The main differences with the Oberon equivalent are:

  1. The procedure ReadChar was added
  2. ReadChar keeps track of pos, Line and Column
  3. The layout of Mark is changed
  4. Strings.StrEq was needed for comparing text strings
  5. INTEGER type casts were needed to satisfy Mocka's type checking
  6. Init only needs a filename to open
Here it comes:
IMPLEMENTATION MODULE OSS; 			(* NW 19.9.93 / 17.11.94*)

(*     Adapted for Mocka Modula-2 by Jan Verhoeven, Feb 2006.  
       E-mail: fruttenboel@gmail.com      	 	    	       	*)

IMPORT  ASCII, InOut, Strings, TextIO;


CONST 	KW 	= 34;  	       		(*  symbols  *)           
	null 	= 0;
	eof 	= 64;


VAR   	ch	     		: CHAR;
      	nkw, pos, Line, Column, 
	errpos		   	: INTEGER;
	inFile			: TextIO.File;
	ProgramEnd		: BOOLEAN;
      	keyTab	  		: ARRAY [0 .. KW-1] OF RECORD 
      		                    	        sym   : INTEGER; 
				    		id    : ARRAY [0..11] OF CHAR 
			          	      END;


PROCEDURE ReadChar (VAR  chr	: CHAR);

BEGIN
   TextIO.GetChar (inFile, chr);
   IF  TextIO.EOF (inFile) = TRUE  THEN  ProgramEnd := TRUE  END;
   INC (pos);
   INC (Column);
   IF  chr = ASCII.LF  THEN
      INC (Line);
      Column := 1
   END
END ReadChar;


PROCEDURE Mark (msg: ARRAY OF CHAR);

VAR  p	  : INTEGER;

BEGIN 
   p := pos - 1;
   IF  p > errpos  THEN
      InOut.WriteString ("Error ");	InOut.WriteString (msg);
      InOut.WriteString ("at pos"); 	InOut.WriteInt (p, 6);
      InOut.WriteString (", line =");	InOut.WriteInt (Line, 5);
      InOut.WriteString (", col =");	InOut.WriteInt (Column, 4);
      InOut.Write (".");    		InOut.WriteLn
   END;
   errpos := p;
   error := TRUE
END Mark;


PROCEDURE Get (VAR sym: INTEGER);
  
    PROCEDURE Ident;
      
    VAR   i, k	    : INTEGER;
    
    BEGIN 
       i := 0;
       REPEAT
          IF  i < IdLen  THEN   id[i] := ch;   INC (i)   END;
          ReadChar (ch)
       UNTIL  (ch < "0") OR (ch > "9") & (CAP (ch) < "A") OR (CAP (ch) > "Z");
       id [i] := 0C;
       k := 0;
       WHILE  (k < nkw) & Strings.StrEq (id, keyTab [k].id)  DO  INC (k)  END;
       IF  k < nkw  THEN 
          sym := keyTab[k].sym 
       ELSE 
          sym := ident 
       END
    END Ident;


    PROCEDURE Number;

    BEGIN
       val := 0; 	sym := number;
       REPEAT
          IF  val <= (MAX (LONGINT) - INTEGER (ORD (ch) + ORD ("0"))) DIV 10  THEN
             val := 10 * val + INTEGER ((ORD (ch) - ORD ("0")))
          ELSE 
	     Mark ("number too large"); 
	     val := 0
          END;
          ReadChar (ch)
       UNTIL  (ch < "0") OR (ch > "9")
    END Number;
    

    PROCEDURE comment;
    
    BEGIN 
       ReadChar (ch);
       LOOP
          LOOP
             WHILE  ch = "("  DO 
	        ReadChar (ch);
            	IF  ch = "*"  THEN  comment  END
             END;
             IF  ch = "*"  THEN  ReadChar (ch);  EXIT  END;
	     IF  ProgramEnd  THEN  EXIT  END;
             ReadChar (ch)
          END;
          IF  ch = ")"  THEN  ReadChar (ch);  EXIT  END;
          IF  ProgramEnd  THEN 
	     Mark ("comment not terminated"); 
	     EXIT 
	  END
       END
    END comment;


BEGIN
   WHILE  (NOT ProgramEnd) & (ch <= " ")  DO  ReadChar (ch)  END;
   IF  ProgramEnd  THEN  
      sym := eof
   ELSE 
      CASE ch OF
         "&"  :   ReadChar (ch); 	sym := and
      |  "*"  :   ReadChar (ch); 	sym := times
      |  "+"  :   ReadChar (ch); 	sym := plus
      |  "-"  :   ReadChar (ch); 	sym := minus
      |  "="  :   ReadChar (ch); 	sym := eql
      |  "#"  :   ReadChar (ch); 	sym := neq
      |  "<"  :   ReadChar (ch);
              	  IF  ch = "="  THEN  ReadChar (ch);  sym := leq  ELSE  sym := lss  END
      |  ">"  :   ReadChar (ch);
              	  IF  ch = "="  THEN  ReadChar (ch);  sym := geq  ELSE  sym := gtr  END
      |  ";"  :   ReadChar (ch); 	sym := semicolon
      |  ","  :   ReadChar (ch); 	sym := comma
      |  ":"  :   ReadChar (ch);
              	  IF  ch = "="  THEN  ReadChar (ch);  sym := becomes  ELSE  sym := colon  END
      |  "."  :   ReadChar (ch); 	sym := period
      |  "("  :   ReadChar (ch);
              	  IF  ch = "*"  THEN  comment;  Get (sym)  ELSE  sym := lparen  END
      |  ")"  :   ReadChar (ch);        sym := rparen
      |  "["  :   ReadChar (ch); 	sym := lbrak
      |  "]"  :   ReadChar (ch); 	sym := rbrak
      |  "0".."9"    :   Number;
      |  "A" .. "Z", 
         "a".."z"    :  Ident
      |  "~"  :  ReadChar (ch); 	sym := not
      ELSE 
         ReadChar (ch); 		sym := null
      END
   END
END Get;


PROCEDURE Init (Name   : ARRAY OF CHAR);
  
BEGIN
   pos := 0;				ProgramEnd := FALSE;
   error := FALSE;			errpos := pos;
   TextIO.OpenInput (inFile, Name);
   ReadChar (ch)
END Init;
  

PROCEDURE EnterKW (sym  : INTEGER;  name  : ARRAY OF CHAR);

BEGIN
   keyTab [nkw].sym := sym;
   Strings.Assign (name, keyTab [nkw].id);
   INC (nkw)
END EnterKW;


BEGIN 
   error := TRUE; 
   nkw := 0;
   EnterKW (null, "BY");
   EnterKW (do,   "DO");
   EnterKW (if,   "IF");
   EnterKW (null, "IN");
   EnterKW (null, "IS");
   EnterKW (of,   "OF");
   EnterKW (or,   "OR");
   EnterKW (null, "TO");
   EnterKW (end,  "END");
   EnterKW (null, "FOR");
   EnterKW (mod,  "MOD");
   EnterKW (null, "NIL");
   EnterKW (var,  "VAR");
   EnterKW (null, "CASE");
   EnterKW (else, "ELSE");
   EnterKW (null, "EXIT");
   EnterKW (then, "THEN");
   EnterKW (type, "TYPE");
   EnterKW (null, "WITH");
   EnterKW (array,  "ARRAY");
   EnterKW (begin,  "BEGIN");
   EnterKW (const,  "CONST");
   EnterKW (elsif,  "ELSIF");
   EnterKW (null,   "IMPORT");
   EnterKW (null,   "UNTIL");
   EnterKW (while,  "WHILE");
   EnterKW (record, "RECORD");
   EnterKW (null,   "REPEAT");
   EnterKW (null,   "RETURN");
   EnterKW (null,   "POINTER");
   EnterKW (procedure, "PROCEDURE");
   EnterKW (div,    "DIV");
   EnterKW (null,   "LOOP");
   EnterKW (module, "MODULE");
END OSS.
   
There is something funny with this KeyTable since it contains keywords which are unknown to Oberon0. I mention:
  1. BY
  2. IN
  3. IS
  4. TO
  5. FOR
  6. NIL
  7. CASE
  8. EXIT
  9. WITH
  10. IMPORT
  11. UNTIL
  12. REPEAT
  13. RETURN
  14. POINTER
  15. LOOP
Apparently, Oberon0 is prepared for some serious extensions. And, as a matter of facts, I want to have LOOP/END/EXIT incorporated in the compiler as soon as possible. I particularly like the LOOP/EXIT/END construct since it allows multiple exit strategies from a strong looper.

Anyway, as we can see below, the units compile without errors:

bash-2.05$ MC
Mocka 9903
>> s OSS
>> c OSS
.. Compiling Implementation of OSS I/0009 II/0009
>> ls -l OSS*
-rw-r--r--    1 jan      users         942 Feb  3 00:21 OSS.d
-rw-r--r--    1 jan      users        1200 Feb  3 00:21 OSS.md
-rw-r--r--    1 jan      users        5539 Feb  3 00:40 OSS.mi
-rw-r--r--    1 jan      users       13388 Feb  3 00:48 OSS.o
-rw-r--r--    1 jan      users          70 Feb  3 00:48 OSS.r
>> q
bash-2.05$
   

OSP and OSG: the big push.

I started work on the OSG and OSP modules but this is quite a work. Lots of qualified imports going back and forth. I tried to dig myself through the OSP module, but got stuck several times. Remember: I'm not a genius. I need time.
Yesterday, I had a bright moment (or rather: THE bright moment of March) and decided to merge OSP and OSG into one file and call that file Oberon0.mi. This removes the majority of qualified imports and makes the transfer from an Oberon controlled task into a Linux command line executable. See how it works out...

Page created February 2006,

Page equipped with FroogleBuster technology