NAMED SCOPES

Any discussion of ppremake script syntax must begin with an
introduction to ppremake's concept of named scopes.  This concept is
relied on heavily within ppremake scripts and is the source of most of
the scripting language's power.

Like many block-scoped languages, ppremake can support arbitrary
nesting levels of scopes.  Each nested scope can access variables in
the outer scopes, and can define new variables that are local to that
scope.

In ppremake, there is one (unnamed) global scope, in which all the
variables defined in Global.pp and related files are declared.  There
are also a number of individually named scopes, one scope for each
directory in the source hierarchy that contains a Sources.pp file.
Each of these scopes is a child of the global scope, each of these
defines the variables defined within its Sources.pp file.  The name of
each scope is the name of the directory, with a trailing slash.

For instance, imagine the following simple directory hierarchy:

    root
    root/Package.pp
    root/Sources.pp
    root/apple
    root/apple/Sources.pp
    root/apple/pear
    root/apple/pear/Sources.pp
    root/banana
    root/banana/Sources.pp

In this example, there will be five scopes, ordered like this:

         global scope
               |
     +------+--+--+-----+
     |      |     |     |
   root/  apple/ pear/ banana/

That is, there is an unnamed global scope, and four named scopes, one
for each of the four Sources.pp files: "root/", "apple/", "pear/", and
"banana/".  Each of the named scopes is a sibling of the others, with
no relation to the directory hierarchy; all of the named scopes are
children of the global scope.  (The trailing slash is used to
differentiate these automatically-created named scopes from explicit
nested scopes, described below.)

It is possible to access, from any one of the scopes, variables that
are defined in another scope.  The syntax to do this is discussed in
more detail below, but for instance the expression $[FOO(apple/)]
returns the value of $[FOO] as if it were evaluated within the "apple"
scope.

Although all of the automatically-created named scopes are given a
flat hierarchy, it is possible to define further nested scopes with
the #begin .. #end syntax (described below) within any of the
Sources.pp files.  For instance, in the apple/Sources.pp file, you may
have the syntax:

  #define var1 abc
  #define var2 def

  #begin foo
    #define var2 123
    #define var3 456
  #end foo

This adds a new named scope called "foo", which is a child of "apple/":

         global scope
               |
     +------+--+--+-----+
     |      |     |     |
   root/  apple/ pear/ banana/
            |
           foo

Within the apple scope, this new named scope can be referenced by the
name "foo"; within other scopes, it must be referenced explicitly as
"apple/foo".  In the example, the value of $[var2(apple/)] is "def",
but the value of $[var2(apple/foo)] is "123".


PPREMAKE COMMANDS

