Using the Preprocessor


The following text describes the syntax of the preprocessor. It should be consulted for any questions regarding the syntax of the directives and their arguments.

General

The preprocessor uses CPP-like syntax but is still a commenting preprocessor to fulfill the requirements set forth by Java editor integration and the Java language itself (e.g., you are not allowed to change the number of lines in the source file). The directives are specified by creating a commented-out line that starts with the //# character sequence immediately followed by the directive, for example //#ifdef.

The preprocessor blocks must be well-formed, meaning that when a block is started with one of the //#if directives, it must be closed with an //#endif directive. Blocks can also be nested, which means that inside a if/elif/else/endif block there can be an arbitrary number of additional if/elif/else/endif blocks. For example:

//#if mmedia    //#if nokia         //#if s60_ver=="1.0"         import com.nokia.mmapi.v1         //#elif s60_ver=="2.0"         import com.nokia.mmapi.v2         //#else         import com.nokia.mmapi.def         //#endif     //#else         import javax.microedition.mmapi     //#endif //#endif


Directives

The following directives are available in the given syntax and function as follows:

  • #ifdefidentifier. The identifier represents a variable of any type (Boolean, String, Integer) and checks whether it is defined. If it is, then the result is true and the code that follows will be left alone. Any nested blocks will be processed as well. Otherwise, code that follows will be commented out and any nested blocks will not be evaluated. Must be closed with endif. This commenting behavior is same for all the other directives.

  • #ifndefidentifier. Works like ifdef but returns TRue only if the identifier is not defined. Must be closed with endif.

  • #elifdefidentifier. Works like a standard else if statement but automatically checks whether or not the identifier is defined. Can only complement inside blocks started by ifdef/ifndef.

  • #elifndefidentifier. Works like a standard else if statement but automatically checks whether the identifier is not defined. Can only complement inside blocks started by ifdef/ifndef.

  • #ifexpression. Evaluates an expression passed to it and fires the appropriate action. How expressions are evaluated is explained below. Must be closed with endif.

  • #elif expression. Works like a standard else if statement and can complement only in blocks started by an if statement. Will preprocess the code that follows based on the result of the expression.

  • #else. Works like a standard else and will only preprocess the code that follows when none of the previous conditions in the defining block are true. Complements inside any block started with if/ifdef/ifndef directive.

  • #endif. This directive must be used to close any block started with if/ifdef/ifndef.

  • #conditionexpression. Must be on the first line in a file. This directive determines if the file should be included in the build based on the result of the expression.

  • #debug level. Determines if the line following the directive should be commented out or not based on the debug level set in project's compile properties. If the level is omitted and the debug level is not set to off in the project properties, it will automatically debug the line in question. Used for debugging purposes in conjunction with System.out.println, for example. Can be nested.

  • #mdebug level. Behaves same as #debug but will uncomment/comment a whole block of lines following the line it is on until it reaches #enddebug. Used for debuging purposes in conjunction with System.out.println, for example. Can be nested. If mdebug block partially intersects if/ifdef/ifndef block (e.g., enddebug is outside a closed if block in which mdebug is called), the preprocessor will generate errors.

  • #enddebug. Must terminate #mdebug block.

  • #define identifier or identifier=value or identifier value. Adds temporary abilities/variables to the preprocessor memory programatically on the fly. Nested block insensitive. Global variables defined in the configuration customizer override these temporary variables.

  • #undefineidentifier. Removes temporary abilities/variables from the memory. Can also be used to remove global variables defined in the configuration customizer from the preprocessor memory but will not remove them from the list of project or configuration variables.

Variables

The preprocessor supports three types of variables: Strings, Integers, and Booleans. The variable names must start with characters that are the same as the start characters of valid Java identifiers. After the first character, you can use the characters period (.), slash (/), and backslash(\), which are normally not allowed in Java identifiers. This is to make migration easier for developers with existing Antenna or J2ME Polish sources that happen to use these characters in some variable and symbol names.

Most common comparisons (such as <=, <, >=, >, and ==) are supported between the variables. Comparisons should not be done on different variable types and a warning will occur but the expression will get evaluated according to the behavior described in the Variable Comparison section. Boolean operations such as &&, ||, !, and ^ can also be performed on all symbol types.

Strings can contain just about anything but must be enclosed in quote marks (""). Definition checks can be made on any variable and, unlike with J2ME Polish, no additional keywords are needed. The preprocessor checks for the J2ME Polish :defined construct and removes it from the variable name before checking for the definition. For example, let string nokia_model be "N60", then //#ifdef nokia_model or //#if nokia_model will yield true because it is defined and is evaluated as such. On the other hand, //#if nokia_model=="7610" will yield false because nokia_model is not "7610".

Here is what it would look like in J2ME Polish:

   //#define nokia_model="N60"    //#ifdef nokia_model:defined   System.out.println("Nokia");   //#else   System.out.println("Other");   //#endif


