Values and Expressions

It is fairly reasonable to expect that enabling or disabling a configuration option such as CYGVAR_KERNEL_THREADS_DATA in some way affects its value . This will have an effect on any expressions that reference this option such as requires CYGVAR_KERNEL_THREADS_DATA . It will also affect the consequences of that option: how it affects the build process and what happens to any constraints that CYGVAR_KERNEL_THREADS_DATA may impose (as opposed to constraints on this option imposed by others).

In a language like C the handling of variables is relatively straightforward. If a variable x gets referenced in an expression such as if (x != 0) , and that variable is not defined anywhere, then the code will fail to build, typically with an unresolved error at link-time. Also in C a variable x does not live in any hierarchy, so its value for the purposes of expression evaluation is not affected by anything else. C variables also have a clear type such as int or long double .

In CDL things are not so straightforward.

Option Values

There are four factors which go into an option's value:

  1. An option may or may not be loaded.

  2. If the option is loaded, it may or may not be active.

  3. Even if the option is active, it may or may not be enabled.

  4. If the option is loaded, active and enabled then it will have some associated data which constitutes its value.

Is the Option Loaded?

At any one time a configuration will contain only a subset of all possible packages. In fact it is impossible to combine certain packages in a single configuration. For example architectural HAL packages should contain a set of options defining endianness, the sizes of basic data types and so on (many of which will of course be constant for any given architecture). Any attempt to load two architectural HAL packages into a configuration will fail because of the resulting name clash. Since CDL expressions can reference options in other packages, and often need to do so, it is essential to define the resulting behavior.

One complication is that the component framework does not know about every single option in every single package. Obviously it cannot know about packages from arbitrary third parties which have not been installed. Even for packages which have been installed, the current repository database does not hold details of every option, only of the packages themselves. If a CDL expression contains a reference to some option CYGSEM_KERNEL_SCHED_TIMESLICE then the component framework will only know about this option if the kernel package is actually loaded into the current configuration. If the package is not loaded then theoretically the framework might guess that the option is somehow related to the kernel by examining the option name but this would not be robust: the option could easily be part of some other package that violates the naming convention.

Assume that the user is building a minimal configuration which does not contain the kernel package, but does have other packages which contain the following constraints:

    requires CYGPKG_KERNEL

Clearly the first constraint is not satisfied because the kernel is not loaded. The second constraint is also not satisfied. The third constraint is trivially satisfied: if there is no kernel then the kernel's timeslicing support cannot possibly be enabled.

Any options which are not in the current configuration are handled as follows:

  1. Any references to that option will evaluate to 0 , so requires !CYGSEM_KERNEL_SCHED_TIMESLICE will be satisfied but requires CYGSEM_KERNEL_THREADS_DATA will not be satisfied.

  2. An option that is not loaded has no consequences on the build process. It cannot directly result in any #define's in a configuration header file, nor in any files being compiled. This is only reasonable: if the option is not loaded then the component framework has no way of knowing about any compile or similar properties. An option that is not loaded can have indirect consequences by being referenced in CDL expressions.

  3. An option that is not loaded cannot impose any constraints on the rest of the configuration. Again this is the only reasonable behavior: if the option is not loaded then any associated requires or legal_values properties will not be known.

Is the Option Active

The next issue to consider is whether or not a particular option is active. Configuration options are organized in a hierarchy of components and sub-components. For example the C library package contains a component CYGPKG_LIBC_STDIO containing all the options related to standard I/O. If a user disables the component as a whole then all the options below it become inactive: it makes no sense to disable all stdio functionality and then manipulate the buffer sizes.

Inactive is not quite the same as disabled, although the effects are similar. The value of an inactive option is preserved. If the user modifies a buffer size option, then disables the whole stdio component, the buffer size value remains in case the stdio component is re-enabled later on. Some tools such as the graphical configuration tool will treat inactive options specially, for example such options may be grayed out.

