Jump to:  OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long)
Index:  All • Variables • Functions • Objects • Targets • Options

Appendix B  OMake grammar

B.1  OMake lexical conventions

The OMake language is based on the language for GNU/BSD make, where there are few lexical conventions. Strictly speaking, there are no keywords, and few special symbols.

B.1.1  Comments

Comments begin with the # character and continue to the end-of-line. Text within a comment is unrestricted.

Examples.

   # This is a comment
   # This $comment contains a quote " character

B.1.2  Special characters

The following characters are special in some contexts.

   $    (    )    ,    .   =    :    "    '    `    \    #

B.1.3  Identifiers

Identifiers (variable names) are drawn from the ASCII alphanumeric characters as well as _, -, ~, @. Case is significant; the following identifiers are distinct: FOO, Foo, foo. The identifier may begin with any of the valid characters, including digits.

Using egrep notation, the regular expression for identifiers is defined as follows.

    identifier ::= [-@~_A-Za-z0-9]+

The following are legal identifiers.

    Xyz    hello_world    seventy@nine
    79-32  Gnus~Gnats     CFLAGS

The following are not legal identifiers.

    x+y    hello&world

B.1.4  Command identifiers

The following words have special significance when they occur as the first word of a program line. They are not otherwise special.

    case     catch  class    declare    default
    do       else   elseif   export     extends
    finally  if     import   include    match
    open     raise  return   section    switch
    try      value  when     while

B.1.5  Variable references

A variable reference is denoted with the $ special character followed by an identifier. If the identifier name has more than one character, it must be enclosed in parentheses. The parenthesized version is most common. The following are legal variable references.

    $(Xyz)    $(hello_world)   $(seventy@nine)
    $(79-32)  $(Gnus~Gnats)    $(CFLAGS)

