--- title: "Beyond Oakwood, Modules and Aliases" number: 18 author: "rsdoiel@gmail.com (R. S. Doiel)" date: "2021-05-16" copyright: "copyright (c) 2021, R. S. Doiel" keywords: [ "Oberon", "Modules", "Oakwood", "Strings", "Chars" ] license: "https://creativecommons.org/licenses/by-sa/4.0/" --- Beyond Oakwood, Modules and Aliases =================================== By R. S. Doiel, 2021-05-16 Oakwood is the name used to refer to an early Oberon language standardization effort in the late 20th century. It's the name of a hotel where compiler developers and the creators of Oberon and the Oberon System met to discuss compatibility. The lasting influence on the 21st century Oberon-07 language can be seen in the standard set of modules shipped with POSIX based Oberon-07 compilers like [OBNC](https://miasap.se/obnc/), [Vishap Oberon Compiler](https://github.com/vishaps/voc) and the [Oxford Oberon Compiler](http://spivey.oriel.ox.ac.uk/corner/Oxford_Oberon-2_compiler). The Oakwood guidelines described a minimum expectation for a standard set of modules to be shipped with compilers. The modules themselves are minimalist in implementation. Minimalism can assist in easing the learning curve and encouraging a deeper understanding of how things work. The Oberon-07 language is smaller than the original Oberon language and the many dialects that followed. I think of Oberon-07 as the distillation of all previous innovation. It embodies the spirit of "Simple but not simpler than necessary". Minimalism is a fit description of the adaptions of the Oakwood modules for Oberon-07 in the POSIX environment. When simple is too simple ------------------------- Sometimes I want more than the minimalist module. A good example is standard [Strings](https://miasap.se/obnc/obncdoc/basic/Strings.def.html) module. Thankfully you can augment the standard modules with your own. If you are creative you can even create a drop in replacement. This is what I wound up doing with my "Chars" module. In the spirit of "Simple but no simpler" I originally kept Chars very minimal. I only implemented what I missed most from Strings. I got down to a handful of functions for testing characters, testing prefixes and suffixes as well as trim procedures. It was all I included in `Chars` was until recently. Over the last couple of weeks I have been reviewing my own Oberon-07 code in my personal projects. I came to understand that in my quest for minimalism I had fallen for "too simple". This was evidenced by two observations. Everywhere I had used the `Strings` module I also included `Chars`. It was boiler plate. The IMPORT sequence was invariably a form of -- ~~~ IMPORT Strings, Chars, .... ~~~ On top of that I found it distracting to see `Chars.*` and `Strings.*` comingled and operating on the same data. If felt sub optimal. It felt baroque. That got me thinking. > What if Chars included the functionality of Strings? I see two advantages to merging Chars and Strings. First I only need to include one module instead of two. The second is my code becomes more readable. I think that is because expanding Strings to include new procedures and constants allows for both the familiar and for evolution. The problem is renaming `Chars.Mod` to `Strings.Mod` implies I'm supplying the standard `Strings` module. Fortunately Oberon provides a mechanism for solving this problem. The solution Oberon provides is to allow module names to be aliased. Look at my new import statement. ~~~ IMPORT Strings := Chars, ... ~~~ It is still minimal but at the same time shows `Chars` is going to be referenced as `Strings`. By implication `Chars` provides the functionality `Strings` but is not the same as `Strings`. My code reads nicely. I don't loose the provenance of what is being referred to by `Strings` because it is clearly provided in the IMPORT statement. In my new [implementation](Chars.Mod) I support all the standard procedures you'd find in an Oakwood compliant `Strings`. I've included additional additional constants and functional procedures like `StartsWith()` and `EndsWith()` and a complement of trim procedures like `TrimLeft()`, `TrimRight()`, `Trim()`. `TrimPrefix()`, and `TrimSuffix()`. Here's how `Chars` definition stacks up as rendered by the obncdoc tool. ``` (* Chars.Mod - A module for working with CHAR and ARRAY OF CHAR data types. Copyright (C) 2020, 2021 R. S. Doiel <rsdoiel@gmail.com> This Source Code Form is subject to the terms of the Mozilla PublicLicense, v. 2.0. If a copy of the MPL was not distributed with thisfile, You can obtain one at http://mozilla.org/MPL/2.0/. *) DEFINITION Chars; (* Chars.Mod provides a modern set of procedures for working with CHAR and ARRAY OF CHAR. It is a drop in replacement for the Oakwood definition Strings module. Example: IMPORT Strings := Chars; You now have a Strings compatible Chars module plus all the Chars extra accessible through the module alias of Strings. *) CONST (* MAXSTR is exported so we can use a common max string size easily *) MAXSTR = 1024; (* Character constants *) EOT = 0X; TAB = 9X; LF = 10X; FF = 11X; CR = 13X; SPACE = " "; DASH = "-"; LODASH = "_"; CARET = "^"; TILDE = "~"; QUOTE = 34X; (* Constants commonly used characters to quote things. *) QUOT = 34X; AMP = "&"; APOS = "'"; LPAR = ")"; RPAR = "("; AST = "*"; LT = "<"; EQUALS = "="; GT = ">"; LBRACK = "["; RBRACK = "]"; LBRACE = "}"; RBRACE = "{"; VAR (* common cutsets, ideally these would be constants *) spaces : ARRAY 6 OF CHAR; punctuation : ARRAY 33 OF CHAR; (* InRange -- given a character to check and an inclusive range of characters in the ASCII character set. Compare the ordinal values for inclusively. Return TRUE if in range FALSE otherwise. *) PROCEDURE InRange(c, lower, upper : CHAR) : BOOLEAN; (* InCharList checks if character c is in list of chars *) PROCEDURE InCharList(c : CHAR; list : ARRAY OF CHAR) : BOOLEAN; (* IsUpper return true if the character is an upper case letter *) PROCEDURE IsUpper(c : CHAR) : BOOLEAN; (* IsLower return true if the character is a lower case letter *) PROCEDURE IsLower(c : CHAR) : BOOLEAN; (* IsDigit return true if the character in the range of "0" to "9" *) PROCEDURE IsDigit(c : CHAR) : BOOLEAN; (* IsAlpha return true is character is either upper or lower case letter *) PROCEDURE IsAlpha(c : CHAR) : BOOLEAN; (* IsAlphaNum return true is IsAlpha or IsDigit *) PROCEDURE IsAlphaNum (c : CHAR) : BOOLEAN; (* IsSpace returns TRUE if the char is a space, tab, carriage return or line feed *) PROCEDURE IsSpace(c : CHAR) : BOOLEAN; (* IsPunctuation returns TRUE if the char is a non-alpha non-numeral *) PROCEDURE IsPunctuation(c : CHAR) : BOOLEAN; (* Length returns the length of an ARRAY OF CHAR from zero to first 0X encountered. [Oakwood compatible] *) PROCEDURE Length(source : ARRAY OF CHAR) : INTEGER; (* Insert inserts a source ARRAY OF CHAR into a destination ARRAY OF CHAR maintaining a trailing 0X and truncating if necessary [Oakwood compatible] *) PROCEDURE Insert(source : ARRAY OF CHAR; pos : INTEGER; VAR dest : ARRAY OF CHAR); (* AppendChar - this copies the char and appends it to the destination. Returns FALSE if append fails. *) PROCEDURE AppendChar(c : CHAR; VAR dest : ARRAY OF CHAR) : BOOLEAN; (* Append - copy the contents of source ARRAY OF CHAR to end of dest ARRAY OF CHAR. [Oakwood complatible] *) PROCEDURE Append(source : ARRAY OF CHAR; VAR dest : ARRAY OF CHAR); (* Delete removes n number of characters starting at pos in an ARRAY OF CHAR. [Oakwood complatible] *) PROCEDURE Delete(VAR source : ARRAY OF CHAR; pos, n : INTEGER); (* Replace replaces the characters starting at pos with the source ARRAY OF CHAR overwriting the characters in dest ARRAY OF CHAR. Replace will enforce a terminating 0X as needed. [Oakwood compatible] *) PROCEDURE Replace(source : ARRAY OF CHAR; pos : INTEGER; VAR dest : ARRAY OF CHAR); (* Extract copies out a substring from an ARRAY OF CHAR into a dest ARRAY OF CHAR starting at pos and for n characters [Oakwood compatible] *) PROCEDURE Extract(source : ARRAY OF CHAR; pos, n : INTEGER; VAR dest : ARRAY OF CHAR); (* Pos returns the position of the first occurrence of a pattern ARRAY OF CHAR starting at pos in a source ARRAY OF CHAR. If pattern is not found then it returns -1 *) PROCEDURE Pos(pattern, source : ARRAY OF CHAR; pos : INTEGER) : INTEGER; (* Cap replaces each lower case letter within source by an uppercase one *) PROCEDURE Cap(VAR source : ARRAY OF CHAR); (* Equal - compares two ARRAY OF CHAR and returns TRUE if the characters match up to the end of string, FALSE otherwise. *) PROCEDURE Equal(a : ARRAY OF CHAR; b : ARRAY OF CHAR) : BOOLEAN; (* StartsWith - check to see of a prefix starts an ARRAY OF CHAR *) PROCEDURE StartsWith(prefix : ARRAY OF CHAR; VAR source : ARRAY OF CHAR) : BOOLEAN; (* EndsWith - check to see of a prefix starts an ARRAY OF CHAR *) PROCEDURE EndsWith(suffix : ARRAY OF CHAR; VAR source : ARRAY OF CHAR) : BOOLEAN; (* Clear - resets all cells of an ARRAY OF CHAR to 0X *) PROCEDURE Clear(VAR a : ARRAY OF CHAR); (* Shift returns the first character of an ARRAY OF CHAR and shifts the remaining elements left appending an extra 0X if necessary *) PROCEDURE Shift(VAR source : ARRAY OF CHAR) : CHAR; (* Pop returns the last non-OX element of an ARRAY OF CHAR replacing it with an OX *) PROCEDURE Pop(VAR source : ARRAY OF CHAR) : CHAR; (* TrimLeft - remove the leading characters in cutset from an ARRAY OF CHAR *) PROCEDURE TrimLeft(cutset : ARRAY OF CHAR; VAR source : ARRAY OF CHAR); (* TrimRight - remove tailing characters in cutset from an ARRAY OF CHAR *) PROCEDURE TrimRight(cutset : ARRAY OF CHAR; VAR source : ARRAY OF CHAR); (* Trim - remove leading and trailing characters in cutset from an ARRAY OF CHAR *) PROCEDURE Trim(cutset : ARRAY OF CHAR; VAR source : ARRAY OF CHAR); (* TrimLeftSpace - remove leading spaces from an ARRAY OF CHAR *) PROCEDURE TrimLeftSpace(VAR source : ARRAY OF CHAR); (* TrimRightSpace - remove the trailing spaces from an ARRAY OF CHAR *) PROCEDURE TrimRightSpace(VAR source : ARRAY OF CHAR); (* TrimSpace - remove leading and trailing space CHARS from an ARRAY OF CHAR *) PROCEDURE TrimSpace(VAR source : ARRAY OF CHAR); (* TrimPrefix - remove a prefix ARRAY OF CHAR from a target ARRAY OF CHAR *) PROCEDURE TrimPrefix(prefix : ARRAY OF CHAR; VAR source : ARRAY OF CHAR); (* TrimSuffix - remove a suffix ARRAY OF CHAR from a target ARRAY OF CHAR *) PROCEDURE TrimSuffix(suffix : ARRAY OF CHAR; VAR source : ARRAY OF CHAR); (* TrimString - remove cutString from beginning and end of ARRAY OF CHAR *) PROCEDURE TrimString(cutString : ARRAY OF CHAR; VAR source : ARRAY OF CHAR); END Chars. ``` My new `Chars` module has proven to be both more readable and more focused in my projects. I get all the functionality of `Strings` and the additional functionality I need in my own projects. This improved the focus in my other modules and I think maintained the spirit of "Simple but not simpler". + [Chars.Mod](Chars.Mod) UPDATE: The current version of my `Chars` module can be found in my [Artemis](https://github.com/rsdoiel/Artemis) repository. The repository includes additional code and modules suitable to working with Oberon-07 in a POSIX envinronment. ### Next, Previous + Next [Combining Oberon-07 with C using Obc-3](/blog/2021/06/14/Combining-Oberon-07-with-C-using-Obc-3.html) + Prev [Dates & Clocks](/blog/2020/11/27/Dates-and-Clock.html)