Natural Rule Language (NRL)
Version 1.4.0

Specification 7 April 2010

This version:
http://nrl.sourceforge.net/spec/spec-nrl-20100407.html
Latest version:
http://nrl.sourceforge.net/spec/
Authors and Contributors:
Christian Nentwich <christian at NO-SPAM.modeltwozero.com>
Rob James, HSBC <rob.james at NO-SPAM.hsbcgroup.com>

Abstract

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.

Status of this Document

This document is the authoritative specification of the constraint rule part of the Natural Rule Language.

Table of Contents

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

Appendices

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)


1 Introduction

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.

2 Model

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:

Metamodel

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.

2.1 Model References

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:

  1. 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.

  2. 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

  3. Static attribute reference - also known as absolute references, these identify a static attribute contained in a model element.

  4. Variable reference - this also refers to an attribute in the model, but references the attribute starting from a variable rather than a model element

All three types of references are covered by the same production in A Concrete Syntax:

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.

2.1.1 Disambiguation of Model References

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:

  1. Are there package references? In this case, the reference definitely refers to a model element. Treat it as a static attribute reference.

  2. Is the first step bound as a variable name? If so, treat it as a variable reference.

  3. 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.

  4. 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.

2.2 Basic Type System

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.

3 Constraint Language

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.

3.1 Rules

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.

Example: Validation Rule
Context: Trade
Validation Rule "date-1"
startDate is before endDate

3.2 Validation Fragments

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.

Example: Fragment definition and use in rule
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:

Example: Fragment definition with two parameters and infix use
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.

3.3 Rule Sets

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.

Example: Rule set and pre-condition
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'

3.4 Constraints

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

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.

Example: Constraints
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

3.5 Quantifiers

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.

The following informative examples demonstrate these cases:

Example: Existence
-- 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.

Example: Legal and illegal use of shortened ExistsStatement
-- 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.

Example: Forall
-- 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:

Example: Simple Global Existence
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:

Example: Existence With a Variable
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:

Example: Existence With a Where
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.

3.6 Variable Declaration

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:

Example: Variables
"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.

3.7 Predicates

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.

Example: Is one of
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:

Example: Is a kind of
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.

Example: MultipleNotExistsStatement
The following are not present: taxes, fees

BinaryPredicate

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"

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.

3.8 Expressions

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 AExpression BResult
String, Boolean, Date, Collection, ElementAnyIllegal
NumberNumberA number, the result of multiplying the first with the second sub-expression result
NumberAny (other than number)Illegal

Operator: /

Expression AExpression BResult
String, Boolean, Date, Collection, ElementAnyIllegal
NumberNumberA number, the result of dividing the first by the second sub-expression result. Will fail if the second sub-expression evaluates to zero.
NumberAny (other than number)Illegal

Operator: mod

Expression AExpression BResult
String, Boolean, Date, Collection, ElementAnyIllegal
NumberNumberA 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.
NumberAny (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 AExpression BResult
Boolean, Collection, ElementAnyIllegal
StringStringA string, concatenation of the value of the second to the first sub-expression.
StringNumber, Date, BooleanA string, concatenation of a string representation of the second value to the first value.
NumberNumberA number, the result of adding the two values.
NumberStringA string, the result of concatenating the string value of the number to the second value.
NumberAny (other than Number, String)Illegal
DateNumberA date; the second parameter is treated as a number of days and added to the date given as the first parameter
DateAny (other than Number)Illegal

Operator: -

Expression AExpression BResult
Boolean, Collection, Element, StringAnyIllegal
NumberNumberA number, the result of subtracting the second value from the first.
NumberAny (other than Number)Illegal
DateNumberA date; the second parameter is treated as a number of days and subtracted from the date given as the first parameter
DateAny (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:

Example: CastExpression
"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.

3.9 Terms

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.

Example: SelectionExpression
"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.

3.10 Identifiers

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.

Example: ModelReference
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.

Example: CollectionIndex
The first of the tradeLegs
			    
The 52nd tradeLeg

3.11 Reporting

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.

Example: Concatenated Report
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.

Example: Concatenated Report
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.

A Concrete Syntax

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:

Rules and Properties
[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)*
Constraints
[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
Quantifiers
[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
Variable Declaration
[22]   SimpleVariableDeclaration   ::=   var:DoubleQuotedString ("is" | "are" | "represent" | "represents") Expression
Predicates
[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
Expressions
[28]   Expression   ::=    MultiplyExpression
[29]   MultiplyExpression   ::=    AdditiveExpression ( ("*" | "/" | "mod") MultiplyExpression)?
[30]   AdditiveExpression   ::=    InfixOperatorExpression ( ("+" | "-") AdditiveExpression)?
[31]   InfixOperatorExpression   ::=    InfixValidationFragmentApplication (OperatorName InfixOperatorExpression)?
[32]   InfixValidationFragmentApplication   ::=    Term (FragmentName InfixValidationFragmentApplication)?
Terms
[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)*
Reporting
[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
Terminals
[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] "]"

B Abstract Syntax

This section defines the abstract syntax of the Natural Rule Language (NRL) in EBNF notation. This syntax serves two purposes:

Terminals and precedence rules are not present in this syntax, they must be resolved by a parser implementing the productions in A Concrete Syntax. To distinguish the notation from the concrete syntax, the prefix A has been added to all definitions - this should not be read as a symbol, not as an article.

B.1 Definitions

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.

Set Definitions
[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

B.2 Syntax

Rules and Fragments
[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
Constraints
[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
Quantifiers
[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
Variables
[88]   AVariableDeclaration   ::=    name:Identifier assigned:AExpression
Predicates
[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
AExpressions
[97]   AExpression   ::=    AArithmeticExpression
| AFunctionalExpression
| AOperatorInvocation
| AValidationFragmentApplication
| ACastExpression
| AIdentifier
[98]   AArithmeticExpression   ::=    AExpression + AExpression
| AExpression - AExpression
| AExpression * AExpression
| AExpression / AExpression
| AExpression mod AExpression
Terms
[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)+
Reporting
[109]   ACompoundReport   ::=    reports:(ASimpleReport)+
[110]   ASimpleReport   ::=    AConcatenatedReport
| AConditionalReport
[111]   AConcatenatedReport   ::=    parts:(AExpression)+
[112]   AConditionalReport   ::=    if:AConstraint then:ACompoundReport (else:ACompoundReport)?

C References

C.1 Normative References

XML Schema
World Wide Web Consortium. XML Schema Part 1: Structures Second Edition. W3C Recommendation. See http://www.w3.org/TR/xmlschema-1/.
UML
Object Management Group. Unified Modeling Language 2.0. OMG Specification. See http://www.uml.org/.

D Acknowledgments (Non-Normative)

These people have contributed to this specification through comments:

Dave Carlson
Peter Geraghty

E Change History (Non-Normative)

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.