Complex Conditional Specification
It's a goal of complex conditionals to not break the syntax of existing conditionals. I believe that this syntax is backwards compatible with the old syntax.
Operators (in order of precedence)
| Operator | Meaning |
| (,) | Grouping |
| ! | Logical not |
| <, >, <=, >=, ==, != | Relational and Equality |
| and | Boolean |
| or | Boolean |
Note that in XML, < and > must be escaped. The syntax for less-than is <, for less-than or equal is <=, greater-than is > and greater-than or equal is >=.
Parentheses are used for grouping expressions and forcing precedence.
Legitimate Boolean conversion
‘yes’, ‘on’, and ‘true’ can all be converted to a Boolean true
‘no’, ‘off’, and ‘false’ can all be converted to a Boolean false
No numeric values will be converted to boolean. '0' isn't the same as 'false' (or any other booleans), etc.
Issue: May need to support '0' and '1' as boolean values to support Windows build scenarios.
Legitimate Numeric conversions
Strings starting with an optional +/- and contain a valid integer or floating point number can be converted to a numeric. Optionally, hex numbers will be represented by 0xdddddddd (you don't need all digits, leading zeroes can be dropped). Other numeric formats such as Octal (077, etc) will not be supported.
Implicit type conversions
Relational operations (<,>,<=,>=) will convert both sides to numeric. If both can’t be converted, a runtime error will be generated.
Equality operations will try to convert both sides to a numeric. If both are successful, the comparison will be numeric. Otherwise, we try to convert both to Boolean. If successful, there will be a Boolean comparison. If this fails, the comparison will be a case sensitive string comparison. There is no way to force mixed-mode (integers and strings) or a pair of strings both containing only numbers to be string compared.
Boolean operations will convert all args to a Boolean. If all can’t be converted, a runtime error will be emitted.
Terminals and Types
Strings
Simple strings don't need single quotes around them. A simple string is any string that starts with an alphabetic character or underscore ('
'), and contains only alphanumeric characters or underscore (''). For example,
$(platform) == x86
is allowed, and is identical to
$(platform) == 'x86'
The words 'and' and 'or' aren't allowed as simple strings either. They must be quoted.
More complex string (those containing any non-alphanumeric characters not including '_') must be surrounded by single quotes. Strings can contain any character. Property and input list expansion will happen within a string. If you want to use a ''', ‘$’ or an ‘@’, there will be a (TBD) method for escaping them.
Property and item-lists are expanded in strings (thus strings may be a mix of properties, item-lists, and characters) and the values are concatenated together. Item-lists support transforms (including the separator character syntax).
Issue: Can we remove the single-quotes from "simple" strings? Things like "$(foo) == x86". This would also allow the boolean values to have their quotes dropped.
Numbers
Numbers are strings of characters that meet the format described above. They do not need single-quotes around them (though if they have them, they’ll be treated as a string and implicitly converted to a number if necessary).
Implemention Note: Numbers will be stored as floating point, and the comparisons done as floating point comparisons. Since we're not currently supporting math operations, there should be no floating-point rounding issues. If we do implement these operations, this implementation may have to be reconsidered.
Functions
Functions require parentheses after the function name, even if there are zero arguments to the function (like C++, C#, etc.) Functions can take arguments of any type, Boolean, string, integer, property, and item lists.
Function names will comply to the CLS rules for type naming.
Issue: Investigate CLS rules for types and functions. Which is appropriate? This may need to wait until we have some idea as to the extensibility implementation.
Functions return numbers, strings or Boolean. These return values are converted implicitly according to the rules above. Having defined return types (numeric or Boolean) may allow us to give an error sooner (e.g. having the return value from function returning a Boolean compared to a function with a return value of int) rather than waiting to evaluation. This may or may not happen.
Having quotes around a function call is not allowed.
Note that expressions may not be passed in as an argument to a function, e.g. if
MyFunc( arg ) expects arg to be a bool, you can't say "MyFunc($(Version) < 5)".
Exists() is the only currently defined function. New functions will be brought online as needed. Extensbility model (if there even is one) is still TBD.
If functions return strings, expansion of properties and item-lists is not performed.
Issue: Need to understand how arguments are passed to functions. When are input lists converted to a string or an array of strings. What about conversions from one type to another?
Properties
Properties are converted to their values at every evaluation of a condition.
Property-string concatenations or property-property concatenations must be done as strings and surrounded by quotes.
Issue: Did I address this? (from Sumedh) I don't think this is right. I can do this $(foo)($bar) > 45, if $(foo)$(bar) resolves to a number.
Item Lists
Item lists may only be used wherever properties are used. It may have transforms applied to it. Single-quotes are allowed and optional for single item-lists; item-lists concatenated with other properties, item-lists or string content must be a string (and thus surrounded by single-quotes). Input lists are expanded at evaluation time. If necessary, item-lists will be converted to a single string with delimiters.
Danmose: What about @(sources.filename), is that allowed?
Danmose: What about %(resources.culture), do we batch if the condition is on a target or task?
Issue: .Net version numbers e.g. 4.0000.00, or whatever the format is. These will be treated as strings, which means that you can't get relational comparisons. Is this a problem? Do we need to support them as "types"?
ChadR: OK, so what about new types, like version numbers? Somehow, people will need to use some component of versions/builds in expressions. Do we just have them store separate values like
MajorVersion/MinorVersion/BuildIdentifier and use those components in expressions? It would be nice to know if 1.00.40112.0 was older or newer than 1.10.040112.0.
Condition Evaluation
Properties and input list aren’t evaluated until evaluation time. Expressions are lazy-evaluated and if the expression has functions, they may not be called. If they have side-effects, they (the side-effects) may not occur.
Some conditions can be parsed, but give runtime errors. Some expressions can be parsed, but can't be evaluated due to type conversion issues. These will be some error that is distinct from a syntax error.
They may also be intermittent, i.e. they may not occur every build, depending on the value of the properties or values returned by functions.
Issue: What culture/locale is used for string comparison? The one that's kept in the process (or thread, wherever the CLR/framework keeps it)? That sounds wrong. That means comparisons may have different results on different machines. Shouldn't the
MSBuild project file specify this?
Weirdisms (or things which may not be readily apparent)
!’true’ is legal
!’foo’ is parseable but will generate a runtime error
“'on' == 'true'” will succeed because relational comparisons get converted to Booleans.
“4 == 4.0” or “04 == 4” will always succeed because equality comparisons are always converted to numeric if possible.
Condition = “$(foo)” is a valid conditional (which may generate a runtime error if not evaluatable)
Raw Grammar
28-Apr: Relational-expr rewritten to disallow "a == b == c" Changed expr on left-hand side of
productions to Factor.
| Expr | -> | Expr TOKEN_AND Boolean-term |
| | | | Boolean-term |
| | ; | |
| Boolean-term | -> | Boolean-term TOKEN_OR Relational-expr |
| | | | Relational-expr |
| | ; | |
| Relational-expr | -> | Factor TOKEN_LT Factor |
| | | | Factor TOKEN_GT Factor |
| | | | Factor TOKEN_GTE Factor |
| | | | Factor TOKEN_LTE Factor |
| | | | Factor TOKEN_EQ Factor |
| | | | Factor TOKEN_NEQ Factor |
| | | | Factor |
| | ; | |
| Factor | -> | TOKENLPAREN Expr TOKENRPAREN |
| | | | TOKEN_STRING |
| | | | TOKEN_NUMBER |
| | | | TOKEN_PROPERTY |
| | | | TOKEN_ITEMLIST |
| | | | TOKENFUNCTIONNAME TOKENLPAREN Arg-list TOKEN_RPAREN |
| | | | TOKEN_NOT Factor |
| | ; | |
| Arg-list | -> | Args |
| | | | ε |
| | ; | |
| Args | -> | Arg TOKEN_COMMA Args |
| | | | Arg |
| | ; | |
| Arg | -> | TOKEN_STRING |
| | | | TOKEN_NUMBER |
| | | | TOKEN_PROPERTY |
| | | | TOKEN_ITEMLIST |
| | ; | |
Recursive Descent Grammar
This is just noise for now. I'm still working on rewriting the grammar. Until it's done, it's ignoreable.
| Expr | -> | Boolean-term Expr-prime |
| | | | Boolean-term |
| | ; | |
| Expr-prime | -> | TOKEN_AND Boolean-term Expr-prime |
| | | | ε |
| | ; | |
| Boolean-term | -> | Relational-expr Boolean-term-prime |
| | | | Relational-expr |
| | ; | |
| Boolean-term-prime | -> | TOKEN_OR Relational-expr Boolean-term-prime |
| | | | ε |
| | ; | |
| Relational-expr | -> | Factor TOKEN_LT Factor |
| | | | Factor TOKEN_GT Factor |
| | | | Factor TOKEN_GTE Factor |
| | | | Factor TOKEN_LTE Factor |
| | | | Factor TOKEN_EQ Factor |
| | | | Factor TOKEN_NEQ Factor |
| | | | Factor |
| | ; | |
| Factor | -> | TOKENLPAREN Expr TOKENRPAREN |
| | | | TOKEN_STRING |
| | | | TOKEN_NUMBER |
| | | | TOKEN_PROPERTY |
| | | | TOKEN_ITEMLIST |
| | | | TOKENFUNCTIONNAME TOKENLPAREN Arg-list TOKEN_RPAREN |
| | | | TOKEN_NOT Factor |
| | ; | |
| Arg-list | -> | Args |
| | | | ε |
| | ; | |
| Args | -> | Arg TOKEN_COMMA Args |
| | | | Arg |
| | ; | |
| Arg | -> | TOKEN_STRING |
| | | | TOKEN_NUMBER |
| | | | TOKEN_PROPERTY |
| | | | TOKEN_ITEMLIST |
| | ; | |