quake

This page documents quake. Quake is a simple, specialized language and its interpreter drawing on elements of the C language, the Bourne shell, and the C pre-processor. The cm3 compiler includes a quake interpreter as its extension language. In fact, the configuration file, cm3.cfg, and m3makefiles are quake scripts.

Quake was designed to be a simple extension language for the builder. Building a complete, general-purpose language was not one of the goals.

Cm3 calls out to quake every time it needs to do something that needs to be specialized such as compiling C files or linking. Consult cm3.cfg in your installation (in the same directory as cm3 executable) for more information.

Values

Quake is designed to make it easy to assemble arrays and tables of strings. The value space is strings, arrays of strings, and tables of strings. An array is a list of elements, and a table is a set of key/value pairs where the keys in a table are all distinct.

Strings

A string is a sequence (delimited by `"') of characters excluding newline and double quote. Several special characters may be quoted (with `\') as follows:

    new-line    ignored
    \\          \
    \n          new-line
    \r          return
    \t          tab
    \b          backspace
    \f          formfeed
    \"          double quote

Arrays

An array is constructed using `[' and `]'. `[]' is the empty array, `["a"]' is an array of the single string "a". Elements are separated by `,'. Thus `["a", "b"]' is an array of two elements---the strings "a" and "b".

Arrays are flattened whenever they are referenced. This means that `["a", ["b", "c"]]' is converted the the array `["a", "b", "c"]'. This means that an array can never be the element of another array.

Arrays are accessed by an integer index. `a[2]' is the third element of the array a. The index expression may also be a string which will be converted to an integer. The range of the index is checked at run-time.

Tables

A table is constructed using `{' and `}'. `{}' is the empty table. Elements of a table are given as key/value pairs. An empty value may be omitted. `{"a", "b"}' is a table containing two keys---the strings "a" and "b". `{"p" : "q"}' is the constructor for a table with the single key "p" whose value is the string "q". Missing values are returned as "".

Tables are accessed using expressions of the form `s{"a"}'. This evaluates to the value of the key "a" in the table s.

Names

Names in quake start with an letter (`a'..`z', `A'..`Z') or an underscore (`_'), followed by those characters, digits (`0'..`9'), hyphens (`-'), or periods (`.').

If the lookup for a name fails (it is undefined in any of the enclosing scopes) it is installed in the current scope with an initial string value of the text of the name.

Comments

C-style comments are supported (delimited by `/*' and `*/') and do not nest.

Single-line comments are introduced with `%' and terminated with a new-line.

Conditionals

A Boolean value is represented as a string. The empty string is false, and any other string is true.

Expressions

An expression is:

    string:                  "baz"
    name:                    foo
    environment variable:    $CPU_TYPE
    array selector:          array[5]
    array constructor:       ["a", "b"]
    table selector:          t{"a"}
    table constructor:       {"a" : "p", b}
    function call:           f(a, "b")
    parentheses:             a and (b or f(c))

Operators are all left-associative, precedence is decreases from top to bottom in the following list.

    &           string catenation (`"Hello, " & foo')
    contains    table inclusion (`s contains "a"')
    not         logical negation (`not a')
    and         logical conjunction (`c and not d')
    or          logical disjunction (`a or b')

A note on string catenation. Operands of `&' are converted to strings whenever required and possible. Arrays and tables are converted to strings by catenating their elements (for tables, their keys) separated by a single spaces. For example, the expression

    "a" & " " & ["b", "c"]

evaluates to the string "a b c".

Statements

A statement is either an assignment, a procedure definition, a procedure invocation, a conditional statement, or a loop.

Assignment

Assign an expression (the string "bar") to the variable `foo' with

    foo = "bar"

If `foo' already exists, and is an array, then

    foo += "baz"

extends the array to include a new final element; the string "baz".

Scope

Quake has a global name space, but local scopes are always introduced when a procedure is called, and a `foreach' loop is executed.

Scopes are searched from innermost to outermost each time a name is looked up. The outermost scope is always the global scope.

Assignments can be made local to the innermost scope by prefixing the assignment statement with the keyword `local'. For example,

    local foo = "bog"

In which case the values of any other instances of `foo' in other scopes are hidden. The previous value of `foo' is restored once the local scope is released by exiting the procedure or `foreach' loop.

To protect a name in the current scope, use

    readonly dec = "digital"

Procedures

Procedures may be defined in the global scope only. Here is the definition of a procedure `simple', taking two arguments bound to the local names `prefix' and `suffix' in the procedure's local scope.

    proc simple(prefix, suffix) is
      q = prefix & "." & suffix
    end

The string `prefix & "." & suffix' is assigned to the global variable `q'.

This procedure can then be invoked with

    simple("Hello", "m3")

Procedures can return values, in which case they become functions. For example,

    proc combine(prefix, suffix) is
      return prefix & "." & suffix
    end

defines a function `combine' which catenates and returns the three strings `prefix', ".", and `suffix'. Now the function `combine' can be used in an expression, for example

    q = combine("Hello", "m3")

assigns the string "Hello.m3" to `q'.

Conditional Statements

Values may be tested using the `if' statement. For example,

    if compiling
      message = "compiling"
    end

If statements may have an else part, for example

    if not (ready or interested)
      return
    else
      message = "congratulations"
    end

returns from the current procedure if the test succeeds, otherwise executes the assignment statement.

Loops

`Foreach' statements iterate over the string values in an array or in a table. For example,

    foreach file in [".login", ".profile", ".Xdefaults"]
      write("Copying " & file & " to " & backup_dir & "0)
      copy_file(file, backup_dir)
    end

binds the name `file' to each of ".login", ".profile", and ".Xdefaults" in turn in a local scope. This example assumes that the procedures `copy_file', and the variable `backup_dir' have already been defined.

Here is a function `squeeze' to remove duplicates from an array

    proc squeeze(array) is
      local t = {}

      foreach i in array
        t{i} = ""
      end

      return [t]
    end

Keywords

Here is a list of reserved words in quake:

    and  contains  else  end  foreach  if  in
    is  local  not  or  proc  readonly  return

Built-in Procedures

Quake has a small collection of built-in procedures. Built-ins cannot be redefined. The built-ins `write', `error', and `exec' are variadic.

Built-in Functions

Output Redirection

Sorry about the syntax for this. Suggestions (YACC permitting) welcome.

Output (from the `write' built-in procedure) may be temporarily redirected as follows:

    > "foo.txt" in
      write("Hello, world0)
    end

The output of the calls to `write' is written to the file `foo.txt'.

Output may be appended to a file by using `>>' in place of `>'.

The special file names `_stdout' and `_stderr' are special and are bound to the file descriptors corresponding to normal and error terminal output respectively.

The default for output is the normal terminal stream.

Running Quake

The syntax of the quake command is

    quake [definitions|files ...]

A definition has the form `-Dvar' or `-Dvar=string'. The first form defines `var' in the global scope of the program, the second form gives it a value.

Each file argument is executed as it is encountered.


Original Author: Stephen Harrison.