Modula-2 Reloaded

A Modern Typesafe & Literate Programming Notation

Site Menu

Project

Specification

Implementation

Recommendations

Reference

Needs Updating

Work in Progress

Wastebasket

Wiki Manual

edit SideBar

Template Based Generics

The following describes a method to support template based generics outside of the language definition.

The basic principle is to let the build system take care of template expansion and hide this process from the compiler altogether. In order to do this, two external utilities are required:

  • a text template engine that can expand templates with placeholders into compilable source
  • a make utility or make script that can determine when a template needs to be expanded before compilation

A suitable template engine utility (about 350 lines of code) is available at Benjamin's private repository:

This template engine takes any input text which contains placeholders tagged with leading and trailing "@@" tags and recursively replaces those placeholders with their translations, copying them into the output text.

For example, the template ...

"Dear @@title@@ @@name@@,"

using the translations title="Mr." and name="Sutcliffe" is expanded into ...

"Dear Mr. Sutcliffe,"

This can be used to expand placeholders in source code templates to generate compilable source.

In order for a make utility or make script to know when to expand templates into compilable source and what translations for the placeholders need to be passed to the template engine, there would either have to be a configuration file or the information would have to be embedded in the definition part source files that import the expanded source files.

A configuration file would have to be created by the user of a library separately from the actual source files for which the expanded source is produced. This would then require three different files to be maintained per module: a configuration file for the make utility, the definition part and the implementation part. This is not as user friendly as it could and should be.

Embedding the information that the make utility needs directly into the source code can be easily accommodated by an additional pragma, for instance

<* MAKE=(information for make goes here) *>

The compiler would ignore the body of this pragma. Its content is only to be understood by the make utility. The delimiters for the actual information could be chosen differently if for some reason parentheses seem unsuitable.

The above is entirely sufficient to implement template based generics that are fully integrated into the build process, that is to say, not requiring the human user to expand templates manually. At the same time, a human user could choose to expand templates manually if he or she wished to do so.

An Example

The following is a concrete example of a generic stack template and its use in a Modula-2 program:

Client Program Using a Template Derived Module

<*MAKE="expand(template:stack, license:"@@BSD@@", module:"CardinalStack", type:"CARDINAL")" *>

MODULE UseCardinalStack;

IMPORT CardinalStack; (* generated by make utility from template *)

VAR
   stack : CardinalStack.Stack;
   num : CARDINAL;

...

CardinalStack.push(stack, num, NIL);

...

num = CardinalStack.pop(stack, NIL);

...

END UseCardinalStack.

Driving the Build Process

There are two principle ways in which a project could then be built. Either the compiler is used as the driver program for the build or the make utility is used as the driver program for the build.

If the compiler is the driver, it would recognise the <*BUILD...*> pragma and invoke the make utility, passing the pragma and its body to the make utility. The make utility would then use the information to generate the CardinalStack module (both definition and implementation parts) by invoking the template engine utility with the required parameters as provided in the pragma body. When the make utility is done, the compiler would continue to compile module UseCardinalStack. If the <*BUILD...*> pragma is missing, then the compiler would generate an error that module CardinalStack could not be found.

If the make utility is the driver, it would first scan the source code for any occurrences of the <*BUILD...*> pragma. When it encounters the pragma it would generate the respective modules by invoking the template engine utility with the placeholder translations as given in the bodies of the respective <*BUILD...*> pragmas.

The Template

An example of a template to generate the CardinalStack definition part is given below:

NB: The CTE template engine recognises %% at the beginning of a line as a template comment which it does not copy into the output.

%% Template to generate definition modules for stacks in Modula-2
%%
%% requires translations for the following placeholders:
%%
%% -  module : the identifier of the generated module
%% -  type : the identifier of the base type for values to be stored in the stack
%%
(* Stack module for @@type@@ derived from template stack

   Copyright (c) 2010 Jon Doe (template author).

   License:

   @@license@@

*)
DEFINITION MODULE @@module@@;

TYPE Stack = OPAQUE;

PROCEDURE new(VAR status : Status) : Stack;

PROCEDURE push(stack : Stack;
               value : @@type@@;
          VAR status : Status);
...

END @@module@@;
%%
%% End of template stack

Expanding the Template

By invoking the template engine utility with a path to the stack template and placeholder translations for 'module' and 'type' the template is expanded and generates the definition part for module CardinalStack. When the compiler is invoked on the generated source, the source will be type checked by the compiler in the same way as usual. The compiler does not have, nor does it need to have any knowledge about the fact that the module was generated from a template. The debugger does not need to know either, nor does the human reader of the resulting output when examining the program.

The tags %% and @@ are not part of the language for which the templates are used to generate output for. They are understood by the template engine utility only. The tags could easily be changed if desired.

Conclusion

The template expansion can be done right now with the CTE library as it is, no modifications required. The autotools and Cmake build systems could be relatively easily scripted/configured to control the build process in the way described above, again without making modifications to the source code of their respective make utilities.

The only addition required to accommodate the above is the <*MAKE...*> pragma.

The language itself does not need any modifications. No complexity is added to any of compiler, linker, debugger.

In the context of education, it would seem that making the template expansion process explicit and transparent should be a benefit to understanding the code and its translation process. The issue becomes another case of "eliminating magic".