Single-character references also include several additional identifiers, including &*<^?][. The following are legal single-character references.

   $@   $&   $*   $<   $^   $+   $?   $[   $]
   $A   $_   $a   $b   $x   $1   $2   $3

Note that a non-parenthesized variable reference is limited to a single character, even if it is followed by additional legal identifier charqcters. Suppose the value of the $x variable is 17. The following examples illustrate evaluation.

    $x           evaluates to    17
    foo$xbar     evaluates to    foo17bar
    foo$(x)bar   evaluates to    foo17bar

The special sequence $$ represents the character literal $. That is, the two-character sequences \$ and $$ are normally equalivalent.

B.1.6  String constants

Literal strings are defined with matching string delimiters. A left string delimiter begins with the dollar-sign $, and a non-zero number of single-quote or double-quote characters. The string is terminated with a matching sequence of quotation symbols. The delimiter quotation may not be mixed; it must contain only single-quote characters, or double-quote characters. The following are legal strings.

    $'Hello world'
    $"""printf("Hello world\n")"""
    $''''
Large "block" of
text # spanning ''multiple'' lines''''

The string delimiters are not included in the string constant. In the single-quote form, the contents of the string are interpreted verbatim–there are no special characters.

The double-quote form permits expression evaluation within the string, denoted with the $ symbol. The following are some examples.

    X = Hello
    Y = $""$X world""             # Hello world
    Z = $'''$X world'''           # $X world
    I = 3
    W = $"6 > $(add $I, 2)"       # 6 > 5

Note that quotation symbols without a leading $ are not treated specially by OMake. The quotation symbols is included in the sequence.

    osh>println('Hello world')
    'Hello world'
    osh>println($'Hello world')
    Hello world
    osh>X = Hello
    - : "Hello" : Sequence
    osh>println('$X world')
    Hello world

B.2  The OMake grammar

OMake programs are constructed from expressions and statements. Generally, an input program consists of a sequence of statements, each of which consists of one or more lines. Indentation is significant–if a statement consists of more than one line, the second and remaining lines (called the body) are usually indented relative to the first line.

B.2.1  Expressions

The following table lists the syntax for expressions.

expr::= 
  (empty)
  – Text (see note)
 |text
 |string-literal
  – Applications
 |dollar <char>
 |dollar ( pathid args )
  – Concatenation
 |expr expr
 
dollar::=$ | $` | $,
pathid::= 
  id
 |pathid . id
 
arg::=expr – excluding special characters )(,)
args::=(empty) | arg, ..., arg

An expression is a sequence composed of text, string-literals, variables references and function applications. Text is any sequence of non-special characters.

B.2.1.1  Inline applications

An application is the application of a function to zero-or-more arguments. Inline applications begin with one of the “dollar” sequences $, $`, or $,. The application itself is specified as a single character (in which case it is a variable reference), or it is a parenthesized list including a function identifier pathid, and zero-or-more comma-separated arguments args. The arguments are themselves a variant of the expressions where the special character )(, are not allowed (though any of these may be made non-special with the \ escape character). The following are some examples of valid expressions.

The additional dollar sequences specify evaluation order, $` (lazy) and $, (eager), as discussed in the section on dollar modifiers (Section B.3).

B.2.2  Statements and programs

The following table lists the syntax of statements and programs.

params::=(empty) | id, ..., id
 
target::=expr – excluding special character :
 
program::=stmt <eol> ... <eol> stmt
 
stmt::= 
  – Special forms
 |command expr optcolon-body
 |command ( args ) optcolon-body
 |catch id ( id ) optcolon-body
 |class id ... id
 
  – Variable definitions
 |pathid {+}= expr
 |pathid {+}= <eol> indented-body
 |pathid[] {+}= expr
 |pathid[] {+}= <eol> indented-exprs
 
  – Functions
 |pathid(args) optcolon-body
 |pathid(params) = <eol> indented-body
 
  – Objects
 |pathid . {+}= <eol> indented-body
 
  – Rules
 |target : target rule-options <eol> indented-body
 |target :: target rule-options <eol> indented-body
 |target : target : target rule-options <eol> indented-body
 |target :: target : target rule-options <eol> indented-body
 
  – Shell commands
 |expr
 
indented-body::=(empty)
 |indented-stmt <eol> ... <eol> indented-stmt
 
indented-exprs::=(empty)
 |indented-expr <eol> ... <eol> indented-expr
 
optcolon-body::=(empty)
 |<eol> indented-body
 |: <eol> indented-body
 
rule-option::=:id: target
rule-options::=(empty)
 |rule-options rule-option

B.2.2.1  Special forms

The special forms include the following.

Conditionals (see the section on conditionals — Section 4.10). The if command should be followed by an expression that represents the condition, and an indented body. The conditional may be followed by elseif and else blocks.

    if expr
        indented-body
    elseif expr
        indented-body
    ...
    else
        indented-body

matching (see the section on matching — Section 4.11). The switch and match commands perform pattern-matching. All cases are optional. Each case may include when clauses that specify additional matching conditions.

    match(expr)
    case expr
       indented-body
    when expr
       indented-body
    ...
    case expr
       indented-body
    default
       indented-body

Exceptions (see also the try function documentation). The try command introduces an exception handler. Each name is the name of a class. All cases, including catch, default, and finally are optional. The catch and default clauses contain optional when clauses.

    try
        indented-body
    catch name1(id1)
        indented-body
    when expr
        indented-body
    ...
    catch nameN(idN)
        indented-body
    default
        indented-body
    finally
        indented-body

The raise command is used to raise an exception.

    raise expr

section (see the section description in Section 4.9). The section command introduces a new scope.

    section
        indented-body

include, open (see also Section 4.8). The include command performs file inclusion. The expression should evaluate to a file name.

The open form is like include, but it performs the inclusion only if the inclusion has not already been performed. The open form is usually used to include library files. [jyh– this behavior will change in subsequent revisions.]

    include expr
    open expr

return (see the description of functions in Section 4.5). The return command terminates execution and returns a value from a function.

    return expr

value (see the description of functions in Section 4.5). The value command is an identity. Syntactically, it is used to coerce a n expression to a statement.

    value expr

export (see the section on scoping — Section 6.3). The export command exports a environment from a nested block. If no arguments are given, the entire environment is exported. Otherwise, the export is limited to the specified identifiers.

    export expr

while (see also the while function description). The while command introduces a while loop.

    while expr
        indented-body

class, extends (see the section on objects — Section 4.12). The class command specifies an identifier for an object. The extends command specifies a parent object.

    class id
    extends expr

B.2.2.2  Variable definitions

See the section on variables (Section 4.1). The simplest variable definition has the following syntax. The = form is a new definition. The += form appends the value to an existing definition.

    id = expr
    id += expr

    osh> X = 1
    - : "1" : Sequence
    osh> X += 7
    - : "1" " " "7" : Sequence

A multi-line form is allowed, where the value is computed by an indented body.

    id {+}=
        indented-body

    osh> X =
             Y = HOME
             println(Y is $Y)
             getenv($Y)
    Y is HOME
    - : "/home/jyh" : Sequence

The name may be qualified qith one of the public, prtected, or private modifiers. Public variables are dynamically scoped. Protected variables are fields in the current object. Private variables are statically scoped.

[jyh: revision 0.9.9 introduces modular namespaces; the meaning of these qualifiers is slightly changed.]

    public.X = $(addsuffix .c, 1 2 3)
    protected.Y = $(getenv HOME)
    private.Z = $"Hello world"

B.2.2.3  Applications and function definitions

See the section on functions (Section 4.5). A function-application statement is specified as a function name, followed a parenthesized list of comma-separated arguments.

    osh> println($"Hello world")

    osh> FILES = 1 2 3
    - : 1 2 3
    osh> addsuffix(.c, $(FILES))
    - : 1.c 2.c 3.c

    # The following forms are equivalent
    osh> value $(println $"Hello world")
    osh> value $(addsuffix .c, $(FILES))
    - : 1.c 2.c 3.c

If the function application has a body, the body is passed (lazily) to the function as its first argument. [jyh: in revision 0.9.8 support is incomplete.] When using osh, the application must be followed by a colon : to indicate that the application has a body.

    # In its 3-argument form, the foreach function takes
    # a body, a variable, and an array.  The body is evaluated
    # for each element of the array, with the variable bound to
    # the element value.
    #
    # The colon is required only for interactive sessions.
    osh> foreach(x => 1 2 3):
            add($x, 1)
    - : 2 3 4

Functions are defined in a similar form, where the parameter list is specified as a comma-separated list of identifiers, and the body of the function is indented.

    osh> f(i, j) =
            add($i, $j)
    - : <fun 2>
    osh> f(3, 7)
    - : 10 : Int

B.2.2.4  Objects

See the section on objects (Section 4.12). Objects are defined as an identifier with a terminal period. The body of the object is indented.

    Obj. =
        class Obj

        X = 1
        Y = $(sub $X, 12)
        new(i, j) =
           X = $i
           Y = $j
           value $(this)
        F() =
           add($X, $Y)
        println($Y)

The body of the object has the usual form of an indented body, but new variable definitions are added to the object, not the global environment. The object definition above defines an object with (at least) the fields X and Y, and methods new and F. The name of the object is defined with the class command as Obj.

The Obj itself has fields X = 1 and Y = -11. The new method has the typical form of a constructor-style method, where the fields of the object are initialized to new values, and the new object returned ($(this) refers to the current object).

The F method returns the sum of the two fields X and Y.

When used in an object definition, the += form adds the new definitions to an existing object.

   pair. =
      x = 1
      y = 2

   pair. +=
      y = $(add $y, 3)
   # pair now has fields (x = 1, and y = 5)

The extends form specifies inheritance. Multiple inheritance is allowed. At evaluation time, the extends directive performs inclusion of the entire parent object.

   pair. =
      x = 1
      y = 2

   depth. =
      z = 3
      zoom(dz) =
         z = $(add $z, $(dz))
         return $(this)

   triple. =
      extends $(pair)
      extends $(depth)

      crazy() =
         zoom($(mul $x, $y))

In this example, the triple object has three fields x, y, and z; and two methods zoom and crazy.

B.2.2.5  Rules

See the chapter on rules (Chapter 8). A rule has the following parts.

  1. A sequence of targets;
  2. one or two colons;
  3. a sequence of dependencies and rule options;
  4. and an indented body.

The targets are the files to be built, and the dependencies are the files it depends on. If two colons are specified, it indicates that there may be multiple rules to build the given targets; otherwise only one rule is allowed.

If the target contains a % character, the rule is called implicit, and is considered whenever a file matching that pattern is to be built. For example, the following rule specifies a default rule for compiling OCaml files.

    %.cmo: %.ml %.mli
       $(OCAMLC) -c $<

This rule would be consulted as a default way of building any file with a .cmo suffix. The dependencies list is also constructed based on the pattern match. For example, if this rule were used to build a file foo.cmo, then the dependency list would be foo.ml foo.mli.

There is also a three-part version of a rule, where the rule specification has three parts.

    targets : patterns : dependencies rule-options
       indented-body

In this case, the patterns must contain a single % character. Three-part rules are also considered implicit. For example, the following defines a default rule for the clean target.

    .PHONY: clean
    
    clean: %:
        rm -f *$(EXT_OBJ) *$(EXT_LIB)

Three-part implicit rules are inherited by the subdirectories in the exact same way as with the usual two-part implicit rules.

There are several special targets, including the following.

There are several rule options.

Several variables are defined during rule evaluation.

B.2.2.6  Shell commands

See the chapter on shell commands (Chapter 11).

While it is possible to give a precise specification of shell commands, the informal description is simpler. Any non-empty statement where each prefix is not one of the other statements, is considered to be a shell command. Here are some examples.

    ls                                 -- shell command
    echo Hello world > /dev/null       -- shell command
    echo(Hello world)                  -- function application
    echo(Hello world) > /dev/null      -- syntax error
    echo Hello: world                  -- rule
    X=1 getenv X                       -- variable definition
    env X=1 getenv X                   -- shell command
    if true                            -- special form
    \if true                           -- shell command
    "if" true                          -- shell command

B.3  Dollar modifiers

Inline applications have a function and zero-or-more arguments. Evaluation is normally strict: when an application is evaluated, the function identifier is evaluated to a function, the arguments are then evaluated and the function is called with the evaluated arguments.

The additional “dollar” sequences specify additional control over evaluation. The token $` defines a “lazy” application, where evaluation is delayed until a value is required. The $, sequence performs an “eager” application within a lazy context.

To illustrate, consider the expression $(addsuffix .c, $(FILES)). The addsuffix function appends its first argument to each value in its second argument. The following osh interaction demonstrates the normal bahavior.

osh> FILES[] = a b c
- : <array a b c>
osh> X = $(addsuffix .c, $(FILES))
- : <array ...>
osh> FILES[] = 1 2 3 # redefine FILES
- : <array 1 2 3>
osh> println($"$X")  # force the evaluation and print
a.c b.c c.c

When the lazy operator $` is used instead, evaluation is delayed until it is printed. In the following sample, the value for X has changed to the $(apply ..) form, but otherwise the result is unchanged because it it printed immediately.

osh> FILES[] = a b c
- : <array a b c>
osh> SUF = .c
- : ".c"
osh> X = $`(addsuffix $(SUF), $(FILES))
- : $(apply global.addsuffix ...)
osh> println($"$X")  # force the evaluation and print
a.c b.c c.c

However, consider what happens if we redefine the FILES variable after the definition for X. In the following sample, the result changes because evaluation occurs after the values for FILES has been redefined.

osh> FILES[] = a b c
- : <array a b c>
osh> SUF = .c
- : ".c"
osh> X = $`(addsuffix $(SUF), $(FILES))
- : $(apply global.addsuffix ...)
osh> SUF = .x
osh> FILES[] = 1 2 3
osh> println($"$X")  # force the evaluation and print
1.x 2.x 3.x

In some cases, more explicit control is desired over evaluation. For example, we may wish to evaluate SUF early, but allow for changes to the FILES variable. The $,(SUF) expression forces early evaluation.

osh> FILES[] = a b c
- : <array a b c>
osh> SUF = .c
- : ".c"
osh> X = $`(addsuffix $,(SUF), $(FILES))
- : $(apply global.addsuffix ...)
osh> SUF = .x
osh> FILES[] = 1 2 3
osh> println($"$X")  # force the evaluation and print
1.c 2.c 3.c

B.4  Programming syntax

This feature was introduced in version 0.9.8.6.

The standard OMake language is designed to make it easy to specify strings. By default, all values are strings, and strings are any sequence of text and variable references; quote symbols are not necessary.

    CFLAGS += -g -Wall

The tradeoff is that variable references are a bit longer, requiring the syntax $(...).

The “program syntax” inverts this behavior. The main differences are the following.

  1. Identifiers represent variables.
  2. Strings must be quoted.
  3. Function application is written f(exp1, ..., expN).

It is only the syntax of expressions that changes. The large scale program is as before: a program is a sequence of definitions, commands, indentation is significant, etc. However, the syntax of expressions changes, where an expression is 1) the value on the right of a variable definition Var = <exp>, or 2) an argument to a function.

