=Quake

Quake is a simple, specialized language drawing on elements of the C language, the Bourne shell, and the C pre-processor.

Quake was designed to be a component of m3build. Building a complete, general-purpose language was not one of the goals. M3build reads a set of quake language templates that define some operators, then executes an m3makefile in this context. The templates define procedures to build lists of modules, libraries, and so on.

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' and `exec' are variadic.

Built-in Functions

Output Redirection

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.

Notes

Quake does not garbage collect. Reckless copying of large arrays can bloat the heap.

Author

Stephen Harrison (sharrison@broadvision.com).