Here is what it would look like in NetBeans IDE:

   //#define nokia_model="N60"    //#ifdef nokia_model   System.out.println("Nokia");    //#else   System.out.println("Other");   //#endif


Integers are numbers and behave the same as strings when it comes to Boolean operations. However, they are compared as true integers and can be used for such tasks as preprocessing code where various screen resolutions require different images that are optimized for different resolutions. For example, //#if screen_width>100 && screen_height>120 can specify a code block that will import images only for devices with screens larger than 100x120.

Booleans are basically empty variables. Better yet, think of them as symbols. If the variable is an empty variable (e.g., configuration name), then it is considered a Boolean with value of TRue. If the variable is not defined anywhere in preprocessor scope (as an ability or configuration), it is considered a Boolean with a value of false.

Operators

There are three types of operators with different priorities.

! (the "not" Operator)

The exclamation point (!) operator (the "not" operator) has the highest priority and can be used on variables and expressions(e.g., !<identifier> or !<expression>). For example, //#if !nokia will check whether nokia is not defined. // #if !(screen_width>100 && screen_height>120) will check if screen size is smaller than 100x120. The expression must be a valid one enclosed in parentheses as shown. Since the ! operator has the highest priority, expressions such as // #if !screen_size=="100x200" are illegal and will yield syntax errors because a Boolean result cannot be compared to a string.

Comparison Operators

The comparison operators have the second highest priority and perform typical comparison operations. They can compare strings lexically and integers mathematically. Cross-type comparisons are supported and behave as defined in Table 14-1. They can be used only in expressions and should compare two variables, not symbols.

Table 14-1. Variable Comparisons

LeftSide

RightSide

==

!=

>

<

>=

<=

@

undef

undef

warn

warn

warn

warn

warn

warn

warn

undef

def

warn

warn

warn

warn

warn

warn

warn

undef

string

warn

warn

warn

warn

warn

warn

warn

undef

integer

warn

warn

warn

warn

warn

warn

warn

def

undef

warn

warn

warn

warn

warn

warn

warn

def

def

warn

warn

warn

warn

warn

warn

warn

def

string

warn

warn

warn

warn

warn

warn

warn

def

integer

warn

warn

warn

warn

warn

warn

warn

integer

undef

warn

warn

warn

warn

warn

warn

warn

integer

def

warn

warn

warn

warn

warn

warn

warn

integer

integer

math

math

math

math

math

math

warn

integer

string

lex

lex

lex

lex

lex

lex

math

string

undef

warn

warn

warn

warn

warn

warn

warn

string

def

warn

warn

warn

warn

warn

warn

warn

string

integer

lex

lex

lex

lex

lex

lex

warn

string

string

lex

lex

lex

lex

lex

lex

math

Key: deftrue, undeffalse, warnwarning, lexlexical comparison, mathmathematical comparison


There is also a special comparison operator that performs a "subset" relationship operation. This operator is denoted by the @ token, and both left and right arguments should be strings that represent two sets of tokens delimited by specific delimiters. It first tokenizes both the left and right string arguments into sets and then determines if the set from the left argument is a subset of the set from the right argument. The valid word delimiters are <whitespace>, ', ' and '; ' and they can be mixed arbitrarily within each argument. It behaves just as in the following four examples:

    "gif" @ "gif86, jpeg, gifaboo" = false "gif" @ "gif gif86 jpeg" = true "1 2 4;7,8" @ "0,1,2,3,4,5,6,7,8,9" = true "3 5 7 11 13" @ "0,1,2,3,4,5,6,7,8,9" = false


