Combining Oberon-07 and C with OBNC
By R. S. Doiel, 2020-05-01
This is the fifth post in the Mostly Oberon series. Mostly Oberon documents my exploration of the Oberon Language, Oberon System and the various rabbit holes I will inevitably fall into.
In my day job I write allot of code in Go and orchestration code in Python. It’s nice having the convenience of combining code written one language with an another. You can do the same with OBNC. The OBNC compiler supports inclusion of C code in a straight forward manner. In fact Karl’s compiler will generate the C file for you!
In learning how to combine C code and Oberon-07 I started by reviewing Karl’s manual page. The bottom part of that manual page describes the steps I will repeat below. The description sounds more complicated but when you walk through the steps it turns out to be pretty easy.
Basic Process
Creating a C extension for use with OBNC is very straight forward.
- Create a Oberon module with empty exported procedures
- Create a Oberon test module that uses your module
- Compile your test module with OBNC
- Copy the generated module
.c
file to the same directory as your Oberon module source - Edit the skeleton
.c
, re-compile and test
Five steps may sound complicated but in practice is straight forward.
Fmt, an example
In my demonstration of Karl’s instructions I will be creating a
module named Fmt
that includes two procedures
Int()
and Real()
that let you use a C-style
format string to format an INTEGER or REAL as an ARRAY OF CHAR. We
retain the idiomatic way Oberon works with types but allow a little more
flexibility in how the numbers are converted and rendered as
strings.
Step 1
Create Fmt.Mod defining two exported procedures
Int*()
and Real*()
. The procedures body should
be empty. Karl’s practice is to use exported comments to explain the
procedures.
MODULE Fmt;
PROCEDURE Int*(value : INTEGER; fmt: ARRAY OF CHAR;
VAR dest : ARRAY OF CHAR);
END Int;
PROCEDURE Real*(value : REAL; fmt: ARRAY OF CHAR;
VAR dest : ARRAY OF CHAR);
END Real;
BEGIN
END Fmt.
Step 2
Create a test module, FmtTest.Mod, for Fmt.Mod.
MODULE FmtTest;
IMPORT Out, Fmt;
PROCEDURE TestInt(): BOOLEAN;
VAR
fmtString : ARRAY 24 OF CHAR;
dest : ARRAY 128 OF CHAR;
i : INTEGER;
BEGIN
i := 42;
fmtString := "%d";
Fmt.Int(i, fmtString, dest);
Out.String(dest);Out.Ln;
RETURN TRUE
END TestInt;
PROCEDURE TestReal(): BOOLEAN;
VAR
fmtString : ARRAY 24 OF CHAR;
dest : ARRAY 128 OF CHAR;
r : REAL;
BEGIN
r := 3.145;
fmtString := "%d";
Fmt.Real(r, fmtString, dest);
Out.String(dest);Out.Ln;
RETURN TRUE
END TestReal;
BEGIN
ASSERT(TestInt());
ASSERT(TestReal());
Out.String("Success!");Out.Ln;
END FmtTest.
Step 3
Generate a new Fmt.c by using the OBNC compiler.
obnc FmtTest.Mod
mv .obnc/Fmt.c ./
the file .obnc/Fmt.c
is your C template file. Copy it to
the directory where Fmt.Mod is.
Step 4
Update the skeleton Fmt.c
with the necessary C code.
Here’s what OBNC generated version.
/*GENERATED BY OBNC 0.16.1*/
#include "Fmt.h"
#include <obnc/OBNC.h>
#define OBERON_SOURCE_FILENAME "Fmt.Mod"
void Fmt__Int_(OBNC_INTEGER value_, const char fmt_[],
, char dest_[],
OBNC_INTEGER fmt_len)
OBNC_INTEGER dest_len{
}
void Fmt__Real_(OBNC_REAL value_, const char fmt_[],
, char dest_[],
OBNC_INTEGER fmt_len)
OBNC_INTEGER dest_len{
}
void Fmt__Init(void)
{
}
Here’s the skeleton revised with do what we need to be done.
#include ".obnc/Fmt.h"
#include <obnc/OBNC.h>
#include <stdio.h>
#define OBERON_SOURCE_FILENAME "Fmt.Mod"
void Fmt__Int_(OBNC_INTEGER value_,
const char fmt_[], OBNC_INTEGER fmt_len,
char dest_[], OBNC_INTEGER dest_len)
{
(dest_, fmt_, value_);
sprintf}
void Fmt__Real_(OBNC_REAL value_, const char fmt_[],
, char dest_[],
OBNC_INTEGER fmt_len)
OBNC_INTEGER dest_len{
(dest_, fmt_, value_);
sprintf}
void Fmt__Init(void)
{
}
NOTE: You need to change the path for the Fmt.h
file
reference. I also add the stdio.h
include so I have access
to the C function I wish to use. Also notice how OBNC the signature for
the functions use the _
character to identify mapped values
as well as the char arrays being provided with a length parameter. If
you are doing more extensive string work you’ll want to take advantage
of these additional parameters so insure that the as strings are
terminated properly for Oberon’s reuse.
Step 5
Recompile and test.
obnc FmtTest.Mod
./FmtTest
Next and Previous
- Next Compiling OBNC on macOS
- Previously Oberon Loops and Conditions