Unix functions for obc

The obc compiler comes with a lot of handy libraries, but once in a while you just need some function that is not in the standard supplied libraries. And it is near impossible to code the function in Oberon. For example when you need the time of day. So I needed a way to access the Linux system through a C functioncall. That's what this topic is about: extend the obc compiler the lazy way.

Luckily, prof Spivey already inserted the foundations for this kind of extensions in his obc compiler. It involves the DynLink library for making things quite easy.

The new functions added

For this moment I needed just three functions added to obc:

There is quite some resemblance between this method and the way Mocka handles the FOREIGN MODULES.

Create unix.mod

First create an oberon file containing the function prototypes (descriptions). I named this file 'unix.mod' all in lowercase letters. These are the contents of the file:

MODULE unix;

(*   Unix functions for obc Oberon; Copyleft Jan Verhoeven	*)

IMPORT DynLink;

PROCEDURE Usleep* (usec: INTEGER) : INTEGER IS "unix_Usleep";

PROCEDURE Time* () : INTEGER IS "unix_Time";

PROCEDURE Clock* () : INTEGER IS "unix_Clock";

BEGIN
  DynLink.Load ("./unix.so")
END unix.
   
The three functions are listed and they are coupled to another function. For example 'Time' is coupled to 'unix_Time'. But this 'unix' refers to another one, not to 'unix.mod'. It refers to the 'unix.c' file.
When compared to Modula-2 this is the DEFINITION MODULE. In oberon parlance: the '*' behind a function name means it is exported to the system and callable as such.

Create unix.c

We declared the oberon function prototypes in 'unix.mod'. Now we need to create a C language file that acts as the 'interpreter', the 'interface' between oberon and C. The file 'unix.c' contains:

#include <unistd.h>
#include <time.h>
#include "obx.h"

/*	Unix interface file for unix functions in obc Oberon
 * 	CopyLeft Jan Verhoeven
 */

PRIMDEF int unix_Clock ()
{
   ob_res.i = clock ();
}


PRIMDEF int unix_Time ()
{
   ob_res.i = time (NULL);
}


PRIMDEF void unix_Usleep(value *bp) 
{
   ob_res.i = usleep (bp [HEAD + 0].i);
}
   
Each line is built like so: Now that isn't too complicated, is it? Although there is not much documentation about this, apart from the webpage How to add primitives to OBC which is very complete. Still I guessed that 'ob_res.i' is a means of returning an INTEGER to the caller process. Until now it works as expected.

Compiling the parts

Of course it is not enough to just make these two text files. We need to bind all the C functions together and make them accessible to obc. First we compile the C part with the magic spell

gcc -fPIC -shared -I /usr/local/lib/obc unix.c -o unix.so
This creates a 'unix.so' file. Make sure this file is ALWAYS around in the directory that is used by the program that uses the unix module.

Now you can just create your obc source file like you always do. But when compiling the source you need to use some more magic. I created a make-like file for it:

#! /usr/bin/bash

obc -C -o $1 unix.mod $2.mod unix.c
   
So when I want to compile tim.mod and make an executable TIME.EXE the command in the console would be:
./mako TIME.EXE tim
This is a fairly complex obc command: Let's make one thing clear: with the operators and options in this order, it works like it ought to. Yes it is not logical that unix.c is the last option. No I will not try and see if other sequences still work.

An example: get the Unix date

Below is a very short program to show how the time is colelcted from the Linux system:

MODULE tim;

IMPORT  unix, In, Out;

VAR 	t, t1, t2 : INTEGER;

BEGIN
  t1 := unix.Time ();
  In.Int (t);
  t2 := unix.Time ();
  Out.Int (t2 - t1, 3);
  Out.Int (t2, 20);
  Out.Ln
END tim.
   
I don't think it needs much explanation. It is short and standard oberon syntax. The time is fetched, I wait for a number to be entered from the keyboard (go fetch a slice of cheese) and then I fetch the time again. The difference is calculated and displayed on screen.

See it compile

I assume you already compiled the C interface files. Now compile tim and make a file TIM.EXE

./mako TIME.EXE tim

See it run

And lets see it run:
jan@fluor:~/Oberon/$ ./TIME.EXE 
2 
 15          1582058447 
   
I entered the '2' which happened 15 seconds after I started the program. The unix time was then '1582058447' This shows it works.

Page created 18 Feb 2020,