MCM Syntax

This page is not aimed at the beginner. You should probably read all the other pages and the test files distributed with MCM first!

Introduction

The MCM language is quite strict, the hope being to help configurations written by one person be read easily by another. This strictness includes the order in which many constructs may appear and the use of whitespace.

Indents in MCM are written using one, two or three tab characters. (On this page, each tab is presented with an indentation equivalent to four spaces.)

Comments

Comments start with the character '#'. Comments must always appear on a line on their own and must be indented by the same amount as the lines that follow them. Comments can currently appear before most MCM constructs, but may in the near future be restricted to those appearing at indentation levels 0 and 1.

Keywords

MCM

The MCM keyword must be found at the very start of the file, followed by a space and the package name. Package names must start with an uppercase character (or a character that does not have an uppercase variant) and may then contain alphanumeric characters and underscores ('_'). Full stops ('.') are used to denote a heirarchy. E.g.

MCM Service.Cron

or

MCM MyLongPackageName

or

MCM Somewhere.Deeper.My_Long_Package_Name3

An arbritrary number of blank lines and comment lines may then follow, followed by the import lines.

import

Zero or more import lines each start with the import keyword, followed by a package name, the as keyword and then the alias to be used locally. Inbetween each of these is a single space. Aliases must start with an uppercase character (or a character that does not have an uppercase variant) and may then contain alphanumeric characters. E.g.

MCM Import

import Hooks as H
import Service.Cron as Cron
import Somewhere.Deeper.My_Long_Package_Name3 as MyName3

(package) let

let expressions may occur both within defines and as package lets, after the import lines. let expressions are used to define constants. The only difference between lets appearing within defines and at the package level is the indentation of the let keyword. A block of let expressions always starts with the keyword let. Each expression takes the form let a = b where a creates a local identifier and b is the value to be assigned to it. The definition of a value may span multiple lines. Single space characters should be written both before and after the '='.

The let keyword may be immediately followed by a space and an expression. Otherwise the let keyword is appears on a line on its own. Following lines (if any) each consist of two tab characters ("\t\t") and an expression.

Local identifiers start with a lowercase alphabetic character (characters that do not have separate lower/upper variants are also accepted). The rest of the identifier can be made up of alphanumeric characters and underscores ('_').

Values can contain some powerful constructs, which will be explained later. For now we present just an example package let section:

MCM PackageLet

let a = some arbitrary text
		b = something else
		c = a forest
		d = the dean
		e = this value
			\ is a multiline value
			\ that doesn't actually contain any newline characters
		f = this value
			+ contains one newline character
		yIdentifier = some value

define

For an MCM package to be useful it must contain at least one define. The define keyword is immediately followed by the define's name, then an opening bracket ('('). Next follow the define's arguments, followed by a closing bracket (')'). The body of the define starts on the next line. The body is all indented (using only tab ('\t') characters).

define names start with a lowercase alphabetic character (characters that do not have separate lower/upper variants are also accepted). The rest of the define name can be made up of alphanumeric characters and underscores ('_').

defines may specify both mandatory and optional arguments. The mandatory arguments must be specified first, and comprise simply a space-separated list of their names. Argument names follow the same format as define names.

If the define specifies no optional arguments then the closing bracket (')') immediately follows the mandatory arguments. Otherwise the optional arguments are each specified on a separate line and the closing bracket follows them on a line of its own indented by one tab ('\t').

Optional arguments are each indented by two tabs ("\t\t") but otherwise follow the format of let expressions but without the inital let keyword.

define bodies comprise local let expressions, then case expressions, then invocations, in that order.

let expressions are as specified for package lets, but as mentioned earlier let expressions within defines have the let keyword indented by a one tab ('\t').

case expressions start with the case keyword (indented by one tab ('\t'), a space, the name of an in-scope variable and a newline. Then follow a number of when expressions.

when expressions start with a when keyword (indented by one tab ('\t'), a space, then the rest of the line contains the text to be matched. The next lines (each indented by two tabs ("\t\t")) follow the format of let expressions but without the initial let keyword. The set of constants declared within each when expression within a case expression must match.

Invocations are the means by which one define calls another and by which files and directories get created and removed. There are three forms of invocation:

  1. calling local defines takes the form of '.' followed by the define's name (e.g. .myOtherDefine);
  2. calling imported defines takes the form of the imported name followed by '.' followed by the define's name (e.g. Hooks.when);
  3. calling one of the builtin primitives (File, Dir, Absent, Symlink and Fragment).

Arguments to the invocation (if any) can be passed on the same line and/or subsequent lines. The format of invocation arguments is detailed in the later INVOCATION ARGUMENTS section. When invoking local or imported defines, the define's name may optionally be specified through one or more local identifiers.

For example:

MCM Define

define myDefine(first second x y z
		m = some default text for this optional argument
		n = another optional argument
		p = @m
		q = @second
	)
	let a = @x@y@z
		b = another
	case z
	when apple
		colour = green
	when banana
		colour = yellow
	when orange
		colour = orange
	when plum
		colour = plum
	.myOtherDefine
	Fruits.draw_@z
	Colours.paint_@colour
	Absent path> /tmp/some/file.txt

Invocation Arguments

Invocations such as Absent, File, .myOtherDefine and Hooks.when frequently require arguments to be passed. Arguments are written in either word form (name> VALUE) or line(s) form (name: VALUE). name is the name of the argument, there is a space before VALUE and VALUE is a standard, potentially multiline value (see the following VALUES section). The word ('>') form requires that the (first line of the) written VALUE contain no whitespace. The line (':') form permits the more general form of VALUE to be provided.

Invocations arguments may be passed on the same line as the invocation and/or on subsequent lines. If passed on subsequent lines each line may provide only one argument. Multiple arguments may be passed on the first line, but only the last of which can be a line (':') form argument.

For example:

MCM InvocationArguments

define invocationArguments()
	Absent path> /tmp/some/file.txt
	File mode> 644 path> /tmp/some/other/file.txt
		content: the textual content

Values

TODO

Global Constants

MCM has a small number of global constants. They may be used anywhere a regular constant can be used.

@TAB
A tab ('\t') character
@COMMA
A comma (',') character
@NEWLINE
A newline ('\n') character

Further Information

For further information, the Parser.hs source file is surprisingly easy to read.