The following commands are available in ppremake scripts.  The
commands are similar in syntax to C preprocessor directives: each
command must be given one per line, with a hash mark (#) as the first
non-blank character of the line.

Simple commands:

  #format <format>

    Defines the type of file that is to be generated by the next
    #output command (see #output, below).  The <format> string must
    evaluate to one of a number of strings that are predefined within
    ppremake.  Presently, this may be one of the following:

      straight - the output file is generated exactly as given,
        without any additional formatting.

      collapse - multiple consecutive blank lines are collapsed into
        one blank line, but otherwise the output is generated exactly
        as given.

      makefile - the output file is a makefile.  This implies that
        multiple consecutive blank lines may be collapsed, and long
        lines (particularly variable assignment lines) may be folded
        with the backslash character for readability.

  #print <text>

    Outputs the indicated text to standard error as soon as it is
    encountered.  This is useful for debugging ppremake scripts; and
    is also a good way to report warnings or status information to the
    user.

  #printvar <varname> [<varname2> ... ]

    Outputs the literal contents of the named variable(s) to standard
    error.  The output is similar to what you would get if you executed:
      #print <varname> = "$[<varname>]" 
    except the variable contents are not themselves expanded, making
    it possible to view the exact literal contents of the variables.
    Note that the variable names should be given without the enclosing
    $[ ... ] syntax.

  #mkdir <dirname> [<dirname2> ... ]

    Creates the indicated directory names, if they do not already
    exist.  This will also implicitly create any intervening directory
    names that are necessary.

  #include <filename>

    Includes the named file.  As in all the other commands given here,
    the angle brackets are not part of the literal syntax; don't
    confuse this with the C #include statement.  Instead, <filename>
    means any ppremake expression which can be evaluated to a string.
    If the file does not exist, an error is generated.

  #sinclude <filename>

    Includes the named file if it exists, or quietly ignores it if it
    does not.  This can be used to include an optional configuration
    file if it exists.

  #call <subroutine> <params>
  
    Calls the named subroutine immediately.  Here <subroutine> is the
    name of the subroutine to call, and <params> is the
    comma-separated list of expressions to substitute in for the
    subroutine's formal parameters.  The subroutine must have
    previously been defined with the #defsub command (see below).

  #error <text>

    Generates an error and immediately terminates ppremake, reporting
    the indicated error message.  Usually this appears within an #if
    .. #endif block testing for an unexpected error condition.  It may
    also be used during debugging.

  #define <varname> <value>

    Declares a new variable within the current scope with the
    indicated name, and sets it to the indicated value.  Variables and
    expressions within <value> are evaluated immediately, and the
    resulting string is stored as the new variable's value.

    If there was already a variable within the current scope with this
    name, that variable is replaced with the new value.  However, if
    there was a variable by the same name in an enclosing scope, the
    variable in the scope above is left unchanged, and a new variable
    is defined within the current scope, shadowing the variable above.

    Note that <varname> in the above does not include the dollar sign
    and square bracket syntax.  This syntax is used when evaluating
    variables, not when referring to them by name.

  #defer <varname> <value>

    Behaves the same as #define, but variables and expressions within
    <value> are not evaluated immediately.  Instead, the <value>
    string is assigned to the variable exactly as it is now.  When the
    variable is evaluated later, expressions appearing within the
    string will be evaluated at that time.  This behaves kind of like
    a simple function declaration: the variable's literal value
    depends on the values of the expressions it depends on, which may
    be modified at any subsequent point (thus changing the value of
    the variable correspondingly).

    This operation is equivalent to VARIABLE = VALUE syntax in GNU
    make (and in most makefile syntax).  On the other hand, #define is
    equivalent to VARIABLE := VALUE in GNU make.

  #set <varname> <value>

    Changes the value of an existing variable to the indicated value.
    Like #define, variables and expressions within <value> are
    evaluated immediately.

    This is different from #define in that (a) the variable must
    already exist (i.e. it has appeared as the target of a previous
    #define or #defer), and (b) it is possible to change the value of
    a variable in an enclosing scope (as opposed to #define and
    #defer, which can only shadow a variable in an enclosing scope,
    but cannot change the parent variable itself).

  #push <count> <varname> [<varname2> ... ]

    Copies the definition of the named variable(s) into the enclosing
    dynamic scope.  This is primarily useful for defining variables
    within an #forscopes or #formap block that must be preserved
    outside the block; normally, since the code within these blocks
    executes within a different scope, invoking the #define command
    within these blocks will define a variable only within the
    referenced scope, and the variable will not be available outside
    the block.

    The solution is to "push" the variable definition into the
    immediately enclosing block, which will be the body of the
    function that invoked the #forscopes command.  It will be as if
    the variable were defined immediately outside the #forscopes.
    Normally, a count of 1 should be used; if multiple nested blocks
    must be pushed through, the count can be increased to indicated
    the number of levels of nesting.

  #map <varname> <key_varname>(<scope_names>)

    Defines a new map variable.  A map variable is a unique construct
    in ppremake which associates scopes with named keys.  See the
    discussion on map variables, below.

    Note that, like #define, #defer, and #set, <varname> and
    <key_varname> in the above do not include the dollar sign and
    square bracket syntax.  This syntax is used when evaluating
    variables, not when referring to them by name.

  #addmap <varname> <key>

    Adds a new entry to the indicated map variable, mapping the
    indicated key string to the current scope.  This command should
    normally appear within a #forscopes .. #end block, to add a series
    of keys for each of a number of different scopes.


Conditional commands:

  #if <expr>
    ...
  #elif <expr>
    ...
  #else
    ...
  #endif

    This defines a block of code that should only be executed if the
    condition is met.  The usual semantics apply: in the case of #if,
    the immediately following code is executed only if <expr>
    evaluates true; otherwise, the condition for any subsequent #elif
    commands are tested, until one is found that evaluates true.  If
    no #elif condition evaluates true, the code under #else is
    executed; in any case, normal evaluation resumes after #endif.

    #if conditions can be nested without limit, but each #endif must
    match a corresponding #if.  Error reporting for a mismatched #if
    .. #endif pair is limited.

    As with ppremake in general, an expression is considered to be
    true if it evaluates to a nonempty string, or false if it
    evaluates to an empty string.


Block commands:

  Each of the following commands opens a block which should be closed
  with a corresponding #end command.  It is akin to the matching of
  #if .. #endif.  Block commands may be nested without limit.  In
  ppremake, each #end has a parameter, which must match the name on
  the corresponding block command, as a visual aid to the programmer.

  #begin <scopename>
    ..
  #end <scopename>

    Begins a new nested scope.  All the variables declared within this
    block will be associated with the new scope of the indicated name,
    which will be a child of the current scope.

  #foreach <iterator> <values>
    ..
  #end <iterator>

    Executes the nested block of code repeatedly, once for each of the
    space-separated words in <values>.  The <iterator> represents the
    name of a variable that will be created each time through the
    loop, with the next value in sequence.

  #forscopes <scopenames>
    ..
  #end <scopenames>

    Executes the nested block of code repeatedly, once for each of the
    scopes named by the space-separated list of names in <scopenames>.
    Each time through the loop, the code will be re-evaluated within a
    different named scope, potentially providing new values for all of
    the variables referenced.  Unlike #foreach, no iterator variable
    is necessary.

  #formap <iterator> <mapvarname>
    ..
  #end <iterator>

    Executes the nested block of code repeatedly, once for each key in
    the map variable.  Each time through the loop, a variable named
    <iterator> is created with the value of the current key, and the
    code is also evaluated within the associated scope.

  #while <condition>
    ..
  #end <condition>

    A general while loop, this repeated executes the nested block of
    code as long as <condition> remains true, i.e. nonempty.

  #for <varname> <start>,<end>[,<increment>]
    ..
  #end <varname>

    A specialized iterative loop.  The variable named by <varname> is
    initialized to the <start> value, then the contents of the loop
    are repeated, adding <increment> each time, until the variable
    reaches the <end> value.

  #defsub <subname> <params>
    ..
  #end <subname>

    Defines a new subroutine.  The nested block of code will be
    evaluated when #call <subname> is later invoked.  The <params>
    list is a comma-delimited list of formal parameter names; these
    names are the names of variables that will be filled in with the
    corresponding actual parameters on the #call command.

  #defun <funcname> <params>
    ..
  #end <funcname>
  
    Defines a new function.  This is similar to a subroutine, except
    that the function is invoked inline, like a variable: $[<funcname>
    <params>], instead of with the #call command.

    All output generated within the function (that is, lines of text
    between #defun and #end that are not part of a command) are
    concatenated together into one string (newlines are removed) and
    returned as the result of the function.

  #output <filename> <flags>
    ..
  #end <filename>

    Sends all output within the block (that is, lines of text between
    #output and #end that are not part of a command) to the indicated
    filename.  The <flags> are an optional space-separated list of
    keywords that modify the output behavior, presently the only
    recognized flag is "notouch".

    If the file does not exist, it is created.  If the file already
    existed and its contents will be changed by this command, a
    message is printed to the user and the file is rewritten.

    If the file already existed and its contents would not have been
    changed by this command, nothing is printed to the user, although
    the file is still rewritten (and the file modification timestamp
    is correspondingly updated with the current time and date).
    However, if the keyword "notouch" is included among the optional
    <flags> following the filename, the file (and consequently its
    timestamp) will not be modified unless the contents are actually
    different.


VARIABLE REFERENCES

The ppremake syntax supports three different kinds of variable
references: ordinary variables, map variables, and function calls.

Ordinary variable references:

  Variables in ppremake are always referenced using a leading dollar
  sign immediately followed by the name of the variable enclosed in
  square brackets.  This syntax was chosen to resemble the GNU make
  variable syntax, but to be visually distinct to avoid confusion with
  actual make variables (which we might be writing to a makefile).

  Note that the dollar sign and square brackets are not actually part
  of the variable names, but are simply the syntax used to reference
  the variable.  However, this syntax is used throughout this document
  to refer to variables, to clarify that we are referring to ppremake
  variable names.

  Common ordinary variables that might be referenced in this way are
  ppremake built-in variables like $[DIRNAME] or $[PLATFORM], or any
  user-defined variable created with #define or #defer.  Since
  environment variables are also automatically pulled into the
  ppremake variable space (similar to the behavior of make),
  environment variables may also be referenced in this way, although
  writing scripts that depend on environment variables is not
  recommended as it is not as portable (not every platform gives the
  user easy access to environment variables).

  There are also some fancy ways to expand ordinary variables.

    Inline pattern substitution:

      Borrowing more syntax from GNU make, ppremake allows you to
      modify the contents of the variable according to a pattern-based
      substitution, similar to the $[patsubst] function.  The syntax
      is $[varname:<from>=<to>], where <from> and <to> are filename
      patterns each involving a percent sign (%).  The percent sign
      stands for the part of the filename that remains the same; the
      rest is modified accordingly.  For instance, $[file:%.c=%.o]
      will expand the variable $[file] and automatically replace a .c
      extension with .o.  It is equivalent to $[patsubst
      %.c,%.o,$[file]].  See ppremake-variables.txt.

    Inline foreign scoping:

      A special ppremake syntax exists to evaluate a variable within
      one or more different named scopes.  The syntax here is
      $[varname(<scope names>)], where <scope names> represents a
      space-separated list of words that name the scope or scopes
      within which the variable is to be evaluated.  The result is the
      space-separated concatenation of the values of the variable in
      each of the named scopes.

      To make a contrived example, suppose you had a scope named
      "foo", in which a variable $[LETTER] is defined to be the string
      "alpha", and a scope named "bar", in which a variable $[LETTER]
      is defined to be the string "beta".  In the current scope,
      however, $[LETTER] is defined as "none".

      In this example, the expression $[LETTER] evaluates to "none",
      but $[LETTER(foo)] evaluates to "alpha" and $[LETTER(foo bar)]
      evaluates to "alpha beta".

Map variables:

  A map variable is a special ppremake construct to index into a table
  of named scopes by key.  The map variable is an indexed lookup into
  a set of named scopes, to determine in which scope a given variable
  has a particular value.

  To define a map variable, you need to have a set of named scopes,
  and an ordinary "key" variable that has been declared in each of
  them.  The syntax is:

    #map <varname> <key_varname>(<scope_names>)

  Where <varname> is the name of the map variable you are declaring,
  <key_varname> is the name of the key variable that exists in each of
  the scopes, and <scope_names> is the list of scope names over which
  the map variable is being built.

  This builds up an index into <scope_names> based on the value of the
  $[<key_varname>] within each scope.  Within each scope, the
  $[<key_varname>] variable is divided at the spaces into words, and
  each word is added to the index as a key referencing this scope.


  For example, consider the $[LETTER] example above.  You could define
  a simple map variable thus:

    #map letmap LETTER(foo bar)

  This defines a new map variable called "letmap" that maps into the
  two named scopes "foo" and "bar", with the key being the value of
  $[LETTER] in each of those two scopes.  That is, evaluating letmap
  with the string "alpha" will return the scope "foo", while
  evaluating letmap with the string "beta" will return the scope
  "bar".

  In other words, letmap is now a map variable with two key/value
  pairs.  The two keys are "alpha" and "beta", which map to the two
  scopes "foo" and "bar", respectively.  The keys represent the values
  of the ordinary variable $[LETTER] as evaluated within the two
  scopes.

  Note the similarity to the $[LETTER(foo bar)] syntax, which
  incidentally returns the string "alpha beta"--the same two keys that
  become part of the map variable.  A map variable is a lot like an
  inline foreign scoping reference, except it remembers which scope
  each key came from.


  To look up scopes in a map variable, use the syntax:

    $[<varname> <expr>,<key>]

  This returns the value of <expr> as evaluated within whatever scope
  is referenced by the string <key>.  To continue our example,

    $[letmap $[upcase $[LETTER]],alpha]

  will evaluate $[upcase $[LETTER]] in the "foo" scope--that is, the
  scope associated with the key "alpha"--which incidentally returns
  the string "ALPHA".

  It is also legal to look up multiple scopes at once.  If <key>
  contains spaces, it is divided up into words at the spaces, and each
  word is taken as a separate key.  The result of the map variable
  reference is the concatenation of all the evaluations of the
  expressions in all the matched keys.


  It is sometimes useful to ask whether a key is defined in a map
  variable or not.  The $[unmapped] function can do this; see
  ppremake-variables.txt.  Also, the $[closure] function is useful for
  evaluating map variables recursively; see ppremake-variables.txt.


Function calls:

  Function calls are a little more familiar from other scripting
  languages.  User functions are defined with the syntax:

    #defun <funcname> <params>
      ..
    #end <funcname>

  where <funcname> is any arbitrary function name, and <params> is an
  optional list of comma-separated formal parameters.  Any text that
  appears between #defun and its matching #end (and is not part of
  some other command) is returned as the result of the function.

  For instance:

    #defun updowncase abc,def
      #if $[def]
        $[upcase $[abc]]
      #else
        $[downcase $[abc]]
      #endif
    #end updowncase

  This defines a function with two parameters.  If the second
  parameter is true (nonempty), the result of the function is the
  upcase of the first parameter; otherwise, the result of the function
  is the downcase of the first parameter.

  To invoke the function, the syntax is somewhat like a variable
  expansion:

    $[<function> <params>]

  E.g.:

    $[updowncase $[filename],]

  Many builtin functions are also available; see
  ppremake-variables.txt.
