UCPP
UnrealScript Class PreProcessor (UCPP) is a commandline utility intended to be executed before ucc make
. It adds some preprocessor features that are not possible with Ucc. UCPP takes special class files with a .puc
extention, processes them and generates a .uc
.
El Muerte started on this tool to add some more magic to an upcoming UnrealScript unit testing framework (UsUnit). This tool is currently in development; any help (like testing) would be greatly appreciated.
Features
Directives
directive | description | |
#define name something ... | adds\overrides an definition name (case insensitive) with the value something ... (till the end of the line) | |
#undef name | removed name from the definition list (for this file only) | |
#if expresion ... #elif expresion ... #else ... #endif |
expression contains an expression constructed from numbers or defined names. The #else is optional. #elif (since version 009) is short of "else if", you can have as many of these as you want. You can nest these. |
|
#ifdef name | evaluates to true if name is defined. Can also be used to check if function definitions have been made: #ifdef MYFUNC/2. | |
#ifndef name | the opposite of #ifdef | |
#include filename | include the file in the output file, this does the same as the #include directive in UE2. By default this directive is disabled, you can enable it in the config file. | |
#ucpp command | various UCPP related items; usually these will be replaced with comments. The following commands are accepted. | |
notice | this will add a notice that the uc file was generated by UCPP, everything after this command is ignored. | |
version | this will add a comment with the UCPP version info and homepage link. | |
include filename | include filename to be processed for macros, nothing else will be done with the file. It has the same effect as using the command argument -imacros filename. The filename is relative to the currently being processed file. | |
error message | produces a preprocessor error. | |
warning message | produces a preprocessor warning. If the message is off warning reporting will be turned off, and on again if it is on (and nothing more), | |
rename filename | renames the resulting file to filename. This can be useful to use UCPP to create include files to be used with the UnrealEngine's #include directive | |
config variable value | this will override certain configuration variables for this file only. Variable can be one of the following: supportIf, supportDefine, supportPreDefine, supportInclude, stripCode. Value can be true, false, 0, 1 or empty to reset to the default value. | |
#pragma ucpp command | Accepts the same commands as the normal #ucpp directive except that using #pragma ucpp is more portable (in case you ever switch preprocessors). |
All macros that UCPP processed will simply be commented out.
Most macros allow comments on the same line; everything after the comment start token (//
or /*
) will be removed. The macro #define
does not support comments. For example:
#if 1 // always evaluates to 'true' #endif /* this comment is also ignored */ #define INC_COMMENT log("Bogus"); // this comment is included with the definition #ifdef 1 /* always evaluates to false */ && 0
Note: in UCPP version 1.3 and earlier you could not have spaces between # and the directive. Also block everything was removed from the start of a block comment.
Note 2: Do not start a block comment in a macro and end it on an other line. This will break your code even tho UCPP accepts it. For example, the following is bad:
#if 1 /* a block comment */ #endif
Expression syntax
Grammar:
EXPR ::= CMPX CMPX ::= ORX ( CMPOP CMPX )* ORX ::= ACCUM ( '||' ORX )* ACCUMX ::= ANDX ( '+'|'-' ACCUM )* ANDX ::= MULTX ( '&&' ANDX )* MULTX ::= UNARYX ( '*'|'/' MULTX )* UNARYX ::= ( '!' )? OPERAND OPERAND ::= LVALUE | '(' EXPR ')' LVALUE ::= integer | BUILTIN '(' EXPR ')' | IDENTIFIER CMPOP ::= '<' | '<=' | '=<' | '=>' | '>=' | '>' | '==' | '!=' BUILTIN ::= 'defined' | 'strcmp' | 'stricmp' | ...
Operator precedence – from top to bottom |
! |
/, * |
&& |
+, - |
|| |
<, <=, =<, =>, >=, >, ==, != |
Note: <= and =< are the same, and so are >= and =>
IDENTIFIER
is resolved in the definition table. If it is not defined, the entire expression will fail (e.g. the result is false). An empty definition (#define DEBUG
) will evaluate to 1
.
The following examples are valid expressions:
#if DEBUG && UNSTABLE_CODE #endif #if !DEBUG || (STABLE_TEST_CODE && COOKIES) #endif
There are a couple of functions that can be used in an expression:
function | description |
defined(name) | Returns 1 if name is defined, 0 otherwise |
strcmp(name or string, name or string) | Returns 0 if both strings are equal, you can use an identifier or a string constant. (v103 and up) |
stricmp(name or string, name or string) | Identical as strcmp but case insensitive. (v103 and up) |
Examples: defined(name)
#undef DEBUG #if defined(DEBUG) // this is accepted #endif #if DEBUG // this will produce a preprocessor error #endif
Examples: strcmp(arg1, arg2)
#define TEST1 "This is a test" #define TEST2 "This is a test" #if strcmp(TEST1, TEST2) == 0 evaluates to true #endif #if strcmp(TEST1, TEST2) result is 0 so it evaluates to false #endif #if stricmp(TEST1, "This Is A Test") == 0 evaluates to true #endif #define TEST4 12345 #if strcmp(TEST4, 12345) == 0 because of a side effect this also evaluates to true #endif #define TEST5 thisIsATest #if strcmp(TEST5, "thisIsATest") == 0 not the same, one is a string, the other isn't, so -> false #endif
Macros\Identifiers
All source code will be parsed for uppercase identifiers (like SOMETHING
or SOMETHING_ELSE
). These will then be replaced by their definition (if defined) AS IS. This means that "this is a value of a definition"
will be inserted into the code like that.
You can add new definitions via the macro #define
on the commandline or in the config file.
Pre-defined macros
The following names are always defined; some have some additional magic (with the name __NAME__). You can never override these magic definitions, however you can disable them by using the -undef
commandline argument.
__FILE__ | The full filename of the current file surrounded by double quotes (the generated .uc files). |
__FILE_BASE__ | Just the filename without the directory name, also surrounded by double quotes. |
__CLASS__ | The filename without an extention, should be equal to the class name. This isn't quoted. (006 and up) |
__LINE__ | The current line in the code, starting from 1. |
__DATE__ | Returns the current date in the long format (as a string), as defined by the current locale settings. |
__TIME__ | Returns the current time in the long format (as a string), as defined by the current locale settings. |
UCPP_VERSION | The UCPP version number. |
UCPP_HOMEPAGE | The UCPP homepage, the URL of this page. |
CLASS_classname | Where classname is the name of the current class. This is an empty define, it can be useful in include files. (007 and up) |
Function Defines
As of version 006 beta it is possible to define macro functions.
#define FUNC(a,b,c) log(a$__FILE__, c); a = b; // ^ ^- implementation // \- definition
The above is a definition of the function FUNC/3
Note that this is the so called footprint of the function, the number reflects the number of arguments. It's a function with the name FUNC
that accepts 3 arguments. The definition part may not contain any spaces, since the space defines where the implementation part starts.
Function arguments must be seperated by commas. Each argument must have an unique name. The implementation part is an unrealscript code snippet that contains the function arguments or anything else (except preprocessor commands). The resulting implementation is parsed again to process all defines used in the implementation.
Function definitions are unique by their number of arguments. FUNC/2
does not replace FUNC/3
. In this way you will be able to overload functions. The following definitions have the same footprint, thus the last one will be come effective:
#define FUNC(a,b,c) log(a$__FILE__, c); a = b; #define FUNC(foo,bar,quux) // every call will be replaced by this comment
There are two special tokens you can use in the implementation part:
token | meaning |
# |
Quote the next function argument (make it an valid unrealscript string): # a -> "a" . This only affects function arguments, if the next token isn't a argument it will not be quoted: # NotAnArgument -> # NotAnArgument (no substitution at all). |
## |
This will concat the previous argument with the next part: a ## b -> ab and a ## NotAnArgument -> aNotAnArgument , but also NotAnArgument ## a -> NotAnArgumenta . Note: in 006 beta this doesn't work as it should, it is fixed in version 007. |
In order to check if a certain function is defined use the footprint:
#ifndef FUNC/3 #define FUNC(a,b,c) log(a$__FILE__, c); a = b; #endif
You can not use these functions in an #if
expression.
Commandline options
Usage: ucpp.exe [switches] [settings] <name> <name> ... <name> is the name of a file or package, depeding on the mode. Switches (case sensitive): -? This message -D<name>=<value> Define a <name> with a <value>. This overrides the standards as defined in the configuration file -env Import environment variables as declarations -imacros <filename> Process <filename> for macros. This will be done for every file that needs to be processed -include <filename> Alias for -imacros -L Print the program's license -undef Do not use predefinitions (like __FILE__) -P Enable package mode. The provided names are package names. In package mode the base directory must be defined using either the SYSTEM or BASE setting -pipe Read from the stdin and write to the stdout. Only works for single files and not in package mode. -q Be quite, only show errors\warnings -stdout Similar to -pipe except that the file is not read from the standard in, but from the file provided on the commandline. So it will just write to the standard output. -strip Strip the code instead of commenting it out -U<name> Undefine <name>. Note: this has a lower precedence than definitions made in the file -V Show the program version -wait Pause at the end of executiong when there where errors -WAIT Always pause at the end of processing Settings are always in the format "<Key>=<Value>". The following keys are accepted: BASE The base directory of the game. Required in Package Mode, unless SYSTEM is already given. CONFIG Alternate configuration file to use. MOD The mod name, as used in the enhanced mod architecture. SYSTEM The "System" directory of the game. Required in Package Mode, unless BASE is already given.
If an error was produced the exit value will be not zero.
It's easy to configure WOTgreal to execute the preprocessor before compiling the selected packages. The commandline you should use is:
ucpp.exe SYSTEM=%SYSTEMDIR% MOD=%CURRENTMOD% -DDEBUG=%DEBUGCOMPILE% -P %SELECTEDPACKAGES%
This will automatically define the identifier DEBUG either set to 0 or 1 depending on if you made a debug compile in WOTgreal. Note: with this DEBUG
is always defined, use it with #if
not with #ifdef
.
Configuration
UCPP has a few configurable options. Unless CONFIG=filename is specified the program will load ucpp.ini in the same directory as the location oc ucpp.exe. You can set the following items:
[Options] supportIf=1 supportDefine=1 supportPreDefine=1 supportInclude=0 stripCode=0 stripMessage=// UCPP: code stripped noticeMessage=// NOTICE: This file was automatically generated by UCPP; do not edit this file manualy.
- supportIf (*)
- toggles the support for #if ... #elif ... #else ... #endif and #ifdef directives. This way the original compiler should take care of these directives (Only useful for the games of Irrational: Tribes: Vengeance and SWAT4).
- supportDefine (*)
- toggles support for #define and #undef
- supportPreDefine (*)
- when turned off it has the same effect as -undef on the commandline (commandline will override this)
- supportInclude (*)
- enable support for the #include macro.
- stripCode (*)
- instead of commenting out the code it will be removed, works the same as the -strip commandline options (commandline will override this)
- stripMessage
- the message to use when stripping the code, if you leave it blank it only the whitespace will remain.
- noticeMessage
- the message to use when the #ucpp notice directive is used.
(*): these settings can be changed through the #pragma ucpp config directive.
[Defines] NAME=Value
The same as #define NAME Value. You can use this for global definitions, although using an include file has more possibilities.
UnrealEngine Licensee Notice
Since version 1.4 there is a new feature that allows you to pipe the source file through UCPP. With this feature it is possible to hook up UCPP with the UnrealScript compiler. This way you don't need special .puc files, when the engine imports the code from the .uc file it can pipe it through UCPP. This way the preprocessing will be done on the fly.
For more information on how to add external preprocessor support to the UnrealEngine you can contact me through the [UDN IRC Server], my nickname is elmuerte (obviously).
Downloads
- [Version 1.6] - [release notes]
- [Version 1.5] - [release notes]
- [Version 1.4] - [release notes]
- [Version 1.3] - [release notes]
- [Version 1.2] - [release notes]
- [Version 1.1] - [release notes]
- [Version 1.0]
- [Version 008 beta]
- [Version 007 beta]
- [Version 006 beta]
- [Version 005 beta]
Since UCPP uses some parts of UnCodeX it is included with the UnCodeX project on [SourceForge]. UCPP's source code is available in the UnCodeX source in the directory src\ucpp
.
Known bugs\issues
- Newlines are not supported
- There is no way to insert a newline via definitions or macros. The reason for this is simple: the line numbers of the puc and uc file won't be skewed. This way it's easier to find compiler errors.
- #include files not processed
- Files included via the UnrealEngine built-in #include macro are not processed for macros. Use #ucpp include filename to include preprocessor directives. It is possible to enable support for include directives, in this case the #include line is replaced with the actual file during rewriting. So the actual unrealscript compiler won't have to include it.
- Code obliviousness
- the preprocessor is completely oblivious about the actual unrealscript source code it preprocesses. This means definitions made in super classes do not exist in subclasses. For global definitions you should use other means like the configuration file, the commandline or an include file (#ucpp include file).
- Recursive defines will break
#define A(x) B(x) #define B(x) A(x)
will break. In 007 and later an error will be generated.- Function defines can not be used in expressions
- You can not use function defines in
#if
expressions, however you can use them in#ifdef
, but you will need to reference them by their footprint:FUNC(a,b,c)
has the footprintFUNC/3
Feature requests
Please add your feature requests here, or on the [SourceForge] project page
Tips and Tricks
Mixing defines and actual functions
Since only uppercase identifiers will be replaced and because unrealscript is case insensitive you can mix them to add extra functionality.
#define LOG(a,b) log(a$chr(10)$__FILE__$":"$string(__LINE__), b) #define LOG(a) LOG(a, name)
Now if you use the LOG
(in uppercase) function it will append the source file and the line number. If the file isn't processed by UCPP it will still work (taking into account that the above macros where defined somewhere else than in the source file).
Using the current working directory
If you want to use the current working directory as BASE or SYSTEM just use a ".". BASE and SYSTEM take relative paths, and "." means the current directory. So for example:
c:\UT2004\System>ucpp -P SYSTEM=. MyPackage
The following does the same
c:\UT2004\System>ucpp -P BASE=.. MyPackage
Comments
Tarquin: Sounds interesting!
El Muerte: Aargh, about 30 minutes after I released 007 I found a minor yet stupid bug. Backslashes are not properly escaped. Not a major thing, but just annoying.
El Muerte: There, another release. Implemented pretty much every feature I wanted. Still have to document how to use the config file. But for the rest if this release holds up I think a stable release is close.
El Muerte: ok, here's version 1.0, did quite some testing a found a few minor bugs, also new in the release is support for #elif
. I'm delaying the press release a bit, just in case of an serious bug.
Guest: Featured on BU's news page. http://www.beyondunreal.com/daedalus/singlepost.php?id=8449
razialx: El Muerte, you are amazing. Now I feel like starting up coding in UScript again.
Devi: We've just started using this on the game my company is working on (I'm under an NDA, so I can say no more I'm afraid) and I'd just like to say: "Thankyouthankyouthankyouthankthankyou, you have made our lives SO much easier"
Jon: Soon you'll be speaking in binary code and taking over all our satellites.