Copyright © 2005-2010 authors and contributors. All rights reserved.
The NRL constraint language is a language for expressing rules that constrain data models, or in other words, provide static semantics for models. Originally conceived as an English language alternative for first-order logic and a possible alternative for OCL, it can be used to write constraint rules over UML documents, XML Schema or other languages that map to its simplified meta-model.
The goals of the NRL are: to retain readability for non-developers; establish a grammar-based structure that is unambiguous and requires no natural language processing; and to efficiently map to execution platforms such as Java, the C dialects or rule engines.
This document is the authoritative specification of the constraint rule part of the Natural Rule Language.
1 Introduction
2 Model
2.1 Model References
2.1.1 Disambiguation of Model References
2.2 Basic Type System
3 Constraint Language
3.1 Rules
3.2 Validation Fragments
3.3 Rule Sets
3.4 Constraints
3.5 Quantifiers
3.6 Variable Declaration
3.7 Predicates
3.8 Expressions
3.9 Terms
3.10 Identifiers
3.11 Reporting
A Concrete Syntax
B Abstract Syntax
B.1 Definitions
B.2 Syntax
C References
C.1 Normative References
D Acknowledgments (Non-Normative)
E Change History (Non-Normative)
This document describes the concrete and abstract syntax of the Natural Rule Language, as well as the simple meta-model that the NRL uses for model representation. While it is illustrated with examples, this document is not intended as a user-guide but is targeted at implementors, researchers and other interested parties who need to understand the structure and meaning of the language.
The Natural Rule Language was first conceived of as a means of specifying validation constraints over [XML Schema] that had been turned into [UML] models. Since then, it has been more generally applied to the specification of complex, near-natural language constraints over UML and XML.
Business data models and technical data models can get very complex, with thousands of classes in a UML model representing the entities in the real world that need to be included in a software system. The static view of such models is typically fairly underconstrained, lacking an expression of the semantics that are key to their understanding. To use a popular example, if we modeled a transaction class as part of a UML diagram, we may give it a start date and end date attribute. We need another mechanism, however, to add additional static semantics that complete the model - in this case a statement that the start date should be before the end date.
Note:
A note on the relationship of the NRL to OCL: NRL has been used as a replacement for OCL, whose syntax makes it prohibitive for non-technical users. However, the NRL intentionally covers only a small subset of OCL, partly to avoid the same fate of reducing readability and making the language too complex, and partly because we had no need for the features we did not cover.
NRL rules are always written against a model. The NRL is very much geared towards use with UML, however its generality makes it useful for other tasks such as constraining XML schemas. It therefore uses its own, very simple meta-model, which is a subset of the UML meta-model. The model is shown in graphical form in the following diagram:
NRL rules are written against classifiers. Classifiers are one type of model element, the only other type being data types, which represent primitive types. Both classifiers have attributes, which have a name and type and can optionally be static. What distinguishes data types from classifiers is that they also have an intrinsic value. All model elements can have a parent element that they inherit attributes from.
Rules need to reference attributes and elements in a model. For every rule and property, a property reference is required to define the context, and constraints need to reference attributes or model elements to restrict values. This section discusses the syntax for model references used by the NRL.
In general, four types of model references occur in the NRL:
Model element references - these refer to model elements themselves, either by name if the model element's name is unique with the model, or by qualified name if the package structure needs to be included.
Attribute references - also known as relative references, these are always evaluated relative to some context model element and identify an attribute of the element or, through navigation, an attribute of a related element
Static attribute reference - also known as absolute references, these identify a static attribute contained in a model element.
Variable reference - this also refers to an attribute in the model, but references the attribute starting from a variable rather than a model element
ModelPath | ::= |
(PackageReference)? elementOrAttribute::ModelElementName (ModelStep)*
|
Model element references
Model element references are references that contain an optional package reference, followed by the name of a model element. Here are some examples of model element reference:
Trade
- references an element called "Trade" that is contained somewhere in the model.
If the model contains two elements with the name Trade then the use of this reference is an error.
main::transactiondata::Trade
- references an element called "Trade" contain in the
"transactiondata" package, which is in turn contained in the "main" package, which is a root level package
in the model.
Attribute references
Attribute references occur more or less in every rule, and are the main mechanism for extracting values from the model for comparison. An attribute reference is always relative to a context. This may be the rule context, or a nested context if quantifiers or variables are used.
id
- references an attribute called "id" in the context model element. If the context
does not contain such an attribute, the reference is in error.
header.id
or id of the header
- references an attribute called "id", which is contained in a model element
referenced by the attribute "header". This reference is only valid if the current context has an attribute
"header", and the type of "header" has an attribute "id", otherwise the reference is in error.
Static attribute references
Static attribute references are used to directly access static attributes of a model element, without reference to the evaluation context. This most frequent use of this is for accessing enumerations:
TransactionType.PERSISTENT
- references the static attribute "PERSISTENT" in the model
element "TransactionType". "TransactionType" is a model element contained somewhere in the
model. If there is more than one model element with this name, the reference is in error and a package
name must be used to disambiguate it.
main::transactiondata::TransactionType.PERSISTENT
- references the same static attribute
but specifies the full package name, starting from the root, of the "TransactionType" element.
It should be clear from the definition of ModelPath that it is not
possible to tell just from the syntax what exactly is being referenced. In the model reference
name.value
, name
could refer to:
A model element, in which case value
must be a static attribute of that element
An attribute
A variable name
The NRL quite deliberately does not put any syntactic sugar around variable names, or enforce any naming conventions regarding model elements versus attributes - this counteracts the goal of making NRL rules as easy to read as possible. Instead, NRL parsers must apply a simple disambiguation algorithm, attempting to cast model references in the following order of priority:
Are there package references? In this case, the reference definitely refers to a model element. Treat it as a static attribute reference.
Is the first step bound as a variable name? If so, treat it as a variable reference.
Is the first step available as an attribute in the current context? If so, treat it as an attribute reference whose context is the current context.
Is the first step available as an attribute in the rule context? If so, treat it as an attribute reference whose context is the rule context.
The NRL has a simple type system that is used to specify static semantic constraints over the language, and to map heterogeneous types of input models to a consistent, basic system. Basic data types in user models, as defined in the previous section, have to be mapped to this type system.
The following types comprise the NRL type set:
String - any form of string representation.
IntegerNumber - the set of integer numbers
DecimalNumber - the set of decimal numbers
Boolean - the type whose domain contains only the values true
and false
.
Date - a date, time or date-time in any format. This is a primitive type in the NRL due to its frequent use in validation rules.
Element - a complex element. This is the type representing all classifiers in a model.
Collection - assignable to any attribute that can occur more than once.
In addition, in the below, Number refers to the set that consists of all members of IntegerNumber and DecimalNumber.
The constraint language is the core of the NRL. A constraint, called a validation rule in NRL, is a statement expressed over a model that can be evaluated to a Boolean result. In addition to validation rules, NRL supports validation fragments, which are reusable parts that are not themselves evaluated at the top level.
ValidationRule | ::= | Context "Validation Rule" id:DoubleQuotedString ValidationRuleVariableDeclaration* Constraint ("report:" CompoundReport)? | |
Context | ::= | "Context:" ModelReference |
A validation rule consists of a unique identifier, a context element and a constraint that must evaluate to a Boolean. Optionally, the rule may specify a report to be displayed if the rule evaluates to false. If the rule is contained in a file with multiple NRL rules, it is an error if any other rule uses the same identifier.
The context element defines the initial context of the rule. The model reference used in the context must be a model element reference, as defined in 2.1 Model References. Furthermore, rules may only be written against context elements that are classifiers - it is an error to make a data type a rule context.
The constraint can be any construct contained in the NRL grammar. Since the grammar is recursive, this means it can be any of the constructs from the 3.4 Constraints section forward, though the result of evaluating the construct must be a Boolean. Rules whose result is non-Boolean are in error and will be rejected by the NRL processor.
Context: Trade Validation Rule "date-1" startDate is before endDate
ValidationFragment | ::= | MultipleParameterContext "Validation Fragment" id:DoubleQuotedString
Constraint |
A validation fragment, like a rule consists of an identifier, a context and a constraint. Such a fragment would, however, never be evaluated by itself, only as part of a rule or within another fragment - it is a type of macro. Unlike rules, fragments do not have a unique context that will be assigned during evaluation. Instead, they are supplied with many parameters. They can then combine the parameters to produce a result.
One other thing that sets fragments apart from rules is that their result does not have to be boolean. They can also be used to return any other results, of a primitive type drawn from the set specified in 2.2 Basic Type System. For example, a fragment could perform some calculation that is repeated in many rules, to avoid repetition.
In order to use a fragment, a rule only needs to refer to its name, and supply any parameters following it. NRL parsers are expected to pre-process fragment names and detect them before further parsing steps, so any name can be chosen including names with spaces. Care should be taken to avoid fragment names that exactly match NRL keywords, however, or unintended parsing errors may occur.
Context: Trade ("trade") Validation Fragment "internal target is specified in" The trade.customer is equal to 'MyOrganisation' Context: Trade Rule "rule-1" If an internal target is specified in the Trade then the value is less than 1000000
Fragments can also be referred to in infix notation, with exactly two parameters surrounding them:
Context: Trade ("trade"), Trade ("trade2") Validation Fragment "is worth more than" trade.value.amount is greater than trade2.value.amount Context: Strategy Rule "rule-1" If the customerTrade is worth more than the tradeTrade then traderTrader.profitable = false
Note:
Note that according to the grammar, a fragment application is a string surrounded by curly brackets. In the previous example, this would mean we would have to refer to the property as {is internal}. NRL Parsers eliminate the need for this by pre-processing the rule file, scanning for any strings that look like property references and inserting the relevant curly brackets.RuleSet | ::= | "Rule set" id:DoubleQuotedString ("applies to" ModelReference "where" Constraint)? |
A rule set is a grouping of rules. In an NRL file that has been arranged top to bottom, anything following a rule set declaration and coming before the next rule set declaration is considered to be in the rule set. This information can be useful to NRL processors or mappings:
A processor can output different rule sets into different output files
A processor can make decisions about applying whole sets instead of individual rules
A rule set comes with an identifier and an optional pre-condition, introduced by the applies to and where keywords. If no pre-condition is specified, the rule set always applies. If a pre-condition is specified, the model reference must refer to a model element. The following is an example of a rule set that ensures the following rules are only applied if a condition holds.
Rule set "audit" applies to a Trade where the value is greater than 100000 Context: Trade Validation Rule "rule-1" The counterParty is not equal to 'UNDESIRABLE' Context: Trade Validation Rule "rule-2" The currency is equal to 'GBP'
Constraint | ::= | IfThenStatement |
Constraints are top-level constructs that can occur in rules, starting with if-then and including the Boolean operators.
IfThenStatement
IfThenStatement | ::= |
"if" IffStatement "then" IfThenStatement "else" IfThenStatement
| |
| IffStatement
|
This is a standard if-then statement that carries an optional else part. If the "if" condition is true, then the "then" condition must also be true, else the "else" condition must be true, if present.
IffStatement
IffStatement | ::= |
ImpliesStatement ("only if" IffStatement)?
|
This is the "if and only if", or "iff" statement that is well known in predicate logic. "a only if b" is true if a is true and b is true, or a is false and b is false. In the other cases, the statement is false.
ImpliesStatement
ImpliesStatement | ::= |
OrStatement ("implies" ImpliesStatement)?
|
"a implies b" means that if a is true, b must also be true. No statement is made about the case where a is false, therefore, if a is false the entire statement is automatically true. This is the most common definition of logical implication in predicate logic.
OrStatement
OrStatement | ::= |
AndStatement ("or" OrStatement)?
|
A Boolean disjunction. "a or b" is true if either a or b is true, or both are true.
AndStatement
AndStatement | ::= |
LogicalStatement ("and" AndStatement)?
|
Boolean conjunction. "a and b" is only true if both a and b are true.
LogicalStatement
LogicalStatement | ::= |
ExistsStatement
| |
| NotExistsStatement
| |||
| GlobalExistsStatement
| |||
| ForallStatement
| |||
| VariableDeclaration
| |||
| Predicate
|
The logical statement is a further breakdown of constraints into quantifiers, variable declarations, predicates and constraints that have been surrounded with parentheses for precedence control. The following examples illustrate some of the types of constraints covered in this section, assuming that "Trade" is the current context.
If internal information is specified in the Trade then its value is less than 1000000 else its value is less than 10000 value > 1000 and value < 10000 value > 10000 implies internal information is specified in the Trade value < 1000000 and the internal information is specified in the Trade or value < 1000 and external information is specified in the Trade
Quantifiers, as defined in first order logic, are used to check whether one or more items exist in a collection (existential quantifier) or whether all items in a collection satisfy a certain property (universal quantifier). The NRL defines ways for expressing both kinds of quantifier.
ExistsStatement
ExistsStatement | ::= |
(Enumerator ("of the")?)? ModelReference ("has" | "have" | "is" | "are") ("present" | SimpleOrComplexConstraint)
| |
| Enumerator ("has" | "have" | "is" | "are") SimpleOrComplexConstraint
| |||
Enumerator | ::= |
("at least" | "at most" | "exactly")? ("one" | "two" | "three" | "four" | "no" | "none" | IntegerNumber)
|
Clearly the syntax allows for quite a bit of variety over what kind of statement can be expressed here. We will concentrate first on the meaning of the first alternative in the ExistsStatement production:
The optional enumerator specifies how many elements have to exist that obey the SimpleOrComplexConstraint, if present, or simply exist. If no enumerator is specified, it defaults to at least one.
The model reference may either identify an attribute that occurs more than once, in which case it is treated as a collection, or an attribute that occurs exactly once, in which case it is treated as a value.
There is a choice between asserting that the specified number of entries in a collection is present, or that there a number of attributes in a collection for which a relative constraint holds.
The following static semantic constraints must hold for the first production alternative:
If the model reference references an attribute with a value, rather than a collection, then: no constraint is allowed and the sentence must finish with "is present" or "are present"; no enumerator other than "one" is allowed. The semantics of the sentence is that the attribute value must be non-null and non-empty.
If the model reference references a collection attribute (an attribute that can occur more than once) and the sentence finishes with "is present" or "are present" rather than a constraint then: if an enumerator is present, the size of the collection is checked against the enumerator; if no enumerator is present, then the collection must be non-empty.
If the model reference references a collection attribute and there is a SimpleOrComplexConstraint, then any attribute references in the constraint are evaluated relative to the model reference. In other words, the model reference becomes the current context for the constraint. All collection entries for which the constraint holds are counted, and the result is checked against the enumerator.
-- Check that an attribute has a value a value is present -- Check that a collection has at least one / at most three elements -- See later comments on 'elements' syntactic sugar a transfer is present at most three transfer elements are present -- Check that a collection has at least one element that meets a -- condition at least one transfer has direction = Direction.OUTBOUND
The second production of the ExistsStatement covers cases where sentences are being continued. For example, we may want to say "one transfer has direction = Direction.OUTBOUND and one transfer has direction = Direction.INBOUND". In this case, it is slightly awkward to have to repeat the reference to "transfer". Essentially, the second production enables us to eliminate this reference and write "one transfer has direction = Direction.OUTBOUND and one has direction = Direction.INBOUND".
Clearly, some rules are necessary to infer what exactly such an abbreviated sentence refers to. According to only the grammar, it is possible to write a sentence like "one has direction = Direction.OUTBOUND", which leaves it unclear what exactly is being referred to. The following rules apply:
ExistStatements that use the abbreviated production are only legal if they follow an ExistsStatement that refers to the full production, order being defined by an in-order traversal of the parse tree. The model reference of the incomplete ExistsStatement is inherited from the complete one immediately before it in in-order traversal of the tree.
It is an error for there to be no complete ExistsStatement before an incomplete one, in an in-order traversal of the tree.
-- Legal: follows a complete ExistsStatement one transfer has direction = Direction.OUTBOUND and one has direction = Direction.INBOUND -- Illegal: no complete ExistsStatement before it - first part ambiguous one has direction = Direction.INBOUND and one transfer has direction = Direction.OUTBOUND
NotExistsStatement
NotExistsStatement | ::= |
ModelReference ("is not present" | "are not present")
|
This statement is really a short-form for stating that an attribute is empty or null (in the case of attributes that occur at most once) or that a collection is null or empty (in the case of attributes that can occur more than once).
The statement "element is not present" is semantically equivalent to "no element is present".
ForallStatement
ForallStatement | ::= |
("each" | "in each" | "all" | "every") ("of the")? ModelReference ("has" | "have" | "is" | "are")? SimpleOrComplexConstraint
| |
| "for each" var:DoubleQuotedString "in the collection of" ModelReference ("has" | "have" | "is" | "are" | ",")? SimpleOrComplexConstraint
|
This statement represents a universal quantifier. The quantifier expresses that a certain condition must hold for every element in a collection. The two syntactic alternatives given above represent quantifier without a variable (the first case) or with a variable, respectively.
The following semantic constraints must be satisfied:
The model reference specified in either case must refer to a collection attribute. It is an error to refer to a scalar attribute
If a variable is not used, the current context of the quantifier's child constraint is the model element pointed to by the collection attribute. Attribute values will be bound to each instance being iterated over in turn
If a variable is used, the current context remains unchanged. To be clear, this means that the current context does not change to the collection attribute as in the above case.
-- No variable used every transfer has direction = Direction.OUTBOUND -- Alternative phrasing, complex condition in each transfer (direction = Direction.OUTBOUND or direction = Direction.INBOUND) -- Variable used. Must be used to refer to the attribute now for each "xfer" in the collection of transfer elements, xfer.direction = Direction.OUTBOUND -- Illegal - "direction not found in current context" for each "xfer" in the collection of transfer elements, direction = Direction.OUTBOUND
GlobalExistsStatement
GlobalExistsStatement | ::= |
("there is" | "there are") ("no")? ModelReference ("(" var:DoubleQuotedString ")")? ("where" SimpleOrComplexConstraint)?
|
This is an advanced construct that checks whether a model element exists globally, or is absent globally. For example, we may want to check whether a "Trade" object exists. The language specification specifically does not define the context in which to look for the object - this is an implementation issue. For example:
A Java implementation may look for the object anywhere in the graph of objects under the current rule context, or a rule set context
A rule engine implementation, e.g. JESS or iLog, may search the "working memory" of the engine
As the grammar shows, the constraint has several optional forms. In the simplest form, it is used to check for the presence or absence of an object:
There is a Trade There is no Trade
If the constraint is used to check for existence, a variable may be assigned to a matching object:
There is a Trade (the "trade") and trade.date = '2007-01-01'
Finally, a where clause may be used to further refined what object is being looked for. This can be used with or without a variable. Note that the variable stays in scope after the global existence statement:
If there is a Trade (the "trade") where trade.date > '2007-01-01' then trade.regulation = '2007' There is no Trade where trade.date > '2007-01-01'
The GlobalExistsStatement is subject to the following semantic constraints:
The ModelReference must refer to a classifier. References to attributes are not allowed.
A variable may be used only if the "no" keyword is not present, otherwise illegal references to the variable - which cannot be bound - would be possible.
If a variable is introduced and a where clause is present, the current context of the where clause remains unchanged, i.e. is equal to the current context before the GlobalExistsStatement.
If no variable is introduced and a where clause is present, the current context of the where clause is the ModelReference.
Variables can introduced and assigned expressions to avoid repetition in rules.
VariableDeclaration
SimpleVariableDeclaration | ::= | var:DoubleQuotedString ("is" | "are" | "represent" | "represents") Expression |
Using this syntax, multiple variables can be introduced at once, in a statement separated by and. The scope of any introduced variable is the Constraint provided, and terminated using a semi-colon. Here is an example:
"percentageValue" represents value * 100, percentageValue >=0 or percentageValue <=100 "startDate" represents the effectiveDate and "endDate" represents the terminationDate, the startDate is before the endDate;
The following rules apply to variables:
The variable name consists of letters, digits and hyphens or underscores, but must start with a letter.
It is illegal to introduce a variable name that matches the name of an attribute in scope at the point where the variable is introduced. An NRL parser must reject such a declaration.
Any type of expression can be assigned to a variable. It is also legal to navigate from variable onwards if a complex attribute has been assigned to it,
for example variable.attribute.attribute
.
Predicates in NRL include the usual comparison operators (less than, greater than, etc.) as well as further existence checks and list inclusion checks.
Is one of / is not one of
Predicate | ::= |
Expression "is one of" ListDefinition
| |
| Expression "is not one of" ListDefinition
| |||
| ModelReference "is a kind of" ModelElementName
| |||
| MultipleExistsStatement
| |||
| MultipleNotExistsStatement
| |||
| Expression (BinaryPredicate Expression)?
| |||
ListDefinition | ::= |
Identifier ("," Identifier)*
|
The semantics of is one of is defined as follows: the expression given as the first parameter is evaluated, and then compared against the value of each of the items in the ListDefinition. The result of the predicate is true if there is at least one item in the list for which a comparison according to the semantics of equality comparison (defined later in this section) is true, otherwise the result is false.
is not one of is the logical opposite of is one of. It returns true whenever is one of returns false, and vice versa.
In each of the transfers, the direction is one of Direction.INBOUND, Direction.OUTBOUND
is a kind of
In models where extensive sub-typing is used, it is sometimes necessary to check if an attribute is of a certain sub-type, rather than the type assigned to it. For example, if we have the types Trade and EquityTrade, and furthermore, a model attribute if trd, we might check its type as follows:
If the trd is a kind of EquityTrade then ...
See 3.8 Expressions for a discussion on how to force conversion of an attribute to a sub-type, so that all its attributes can be accessed.
MultipleExistsStatement
MultipleExistsStatement | ::= | "following" ("is present" | "are present") ":" ModelReferenceList | |
ModelReferenceList | ::= | (ModelReference)+ |
This predicate is used to assert that several model elements are present. The predicate returns true if and only if every element referenced in the list is present.
MultipleNotExistsStatement
MultipleNotExistsStatement | ::= | "following" ("is not present" | "are not present") ":" ModelReferenceList |
This predicate returns true if and only if no element in a list is present in the model instance.
The following are not present: taxes, fees
BinaryPredicate
All binary predicates can be written in symbolic or textual form, and the optional "is" can be used to tailor the predicate as necessary in the context of the sentence it appears in. The precise definition of how comparisons are performed is dependant on the implementation language used - for example, if NRL is translated to Java, number comparisons may be subtly different from comparisons performed in another programming language due to precision issues. Nevertheless, there are some overriding guiding principles that should be adhered to.
In the NRL type system, the following order is used:
String: the order is alpha-numeric, following the order definition of the Java programming language.
Number: numerical order is used, with precision issues depending on the implementation language.
Boolean: follows the order [true,false]
Date: both dates are first adjusted to UTC equivalents using any available time zone information; comparison then proceeds from year to micro-seconds, or whatever the highest level of granularity. If any of the components of the dates are different, the order is the numerical order of that component; otherwise, the dates are equal.
Importantly, NRL implementations are not expected to perform comparisons correctly where either argument may have a
value of null in an implementation language. Null is a technical concept and does not exist in the
NRL. Therefore, the result of null = 'abc'
is undefined and implementations may well terminate the evaluation
with an exception. A rule writer who wishes to handle these cases needs to be explicit, for example: If the attribute
is present then the attribute = 'abc'
. This way of dealing with null issues forces the rule writer to resolve the issue
and be ambiguous, rather than building hidden semantics into the language.
MultiplyExpression
MultiplyExpression | ::= |
AdditiveExpression ( ("*" | "/" | "mod") MultiplyExpression)?
|
Evaluation of this expression proceeds by evaluating its two sub-expressions first, and then calculating a result according to the type of the sub-expressions:
Operator: *
Expression A | Expression B | Result |
---|---|---|
String, Boolean, Date, Collection, Element | Any | Illegal |
Number | Number | A number, the result of multiplying the first with the second sub-expression result |
Number | Any (other than number) | Illegal |
Operator: /
Expression A | Expression B | Result |
---|---|---|
String, Boolean, Date, Collection, Element | Any | Illegal |
Number | Number | A number, the result of dividing the first by the second sub-expression result. Will fail if the second sub-expression evaluates to zero. |
Number | Any (other than number) | Illegal |
Operator: mod
Expression A | Expression B | Result |
---|---|---|
String, Boolean, Date, Collection, Element | Any | Illegal |
Number | Number | A number, the result of taking the first sub-expression's value modulo the second sub-expressions. Fails if: the second sub-expression does not have an integer value, or its value is 0. |
Number | Any (other than number) | Illegal |
AdditiveExpression
AdditiveExpression | ::= |
InfixOperatorExpression ( ("+" | "-") AdditiveExpression)?
|
Basic addition and subtraction. Somewhat more permissive than the multiplication expressions, these expressions can sometimes be used on strings and dates.
Operator: +
Expression A | Expression B | Result |
---|---|---|
Boolean, Collection, Element | Any | Illegal |
String | String | A string, concatenation of the value of the second to the first sub-expression. |
String | Number, Date, Boolean | A string, concatenation of a string representation of the second value to the first value. |
Number | Number | A number, the result of adding the two values. |
Number | String | A string, the result of concatenating the string value of the number to the second value. |
Number | Any (other than Number, String) | Illegal |
Date | Number | A date; the second parameter is treated as a number of days and added to the date given as the first parameter |
Date | Any (other than Number) | Illegal |
Operator: -
Expression A | Expression B | Result |
---|---|---|
Boolean, Collection, Element, String | Any | Illegal |
Number | Number | A number, the result of subtracting the second value from the first. |
Number | Any (other than Number) | Illegal |
Date | Number | A date; the second parameter is treated as a number of days and subtracted from the date given as the first parameter |
Date | Any (other than Number) | Illegal |
CastExpression
CastExpression | ::= | ModelReference ("as a" | "as an") ModelElementName |
A cast expression is used to force conversion of an attribute to a sub-type of the type assigned to it. This can be quite common in models that use extensive inheritance. For example, we may have a type Transaction that carries an attribute trd of type Trade. We may also know that in this particular transaction, the attribute will be of type EquityTrade, and we may want to access attributes of that type. The cast expression can be used in conjuction with a variable to make this accessible:
"equity" represents the trd as an EquityTrade, equity.tradeDate is greater than [today]
Semantic constraints:
The type being cast to must be a sub-type of the attribute being cast.
To be completed. Grammar is complete - refer to it in the meantime.
To be completed. Grammar is complete - refer to it in the meantime.
SelectionExpression
SelectionExpression | ::= | ("first" "of"?)? ModelReference "where" SimpleOrComplexConstraint |
Added in version 1.4 of the language. This expresses the selection of a subset of elements from a collection, or of a single element from a collection using certain criteria. It has the same role as the SELECT statement in SQL, albeit with somewhat less expressive power - it only works on one collection, and only has a where clause.
"specificLegs" are the legs where a rate is present, the number of specificLegs is greater than 2 "fixedLeg" is the first of the legs where a rate is present, fixedLeg.rate is greater than 0
In the example above, note that the first NRL rule uses a selection to identify a list of elements that meet the criteria, whereas the second identifies a single element.
Semantic constraints:
The constraint supplied with the selection must be a Boolean constraint.
The model reference must refer to a collection attribute.
LiteralString, Number, IntegerNumber, BooleanLiteral
LiteralString | ::= | "'" [^\'\n\r]* "'" | |
Number | ::= | ("-")? (Digit)+ "." (Digit)* | |
IntegerNumber | ::= | ("-")? (Digit)+ | |
BooleanLiteral | ::= | "true" | "false" |
These are basic atomic values that should be self-explanatory.
ModelReference
ModelReference | ::= | ModelPath | |
ModelPath | ::= |
(PackageReference)? elementOrAttribute::ModelElementName (ModelStep)*
| |
PackageReference | ::= |
ModelElementName ("::" ModelElementName)*
| |
ModelStep | ::= |
(("." | "of" ) attribute:ModelElementName)*
|
Model references are widely used in all NRL constructs. For example, they are used to retrieve values for comparison or to retrieve collections for checking using a quantifier.
The grammar allows for some flexibility in how model elements are referenced. Please refer to 2.1 Model References at the beginning of this specification for a full discussion.
The alternative "of" keyword between model steps turns the model reference into a backward reference.
Therefore, step1.step2.stepX
is equivalent to stepX of step2 of step1
and vice versa.
tradeHeader.tradeDate -- Attribute reference The tradeDate of the tradeHeader -- Equivalent to preceding statement, using "of" syntax
CollectionIndex
CollectionIndex | ::= | OrdinalNumber ("of")? collection:ModelReference |
Added in version 1.3 of the language, this statement is used to reference elements at a specific position in a collection. The model reference that forms the second argument of the statement must refer to a collection attribute. The ordinal number provides the index - note the index of the first element of a collection is 1.
The optional "of" keyword between the arguments can be used to make the statement more readable where the collection attribute name is in plural.
The first of the tradeLegs The 52nd tradeLeg
When a constraint rule fails, it is sometimes not enough to simply indicate this fact. User-readable reports are necessary if validation results have to be sent back to end-users to inspect. To this end, all NRL rules carry an optional report, which is a sequence of instructions on how to build up a string that can be reported to a user when a rule returns false.
CompoundReport
CompoundReport | ::= |
SimpleReport ( (",")? SimpleReport )*
| |
SimpleReport | ::= |
ConcatenatedReport
| |
| ConditionalReport
|
Attached directly to the rule is a compound report. This is a list of one or more simple reports, discussed below, all of which will be evaluated to a string. The result of a compound report is a concatentation of the results of its children. The simple reports included in the compound report may be separated by a comma, though this can also be omitted and replaced with any whitespace.
ConcatenatedReport
ConcatenatedReport | ::= |
("report")? SimpleTerm ( ("+")? SimpleTerm )*
| |
SimpleTerm | ::= |
Identifier
| |
| FunctionalExpression
| |||
| OperatorInvocation
|
A concatenated report is a series of simple terms, separated by the plus sign or just whitespace. The result of a concatenated report is a string that is produced by evaluating all terms, converting them to strings, and concatenating them. Where model references occur in a concatenated report, the current context is the rule context and no variables from the rule are in scope. Therefore, model references must begin either with an attribute of the rule context, or with the model element representing the rule context itself.
Context: Trade Rule "no-historic-trades" tradeHeader.tradeDate > [today] Report: 'The trade date ' + tradeHeader.tradeDate + ' is in the past'
The example shown above might result in the report, "The trade date 2005-01-01 is in the past
". Note again that the
plus signs can be omitted: this is a matter of preference.
ConditionalReport
ConditionalReport | ::= |
"if" Constraint "then" CompoundReport ("else" CompoundReport)? ";"
|
A conditional report is turned into a string depending on whether a condition is true. This takes the form of an if-then-else statement, with the else part being optional. The condition may be any NRL constraint. Note that this if statement must be terminated with a semi-colon, which is not the case for the if statement in the main constraint language.
The NRL constraint included in the "if" part of the report must evaluate to an NRL type of Boolean.
Context: Trade Rule "no-historic-trades" The following are not present: taxes, fees Report: If taxes are present then report 'Taxes should not be present' else report 'Fees should not be present';
The example above shows a conditional report. Note the use of the optional "report" keyword inside the two reports, which is permitted by the definition of concatenated reports.
This section defines the complete concrete syntax of the Natural Rule Language (NRL) in EBNF notation.
The definitions in the first section, Rules and Properties, only apply to applications where NRL rules are being kept in separate files, making it necessary to declare reference a model file and to declare contexts. If NRL rules are included directly into models, for example as part of a UML case tool, the correct place to start is the Constraint non-terminal.
Pre-processing:
NRL processors are expected to perform article stripping before parsing rules. The following keywords should be eliminated during lexical scanning: a, an, the, its and their. The word element should also be eliminated.
NRL processors are also expected to scan the file for all possible fragment declarations before parsing, and inserting curly brackets ({ }) around possible fragment references, eliminating the need to write them explicity.
[1] | ValidationRule | ::= | Context "Validation Rule" id:DoubleQuotedString ValidationRuleVariableDeclaration* Constraint ("report:" CompoundReport)? |
[2] | ValidationRuleVariableDeclaration | ::= | SimpleVariableDeclaration ","? |
[3] | ValidationFragment | ::= | MultipleParameterContext "Validation Fragment" id:DoubleQuotedString
Constraint |
[4] | RuleSet | ::= | "Rule set" id:DoubleQuotedString ("applies to" ModelReference "where" Constraint)? |
[5] | GlobalVariableDeclaration | ::= | SimpleVariableDeclaration /* (Additional constraints apply! */ |
[6] | Context | ::= | "Context:" ModelReference |
[7] | MultipleParameterContext | ::= | "Context:" MultipleContextParameter |
[8] | MultipleContextParameter | ::= | ModelReference "(" name:DoubleQuotedString ")" ("," MultipleContextParameter)* |
[9] | Constraint | ::= | IfThenStatement |
[10] | IfThenStatement | ::= |
"if" IffStatement "then" IfThenStatement "else" IfThenStatement
|
| IffStatement
| |||
[11] | IffStatement | ::= |
ImpliesStatement ("only if" IffStatement)?
|
[12] | ImpliesStatement | ::= |
OrStatement ("implies" ImpliesStatement)?
|
[13] | OrStatement | ::= |
AndStatement ("or" OrStatement)?
|
[14] | AndStatement | ::= |
LogicalStatement ("and" AndStatement)?
|
[15] | LogicalStatement | ::= |
ExistsStatement
|
| NotExistsStatement
| |||
| GlobalExistsStatement
| |||
| ForallStatement
| |||
| VariableDeclaration
| |||
| Predicate
|
[16] | ExistsStatement | ::= |
(Enumerator ("of the")?)? ModelReference ("has" | "have" | "is" | "are") ("present" | SimpleOrComplexConstraint)
|
| Enumerator ("has" | "have" | "is" | "are") SimpleOrComplexConstraint
| |||
[17] | Enumerator | ::= |
("at least" | "at most" | "exactly")? ("one" | "two" | "three" | "four" | "no" | "none" | IntegerNumber)
|
[18] | NotExistsStatement | ::= |
ModelReference ("is not present" | "are not present")
|
[19] | GlobalExistsStatement | ::= |
("there is" | "there are") ("no")? ModelReference ("(" var:DoubleQuotedString ")")? ("where" SimpleOrComplexConstraint)?
|
[20] | ForallStatement | ::= |
("each" | "in each" | "all" | "every") ("of the")? ModelReference ("has" | "have" | "is" | "are")? SimpleOrComplexConstraint
|
| "for each" var:DoubleQuotedString "in the collection of" ModelReference ("has" | "have" | "is" | "are" | ",")? SimpleOrComplexConstraint
| |||
[21] | SimpleOrComplexConstraint | ::= |
"(" Constraint ")"
|
| Predicate
|
[22] | SimpleVariableDeclaration | ::= | var:DoubleQuotedString ("is" | "are" | "represent" | "represents") Expression |
[23] | Predicate | ::= |
Expression "is one of" ListDefinition
|
| Expression "is not one of" ListDefinition
| |||
| ModelReference "is a kind of" ModelElementName
| |||
| MultipleExistsStatement
| |||
| MultipleNotExistsStatement
| |||
| Expression (BinaryPredicate Expression)?
| |||
[24] | BinaryPredicate | ::= |
"=" | ("is")? "equal to"
|
| "<>" | ("is")? "not equal to"
| |||
| "<" | ("is")? "less than" | ("is")? "before"
| |||
| ">" | ("is")? "greater than" | ("is")? "after"
| |||
| "<=" | ("is")? "less than or equal to"
| |||
| ">=" | ("is")? "greater than or equal to"
| |||
[25] | ListDefinition | ::= |
Identifier ("," Identifier)*
|
[26] | MultipleExistsStatement | ::= | "following" ("is present" | "are present") ":" ModelReferenceList |
[27] | MultipleNotExistsStatement | ::= | "following" ("is not present" | "are not present") ":" ModelReferenceList |
[28] | Expression | ::= |
MultiplyExpression
|
[29] | MultiplyExpression | ::= |
AdditiveExpression ( ("*" | "/" | "mod") MultiplyExpression)?
|
[30] | AdditiveExpression | ::= |
InfixOperatorExpression ( ("+" | "-") AdditiveExpression)?
|
[31] | InfixOperatorExpression | ::= |
InfixValidationFragmentApplication (OperatorName InfixOperatorExpression)?
|
[32] | InfixValidationFragmentApplication | ::= |
Term (FragmentName InfixValidationFragmentApplication)?
|
[33] | Term | ::= |
Identifier
|
| FunctionalExpression
| |||
| OperatorInvocation
| |||
| ValidationFragmentApplication
| |||
| CastExpression
| |||
| SelectionExpression
| |||
| "(" Constraint ")"
| |||
[34] | Identifier | ::= |
ModelReference | LiteralString | Number | IntegerNumber | BooleanLiteral | CollectionIndex
|
[35] | FunctionalExpression | ::= |
"sum of" ModelReference
|
| "number of" ModelReference
| |||
| "number of" "unique" ModelReference "(" "by" ModelReference ")"
| |||
[36] | OperatorInvocation | ::= |
OperatorName (OperatorParameterList)?
|
[37] | OperatorParameterList | ::= |
Expression ( ("and" | "from" | "to" | "with" | "using") Expression )*
|
[38] | ValidationFragmentApplication | ::= |
fragmentId:FragmentName OperatorParameterList
|
[39] | CollectionIndex | ::= | OrdinalNumber ("of")? collection:ModelReference |
[40] | CastExpression | ::= | ModelReference ("as a" | "as an") ModelElementName |
[41] | SelectionExpression | ::= | ("first" "of"?)? ModelReference "where" SimpleOrComplexConstraint |
[42] | ModelReference | ::= | ModelPath |
[43] | ModelReferenceList | ::= | (ModelReference)+ |
[44] | ModelPath | ::= |
(PackageReference)? elementOrAttribute::ModelElementName (ModelStep)*
|
[45] | ModelStep | ::= |
(("." | "of" ) attribute:ModelElementName)*
|
[46] | PackageReference | ::= |
ModelElementName ("::" ModelElementName)*
|
[47] | CompoundReport | ::= |
SimpleReport ( (",")? SimpleReport )*
|
[48] | SimpleReport | ::= |
ConcatenatedReport
|
| ConditionalReport
| |||
[49] | ConcatenatedReport | ::= |
("report")? SimpleTerm ( ("+")? SimpleTerm )*
|
[50] | ConditionalReport | ::= |
"if" Constraint "then" CompoundReport ("else" CompoundReport)? ";"
|
[51] | SimpleTerm | ::= |
Identifier
|
| FunctionalExpression
| |||
| OperatorInvocation
|
[52] | BooleanLiteral | ::= | "true" | "false" |
[53] | LiteralString | ::= | "'" [^\'\n\r]* "'" |
[54] | DoubleQuotedString | ::= | "\"" [^\"\n\r]* "\"" |
[55] | ModelElementName | ::= |
Alpha (AlphaNumeric | "_" | "-")*
|
[56] | VariableName | ::= |
ModelElementName
|
[57] | Alpha | ::= | [a-zA-Z] |
[58] | Digit | ::= | [0-9] |
[59] | AlphaNumeric | ::= | Alpha | Digit |
[60] | Number | ::= | ("-")? (Digit)+ "." (Digit)* |
[61] | OrdinalNumber | ::= | "first" | "second" | "third" | (Digit+ ("th" | "st" | "nd" | "rd")) |
[62] | IntegerNumber | ::= | ("-")? (Digit)+ |
[63] | FragmentName | ::= | "{" [^\{\n\r] "}" |
[64] | OperatorName | ::= | "[" [^\]\[\n\r] "]" |
This section defines the abstract syntax of the Natural Rule Language (NRL) in EBNF notation. This syntax serves two purposes:
To act as the source of any mapping from the NRL to a target language
To specify what a parse tree for the NRL looks like
In this section we will define some basic sets that the abstract syntax will refer to. These are presented using EBNF notation for now, for no other reason than simplified integration into this specification. The sets are also defined descriptively, rather than formally.
[65] | Alphabet | ::= | The set of all characters, digits and symbols |
[66] | Boolean | ::= | { true, false } |
[67] | LiteralString | ::= | Powerset of Alphabet |
[68] | Identifier | ::= | Set of all rule and fragment identifiers, subset of LiteralString |
[69] | Number | ::= | The set of decimal numbers |
[70] | Operator | ::= | The set containing all user-supplied operators |
[71] | ModelElement | ::= | The set of all model elements |
[72] | Attribute | ::= | The set of all attributes of all model elements |
[73] | AValidationRule | ::= | AContext Identifier AVariableDeclaration* AConstraint (ACompoundReport)? |
[74] | AValidationFragment | ::= | AMultipleParameterContext Identifier AConstraint |
[75] | ARuleSet | ::= | Identifier (ModelElement precondition:AConstraint)? |
[76] | AContext | ::= | ModelElement |
[77] | AMultipleParameterContext | ::= | AMultipleContextParameter+ |
[78] | AMultipleContextParameter | ::= | name:Identifier type:ModelElement |
[79] | AConstraint | ::= | AIfThenStatement |
| ABinaryOperatorStatement | |||
| AExistsStatement | |||
| ANotExistsStatement | |||
| AGlobalExistsStatement | |||
| AForallStatement | |||
| APredicate | |||
| AVariableDeclaration | |||
| AExpression | |||
[80] | AIfThenStatement | ::= |
if AConstraint then AConstraint else AConstraint
|
[81] | ABinaryOperatorStatement | ::= |
AConstraint and AConstraint
|
| AConstraint or AConstraint
| |||
| AConstraint implies AConstraint
| |||
| AConstraint iff AConstraint
|
[82] | AExistsStatement | ::= |
AEnumerator AModelReference (AConstraint)?
|
[83] | AEnumerator | ::= |
AQualifier Number
|
[84] | AQualifier | ::= |
atleast | atmost | exactly
|
[85] | ANotExistsStatement | ::= |
AModelReference
|
[86] | AGlobalExistsStatement | ::= |
(no)? AModelReference (var:Identifier)? (AConstraint)?
|
[87] | AForallStatement | ::= |
AModelReference (var:Identifier)? AConstraint
|
[88] | AVariableDeclaration | ::= |
name:Identifier assigned:AExpression
|
[89] | APredicate | ::= |
AInListPredicate | ANotInListPredicate | ABinaryPredicate | AMultipleExistsStatement | AMultipleNotExistsStatement
| AIsSubtypePredicate
|
[90] | AInListPredicate | ::= |
AExpression in AListDefinition
|
[91] | ANotInListPredicate | ::= |
AExpression not in AListDefinition
|
[92] | AListDefinition | ::= |
(AIdentifier)+
|
[93] | AIsSubtypePredicate | ::= | toCheck:AModelReference type:ModelElement |
[94] | AMultipleExistsStatement | ::= | ModelElement+ |
[95] | AMultipleNotExistsStatement | ::= | ModelElement+ |
[96] | ABinaryPredicate | ::= |
AExpression = AExpression
|
| AExpression <> AExpression
| |||
| AExpression < AExpression
| |||
| AExpression > AExpression
| |||
| AExpression <= AExpression
| |||
| AExpression >= AExpression
|
[97] | AExpression | ::= |
AArithmeticExpression
|
| AFunctionalExpression
| |||
| AOperatorInvocation
| |||
| AValidationFragmentApplication
| |||
| ACastExpression
| |||
| AIdentifier
| |||
[98] | AArithmeticExpression | ::= |
AExpression + AExpression
|
| AExpression - AExpression
| |||
| AExpression * AExpression
| |||
| AExpression / AExpression
| |||
| AExpression mod AExpression
|
[99] | AIdentifier | ::= |
AModelReference | LiteralString | Number | Boolean | ACollectionIndex | ASelectionExpression
|
[100] | AFunctionalExpression | ::= |
sumof AModelReference
|
| numberof AModelReference (uniqueby AModelReference)?
| |||
[101] | AOperatorInvocation | ::= |
Operator (param:AExpression)*
|
[102] | AValidationFragmentApplication | ::= |
fragmentId:Identifier (param:AExpression)+
|
[103] | ACollectionIndex | ::= |
index:Number collection:AModelReference
|
[104] | ACastExpression | ::= |
target:AModelReference type:ModelElement
|
[105] | ASelectionExpression | ::= | collection:AModelReference criteria:AConstraint firstonly:Boolean |
[106] | AModelReference | ::= |
AAbsoluteReference
|
| ARelativeReference
| |||
[107] | AAbsoluteReference | ::= | ModelElement steps:(Attribute)* |
[108] | ARelativeReference | ::= | steps:(Attribute)+ |
[109] | ACompoundReport | ::= |
reports:(ASimpleReport)+
|
[110] | ASimpleReport | ::= |
AConcatenatedReport
|
| AConditionalReport
| |||
[111] | AConcatenatedReport | ::= |
parts:(AExpression)+
|
[112] | AConditionalReport | ::= |
if:AConstraint then:ACompoundReport (else:ACompoundReport)?
|
These people have contributed to this specification through comments:
Dave Carlson
Peter Geraghty
2010-04-07: NRL 1.4 release.
"Rule" is now "Validation Rule"
"Property" is "Validation Fragment"
In validation rules, variable declarations must now occur up-front, at the start of the rule
A new construct, "selection expression", was added to allow easier retrieval of items from collections
2009-03-18: CN: ForallStatement can now take an optional variable (important for complex looping);
2009-03-18: CN: Reporting framework added
2009-01-23: CN: Added: CollectionIndex
2008-07-11: CN: Added: IsSubtypePredicate, CastExpression
2008-01-09: CN: Added: MultipleContext, MultipleExistsStatement, MultipleNotExistsStatement, "is not one of"; "is one of" takes any identifier as a parameter now, not just literal strings. Modulo arithmetic added. Rule set concrete syntax has been changed. Semantics of properties changed: do not have evaluate to a boolean any longer.
2007-02-07: CN: Added GlobalExistsStatement and boolean literals/boolean set.
2006-09-15: CN: Updated for new variable syntax; Added rule sets; Added global variables.
2006-02-24: CN: Added missing metamodel diagram; added a section listing the basic data types.
2006-02-24: CN: Changes in variable syntax, and added a section on variables in the long text.
2006-02-22: First draft released to the public.