Introduction

This document describes the simple and flexible control language used by the Jam build tool.

I. Language Overview:

1. Input text tokenization:

Jam decomposes its input files into a list of elements, called tokens, separated by whitespace ( i.e. space, tabs and newlines). Each token is simply an 8-bit string. Double quotes (") can enclose whitespace to embed it into a token. A backslash (\) can escape a double quote or any single whitespace character. Empty tokens, defined by "" are allowed and recognized as such.

A sharp character (#) begins a comment, unless it is embedded in a string. All characters until the next newline will be ignored.

For example, the following text:

    this is just   # comment
    me

will produce a list of 4 tokens, i.e. [ 'this', 'is', 'just', 'me' ]. And the following lines:

    this is "just me"
    this is just" "me
    this is "just\ me"

will all produce the same list of 3 tokens, i.e. [ 'this', 'is', 'just me' ]

As a special exception, in the definition of updating actions (later described in detail), everything between the matching curly braces ({}) is recorded as a single string. This means that the italicized text in the following code is recorded as-is in a single string, because of the "actions" keyword:

    actions ArchiveFiles
    {
      tar czf $(1) $(2)
    }

Note that Jam doesn't separate symbols from tokens, this includes the colon (:) and semi-column (;), used as language separators. A common mistake is to forget the whitespace when needed. Consider this example:

    Main myprog: main.c util.c ;   # WRONG!
    Main myprog : main.c util.c;   # WRONG!
    Main myprog : main.c util.c ;  # CORRECT

the parser doesn't distinguish between a token named "myprog:" and a typographic error !! Sames goes with "util.c;"


2. Statements :

The parser decomposes the input stream of tokens into a list of statements. Statements are simple token lists terminated with a semi-column (;) token, or grouped with curly braces ({}). Don't forget the white-space around these tokens.


3. Variables :

All variables in Jam are simple lists of strings with arbitrary content. Note that there is no difference to Jam between an undefined and an empty variable. However, empty strings are recognized as list items.

3.a. Variable definitions

Use an assignement statement to define a variable, with a syntax like:

   VARIABLE = values ;

Where VARIABLE is the name of a variable, and values is made of one or more tokens from the parser. Consider the following:

  X = foo ;
  Y = foo bar ;
  Z = "foo bar" ;
  T = foo
      bar
      ;
  U = ;
  V = "" ;
  FOO+BAR = foo + bar ;

Here, we can see that:

the "+=" and "?=" operators can be used to add new elements to a variable, or to set its default value when it is undefined, like in:

    ZOO  = foo ;   # gives 'foo'
    ZOO += bar ;   # gives 'foo', 'bar'
    FOO ?= foo ;   # sets FOO to 'foo'
    FOO ?= bar ;   # don't touch FOO, it's already defined

3.b. Variable Expansion:

It is possible to return the values of a given variable in expression with the $(VARIABLE) syntax. Consider the following:

    BIGCATS = lion tiger ;
    BIRDS   = eagle condor ;
    ANIMALS = $(BIGCATS) $(BIRDS) ;

Here, the variable ANIMALS contains four elements, which are 'lion', 'tiger', 'eagle' and 'condor', in that order. This comes from the way Jam computes expressions:

The parentheses following the dollar sign $ are mandatory. Otherwise, Jam will not expand the expression:

  MYDIR = $HOME/mydir ;   # this is wrong, since '$HOME' won't be expanded !!

Note that anything between the $( and ) used in variable expansion can be the result of another expansion. For example:

    FOO-1 = foo1 ;         # "FOO-1" is a valid variable name
    FOO-2 = foo2 ;
    INDEX = FOO-1 ;
    FOO   = $($(INDEX)) ;  # sets FOO to $(FOO-1), i.e. "foo1"

Note that expanding an undefined variable is not an error, and produces an empty list. It is also possible to perform string concatenation during expansion. Consider the following:

  X = foo bar ;
  Y = $(X)s ;
  Z = lib$(X).lib ;

Here, we have:

Another example:

    FOO-1 = foo1 ;             # "FOO-1" is a valid variable name
    FOO-2 = foo2 ;
    INDEX = 1 ;
    FOO   = $(FOO-$(INDEX)) ;  # sets FOO to $(FOO-1), i.e. "foo1"

It is possible to concatenate several expansions together, e.g. $(VAR1)$(VAR2) and the result is always a cartesian product. More precisely, it will contain all elements of $(VAR1) concatenated to all elements of $(VAR2). Consider the following:

    EMPTY   = ;                     # define an empty variable/list
    NULL    = "" ;                  # define a variable with one item, which is the empty string
    LIST1   = foo bar ;
    LIST2   = 1 2 ;     
    CONCAT1 = $(LIST1)$(LIST2) ;    # gives 'foo1', 'bar1', 'foo2', 'bar2'
    CONCAT2 = $(EMPTY)$(LIST1) ;    # gives empty variable
    CONCAT3 = $(NULL)$(LIST1)  ;    # gives same as $(LIST1)

This example shows why $(EMPTY) and $(NULL) are different things in Jam. When you concat an empty variable with something, the result is always empty. On the other hand, if you concat the empty string with something, the result doesn't change.

Note that, interestingly, there is no way to directly access the number of elements in a given variable, but it's possible to iterate over them with the "for" statement (described below). Otherwise, it is possible to expand individual elements with the following syntaxes:

$(VARIABLE[n])

expand the n-th element of a given variable, where n is a number, starting at 1. if the index is invalid, this simply expands to an empty list

$(VARIABLE[n-m]) expands the slice of elements from index 'n' to index 'm' inclusive, results in empty list if 'm' is smaller than 'n'
$(VARIABLE[n-])

expand all elements starting from index 'n' in a variable. Hence, $(VARIABLE[1-]) is equivalent to $(VARIABLE)

More advanced expansions are possible with the $(VARIABLE:modifiers) syntax, described later, in order to painlessly manage file names and directories within Jamfiles.

Finally, the left side of an assignment is subject to expansion, which means that the following script is legal:

    FOO    = BAR ;    # sets FOO to 'BAR'
    $(FOO) = AHAH ;   # sets BAR to 'AHAH'

3.c. Local variables:

It is possible to define local variables within statement blocks with the local keyword. these definitions can also be nested at will, as in:

  x = first ;
  {
    local x ;

    x = second ;

    {
      local x = third ;
      
      Echo $(x) ;  # prints "third"
    }

    Echo $(x) ;   # prints "second"
  }

  Echo $(x) ;  # prints "first"

Each new local variable is initially empty. One can define several local variables by listing them after the local keyword, as in:

  local  x y z ;   # defines three empty local variables

It is also possible to define the value of a local variable within a definition, through the following syntax:

  local VARIABLE = initial value ;

Where initial value is expanded and assigned to the local variable. An interesting thing is that initial value is expanded before the definition of the local variable at runtime. This allows you to write code like:

  FOO = foo ;
  {
    local  FOO = $(FOO) bar ;   # defines local foo as global foo with 'bar' appended
    
    Echo $(FOO) ;  # prints "foo bar"
  }
  Echo $(FOO) ;   # prints "foo"

4. Output :

You can use Echo to print something to the standard output during Jamfile parsing. This is generally useful for debugging and informational purposes when building complex projects with Jam. This command will simply print all tokens that follow it until a ";" token is encountered.

Here are four different ways to print "hello world"

    MESSAGE = "hello world" ;
    Echo $(MESSAGE) ;

    Echo hello   # the newline is ignored since it's whitespace
    world ;      # to the parser. this will print "hello world"

    Echo "hello   
    world" ;     # the newline is part of the token, prints on two lines
    
    Echo hello ; Echo world ;  # prints on two lines

You can also use Exit to do the same thing than Echo then immediately exit from Jam. Its syntax is strictly similar, e.g.:

    if $(UNIX)
    {
      Exit sorry, this program doesn't compile on Unix ;
    }

5. Control:

Several constructs allow you to control the execution of your Jam scripts using common programming keywords

5.a. Loops with "for":

The "for" keyword can be used to iterate over all elements of a string list. It's used as:

for varname in string-list { statements }

where "varname" is a variable name that will receive the content of each element in "string-list" during the execution of statements. For example:

    # print all elements in "VARIABLE"
    for x in $(VARIABLE) { Echo $(x) ; }

    # put all elements of LIST into RESULT in reverse order
    RESULT = ;         # empty list
    for x in $(LIST)
    {
      RESULT = $(x) $(RESULT) ;
    }

    # create a string made of the concatenation of all
    # elements of STRING separated by spaces, put the
    # result in CONCAT
    CONCAT = $(STRING[1]) ;          # first element
    for x in $(STRING[2-])
    {
      CONCAT = $(CONCAT)" "$(x) ;
    }

You can use the break and continue statements to respectively exit the current loop or directly jump to the next iteration if any.

5.b. Conditional execution with "if" and "else":

The "if" keyword can be used to test certain conditions and execute statements based on their result. It's used as:

    if condition { statements }

the optional "else" keyword can also be used, as in:

  if condition { statements-if-true } else { statements-if-false }

several "if" conditional tests can be chained, as in:

    if condition1
    {
      statements-if-condition1-true
    }
    else if condition2
    {
      statements-if-condition2-true
    }
    else
    {
      statements-if-conditions-false
    }

Each condition is an expression, which are built according to:

a

true if a is not empty, i.e. if it is not an empty string list

a = b

a matches b, element by element

a != b

a differs from b

a in b

true if all elements of a are in b, or if a is empty

a < b

true when a[i] is less then b[i], where i is the index of the first mismatched element in the lists. note that this is a string comparison, not a numerical one, thus the condition "12 < 3" is true !

a > b

same as a < b, but true when a[i] is greater than b[i]

a <= b

true when every element of a is less than or equal to its b counterpart

a >= b

true when every element of a if greater than or equal to its b counterpart

! cond

negate condition

cond1 && cond2

true when both cond1 and cond2 are true.

cond1 || cond2

true when either cond1 or cond2 is true

( cond )

precedence grouping


5.c. Looping with the while statement

IMPORTANT: This statement is only available since Jam 2.4 !! It is written as

    while cond { statements }

where the condition is expressed with the same syntax than the one described for the "if". You can also use break and continue to respectively exit the loop or jump to the next iteration


5.d. Filtering with switch

The switch statement is used to execute zero or one of any given statements depending on wether a given value (string list) matches a given pattern. The pattern are never variable-expanded, and the statement looks like:

    switch value
    {
    case pattern1 : statements
    case pattern2 : statements
    ...
    }

The statements that correspond to the first matched pattern in the list are executed, before control is transfered out of the switch statement. the pattern values can contain the following wildcards:


6. Rules

Jam allows you to define rules, which are similar to procedures or functions. Each rule is defined with something like:

    rule RuleName
    {
      statements
    }

Where RuleName is the rule's name. To invoke the rule, you should write something like:

    RuleName  arguments ;

Each rule can receive up to 9 arguments, each one of them being a string list, identified within the rule's body by $(1), $(2), ... $(9). The colon (:) must be used explicitely in rule invokations to separate arguments. Undefined arguments simply expand to an empty list within a rule body during the call. Consider the following:

  rule Dump
  {
    Echo $(1) ;
    Echo $(2) ;
  }
  
  str = hello : world ;   # defines 'hello', ':', 'world'
  Dump  hello world ;
  Dump  hello : world ;
  Dump  $(str) ;

Here, we define a rule named Dump, and call it three times:

Jam defines a small set of builtin rules, described later in this document. You should not define a rule with the name of a reserved builtin.

Since Jam 2.5, it is possible to invoke a rule with variable expansion. This means that the following code should work correctly on Jam 2.5, but will fail on earlier versions:

  rule MyRule
  {
    Echo "Success" ;
  }

  MYRULE = MyRule ;
  
  $(MYRULE) ;   # try to call MyRule, fails on Jam 2.4 and before !

Note that the result of the expansion, as a single string, is used as the rule name. So the following will not work:

  rule Dump
  {
    Echo $(1) ;
  }
  
  DUMP = Dump Hello ;
  
  $(DUMP) ;

That's because there is no rule named "Dump Hello" in this script.

Finally, note that for historical reasons (i.e. to better match Make behaviour), Jam also provides the following aliases:

This is especially useful to write actions block (i.e. the build commands sent to the shell, decribed later) just like in Make. Since these are strict aliases, you can use them interchangeably in your Jam scripts.

7. Functions

Jam also allows you to define "function" rules, i.e. rules that return a value (i.e. a string list, of course), with the help of the return statement. A function rule is no different than a normal rule, except for its use of return. Consider the following:

  # return the lexicographical minimum of two strings.
  # this is not a numerical comparison !
  rule Min
  {
    if $(1) <= $(2)
    {
      return $(1) ;
    }
    return $(2) ;
  }

Note that prior to Jam 2.4, the return statement didn't stop the execution of the current rule, but simply set its result value. One had to use if and else to be able to return different values depending on context. This means that the above code would always return $(2). The function would then need to be written as:

  # return the minimum of two strings.
  # note that we do not perform numerical comparisons here !
  # this version works with all versions of Jam (i.e < 2.4)
  rule Min
  {
    if $(1) <= $(2)
    {
      return $(1) ;
    }
    else
    {
      return $(2) ;
    }
  }

Generally speaking, you shouldn't care about this, unless you need to maintain old Jamfiles.

The result of functions is available by bracketing the call within [ and ], as in:

  VARIABLE = [ RuleName  arg1 : arg2 : ... ] ;

Hence the following

   rule Swap
   {
     return $(2) $(1) ;
   }
   
   X = [ Swap a b : c d ] ;  # set X to 'c', 'd', 'a', 'b'

Finally, you can also call functions through variable expansion, as in:

  rule Test
  {
    return $(1) is OK ;
  }
  
  TEST = Test ;
  
  Echo [ $(TEST) program ] ;   # will dump "program is OK"