## mamake and the MAM language ##

MAM (Make Abstract Machine) is a simple rule-based make language
that is implemented in just six four-letter commands and five attributes,
yet allows unlimited flexibility as it can execute arbitrary shell code.
The program implementing MAM, `mamake`,
is a portable C90 program written in a single file, `mamake.c`.
This allows ksh 93u+m,
or other programs using this build system,
to be built using only a standard C compiler and utilities installation
without any other dependencies or complications.

MAM was designed by Glenn Fowler at AT&T.
The [original documentation](http://web.archive.org/web/20041227143022/http://www2.research.att.com/~gsf/mam/mam.html)
for MAM specified a more extensive language than was actually implemented in `mamake.c`,
while the `ignore` attribute is not documented there.
This file documents the MAM implementation that we are actually using.

`mamake` processes make files called `Mamfile`.
These were originally generated by a complex AT&T make system called `nmake`.
The intention was for `mamake` to process generated Mamfiles only
and not for it to replace `make`.
Yet, we are using it as our `make` in the ksh 93u+m distribution.
`nmake` was slow, brittle, and did not work on some modern systems, e.g. macOS.
It was not worth fixing because it is about as complex as ksh itself --
whereas `mamake` is simple and portable, and offers all the same flexibility.

Compared to the 2012-08-01 AT&T distribution,
ksh 93u+m made a few minor changes to `mamake` that make it easier to maintain Mamfiles by hand:
* All Mamfiles have been indented for legibility. (See `bin/Mamfile_indent` in the distribution.)
* Indentation and word separators may use any whitespace (e.g. tabs), not only spaces.
* Unrecognized commands and rule attributes throw an error instead of being silently ignored.
* Fixed some crashing bugs and memory leaks.

## Commands ##

MAM commands have the following basic form:

*command* [ *argument* [ *operand string* ] ]

The *command* name consists of four lower-case letters.
Unrecognized commands or attributes are an error.
The *argument* is a single word.
The *operand string* is any arbitrary text until the end of the line.

### Comments ###

`note` is the comment command and is ignored.
For historical reasons, `info` and `meta` are also ignored.

### Rules ###

`make` *rule* [ *attribute* ... ]    
`done` *rule* [ *attribute* ... ]

A `make`...`done` block defines the target rule named *rule* using the other commands described here.
Unless the `virtual` attribute is used, *rule* names the pathname of the file generated or referenced by the rule.

Dependencies may be defined in two ways:
1. By nesting `make`...`done` blocks:
   the enclosing *rule* is the parent
   and the enclosed *rule*s are the prerequisites.
2. By using the `prev` command (see **Referencing previously defined rules** below)
   to reference a previous `make`...`done` block.
   The dependency is defined as if that block were repeated at the `prev` command's location.

If the block contains one or more `exec` commands (see **Shell actions** below),
the `done` command executes the shell script defined by them.

One or more of the following *attribute*s may be specified
by appending them to the `make` or `done` command:
* `archive`: Ignored.
  Historically used to mark the generation of an `ar`(1) archive.
* `dontcare`: Marks files that do not need to exist.
  If the file exists then its last-modified timestamp is checked and propagated,
  otherwise it is silently ignored. 
* `generated`: Marks rules that produce output files generated by a shell action.
  The `exec` command implicitly assigns this attribute, but it is customary to specify it regardless.
* `ignore`: The timestamp associated with *rule* is ignored in dependency resolution.
* `implicit`: Marks the current rule as an implicit prerequisite of the enclosing parent rule.
  An implicit prerequisite can make the parent rule out of date without triggering the parent action.
  Implicit prerequisites usually correspond to `#include` prerequisites.
  For example, if `foo.o` is generated from `foo.c` and `foo.c` includes `foo.h`,
  then `foo.h` should be marked as an implicit prerequisite of `foo.c`
  so that touching `foo.h` does not make `foo.c` out of date while making `foo.o` out of date.
* `joint`: Ignored.
  Historically used to mark one of a group of rules that are built by a single shell action.
* `virtual`: Marks a rule that is not associated with any file.
  The commands within are executed every time the Mamfile is processed.
  By convention, a virtual rule named `all` makes everything,
  and a virtual rule named `install` performs installation.

Unrecognized attributes produce a warning and are then ignored.

### Referencing previously defined rules ###

`prev` *rule* [ *attribute* ... ]

This command references a rule that has previously been defined by `make`...`done`,
regardless of block nesting level.
It can be used to make a rule a prerequisite of multiple `make`...`done` blocks without repeating the rule.
By convention, the *attribute*s of the referenced block are repeated in the `prev` command.
However, `mamake` ignores anything after *rule*.

### MAM variables ###

`setv` *variable* [ *defaultvalue* ]

Defines a new MAM *variable*, optionally assigning the initial *defaultvalue*.
If the *defaultvalue* begins and ends with double quotes (`"`), those are discarded.
If the variable already has a value, the `setv` command is ignored; assigning a new value is not possible.
When `mamake` starts, it imports all environment variables as MAM variables,
so any variable's default value can be overridden by exporting an environment variable by its name.

MAM variables are referenced using the sh-style `${`...`}` syntax, though the braces are *not* optional.
Any reference to an undefined variable is silently left unexpanded (and not replaced by the empty string).
Expansion of MAM variable references is recursive, i.e., the value may itself contain other variable references.
Beware: there is no reference loop detection.

[`TODO`: figure out and document advanced expansion syntax supported by `substitute()` in `mamake.c`]

### Shell actions ###

`exec` `-` *code*

One or more `exec` commands within a `make`...`done` block
define a shell script that is executed for *rule*.
The word following `exec` is ignored; by convention it is `-`.
Each `exec` command appends a line of code to the shell script for the current rule.
It is customary for a rule's `exec` commands to be contiguous, but not necessary.
Before adding each line of code to the script,
MAM variable references (see **MAM variables** above)
are expanded; their literal values are inserted into the *code* line
(beware: no quoting is applied!).
When `mamake` encounters the `done` command,
the script is executed by the shell whose path is in the `SHELL` environment variable
or, absent that, by `/bin/sh`.
The `exec` command assigns the `generated` attribute to the current rule, even if it was not specified.

### Binding libraries ###

`bind` `-l`*libraryname* [ `dontcare` ]

An argument of `-l`*libraryname* (or `+l`*libraryname*)
causes a MAM variable `mam_lib`*libraryname* to be defined (see **MAM variables** above).
The variable will contain either the compiler argument for linking to the library *libraryname*
(either the `-l`*libraryname* flag, or the full path in case of a static library)
or, if the `dontcare` attribute is specified, possibly the empty string.
This can be used both for AST libraries shipped with the distribution and for system libraries.
If the library file is found in the distribution,
its time stamp is checked and the current target is marked as outdated if it is newer.

There is also a mechanism to communicate library dependency information across Mamfiles and `mamake` invocations.
If a file named *libraryname* in the current directory
or an `${INSTALLROOT}/lib/lib/`*libraryname*`.req` file
exists, `mamake` processes each of the words in the form `-l`*libraryname* in its contents
as if they were arguments to `bind` commands
and the resulting values are appended to the value of `mam_lib`*libraryname*
as dependencies separated by spaces.
`mamake` does not create these dependency files;
they are expected to be generated by Mamfile shell actions (see **Shell actions** above).

If no such dependency file exists, and the `dontcare` attribute is added,
then `mamake` compiles a small test program on the fly to check if the library exists;
if this fails, the `mam_lib`*libraryname* variable will be emptied.

Any `bind` command whose argument does not start with `-l` or `+l` is ignored.

[`TODO`: `bind` is not yet fully understood; more `mamake.c` code analysis is required.
In `require()` in `mamake.c` there is some special handling for dynamic libraries.
Note that the `bind` functionality implemented in `mamake.c`
is completely different from that described in the original documentation.]