Even though the variables should be of the same type when comparing, the preprocessor will not fail if you compare variables of different types. Instead, you will be presented with a warning (in the form of an annotation in the Source Editor or a warning in the task's output) and an evaluation of the expression. When the left and right sides of the comparison operation are of different types, both will be considered strings and compared lexically with the exception of the @ operation, where the "subset" relationship operation will still be performed. (As such, you can check if a certain integer is in a particular set). If one of the variables is not defined or is defined as a Boolean symbol, it will be considered an empty string (""). If one of the variables is an integer, it will also be converted to a string and lexical comparison will take place. You should avoid comparing variables of different types, but doing so by accident will not break the build process.

Boolean Operators

The Boolean operators have the lowest priority relative to the rest of the operators, but they do have different priorities among each other, just like in the Java language. They perform typical logical operations such as &&, ||, and ^ on Booleans; provide expression results; and check for variable definitions and then treat those as Booleans as well. The && operator has the highest priority of all three Boolean operators, while ^ and || have lower priorities, respectively. For example, if we preprocess the following example with active configuration being Series40:

  //#ifdef Series60        //#define tmpNokia3650        //#define tmpNokia3660        //#define tmpSiemensSX1        //#define tmpScreenWidth=176        //#define tmpScreenHeight=208   //#elifdef Series40        //#define tmpNokia3220        //#define tmpNokia3210        //#define tmpScreenWidth=80       //#define tmpScreenHeight=100  //#elifdef Series20       //#define tmpNokia8320  //#else       //#define default  //#endif //#if tmpScreenWidth==176 && tmpScreenHeight==208 || tmpNokia3650 && tmpNokia3660     System.out.println("Series 60 configuration active!"); //#elif tmpScreenWidth==176 || tmpScreenWidth==80 && tmpScreenHeight==100 || tmpNokia8320     System.out.println("One of the Nokia configurations active!"); //#else     System.out.println("Default configuration active!); //#endif


The result is:

//#if tmpScreenWidth==176 && tmpScreenHeight==208 || tmpNokia3650 &&  tmpNokia3660     //# System.out.println("Series 60 configuration active!"); //#elif tmpScreenWidth==176 || tmpScreenWidth==80 && tmpScreenHeight==100 || tmpNokia8320     System.out.println("One of the Nokia configurations active!"); //#else     //# System.out.println("Default configuration active!); //#endif


Expressions

Expressions are evaluated according to the following Backus-Naur Form (BNF) grammar:

 <expression>::= <term> {<boolop> <term> } <term>::= <factor> {<compop> <factor> } <factor>::= <notop> <factor> | value <expression> <boolop>::= && | || | ^ <compop>::= > | >= | < | <= | == | != | @ <notop>::= !


Assuming that the variables in Table 14-2 are in the scope of the current configuration, the expressions in Table 14-3 are examples of what is legal and is not.

Table 14-2. Example Variables

VariableName

VariableType

Value

nokia

Boolean null

null

screen_width

Integer

100

screen_height

Integer

160

symbVer

String

v7.0

mmapi

Boolean

null


Table 14-3. Example Expressions and Results

Expression

Yields

!nokia && mmapi

true

symbVer=="v7.0" || (screen_width>=100 && screen_width>=100)

true

siemens && !screen_width!=100 //

Syntax error

siemens || !nokia

false

nokia && (screen_width>100 || screen_height>100

Syntax error


Preprocessor Coding TipsCompilation Based on Device Platform Versioning

On occasion, device lines from certain manufacturers have various platforms and platform versions. In this example, we take Nokia and their s60 and s40 platforms, both having versions 1 and 2. To accomplish this, we create configurations and add abilities as shown in Table 14-4.

Table 14-4. Example Configurations and Abilities

Nokia.s60.v2

Nokia.s60.v1

Nokia.s40.v2

Nokia.s40.v1

manufacturer=Nokia

manufacturer=Nokia

manufacturer=Nokia

manufacturer=Nokia

platform=s60

platform=s60

platform=s40

platform=s40

platform_version=2

platform_version=1

platform_version=2

platform_version=1


Now when the symbols and variables are properly defined, the code that will compile for all four platforms based on their differences could look something like the following:

   //#if manufacturer=="Nokia"    .    compile all code common to nokias, e.g. using FullCanvas    .    //#if platform=="s40"    ...    add s40 specific code    ...         //#if platform_version==1         ...         compile s40.v1 specific stuff         ...         //#elif platform_version==2         ...         compile s40.v2 specific stuff         ...         //#else         ...         compile s40 generic         //#endif     ...     ...     //#elif platform=="s60"     ...     add s60 specific code       ...           //#if platform_version==1           ...           compile s60.v1 specific stuff           ...           //#elif platform_version==2           ...           compile s60.v2 specific stuff           ...           //#else           ...           compile 640 generic           //#endif       ...       ...       //#else       ...       add generic Nokia code       //#endif //#endif


Under many circumstances, however, this approach might not be possible or will need a lot of redundant code writing, especially when actual features in the software depend on the platform specification. This approach will yield good results when handling various video/audio formats, for example. When functionality and features in the software are based on the platform specification, a more incremental approach is desirable:

    code common to all devices     ...     //#if manufacturer=="Nokia" && platform=="s60" && platform_version==1     ...    //#elif manufacturer=="Nokia" && platform=="s60" && platform_version==2    ...    //#elif manufacturer=="Nokia" && platform=="s40" && platform_version==1    ...    //#elif manufacturer=="Nokia" && platform=="s40" && platform_version==2    ...    //#endif


Depending on the situation, both approaches can be mixed together to get the desired results. The IDE provides some predefined abilities such as CLDC and MIDP versions, as well as symbols for various JSRs that can be used and do not have to be defined by hand.



NetBeans IDE Field Guide(c) Developing Desktop, Web, Enterprise, and Mobile Applications
NetBeans IDE Field Guide(c) Developing Desktop, Web, Enterprise, and Mobile Applications
ISBN: N/A
EAN: N/A
Year: 2004
Pages: 279

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net