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.

Contents

1 History 2 Values 2.1 Strings 2.2 Arrays 2.3 Tables 3 Names 4 Comments 5 Conditionals 6 Expressions 7 Statements 7.1 Assignment 7.2 Scope 7.3 Procedures 7.4 Conditional Statements 7.5 Loops 8 Keywords 9 Built-in Functions and Procedures 9.1 Input Output 9.2 Code Execution 9.3 File System Functions and Procedures 9.4 Pathname Functions 9.5 Argument Lists 9.6 Generic Predicates 9.7 Text Functions 9.8 Directory (Stack) Functions and Procedures 9.9 System Information 10 Output Redirection 11 Running Quake

1 History

During the history of Modula-3, there have also been several different versions and implementations of quake. The first Modula-3 compilers didn't have any quake support at all. Unix makefiles were generated by the m3make utility from m3makefiles.

The first quake implementation was written in C and called as a standalone executable. Later, in the PM3 and CM3 distributions, quake was reimplemented in Modula-3, and then integrated into the builder (m3build) and later the compiler frontend, which led to considerable performance improvements of the Modula-3 compilation process.

This manual cannot achieve to document all the different variants and versions of quake. If not otherwise stated, it refers to the latest version of quake as it is contained in CM3 5.6.0 and later.

2 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.

Quake also has some limited support for integers and booleans (at least internally), those everything is converted on the fly to the needed type (which is almost always string). Denotations for boolean and integer values as well as arithmetic operations are mostly missing.

2.1 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

2.2 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.

2.3 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.

3 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.

4 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.

5 Conditionals

A Boolean value is represented as a string. The empty string is false, and any other string is true. If builtin quake functions return boolean values, they use "TRUE" for true and "" for false.

6 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".

7 Statements

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

There is no explicit statement separator or terminator, which may seem quite unfamiliar to those used to C or Algol-derived languages. The next statement just starts after the previous is finished without semicolon or line-break.

7.1 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".

7.2 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"

If local or readonly are not used, all variables are located in the outermost (global) scope. Beware of side-effects!

7.3 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 procedures 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.

7.4 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.

7.5 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

8 Keywords

Here is a list of reserved words in quake:

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

9 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.

The list of built-ins was substantially extended in CM3 5.6 due to the needs of enhanced regression testing on different target platforms without further prerequisites.

9.1 Input / Output

write(...)

Writes its arguments to the current output stream. Unlike the conversion of arrays to strings, there are no extra spaces inserted between arguments.

error(...)

Writes its arguments to the standard error stream and stop running quake with an error return value.

9.2 Code Execution

include(file)

The contents of file is interpreted by quake in place of the call to include. This is analogous the #include directive in the C preprocessor. Calls to include nest until you run out of file descriptors or something equally bad.

exec(...)

The arguments are catenated and passed to the operating system to be executed as a child process. The command may start - or @. These are stripped before the command is passed to the command interpreter.

A prefix of @ indicates that the default action of printing the command to the standard output stream before execution should be overridden. A prefix character of - overrides quake's default action of aborting if it detects an error return code after executing the command.

cm3_exec(...) compatibility, since CM3 d5.5.1

Same as the above, but imitating the old implementation which merged stdout and stderr streams.

quake( code ) since CM3 d5.6.0

Executes the quake code passed as a text.

try_exec(...) --> int

The arguments are catenated and passed to the operating system to be executed as a child process. The command may start - or @. These are stripped before the command is passed to the command interpreter.

A prefix of @ indicates that the default action of printing the command to the standard output stream before execution should be overridden. The exit code of the program is returned.

try_cm3_exec(...) --> int compatibility, since CM3 d5.5.1

Same as the above, but imitating the old implementation which merged stdout and stderr streams.

q_exec( cmd ) --> int since CM3 d5.6.0

Executes the passed command or command list which may contain the following input/output redirections:

     < fn   : read stdin from file fn
     > fn   : write stdout into file fn
     1> fn  : write stdout into file fn
     2> fn  : write stderr into file fn
     &> fn  : write stdout and stderr into file fn
     >> fn  : append stdout to file fn
     1>> fn : append stdout to file fn
     2>> fn : append stderr to file fn
     &>> fn : append stdout and stderr to file fn