The active or inactive state of an option may affect other packages. For example a package may use the sprintf function and require support for floating point conversions, a constraint that is not satisfied if the relevant option is inactive. It is necessary to define exactly what it means for an option to be inactive:

  1. An option is inactive if its parent is either inactive or disabled. For example if CYGPKG_LIBC_STDIO is disabled then all the options and sub-components become inactive; since CYGPKG_LIBC_STDIO_FLOATING_POINT is now inactive, CYGSEM_LIBC_STDIO_PRINTF_FLOATING_POINT is inactive as well.

  2. Options may also be inactive as a result of an active_if property. This is useful if a particular option is only relevant if two or more disjoint sets of conditions need to be satisfied, since the hierarchical structure can only cope with at most one such set.

  3. If an option is inactive then any references to that option in CDL expressions will evaluate to 0 . Hence a constraint of the form requires CYGSEM_LIBC_STDIO_PRINTF_FLOATING_POINT is not satisfied if the entire stdio component is disabled.

  4. An option that is inactive has no consequences on the build process. No #define will be generated. Any compile or similar properties will be ignored.

  5. An option that is inactive cannot impose any constraints on the rest of the configuration. For example CYGSEM_LIBC_STDIO_PRINTF_FLOATING_POINT has a dependency requires CYGPKG_LIBM , but if all of the stdio functionality is disabled then this constraint is ignored (although of course there may be other packages which have a dependency on CYGPKG_LIBM .

Is the Option Enabled? What is the Data?

The majority of configuration options are boolean in nature, so the user can either enable or disable some functionality. Some options are different. For example CYGNUM_LIBC_STDIO_BUFSIZE is a number, and CYGDAT_LIBC_STDIO_DEFAULT_CONSOLE is a string corresponding to a device name. A few options like CYGDAT_UITRON_TASK_EXTERNS can get very complicated. CDL has to cope with this variety, and define the exact behavior of the system in terms of constraints and build-time consequences.

In CDL the value of an option consists of two parts. There is a boolean part, controlling whether or not the option is enabled. There is also a data part, providing additional information. For most options one of these parts is fixed, as controlled by the option's flavor property:

Flavor Enabled Data
none Always enabled 1 , not modifiable
bool User-modifiable 1 , not modifiable
data Always enabled User-modifiable
booldata User-modifiable User-modifiable

The effects of the boolean and data parts are as follows:

  1. If an option is disabled, in other words if the boolean part is false, then any references to that option in CDL expressions will evaluate to 0 . This is the same behavior as for inactive options. The data part is not relevant. The none and data flavors specify that the option is always enabled, in which case this rule is not applicable.

  2. If an option is enabled then any references to that option in CDL expressions will evaluate to the option's data part. For two of the flavors, none and bool , this data part is fixed to the constant 1 which generally has the expected result.

  3. If a component or package is disabled then all sub-components and options immediately below it in the hierarchy are inactive. By a process of recursion this will affect all the nodes in the subtree.

  4. If an option is disabled then it can impose no constraints on the rest of the configuration, in particular requires and legal_values properties will be ignored. If an option is enabled then its constraints should be satisfied, or the component framework will report various conflicts. Note that the legal_values constraint only applies to the data part of the option's value, so it is only useful with the data and booldata flavors. Options with the none and data flavors are always enabled so their constraints always have to be satisfied (assuming the option is active).

  5. If an option is disabled then it has no direct consequences at build-time: no #define will be generated, no files will get compiled, and so on. If an option is active and enabled then all the consequences take effect. The option name and data part are used to generate the #define in the appropriate configuration header file, subject to various properties such as no_define , but the data part has no other effects on the build system.

By default all options and components have the bool flavor: most options are boolean in nature, so making this the default allows for slightly more compact CDL scripts. Packages have the booldata flavor, where the data part always corresponds to the version of the package that is loaded into the configuration: changing this value corresponds to unloading the old version and loading in a different one.

CDL Flavors: The concept of CDL flavors tends to result in various discussions about why it is unnecessarily complicated, and would it not have been easier to do … However there are very good reasons why CDL works the way it does.

The first common suggestion is that there is no need to have separate flavors bool , data , and so on. A boolean option could just be handled as a data option with legal values 0 and 1 . The counter arguments are as follows:

  1. It would actually make CDL scripts more verbose. By default all options and components have the bool flavor, since most options are boolean in nature. Without a bool flavor it would be necessary to indicate explicitly what the legal values are somehow, e.g. with a legal_values property.

  2. The boolean part of an option's value has a very different effect from the data part. If an option is disabled then it has no consequences at build time, and can impose no constraints. A data option always has consequences and can impose constraints. To get the desired effect it would be necessary to add CDL data indicating that a value of 0 should be treated specially. Arguably this could be made built-in default behavior, although that would complicate options where 0 is a perfectly legal number, for example CYGNUM_LIBC_TIME_STD_DEFAULT_OFFSET .

  3. There would no replacement for a booldata option for which 0 is a valid value. Again some additional CDL syntax would be needed to express such a concept.

Although initially it may seem confusing that an option's value has both a boolean and a data part, it is an accurate reflection of how configuration options actually work. The various alternatives would all make it harder to write CDL scripts.

The next common suggestion is that the data part of a value should be typed in much the same way as C or C++ data types. For example it should be possible to describe CYGNUM_LIBC_STDIO_BUFSIZE as an integer value, rather than imposing legal_values constraints. Again there are very good reasons why this approach was not taken:

  1. The possible legal values for an integer are rarely correct for a CDL option. A constraint such as 1 to 0x7fffffff is a bit more accurate, although if this option indicates a buffer size it is still not particularly good — very few targets will have enough memory for such a buffer. Forcing CDL writers to list the legal_values constraints explicitly should make them think a bit more about what values are actually sensible. For example CYGNUM_LIBC_TIME_DST_DEFAULT_OFFSET has legal values in the range -90000 to 90000 , which helps the user to set a sensible value.

  2. Not all options correspond to simple data types such as integers. CYGDAT_LIBC_STDIO_DEFAULT_CONSOLE is a C string, and would have to be expressed using something like char [] . This introduces plenty of opportunities for confusion, especially since square brackets may get processed by the Tcl interpreter for command substitution.

  3. Some configuration options can get very complicated indeed, for example the default value of CYGDAT_UITRON_TASK_INITIALIZERS is:

    CYG_UIT_TASK( "t1", 1, task1, &stack1, CYGNUM_UITRON_STACK_SIZE ), \
    CYG_UIT_TASK( "t2", 2, task2, &stack2, CYGNUM_UITRON_STACK_SIZE ), \ 
    CYG_UIT_TASK( "t3", 3, task3, &stack3, CYGNUM_UITRON_STACK_SIZE ), \
    CYG_UIT_TASK( "t4", 4, task4, &stack4, CYGNUM_UITRON_STACK_SIZE )

    This would require CDL knowing about C macros, structures, arrays, static initializers, and so on. Adding such detailed knowledge about the C language to the component framework is inappropriate.

  4. CDL needs to be usable with languages other than C. At present this includes C++, in future it may include languages such as Java. Each language adds new data types and related complications, for example C++ classes and inheritance. Making CDL support a union of all data types in all possible languages is not sensible.

The CDL approach of treating all data as a sequence of characters, possibly constrained by a legal_values property or other means, has the great advantage of simplicity. It also fits in with the Tcl language that underlies CDL .

Some Examples

The following excerpt from the C library's CDL scripts can be used to illustrate how values and flavors work in practice:

cdl_component CYGPKG_LIBC_RAND {
    flavor        none
    compile       stdlib/rand.cxx

        requires      CYGVAR_KERNEL_THREADS_DATA
        default_value 0

    cdl_option CYGNUM_LIBC_RAND_SEED {
        flavor        data
        legal_values  0 to 0x7fffffff
        default_value 1

        flavor        data
        legal_values  0 to 1
        default_value 0

If the application does not require any C library functionality then it is possible to have a configuration where the C library is not loaded. This can be achieved by starting with the minimal template, or by starting with another template such as the default one and then explicitly unloading the C library package. If this package is not loaded then any references to the CYGPKG_LIBC_RAND component or any of its options will have a value of 0 for the purposes of expression evaluation. No #define's will be generated for the component or any of its options, and the file stdlib/rand.cxx will not get compiled. There is nothing special about the C library here, exactly the same would apply for say a device driver that does not correspond to any of the devices on the target hardware.

Assuming the C library is loaded, the next thing to consider is whether or not the component and its options are active. The component is layered immediately below the C library package itself, so if the package is loaded then it is safe to assume that the package is also enabled. Therefore the parent of CYGPKG_LIBC_RAND is active and enabled, and in the absence of any active_if properties CYGPKG_LIBC_RAND will be active as well.

The component CYGPKG_LIBC_RAND has the flavor none . This means the component cannot be disabled. Therefore all the options in this component have an active and enabled parent, and in the absence of any active_if properties they are all active as well.

The component's flavor none serves to group together all of the configuration options related to random number generation. This is particularly useful in the context of the graphical configuration tool, but it also helps when it comes to naming the options: all of the options begin with CYGxxx_LIBC_RAND , giving a clear hint about both the package and the component within that package. The flavor means that the component is always enabled and has the value 1 for the purposes of expression evaluation. There will always be a single #define of the form:


In addition the file stdlib/rand.cxx will always get built. If the component had the default bool flavor then users would be able to disable the whole component, and one less file would need to be built. However random number generation is relatively simple, so the impact on eCos build times are small. Furthermore by default the code has no dependencies on other parts of the system, so compiling the code has no unexpected side effects. Even if it was possible to disable the component, the sensible default for most applications would still leave it enabled. The net result is that the flavor none is probably the most sensible one for this component. For other components the default bool flavor or one of the other flavors might be more appropriate.

Next consider option CYGSEM_LIBC_PER_THREAD_RAND which can be used to get a per-thread random number seed, possibly useful if the application needs a consistent sequence of random numbers. In the absence of a flavor property this option will be boolean, and the default_value property means that it is disabled by default — reasonable since few applications need this particular functionality, and it does impose a constraint on the rest of the system. If the option is left disabled then no #define will be generated, and if there were any compile or similar properties these would not take effect. If the option is enabled then a #define will be generated, using the option's data part which is fixed at 1 :


The CYGSEM_LIBC_PER_THREAD_RAND option has a requires constraint on CYGVAR_KERNEL_THREADS_DATA . If the C library option is enabled then the constraint should be satisfied, or else the configuration contains a conflict. If the configuration does not include the kernel package then CYGVAR_KERNEL_THREADS_DATA will evaluate to 0 and the constraint is not satisfied. Similarly if the option is inactive or disabled the constraint will not be satisfied.

CYGNUM_LIBC_RAND_SEED and CYGNUM_LIBC_RAND_TRACE_LEVEL both have the data flavor, so they are always enabled and the component framework will generate appropriate #define's :


Neither option has a compile or similar property, but any such properties would take effect. Any references to these options in CDL expressions would evaluate to the data part, so a hypothetical constraint of the form { requires CYGNUM_LIBC_RAND_SEED > 42 } would not be satisfied with the default values. Both options use a simple constant for the default_value expression. It would be possible to use a more complicated expression, for example the default for CYGNUM_LIBC_RAND_TRACE_LEVEL could be determined from some global debugging option or from a debugging option that applies to the C library as a whole. Both options also have a legal_values constraint, which must be satisfied since the options are active and enabled.

Note: The value 0 is legal for both CYGNUM_LIBC_RAND_SEED and CYGNUM_LIBC_RAND_TRACE_LEVEL , so in a CDL expression there is no easy way of distinguishing between the options being absent or having that particular value. This will be addressed by future enhancements to the expression syntax.

Ordinary Expressions

Expressions in CDL follow a conventional syntax, for example:

    default_value { (CYG_HAL_STARTUP == "RAM" &&
                     !CYGDBG_HAL_DEBUG_GDB_INCLUDE_STUBS &&
                     !CYGSEM_HAL_POWERPC_COPY_VECTORS) ? 1 : 0 }
    default_value { "\"/dev/ser0\"" }

However there is a complication in that the various arguments to a default_value property will first get processed by a Tcl interpreter, so special characters like quotes and square brackets may get processed. Such problems can be avoided by enclosing non-trivial expressions in braces, as in the second example above. The way expression parsing actually works is as follows:

  1. The Tcl interpreter splits the line or lines into a command and its arguments. In the first default_value expression above the command is default_value and there are three arguments, CYGGLO_CODESIZE , > and CYGGLO_SPEED . In the second and third examples there is just one argument, courtesy of the braces.

  2. Next option processing takes place, so any initial arguments that begin with a hyphen will be interpreted as options. This can cause problems if the expression involves a negative number, so the special argument -- can be used to prevent option processing on the subsequent arguments.

  3. All of the arguments are now concatenated, with a single space in between each one. Hence the following two expressions are equivalent, even though they will have been processed differently up to this point.

        default_value CYGGLO_CODESIZE > CYGGLO_SPEED
        default_value {CYGGLO_CODESIZE > CYGGLO_SPEED}
  4. The expression parsing code now has a single string to process.

CDL expressions consist of four types of element: references to configuration options, constant strings, integers, and floating point numbers. These are combined using a conventional set of operators: the unary operators - , ~ and ! ; the arithmetic operators + , - , * , / and % ; the shift operators << and >> ; the comparison operators == , != , < , <= , > and >= ; the bitwise operators & , ^ and | ; the logical operators && and || ; and the ternary conditional operator A ? B :C . Bracketed sub-expressions are supported, and the operators have the usual precedence:

Priority Operators Category
13 references and constants basic elements
12 ~ bitwise not
12 ! logical not
12 - arithmetic negation
11 * / % multiplicative arithmetic
10 + - additive arithmetic
9 << >> bitwise shifts
8 <= < > >= inequality
7 == != comparison
6 & bitwise and
5 ^ bitwise xor
4 | bitwise or
3 && logical and
2 || logical or
1 ? : conditional

A valid CDL identifier in an expression, for example CYGGLO_SPEED , will be interpreted as a reference to a configuration option by that name. The option does not have to be loaded into the current configuration. When the component framework evaluates the expression it will substitute in a suitable value that depends on whether or not the option is loaded, active, and enabled. The exact rules are described in the section called Option Values .

A constant string is any sequence of characters enclosed in quotes. Care has to be taken that these quotes are not stripped off by the Tcl interpreter before the CDL expression parser sees them. Consider the following:

    default_value "RAM"

The quote marks will be stripped before the CDL expression parser sees the data, so the expression will be interpreted as a reference to a configuration option RAM . There is unlikely to be such an option, so the actual default value will be 0 . Careful use of braces or other Tcl quoting mechanisms can be used to avoid such problems.

String constants consist of the data inside the quotes. If the data itself needs to contain quote characters then appropriate quoting is again necessary, for example:

    default_value { "\"/dev/ser0\"" }

An integer constant consists of a sequence of digits, optionally preceeded with the unary + or - operators. As usual the sequence 0x or 0X can be used for hexadecimal data, and a leading 0 indicates octal data. Internally the component framework uses 64-bit arithmetic for integer data. If a constant is too large then double precision arithmetic will be used instead. Traditional syntax is also used for double precision numbers, for example 3.141592 or -3E6 .

Of course this is not completely accurate: CDL is not a typed language, all data is treated as if it were a string. For example the following two lines are equivalent:

    requires CYGNUM_UITRON_SEMAS > 10
    requires { CYGNUM_UITRON_SEMAS > "10" }

When an expression gets evaluated the operators will attempt appropriate conversions. The > comparison operator can be used on either integer or double precision numbers, so it will begin by attempting a string to integer conversion of both operands. If that fails it will attempt string to double conversions. If that fails as well then the component framework will report a conflict, an evaluation exception. If the conversions from string to integer are successful then the result will be either the string 0 or the string 1 , both of which can be converted to integers or doubles as required.

It is worth noting that the expression CYGNUM_UITRON_SEMAS >10 is not ambiguous. CDL identifiers can never begin with a digit, so it is not possible for 10 to be misinterpreted as a reference to an identifier instead of as a string.

Of course the implementation is slightly different again. The CDL language definition is such that all data is treated as if it were a string, with conversions to integer, double or boolean as and when required. The implementation is allowed to avoid conversions until they are necessary. For example, given CYGNUM_UITRON_SEMAS > 10 the expression parsing code will perform an immediate conversion from string to integer, storing the integer representation, and there is no need for a conversion by the comparison operator when the expression gets evaluated. Given { CYGNUM_UITRON_SEMAS > "10" } the parsing code will store the string representation and a conversion happens the first time the expression is evaluated. All of this is an implementation detail, and does not affect the semantics of the language.

Different operators have different requirements, for example the bitwise or operator only makes sense if both operands have an integer representation. For operators which can work with either integer or double precision numbers, integer arithmetic will be preferred.

The following operators only accept integer operands: unary ~ (bitwise not), the shift operators << and >> , and the bitwise operators & , | and ^ .

The following operators will attempt integer arithmetic first, then double precision arithmetic: unary - , the arithmetic operators + , - , * , / , and % ; and the comparision operators < , <= , > and >= .

The equality == and inequality != operators will first attempt integer conversion and comparison. If that fails then double precision will be attempted (although arguably using these operators on double precision data is not sensible). As a last resort string comparison will be used.

The operators ! , && and || all work with boolean data. Any string that can be converted to the integer 0 or the double 0.0 is treated as false, as is the empty string or the constant string false . Anything else is interpreted as true. The result is either 0 or 1 .

The conditional operator ? : will interpret its first operand as a boolean. It does not perform any processing on the second or third operands.

In practice it is rarely necessary to worry about any of these details. In nearly every case CDL expressions just work as expected, and there is no need to understand the full details.

Note: The current expression syntax does not meet all the needs of component writers. Some future enhancements will definitely be made, others are more controversial. The list includes the following:

  1. An option's value is determined by several different factors: whether or not it is loaded, whether or not it is active, whether or not it is enabled, and the data part. Currently there is no way of querying these individually. This is very significant in the context of options with the bool or booldata flavors, because there is no way of distinguishing between the option being absent/inactive/disabled or it being enabled with a data field of 0 . There should be unary operators that allow any of the factors to be checked.

  2. Only the == and != operators can be used for string data. More string-related operators are needed, including sub-string operations and concatenation.

  3. An implies operator would be useful for many goal expression, where A implies B is equivalent to !A ||B .

  4. Similarly there is inadequate support for lists. On occasion it would be useful to write expressions involving say the list of implementors of a given interface, for example a sensible default value could be the first implementor. Associated with this is a need for an indirection operator.

  5. There is no support for function calls, for example sin(x) . This would be useful mainly in the context of double precision data, and relatively few options involve such data. There are complications because the component framework needs to run on different host platforms which may have subtly different floating point behavior, introducing a possibility of non-deterministic behavior. Function calls might also be useful for some of the desired string and list support.

  6. Arguably extending the basic CDL expression syntax with lots of new operators is unnecessary, instead expressions should just support Tcl command substitution and then component writers could escape into Tcl scripts for complicated operations. This has some major disadvantages. First, the inference engine would no longer have any sensible way of interpreting an expression to resolve a conflict. Second, the component framework's value propagation code keeps track of which options get referenced in which expressions and avoids unnecessary re-evaluation of expressions; if expressions can involve arbitrary Tcl code then there is no simple way to eliminate unnecessary recalculations, with a potentially major impact on performance.

Note: The current implementation of the component framework uses 64 bit arithmetic on all host platforms. Although this is adequate for current target architectures, it may cause problems in future. At some stage it is likely that an arbitrary precision integer arithmetic package will be used instead.

Goal Expressions

The arguments to certain properties, notably requires and active_if , constitute a goal expression. As with an ordinary expression, all of the arguments get combined and then the expression parser takes over. The same care has to be taken with constant strings and anything else that may get processed by the Tcl interpreter, so often a goal expression is enclosed entirely in braces and the expression parsing code sees just a single argument.

A goal expression is basically just a sequence of ordinary expressions, for example:


This consists of three separate expressions, all of which should evaluate to a non-zero result. The same expression could be written as:


Alternatively the following would have much the same effect:


Selecting between these alternatives is largely a stylistic choice. The first is slightly more concise than the others. The second is more likely to appeal to mathematical purists. The third is more amenable to cutting and pasting.

The result of evaluating a goal expression is a boolean. If any part of the goal expression evaluates to the integer 0 or an equivalent string then the result is false, otherwise it is true.

The term “goal expression” relates to the component framework's inference engine: it is a description of a goal that should be satisfied for a conflict-free configuration. If a requires constraint is not satisfied then the inference engine will examine the goal expression: if there is some way of changing the configuration that does not introduce new conflicts and that will cause the goal expression to evaluate to true, the conflict can be resolved.

The inference engine works with one conflict and hence one goal expression at a time. This means that there can be slightly different behavior if a constraint is specified using a single requires property or several different ones. Given the above example, suppose that none of the three conditions are satisfied. If a single goal expression is used then the inference engine might be able to satisfy only two of the three parts, but since the conflict as a whole cannot be resolved no part of the solution will be applied. Instead the user will have to resolve the entire conflict. If three separate goal expressions are used then the inference engine might well find solutions to two of them, leaving less work for the user. On the other hand, if a single goal expression is used then the inference engine has a bit more information to work with, and it might well find a solution to the entire conflict where it would be unable to find separate solutions for the three parts. Things can get very complicated, and in general component writers should not worry about the subtleties of the inference engine and how to manipulate its behavior.

It is possible to write ambiguous goal expressions, for example:


This could be parsed in two ways:


The goal expression parsing code will always use the largest ordinary expression for each goal, so the first interpretation will be used. In such cases it is a good idea to use brackets and avoid possible confusion.

List Expressions

The arguments to the legal_values property constitute a goal expression. As with an ordinary and goal expressions, all of the arguments get combined and then the expression parser takes over. The same care has to be taken with constant strings and anything else that may get processed by the Tcl interpreter, so often a list expression is enclosed entirely in braces and the expression parsing code sees just a single argument.

Most list expressions take one of two forms:

    legal_values <expr1> <expr2> <expr3> ...
    legal_values <expr1> to <expr2>

expr1 , expr2 and so on are ordinary expressions. Often these will be constants or references to calculated options in the architectural HAL package, but it is possible to use arbitrary expressions when necessary. The first syntax indicates a list of possible values, which need not be numerical. The second syntax indicates a numerical range: both sides of the to must evaluate to a numerical value; if either side involves a floating point number then any floating point number in that range is legal; otherwise only integer values are legal; ranges are inclusive, so 4 is a valid value given a list expression 1 to  ; if one or both sides of the to does not evaluate to a numerical value then this will result in a run-time conflict. The following examples illustrate these possibilities:

    legal_values { "red" "green" "blue" }
    legal_values 1 2 4 8 16
    legal_values 1 to CYGARC_MAXINT
    legal_values 1.0 to 2.0

It is possible to combine the two syntaxes, for example:

    legal_values 1 2 4 to CYGARC_MAXINT -1024 -20.0 to -10

This indicates three legal values 1 , 2 and -1024 , one integer range 4 to CYGARC_MAXINT , and one floating point range -20.0 to -10.0 . In practice such list expressions are rarely useful.

The identifier to is not reserved, so it is possible to have a configuration option with that name (although it violates every naming convention). Using that option in a list expression may however give unexpected results.

The graphical configuration tool uses the legal_values list expression to determine how best to let users manipulate the option's value. Different widgets will be appropriate for different lists, so { "red" "green" "blue" } might involve a pull-down option menu, and 1 to 16 could involve a spinner. The exact way in which legal_values lists get mapped on to GUI widgets is not defined and is subject to change at any time.

As with goal expressions, list expressions can be ambiguous. Consider the following hypothetical example:


This could be parsed in two ways:


Both are legal. The list expression parsing code will always use the largest ordinary expression for each element, so the first interpretation will be used. In cases like this it is a good idea to use brackets and avoid possible confusion.