The following table lists the syntax for expressions.

e::=0, 1, 2, ...integers
 |0.1, 1E+23, ...floating-point constants
 |x, ABC, ...identifiers
 |id::idscoped name
 |id.id. ... idprojection
 |- enegation
 |e + e | e - e | e * e | e / e | e % earithmetic
 |e ^ e | e & e | e | ebitwise operations
 |e << e | e >> e | e >>> eshifting
 |e && e | e || eBoolean operations
 |e < e | e <= e | e = e | e >= e | e > ecomparisons
 |e(e, ..., e)function application
 |e[e]array subscripting
 |( e )parenthesized expressions
 |" ... " | ' ... 'strings
 |$" ... " | $' ... 'strings
 |$( ... )variables and applications

Note that the $-style expressions are still permitted.

B.4.1  Examples

The program-syntax is specified with the directive program-syntax, which is scoped in the normal way.

    program-syntax

    i = 0
    l[] =
    while i < 10
        l[] += i
        i = i + 1
    println($"The indexes are: $l")

You can mix normal and program syntax.

program-syntax

add2(i) =
    eprintln($"add2($i)")
    return i + 2

mul2(i, j) =
    eprintln($"mul2($i, $j)")
    return i * j

println(mul2(mul2(3, 4), $(add2 1 << 20)))
Jump to:  OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long)
Index:  All • Variables • Functions • Objects • Targets • Options