cmd is parsed and split into single commands at every ;, |, &&, and ||. The concatenation characters have the usual Bourne Shell meaning:

     a ; b    execute a followed by b
     a | b    execute a and pipe its output to b's standard input
     a && b   execute a, then b if a has succeeded (returned 0)
     a || b   execute a, then b if a has failed (returned #0)

Token may be grouped by single or double quotes.

The exit code of the command list is returned.

q_exec_put( cmd, text ) --> int since CM3 d5.6.0

Executes the given single command and pipe text to its standard input. No other redirections are performed.

The exit code of the command list is returned.

q_exec_get( cmd ) --> [int, text] since CM3 d5.6.0

Executes the given single command and captures its stdout and stderr output in text

This functions returns an array with two elements: First is the exit code of the command, second the captured output.

9.3 File System Functions and Procedures

cp_if(src, dest)

If the file src differs from the file dest, or dest is missing, copy src to dest.

fs_touch( pn ) since CM3 d5.6.0

Touches file pn, i.e. sets its access time to the current time. If the files doesn't exist, it is created.

fs_rmfile( pn ) since CM3 d5.6.0

Removes file pn if it exists.

fs_rmdir( pn ) since CM3 d5.6.0

Removes directory pn if it exists and is empty.

fs_rmrec( pn ) since CM3 d5.6.0

Removes the complete directory hierarchy at pn.

fs_mkdir( pn ) since CM3 d5.6.0

Creates all non-existing elements of pathname pn as directories.

fs_cp( pn, pn2 ) since CM3 d5.6.0

Copies file pn to pn2.

fs_putfile( pn, text ) since CM3 d5.6.0

Replaces the contents of file pn with text. If the files does not exist, it is created.

stale(target, deps) --> bool

target is interpreted as a file name, as is deps (or the elements of deps if it's an array). If the files target or deps cannot be found, or if (one of) deps is more recent than target, stale returns true, otherwise false.

unlink_file(f) --> bool

Deletes the file f. Returns "TRUE" in case of success, else "".

path() --> text

Returns the directory of the currently executing file as a string.

fs_exists( pn ) --> bool since CM3 d5.6.0

Returns "TRUE" if pn exists as a file system object, else "" (false).

fs_readable( pn ) --> bool since CM3 d5.6.0

Returns "TRUE" if pn is readable, else "" (false).

fs_writable( pn ) --> bool since CM3 d5.6.0

Returns "TRUE" if pn is writable, else "" (false).

fs_executable( pn ) --> bool since CM3 d5.6.0

Returns "TRUE" if pn is considered to be executable, else "" (false).

fs_isdir( pn ) --> bool since CM3 d5.6.0

Returns "TRUE" if pn is a directory, else "" (false).

fs_isfile( pn ) --> bool since CM3 d5.6.0

Returns "TRUE" if pn is an ordinary file, else "" (false).

fs_lsdirs( pn, baseonly ) --> text[] since CM3 d5.6.0

Returns an array of all subdirectories of pn. If baseonly is "" (false), only the basenames are returned, else pathnames are returned.

fs_lsfiles( pn, baseonly ) --> text[] since CM3 d5.6.0

Returns an array of all ordinary files in directory pn. If baseonly is "" (false), only the basenames are returned, else pathnames are returned.

fs_contents( pn ) --> text since CM3 d5.6.0

Returns the contents of file pn as a text.

9.4 Pathname Functions

pn_valid(pn) --> bool

"TRUE" iff pn is a valid pathname.

pn_absolute(pn) --> bool

"TRUE" iff pn is an absolute pathname.

pn_compose(text[]) --> text

Builds a new pathname from text[]. The first element is the root (which may be empty), the rest are arcs of the pathname.

pn_decompose(pn) --> text[]

Decomposes pn into its root and arcs.

pn_prefix(pn) --> text

Strips the last arc from pn.

pn_last(pn) --> text

Returns the last arc of pn.

pn_base(pn) --> text

Strips the last extension from pn.

pn_lastbase(pn) --> text

Returns the last arc from pn without its extension.

pn_lastext(pn) --> text

Returns the extension of the last arc of pn.

pn_join(pna, pnb) --> text

Returns the concatenation of pna and pnb. pnb must be a relative pathname.

pn_join2(pna, pnb, ext) --> text

Returns the concatenation of pna and pnb and the extension ext. pnb must be a relative pathname.

pn_replace_text(pn) --> text

Replaces the extension of the last arc of pn.

pn_current() --> text

Returns a representation of the special arc denotating the current directory.

pn_parent() --> text

Returns a representation of the special arc denotating the parent directory.

9.5 Argument Lists

arglist(pfx, array)

This function may be used to avoid the problems of limited space for argument lists in some command interpreters. Some commands (notably m3, the Modula-3 driver program) are prepared to accept arguments from a file. The syntax for this is -Ffile.

Thus, arglist("-F", really_huge_array) returns either the original array if it's not really all that huge, or it creates a temporary file containing a list of the strings in the array (one to a line) and returns the string "-Ftemp" where temp is the name of the temporary file.

9.6 Generic Predicates

defined(foo) --> bool

Returns true if foo is defined, otherwise returns false. Remember that the Boolean values false and true are represented as empty and non-empty strings respectively. In this example, foo looks like a name, and is evaluated before begin passed to defined. So if you really want to find out whether foo is defined, use defined("foo").

empty(foo) --> bool

Returns true if the string, array, or table is empty, false otherwise.

equal(foo, bar) --> bool

Returns true if the strings foo and bar are the equivalent. For arrays and tables, equal is treated as reference equality and not as structural equality as in Modula-3.

9.7 Text Functions

escape(s) --> text

Returns the string s with backslash characters doubled.

format(s, a...) --> text

Returns the string s with each instance of %s replaced by successive a arguments. The number of a arguments must match the number of %ss.

normalize(p, q) --> text

If the path p is a prefix of the path q, returns the path from directory p to file q. Otherwise, returns q.

split( text, seps ) --> text[] since CM3 d5.6.0

Splits the text into an array at every occurence of a character in seps.

sub( text, off, len ) --> text since CM3 d5.6.0

Returns the subtext of text starting at position off of length len.

skipl( text ) --> text since CM3 d5.6.0

Removes all white space at the left of the text.

skipr( text ) --> text since CM3 d5.6.0

Removes all white space at the right of the text.

compress( text ) --> text since CM3 d5.6.0

Removes all white space at the start and end of text.

squeeze( text ) --> text since CM3 d5.6.0

Removes multiple empty lines within text.

pos( text, sub ) --> int since CM3 d5.6.0

Returns the position of sub within text or -1 if sub is not found.

tcontains( text, sub ) --> bool since CM3 d5.6.0

Returns true if sub is contained in text.

bool( text ) --> bool since CM3 d5.6.0

Returns "TRUE" for "true", "TRUE", "T", "yes", "YES", "1", otherwise "" (false).

subst_chars( text, a, b ) --> text since CM3 d5.6.0

Returns text in which every character of a is replaced with the character at the same position of b. a and b must be of the same length.

del_chars( text, a ) --> text since CM3 d5.6.0

Returns text in which every occurence of characters from a is removed.

subst( text, a, b, n ) --> text since CM3 d5.6.0

Returns text in which the subtext a is replaced by subtext b for at most n times.

subst_env( text ) --> text since CM3 d5.6.0

Returns text in which all environment variables with the denotation ${name} or $name are replaced by the values of the current process execution environment.

add_prefix( text[], pre ) --> text[] since CM3 d5.6.0

Returns an array of text where each element is prefixed with pre.

add_suffix( text[], suf ) --> text[] since CM3 d5.6.0

Returns an array of text where each element is suffixed with suf.

9.8 Directory (Stack) Functions and Procedures

pushd( dir ) since CM3 d5.6.0

Pushes the current directory onto the stack and changes the working directory of the quake process to dir.

popd() since CM3 d5.6.0

Pops the topmost directory from the directory stack and makes it the working directory of the quake process.

cd( dir ) since CM3 d5.6.0

Changes the current directory of the quake process to dir.

getwd() --> text since CM3 d5.6.0

Returns the current working directory of the quake process.

9.9 System Information

hostname() --> text since CM3 d5.6.0

Returns the hostname of the computer quake is running on.

date() --> text since CM3 d5.6.0

Returns the current date in ISO format: YYYY-MM-DD.

datetime() --> text since CM3 d5.6.0

Returns the current date and time in ISO format: YYYY-MM-DD hh:mm:ss.

datestamp() --> since CM3 d5.6.0

Returns the current date and time in this format: YYYY-MM-DD-hh-mm-ss.

10 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.

11 Running Quake

Older versions of quake were used as standalone executables and run like this:

    quake [definitions|files ...]

Each file argument is executed as it is encountered.

Since the integration of quake into the builder and compiler frontend, this is no more available (though it can be easily re-added it needed).

Quake is now run implicitly by invoking the compiler with

    cm3 -build | -clean

Usually, the file src/m3makefile is executed. If -override or -x is specified, the definitions from src/m3overrides are executed before.

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.


Original Author: Stephen Harrison.