This commit is contained in:
David Rose 2002-02-07 17:57:15 +00:00
parent a7cd155bb1
commit 4a7f71dbd7
2 changed files with 518 additions and 1 deletions

View File

@ -0,0 +1,510 @@
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 varibles defined within its Sources.pp file. The name of
each scope is the name of the directory.
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 primarily useful for debugging ppremake
scripts.
#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 subsitute 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).
#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.
#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 conventional to 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 pre-defined functions are also available; see
ppremake-variables.txt.

View File

@ -5,6 +5,14 @@ heavily from that of GNU makefile syntax, with GNU make's parentheses
replaced by square brackets to prevent confusion with actual makefile
syntax.
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. This syntax is included with each variable definition in
this document, because that is how you are most likely to see the
variable references.
The general convention is for variables that are built into ppremake,
or defined by the system ppremake scripts in the $DTOOL root
directory, should be defined with uppercase letters. User-defined
@ -14,7 +22,6 @@ lowercase letters. However, following the GNU makefile convention,
all built-in function names are lowercase.
The following variables are built into the ppremake executable, and
are always defined: