9.7. Expression Language pvSCL

The pure::variants expression language pvSCL is a simple language to express constraints, restrictions and calculations. It provides logical and relational operators to build simple but also complex Boolean expressions.

The language is based on a simple object model. An object has an identity, attributes (data) and functions which can be applied to it. Some functions can be used without an explicit object context. Objects represent either simple data items such as numbers, or collections of objects; or in many cases they represent pure::variants model items such as elements or models.

Both full and partial configuration mode is fully supported when evaluating pvSCL expressions. See also Section 5.8.2, “ Partial Evaluation ” for details about model evaluation in these modes. In partial evaluation, calculations are done also with a special open value. So, the result of a constraint, restriction, or calculation can be also open.

The reference use the term context to denote the object to which an operator or function is applied to. This term is not to be confused with the keywords context/CONTEXT, which deliver a special object, see details below.

Expressions can be commented. A comment is started with a slash immediately followed by a star. The comment itself can span multiple lines. It is ended with a star immediately followed by a slash. Comments are ignored when an expression is evaluated.

Expressions can resolve to a boolean value, i.e. TRUE or FALSE. An expression is said to fail if its boolean value is FALSE, and to succeed otherwise. Boolean values have type ps:boolean.

Numbers can either be decimal and hexadecimal integers, or floating point numbers. Hexadecimal integers are introduced by 0x or 0X followed by digits and / or characters between a and f. Floating point numbers contain a decimal point and / or positive or negative exponent.

Integers have type ps:integer, and floating point numbers have type ps:float.

Strings are sequences of characters and escape sequences enclosed in single quotation marks. The allowed characters are those of the Unicode character set. Strings have type ps:string.

Following escape sequences are supported.

Escape SequenceMeaning
\nNew line
\tHorizontal tabulator
\bBackspace
\rCarriage return
\fForm feed
\'Single quotation mark
\"Quotation mark
\\Backslash
\0 - \777Octal character code
\u0000 - \uffffUnicode character code

Strings can be concatenated with other strings and numbers using the plus operator. The result is a new string containing the source strings and numbers in the order they were concatenated.

Collections are lists or sets of values of the same type. Lists may contain one and the same value twice, whereas sets only contain unique values. The type of lists either is ps:list or the value type followed by [], e.g. ps:string[] for a list of strings. The type of sets either is ps:set or the value type followed by {}, e.g. ps:integer{} for a set of integers.

Collection literals have list type. Their items are constructed from the values of any expressions, particularly nested collections, and must have the same type.

In partial evaluation, if the result of a calculation is a collection with at least one open member, instead of this incomplete collection only the open value will be returned.

The keywords SELF and CONTEXT are context dependent name references. The type of SELF and CONTEXT is ps:model if a model is referenced, ps:element for an element, ps:relation for a relation, ps:attribute for an attribute, and ps:constant for an attribute value.

Model ObjectSELFCONTEXT
ConstraintElement containing the constraintModel containing the constraint
Restriction on elementElement containing the restrictionElement containing the restriction
Restriction on relationRelation containing the restrictionElement containing the relation
Restriction on attributeAttribute containing the restrictionElement containing the attribute
Restriction on attribute valueAttribute value containing the restrictionElement containing the attribute value
Attribute value calculationAttribute value being calculatedElement containing the attribute value

Models, elements, and attributes can be referenced by their unique identifiers. Models can also be referenced by their names, and elements by their unique names, optionally prefixed by the name of the model containing the element. For a referenced model the result type is ps:model, for an element ps:element, and for an attribute ps:attribute.

Elements can be referenced across linked variants, i.e. variant collections, instances, and references, by means of a path name. Path names navigate to elements in another variant along the variant elements in a variant hierarchy. Variant elements are elements with type ps:variant representing the root element of a linked variant.

Path Name ElementDescription
variant-name:nameRelative path name
:nameAbsolute path name
parent:nameParent variant navigation
variant-collection-or-instance-name[3]:nameAnonymous variant navigation for variant collections and instances

A name is resolved as follows.

Elements can be referenced independently of their selection, i.e. existence, in the current variant.

To check the selection state of a given element, the meta-attribute pv:Selected can be called on that element. Depending on the configuration mode and selection state following values will be returned:

Selection stateFull evaluationPartial evaluation
SelectedTRUETRUE
ExcludedFALSEFALSE
UnselectedFALSEopen

Applying Boolean operations on element references enforce an implicit conversion to the Boolean selection state. So an explicit call of pv:Selected on element references is not necessary in following use cases:

Attributes and meta-attributes can be accessed using the call operator ->. The left operand of the call operator is the context of the call, the right operand the attribute or meta-attribute to call. It is an error if there is no attribute or meta-attribute with the given name for the context of a call.

If the context has model or element type, ordinary model and element attributes can be accessed. The result type is ps:attribute.

The value of an attribute is automatically accessed in all contexts a value is required, e.g. operand of a logical, relational, arithmetic, or comparison operator. Meta-attribute pv:Get can be used to access an attribute value explicitly. For an attribute with collection type a specific value can be accessed by specifying the index of the value as argument to the call (function call syntax).

In full configuration mode, an error is created, if the accessed attribute has no value. In partial configuration mode, instead an open value is returned.

The context types meta-attributes can be called on, depend on the implementation of a meta-attribute. Meta-attributes may accept an argument list (function call syntax). The result of calling a meta-attribute also depends on its implementation.

Since meta-attributes (built-in and user-defined) and attributes use the same calling syntax, the calling precedence of meta-attribute and attribute calls needs to be considered:

  • The built-in meta-attributes (see Section 9.7.22, “Function Library”) will override all attribute calls with the same name. However, it is generally not recommended to name attributes as same as built-in meta-attributes.

  • A user-defined function in the meta-attribute syntax (see Section 9.7.17, “Function Definitions”) will override attribute calls with the same name and the same number of arguments counted in the meta-attribute syntax. That is, a user-defined function with one argument (in the corresponding meta-attribute syntax called with zero arguments) will override an attribute call without arguments. Analogous, a user-defined function with two arguments (in the meta-attribute syntax called with one argument) will override an attribute call with index argument.

To access such hidden attributes, the meta-attribute pv:Attribute has to be used instead.

Syntax
context-expr -> attr-name
context-expr -> attr-name(index-expr)
context-expr -> meta-attr-name
context-expr -> meta-attr-name(expr, expr, ...)
Examples
product->version > 3
seasons->names = { 'spring', 'summer', 'autumn', 'winter' }
seasons->names(1) = 'summer' AND seasons->names(2) = 'autumn'
seasons->names->pv:Size = 4
seasons->names->pv:Get(3) = 'winter'

Expressions can be logically combined. For this purpose the expressions are evaluated to their boolean values. It is an error if this conversion is not possible. The logical operator is then applied to the boolean values resulting in TRUE or FALSE.

In partial evaluation, logical operations are applied using three-valued logic. So, Boolean open values are supported as operands. The result can then be also open.

Following logical operators are supported:

OperatorMeaning
ANDBinary operator that yields TRUE if both operands are TRUE.
ORBinary operator that yields TRUE if at least one operand is TRUE. If the first operand is TRUE then the second operand will not be evaluated.
XORBinary operator that yields TRUE if exactly one operand is TRUE.
NOTUnary operator that yields TRUE if the operand is FALSE.

Logical operators have a lower precedence than comparison operators but a higher precedence than relational operators.

Expressions can be set in relation to each other. For this purpose the expressions are evaluated to their boolean values. It is an error if this conversion is not possible. The relational operator is then applied to the boolean values resulting in TRUE or FALSE.

In partial evaluation, relation operations are applied using three-valued logic. So, Boolean open values are supported as operands. The result can then be also open.

Following relational operators are supported:

OperatorMeaning
REQUIRESEvaluates to TRUE, iff a) both operands evaluate to TRUE or b) the left operand evaluates to FALSE. In the latter case, the right operand will not be evaluated.
IMPLIESSame as REQUIRES.
CONFLICTSEvaluates to TRUE, iff a) the left operand evaluates to TRUE and the right operand evaluates to FALSE or b) the left operand evaluates to FALSE. In the latter case, the right operand will not be evaluated.
RECOMMENDSLike REQUIRES but always yields TRUE.
DISCOURAGESLike CONFLICTS but always yields TRUE.
EQUALSEvaluates to TRUE, iff either both operands evaluate to TRUE or both operands evaluate to FALSE.

Relational operators have a lower precedence than conditionals, and logical and arithmetic operators.

Conditionals allow to evaluate alternative expressions depending on the boolean value of a condition. If boolean-condition-expr evaluates to TRUE, expression consequence-expr is evaluated to determine the result of the conditional expression. If the condition evaluates to FALSE, expression alternative-expr is evaluated instead. In partial evaluation, if the condition is open, both consequence-expr and alternative-expr are evaluated. If both result values are equal, that equal value with be the result of the conditional. Otherwise the result is an open value. It is an error if boolean-condition-expr cannot be evaluated to a Boolean value.

Conditionals can occur everywhere where expressions are allowed. This means in particular that conditionals can be nested. Conditionals have a higher precedence than relational, logical, arithmetic and compare operators.

Expressions can be compared based on their values. For this purpose the expressions are evaluated to their values first, and then the comparison operator is applied to the values resulting in TRUE or FALSE. In partial evaluation, if one if the operands is open, the result of the comparison will be also open.

Beginning with pure::variants 5.0.0, in general values of different base types are not comparable. A comparison of such value combinations will create an error. Exceptions are a) the number types (ps:float and ps:integer are comparable) and b) versions (type ps:version), which also can be compared with strings (type ps:string).

Two numbers are compared based on their numeric values, two strings lexically, two collections item by item, two booleans by their boolean values, and model and element references by their ID.

Following comparison operators are supported:

OperatorMeaning
=Yields TRUE if both operands have the same value.
<>Yields TRUE if the operands have different values.
>Yields TRUE if the left operand's value is greater than the right operand's value.
<Yields TRUE if the left operand's value is less than the right operand's value.
>=Yields TRUE if the left operand's value is greater than or equals the right operand's value.
<=Yields TRUE if the left operand's value is less than or equals the right operand's value.

The types ps:boolean, ps:element, and ps:model do not have a natural order. Thus, beginning with pure::variants 5.0.0 any order comparison of such values will create an error.

Comparison operators have a lower precedence than arithmetic operators but a higher precedence than logical operators.

Numbers can be negated, added, subtracted, multiplied, and divided. If at least one operand of an arithmetic operation has floating point type, the result also will have floating point type. Division by zero and floating point overflows create errors.

In partial evaluation, if one of the operands is open, the result will usually also be open. Exceptions are: Multiplication of open by zero and division of zero by open results both in zero.

Arithmetic operators have a higher precedence than comparison operators and a lower precedence than conditionals. Addition and subtraction have a lower precedence than multiplication and division. That means, 2*3+3*2 is calculated as (2*3)+(3*2)=6 instead of ((2*3)+3)*2=18.

The LET keyword declares at least one variable with name var-name and initializes it with the value of expression init-expr. The variable is visible only in the expression following keyword IN, and in the init-expr of subsequent variable declarators.

Variable declarations can occur everywhere expressions are allowed. To avoid name conflicts it is recommended to use own namespaces for the variable names (e.g. my:var-name instead of var-name).

The result of a variable declaration is the value of the expression following keyword IN.

The DEF keyword defines a function with name fct-name and the given parameter list (see syntax below). Multiple functions with the same name can be defined, if they have different numbers of parameters. Defining multiple functions with the same name and same number of arguments are not allowed (one-definition rule (ODR)). Using the same function name as for built-in functions is also not allowed. The parameters of the definition are only accessible in the function body (fct-body-expr). The result of calling such a function is the value of the fct-body-expr calculated for the given argument list.

Since pure::variants 5.0.0, such functions can also be called using meta-attribute syntax if they have at least one parameter. In this case, the context on which the function is called is assigned to the first parameter of the function. The arguments of the function call are assigned to the remaining parameters of the function.

Function definitions are only allowed at the beginning of a pvSCL expression. pvSCL expressions which contain only function definitions evaluate to TRUE. To avoid name conflicts, it is recommended to use own name spaces for the function and parameter names (e.g. my:fct-name instead of fct-name, and my:param-name instead of param-name). To avoid future name conflicts it is recommended not to use the pv name space for function names.

If not defined in a pvSCL code library, such a function is visible only in the constraint, restriction or calculation containing the function definition.

A function call executes the built-in or user-defined function fct-name with the given argument list and returns the value calculated by the function. It is an error if the function does not exist.

Since pure::variants 5.0.0, functions can also be called using meta-attribute syntax if they have at least one parameter. In this case, the context on which the function is called is assigned to the first parameter of the function. The arguments of the function call are assigned to the remaining parameters of the function.

Iterators are special functions able to iterate collections. For each collection item expression expr is evaluated. The current collection item is accessible in the expression using iterator variable iter-name, which is visible there only. The value of an iterator function call depends on the implementation of that function.

Accumulators are special functions able to iterate collections. For each collection item expression expr is evaluated and its value is assigned to the accumulator variable acc-name. The initial value of accumulator variable acc-name is the value of expression acc-init-expr. The current collection item is accessible in the expression using iterator variable iter-name. Both variables, iter-name and acc-name, are visible in expression expr only.

The value of an accumulator function call is the final value of the accumulator variable.

During evaluation of pvSCL expressions, using wrong syntax, wrong input types or invalid values will create evaluation errors and the evaluation of that expression is canceled. In partial evaluation, the usage of open values can hide such errors. An example is getting an item of a collection by using function pv:Item(n), when n is open. If n evaluates to a concrete number in future configurations, the function will return either the nth item or cancels with an index-out-of-range error. Since it cannot be known beforehand, the partial evaluation returns not only open, but also sets a potential-error flag for the evaluation of that pvSCL expression. Even if the evaluation of the complete expression results in a constant value, like in

collection->pv:Item(Feature->openattr) = 2 OR SelectedFeature

which will return either TRUE or create an error, the partial evaluation will always return open if that potential-error flag is set.

Errors, warnings, and information markers can also be created using functions pv:Fail, pv:Warn, and pv:Inform, respectively. Usually they are applied in expressions like

condition OR pv:Fail('Error: Condition is not fulfilled.')

So if condition evaluates to FALSE, the right operand of OR, pv:Fail, is executed and an error marker is created. If the condition evaluates to TRUE, the shortcut applies and pv:Fail is not executed. However, if in partial evaluation condition evaluates to open, the right operand of OR also needs to be executed. So the execution of pv:Fail actually needs to create an error marker, although it is not clear, if the condition is fulfilled or not. To avoid this, operand expressions, which needs to be only executed because a shortcut could not be applied because of an open operand, will be executed in a special mode, where pv:Fail, pv:Warn, and pv:Inform will not create any markers.

In partial evaluation all functions can process open values as context and as each of their arguments. Depending on the functionality the return value can be also open.

Return a formatted string representation of the context number. Fails if the context does not have number type. The return type is ps:string.

The argument is a C-printf-like format specifier string. The supported strings are shown in Table 9.9, “Supported format specifiers”. The output is not localized.


Due to limited precision of ps:float values (they are internally represented in double-precision floating-point format), each ps:float value can be represented by at most 17 significant decimal digits. So, formatting a ps:float value with more digits would pretend a higher precision in the output compared to the input. Additionally, the exact formatted output in this excessive precision range depends on the runtime libraries of the used operation system. So, the resulting string can be different across operation systems. In result, it is recommended to use the format specifiers %.ne and %.nf only with n <= 16 and n <= 17-i respectively, where i is the number of significant digits before the decimal point in non-exponential notation.

Examples
51966->pv:Format('%x') = 'cafe'
3.14159265->pv:Format('%f') = '3.141593'						
3.14159265->pv:Format('%f') = '3.141593'
6.62607015e-34->pv:Format('%e') = '6.626070e-34'
6.62607015e-34->pv:Format('%.0e') = '7e-34'

Get all models of a configuration space as ps:model[] collection. Optionally accepts a model type as argument to get only the models of a specific type. The parameter type is of ps:string type. See Table 5.1, “Mapping between input and concrete model types” for the list of applicable type names. If applied on a object, call fails if the object is not of configuration space type ('ps:configspace').

Examples
pv:Models('ps:fm')->pv:Size() > 1
/* applicable everywhere, number of feature models more than 1 */

context->pv:Parent()->pv:Models()->pv:Size() > 1
/* this form only in constraints*/
/* context of constraint is a model, parent is config space */

For complex restrictions and calculations it may be useful to provide additional functions, e.g. to simplify the expressions or to share code. For the expression language pvSCL a code library can be defined in each model. This is done by entering the code into the pvSCL Code Library properties page of a model (see Figure 9.1, “pvSCL Code Library Model Property Page”).


Each feature or family model in a Configuration Space can define code libraries. Code defined in one model is also available in all other models of the same configuration space. Defining the same function in more than one model, will redefine the function. Since there is no explicit model loading order the used version of the function may differ.