I love the smell of UnrealEd crashing in the morning. – tarquin

Legacy:UCPP

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

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[edit]

Directives[edit]

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[edit]

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)[edit]

#undef DEBUG
 
#if defined(DEBUG)
  // this is accepted
#endif
 
#if DEBUG
  // this will produce a preprocessor error
#endif

Examples: strcmp(arg1, arg2)[edit]

#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[edit]

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[edit]

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[edit]

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[edit]

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[edit]

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[edit]

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[edit]

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[edit]

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 footprint FUNC/3

Feature requests[edit]

Please add your feature requests here, or on the SourceForge project page

Xian: Maybe add __USER__ and __PROJECT__, them being config-ed in the INI. Maybe if user is empty, use the current windows user, if the project macro is empty, use the Folder we're working in (not the package foler, i.e. C:\MyMod\MyPkg\Classes, it will use "MyMod"). Perhaps some code support too, such as checking if a var/func is replicated and/or if a var name/function are defined in the .uc file (unless #ifdef MYFUNC/2 does this and I misunderstood). And last but not least, an option to comment out only parts (and remove the other code if stripping is enabled) with some possible custom comment macros.

El Muerte: if you need a __USER__ and __PROJECT__ then use the standard define functionality already provided. Additionally you can import all evironment variables as defines using the -env commandline argument. In that case USERNAME will expand to the current username. PreProcessors are oblivious to the actual code, it doesn't know about anything related to replication. The #ifdef MYFUNC/2 tests if there is a macro defined with that signature, it has nothing to do with the unrealscript code.

Xian: Thanks for the reply. I see, I'll check the environ define. Ah, so it's basically just generation occurring, not parsing at all (of the UScript code, that is). Well, not so bad me thinks sine that might complicate things due to inheritance... Thanks for the tool, though, it's amazing :)

Tips and Tricks[edit]

Mixing defines and actual functions[edit]

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[edit]

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[edit]

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. :)