Simple Domain Modeling Language
Language Reference
Table of Contents
- 1. Introduction
- 2. The Surface Syntax
- 3. Common Modeling Questions
- 4. Capturing Model Constraints
- 5. Walk-Through Example
- 6. Implementation and Tools
- 7. The RDF Semantics
- 8. Appendix: Unicode Usage
- 9. Appendix: Standard Library
- 9.1. Type Classes
- 9.2. SDML Module
- 9.3. Commonly Used Modules
- 9.3.1. Dublin Core Elements
- 9.3.2. DCMI Abstract Model
- 9.3.3. DCMI Type Vocabulary
- 9.3.4. DCMI Metadata Terms
- 9.3.5. The OWL 2 Schema Vocabulary
- 9.3.6. The RDF Concepts Vocabulary
- 9.3.7. The RDF Schema Vocabulary
- 9.3.8. Simple Knowledge Organization System Namespace
- 9.3.9. The xml Namespace
- 9.3.10. XML Schema Datatypes
- 9.4. Constraint Language Library
- 9.4.1.
Named
andTyped
- 9.4.2. Equality and Inequality
- 9.4.3. Identifiers
- 9.4.4. Module Definition
- 9.4.5. Datatypes and Values
- 9.4.6. Sequence Types
- 9.4.7. Annotations
- 9.4.8. Structured Type Members
- 9.4.9. Entities
- 9.4.10. Entity Events
- 9.4.11. Enumerations
- 9.4.12. Structures
- 9.4.13. Disjoint Unions
- 9.4.14. Property Definitions
- 9.4.15. Formal Constraints
- 9.4.16. Constraint Sentences
- 9.4.17. Constraint Terms
- 9.4.18. Constraint Environment
- 9.4.1.
- 10. Appendix: Surface Syntax Grammar
- 11. Appendix: Surface Syntax to RDF
- 11.1. Whitespace
- 11.2. Comments
- 11.3. Identifiers
- 11.4. Values
- 11.5. Modules
- 11.6. Imports
- 11.7. Annotations
- 11.8. Constraints
- 11.9. Datatype Definitions
- 11.10. Entity Definitions
- 11.11. Enumeration Definitions
- 11.12. Event Definitions
- 11.13. Structure Definitions
- 11.14. Union Definitions
- 11.15. Property Definitions
- 12. Appendix: Constraint Semantics
- 13. Appendix: UML Profile for SDML
- 14. License
- 15. Tables of Other Content
1. Introduction
The Simple Domain Modeling Language1 is a small data-oriented language for constructing, documenting, and reasoning about a conceptual domain model. For a background on domain modeling see section 1.1. At it’s core SDML comprises 1. a Semantic Model whose structure and semantics are described in RDF2 by an OWL3 ontology (see section 11 for more details), 2. a surface syntax for editing and sharing model artifacts (see section 2 for more details), 3. a constraint language to capture model invariants not covered by the data descriptions in the surface syntax. Figure 1 shows the relationship between these high-level components. This book covers both the surface syntax and semantic model as well as the correspondence between them. If you are looking for a quick introduction to the language you may prefer the SDML Primer.
Figure 1: Model Overview
The problem with any language, or technology in general, that aims to be simple it has to leave a lot of things out that at least some people would like to see. SDML is simple in that it started with the simplest possible set of constructs and set a very high bar for the addition of any additional features. This means that SDML is opinionated and that opinion may reflect more on the author than on any methodology. For example, SDML does not model the behavior of the business and some readers will expect some behavior description. However, it is far harder to define a model of behavior than for data, and such a model gets out of the realm of simple very quickly. SDML also does not support polymorphism in ways that programmers will expect, but as programmers are only one of the groups involved in domain modeling we err on the side of broadest understandability.
The SDML language uses some ideas from UML4, a language which is often used in the construction of such models today. Unfortunately, UML does not pass the simple test and while there are good ideas and well-understood concepts it was not possible to use them without taking away some options where they don’t make sense or where their use may distract from the major purpose of the model. While UML is predominantly a visual language, the OMG Human-Usable Textual Notation (HUTN)5 specification notwithstanding, SDML is a textual language with a mapping to UML for visualization. The development of SDML as a textual language additionally allows existing tools in the software development space such as version control and continuous integration tools to be applied to the models, in effect treating them as source code.
The SDML language also takes some of the concepts from Domain-Driven Design (DDD)6, namely the separation of entities (an object defined not by its attributes, but its identity) from value objects (an immutable object that contains attributes but has no conceptual identity), simply termed structures in SDML. This model does not include the following DDD first-class concepts: aggregates, repositories, factories, or services.
1.1. What is Domain Modeling?
Domain modeling is the process by which we develop a model of our business domain. In particular, we model what our business does and not how it is done. The resulting model should be recognizable by different stakeholders both technical and non-technical, internal to the business as well as external partners. In some domains with a level of standardization, or regulation, a level of commonality between different businesses and so domain models may be used to describe inter-business interactions. This approach has been used by a number of standards such as RosettaNet7.
There is often confusion around the use of the term Domain Model especially in it’s relation to terms such as Data Model, Class Model, Schema and more. The rest of this section is an attempt to clarify the terminology and how the relevant modeling efforts are undertaken. This is not intended as an exhaustive review of modelling approaches, standards, or technologies and neither does it make any judgement as to the usefulness of any approach or technology.
1.1.1. Model Terminology
A model is an informative representation of an object, person or system. The term originally denoted the plans of a building in late 16th-century English, and derived via French and Italian ultimately from Latin modulus, a measure. As is common in software development the term model is overloaded and it’s use can lead to confusion as people have different expectations and assumptions regarding what kind of model is being discussed. To this end we introduce some terminology here that, if simply used as model qualifiers, helps to disambiguate the purpose of any particular model.
For now we will use the following classification to partition the major purpose of different model instances.
- Data: A data model in software engineering is a representation of concepts and the relationships, constraints, rules, and operations to specify data semantics for a chosen domain of discourse. Typically it specifies relations between kinds of things, but may also include relations with individual things. It can provide sharable, stable, and organized structure of information requirements or knowledge for the domain context.
- Behavior: Behavioral languages are designed to describe the observable behavior of complex systems consisting of components that execute concurrently. These languages focus on the description of key concepts such as: concurrency, nondeterminism, synchronization, and communication.
- Process: [Business] process modeling (BPM) in business process management and systems engineering is the activity of representing processes of an enterprise, so that the current business processes may be analyzed, improved, and automated.
A common framework for defining the level of abstraction inherent to a model are the three perspectives introduced by ANSI in 1975 (introduced in section 1.3.
- Conceptual: In the field of computer science a conceptual model aims to express the meaning of terms and concepts used by domain experts to discuss the problem, and to find the correct relationships between different concepts. The conceptual model attempts to clarify the meaning of various, usually ambiguous terms, and ensure that confusion caused by different interpretations of the terms and concepts cannot occur.
- Logical: A logical data model or logical schema is a data model of a specific problem domain expressed independently of a particular database management product or storage technology (physical data model) but in terms of data structures such as relational tables and columns, object-oriented classes, or XML tags. This is as opposed to a conceptual data model, which describes the semantics of an organization without reference to technology.
- Physical: A physical data model (or database design) is a representation of a data design as implemented, or intended to be implemented, in a database management system. In the lifecycle of a project it typically derives from a logical data model, though it may be reverse-engineered from a given database implementation.
With these two classifications we can construct a simple grammar for the qualification of the term model:
Perspective = "conceptual" | "logical" | "physical" Scope = "data" | "behavior" | "process" ModelReference = Perspective, Scope "model"
1.1.2. Modeling Language/Notation
There have been a number of modeling techniques, notations, and languages over the years. The following table describes those in common use today. It is worth noting that after OMG standardization there are common meta-model underpinnings for UML and BPMN.
Data | Behavior | Process | |
Conceptual | UML1, Concept Map, Semantic Net | UML2, Flowchart | UML3, BPMN, EPC4 |
Logical | UML1, EXPRESS, IDEF1X, IE5 | UML2 | BPMN |
Physical | UML1, … | UML6, Statecharts, Petri net |
- Class and Component diagrams.
- Sequence, State, and Activity diagrams.
- Use Case and Activity diagrams.
- Event-driven Process Chains.
- Information Engineering (IE) introduced the commonly used crows-foot notation.
- Action Semantics and action languages.
UML has a built-in extension mechanism with stereotypes and profiles and data modeling concerns are often introduced with specific extensions such as stereotypes for different key types.
1.1.3. Modeling Themes
The reasons for embarking on a domain modeling exercise vary from informational to transformational. However, there are a number of
(TODO: Complete this section)
Commonality
Variability
- Inheritence
- …
- Interface and Implementation
- …
- (no term)
Scope: Breadth and Depth
1.1.4. Domain-Driven Design
Domain-driven design (DDD) is a software design approach focusing on modeling software to match a domain according to input from that domain’s experts. Under domain-driven design, the structure and language of software code (class names, class methods, class variables) should match the business domain.
Within this approach the domain model is usually a conceptual or logical data model that describes the key entities that comprise the domain. The representation of this model can be any of the languages described above although UML is the common. The discipline of domain modeling is unique in it’s focus on deriving data/information models directly from the business and stresses the involvement of domain experts from the business in the development of models. Additionally the literature describes specific kinds of model entities and their unique semantics, Entities, Value Objects, Events, and Aggregates, etc. which help focus conversations on key modeling concepts such as identity, lifecycle, and ownership.
Thus, a Domain Model is a conceptual (UML or Ontology) or logical (UML) data model developed according to a specific methodology (DDD) and employing a specific meta-language (UML Profile).
Some of the defined kinds, Repository, Factory, and Service are lower-level concerns and the lack of a clear separation between different levels of abstraction is one criticism of the method. Additionally terms such as Entity as defined by DDD are not entirely compatible with the use in pre-existing methods such as E/R modeling.
However, the method’s focus on the business and it’s vocabulary as the basis for models is valuable in an effort such as the Universal API where the model will represent our business model in different presentations.
1.3. Domain Modeling’s Role in Development
The software development process may be described as the management of abstraction; requirements are an abstraction representing actual needs of some group of individuals, a whiteboard drawing may be an (informal) abstraction of a system design, a UML model may be a more-or-less formal abstraction of system or component dependencies, and so forth. We can stratify these levels of abstraction as shown in figure 3 although almost any such stratification will crash headlong into any number of detractors.
Figure 3: Artifact Layers in Software Development
While this figure groups these levels into three groups, abstract models, concrete models, and artifacts this is again somewhat arbitrary given that even the executable deployment artifacts are an abstraction concerning how the operating system and CPU will layout and execute our application.
Figure 4 demonstrates the key element of a Model-Driven Development9 style, namely the transformation between models and between models and other development artifacts. In this case we use the terms introduced above where we transform from a more abstract model to a more concrete model or directly to other artifacts. The transformation also should allow configuration to direct the transform, perhaps injecting properties not a part of the domain model but which are required in the transformation target. For example, converting an SDML structure into a SQL DDL statement may need to know the specific SQL dialect to target.
Figure 4: Model Transformations
Example:
In the following example (figure 5) we show a tree of transformations from a domain model through three intermediate models to various artifacts such as SQL, code, and Thrift10. Some readers may feel a sense of déjà vu, you or your employer may have tried different model-driven development tools in the past and noted a number of significant issues. One of the major reasons for giving up on this approach is the round-trip problem – what if I edit the generated thing, how does that get reverse engineered back into the higher-level model?
Figure 5: Example Model Transformations
One key aspect of figure 5 is that all of the arrows flow in a single direction. Modeling tool vendors got hung up on the fact that they generated artifacts in programming languages that could be edited and so almost certainly would be; therefore, they had to attempt to round-trip those changes. If the transformations had been given additional configuration that took care of the kinds of edits that people may need to perform then no editing would be needed and no round-tripping. Inside a modern compiler your source code is transformed multiple times, sometimes a pre-processor, then an intermediate machine-independent language used by the optimizer and then to the machine specific assembler. In some compilers there are even multiple intermediate languages at different levels. While it is often possible to inspect these intermediate forms it is not realistic to edit them (unless you are a compiler developer) and there is no expectation that the compiler will put your changes back into your source code. C
1.4. SDML as a Domain Modeling Language
TBD
1.4.1. Goals
The purpose of SDML is to be the source of truth concerning the enterprise’s domain, and the root of the tree shown in figure 5. It needs to provide an abstraction that is expressive enough to model the structure of entities that define the domain, while providing enough detail to be useful in transforming to the next level of more concrete models and artifacts.
SDML has two key tenets that help in this goal:
- Provide the ability to capture correct, but incomplete models to allow fast capture of key information first.
- Provide an extension mechanism that goes beyond marker values and allows semantic extension when necessary.
Additionally, by focusing first on a text-based syntax we provide a resource representation that fits well into most software processes, it can be version controlled, it’s diff-friendly, you can use code-reviews and pull-requests to build governance processes. While this document does not preclude alternative representations, including visual ones, the surface syntax is the canonical authoring form.
(TODO: Add data dictionary discussion)
(TODO: Add vocabulary discussion)
1.4.2. Non-Goals
(TODO: Complete this list)
- Versioning
- Object Modeling
- Behavior Modeling
- Standardization
1.5. Acknowledgments
Obviously this work relies on the decades of prior art in domain modeling and modeling languages in general; in particular, the many authors and contributors to the UML family of specifications. Similarly SDML relies heavily on the work done by the W3C on RDF and OWL and I owe thanks to the many authors and contributors of those specifications and the myriad examples of RDF usage and application that I have drawn from.
The syntax diagrams generated for this document were generated from the BNF source by the Railroad Diagram Generator tool, written by Gunther Rademacher, and hosted at https://www.bottlecaps.de/rr/ui.
2. The Surface Syntax
This section describes the SDML surface syntax, a syntax intended to look and feel like a programming language but with extensibility provided by it’s underlying RDF and OWL foundation. The goal of the following syntax is to allow for rapid capture of domain information but allowing elaboration of details over time. To this end we have made language features optional wherever possible such that the minimum information neccessary is required to build a valid model. This document will use the terms valid to denote a model that has no syntactic errors and complete to denote a valid model that has no short-form, or incomplete, model elements.
The grammar, where possible, supports Unicode representations of keywords and operators that allow for formatting a source module with more compact expressions. This is to obviate the need for separate type-setting tools that typically perform a one-way transformation from source to some other format. See table 15 in appendix 8 for details.
For the complete grammar, and notes on BNF notation, see section 10.
2.1. Whitespace and Comments
This grammar chooses to use the JavaScript regular expression character class \s
which includes the following values,
including all Unicode whitespace.
Whitespace ::= [^\f\n\r\t\v#x20#xa0#x1680#x2000#x200a#x2028#x2029#x202f#x205f#x3000#xfeff]
Comments follow Lisp-like forms, starting with a semi-colon character and continuing to the end of line. Comments are intended to capture only information regarding the file contents and not model contents. Because of this, parsers are not required to capture comments, and tools are not required to emit comments back into changed files. For persistent comments within the model, relevant annotation properties may be attached to the model elements.
LineComment ::= ";" [^\r\n]*
; <- comments start here and end here ->
In the following example (2) a tool reading the following file is not required to preserve the comment, which in this case
is OK as the editorialNote
will be preserved and correctly used for the same purpose.
module foo is import [dc skos] ; Should come up with a better name. @skos:prefLabel = "Foo"@en @dc:description = "Here's a descriptive comment" @skos:editorialNode = "Someone should fix the name" end
2.2. Identifiers
An identifier is a string of characters corresponding to the Unicode General Category11 Lu (an uppercase letter),
Ll (a lowercase letter), Nd (decimal digit), or the Unicode Low Line (underscore, “_”, U+005F
) character. Specifically
an identifer must start with and must begin and end with a letter such that the low line character may only appear
within the identifier.
The following is the BNF description of the identifier syntax.
Identifier ::= IdentifierInitial IdentifierChar* ( "_" IdentifierChar+ )*
IdentifierInitial ::= UC_LETTER_UPPER | UC_LETTER_LOWER
IdentifierChar ::= IdentifierInitial | UC_NUMBER_DECIMAL
Naming Conventions:
- Module Name
- lower Camel Case; i.e.
xsd
,skos
,bank
, orcustomer
. In general this should be a broad noun. - Structure/Entity/Event/Enum Name
- Upper Camel Case; i.e.
Customer
,Account
,Address
, orName
. In general these are nouns related to, and often narrower than, the module name. - Data Type Name
- Upper Camel Case; i.e.
ShortString
,AccountNumber
, orSocialSecurityNumber
. In general these are broad nouns, with either a property noun suffix as inAccountNumber
or a qualifier noun prefix as inShortString
. Do not create capitalized acronym (SSN) or abbreviation (ID) forms unless these are universally understood within the domain. - Type Member Name
- lower Camel Case; i.e.
street
,accountName
, orcurrentValue
. As in data type definitions these are broad nouns, with either a property noun suffix as inaccountNumber
or a qualifier noun prefix as inshortName
. These are commonly simply the lower Camel Case version of the corresponding type name. - Enum and Union Variant Name
- Upper Camel Case is the preferred, although SHOUTY CASE is acceptable.
2.2.1. Qualified Identifier
A qualified identifier comprises a module identifier followed by the Colon (“:”, U+003A
) character and the identifier of
a member within that module.
QualifiedIdentifier
::= Identifier ":" Identifier
A type reference may be to a locally defined type by an identifier, or an imported type by a qualified identifier.
IdentifierReference ::= QualifiedIdentifier | Identifier
2.3. Module Definition and Imports
A module is the container for a set of type definitions and typically models a Domain or Sub-domain. A single resource (in the web sense of some thing identified by an IRI) may contain only a single module; therefore, the smallest valid resource is one with an empty module.
Module
::= "module" Identifier ModuleBase? ModuleBody
An empty module (listing 3) is any module with an empty body, delimited by the keywords is
and
end
.
module example is end
2.3.1. The base Specifier
ModuleBase
::= "base" IriReference
The keyword base
provides the base IRI for the module and corresponding RDF schema or OWL ontology. This corresponds
to the use of the xml:base
attribute in RDF/XML and the @base
directive in Turtle and SPARQL12. This IRI is
used as a prefix for defined type names so that each type has a unique IRI. The example in listing
4 demonstrates the assigned IRI identifier for the structure type named Thing
.
module example base <https://example.org/rdf/example#> is structure Thing ;; IRI: https://example.org/rdf/example#Thing end
Module URI Resolution
Each module has an identifer/name defined within the module source as well as a base URI used to assign URIs to every model element within the resource. This URI identifier for each model element necessary as the identifier for the corresponding RDF resource(s). Our first problem is to determine the base URI for the module,as follows:
- If the module has a base specifier it MUST be used as the module URI.
- If the reader has access to an SDML catalog file (For details of the catalog file format see appendix 6.8), a matching catalog entry MUST be used as the module URI.
- A URI is calculated for the module using the
file:
scheme and the file path relative to a project root.
Considering the following simple module in the file vehicle.sdm
.
module vehicle is end
If the following file is in the same directory as the vehicle module it is possible to
{ "base": "https://example.org/rentals/", "entries": { "vehicle": { "item": { "relative_uri": "vehicle#", "relative_path": "vehicle-v1.sdm" } } } }
2.3.2. Importing Modules and Members
ModuleBody ::= "is" ImportStatement* Annotation* Definition* "end"
To import types from other modules you add one or more import statements to the module body. Each statement may reference one or more module names or the qualified name of a type within a module.
ImportStatement ::= "import" ( Import | "[" Import+ "]" ) Import ::= MemberImport | ModuleImport
Importing a module allows access to all members of that module using qualified identifiers. For example, importing
module sdml
allows the reference of core types as sdml:string
or sdml:decimal
.
ModuleImport ::= Identifer
Importing a module member directly still results in a qualified identifier, but makes the dependency clearer. For
example, in the case above if we are only using the string
member from the sdml
module importing sdml:string
is cleaner.
MemberImport ::= QualifiedIdentifier
The module in listing 5 contains a single import statement referencing the module named xsd
making it’s members visible in the qualified form xsd:*
.
module example is import xsd end
The module in listing 6 contains a single import statement referencing the member named integer
from
the module xsd
making it visible.
module example is import xsd:integer end
To reduce the number of import statements a list of identifiers, both module and member, may be referenced as a list.
Listing 7 makes the members author
and title
from the module dc
visible. Lists in SDML start
with the Left Square Bracket (“[”, U+005B
) character and end with the Right Square Bracket (“]”, U+005D
) character with
no separator character between members of the list.
module example is import [ dc:title dc:author ] end
2.3.3. Definitions
The members of a module are a set of definitions that may be combined to develop a domain model.
Definition ::= DataTypeDef | EntityDef | EnumDef | EventDef | PropertyDef | StructureDef | UnionDef
Data types are further described in section 2.4; entities, enumerations, events, structures, and disjoint unions are described in section 2.7; and annotation properties are described in section 2.6.1.
2.3.4. Type Model
A subset of the definition types in the EBNF above are types, and the complete type model for SDML is shown in figure 6. In this figure the following types exist that are not directly reified in the surface syntax.
- Any
- The root of our type system, or the type of all individuals. This is sometimes termed top or a tautology
⊤
. - Scalar
- A scalar type refers to a data type whose individuals represent a single discrete value. In SDML all datatypes and enumerations are scalar types.
- Sum
- A type whose members are discrete but not necessarily scalar, i.e. it’s individuals are either one member or another, but not both. In the case of SDML only disjoint unions are sum types.
- Product
- A type whose members are all present, i.e. it’s individuals have one member and another and so on. In SDML entities, events, and structures are product types.
- Unknown
- A pseudo-type that is applied when the author does not yet know what the type should be but wishes to
create a valid model. In SDML member types may be defined as
unknown
during module development but such a type, and therefore it’s containing module, is incomplete. - None
- A type which represents the type of zero individuals and is therefore invalid as a member type. This is
sometimes termed bottom or absurdity
⊥
.
Figure 6: SDML Type Model
Sum vs. Product
Given a type \(T\) with members \(m_1, \cdots m_n\), and a relation \(\tau_{m_i}\) that returns the type of a value or definition, we can show this mathematically as follows.
Because a sum type may only be one member or another at any time the number of potential values (shown below as the cardinality of the type) is the number of values in each member added together \(m_1 + m_2 + \cdots m_n\).
\[ \sum_{m \in T} |\tau_m| \]
Because a product type has it’s first member and second and so on, the number of potential values is the number of values in each member multiplied together \(m_1 \times m_2 \times \cdots m_n\). This is also known as the Cartesian Product of the set \(\{m_1, \cdots m_n\}\).
\[ \prod_{m \in T} |\tau_m| \]
2.4. Data Types and Values
A data type individual, or simply a value, is either a simple value, a value constructor, an identifier reference, a mapping value, or a list of values.
Value ::= SimpleValue | ValueConstructor | IdentifierReference | MappingValue | SequenceOfValues
The inclusion of an identifier reference as a value allows for annotations whose value is another model element. It is invalid to use the identifier of an individual that is not itself a value.
The core data types supported by SDML are boolean
, integer
, decimal
, double
, string
, (Language-Tagged String), and iri
for IRI References. Values corresponding to these types are termed simple values.
SimpleValue ::= Boolean | Unsigned | Integer | Decimal| Double | String | Iri | Binary
These data types have corresponding keywords in SDML, and may have specific rules applied when parsing. Each keyword is
translated into a qualified identifier where the module name is the reserved name sdml
. This set of standard library
types have an underlying RDF/OWL equivalence relationship to a subset of the XML Schema datatypes, all shown in table
2.
BuiltinSimpleType ::= "boolean" | "unsigned" | "integer" | "decimal" | "double" | "string" | "iri" | "binary"
Type Keyword | Qualified Identifier | Equivalent XML Schema Datatype |
---|---|---|
boolean |
sdml:boolean |
xsd:boolean |
unsigned |
sdml:unsigned |
xsd:unsignedLong |
integer |
sdml:integer |
xsd:long |
decimal |
sdml:decimal |
xsd:decimal |
double |
sdml:double |
xsd:double |
iri |
sdml:iri |
xsd:anyURI |
string |
sdml:string |
xsd:string or rdf:langString |
binary |
sdml:binary |
xsd:hexBinary |
Some additional types are defined in the sdml
module, although they are not considered built-in types as they must be
referenced explicitly.
Qualified Identifier | Equivalent XML Schema Datatype |
---|---|
sdml:u8 |
xsd:unsignedByte |
sdml:i8 |
xsd:byte |
sdml:u16 |
xsd:unsignedShort |
sdml:i16 |
xsd:short |
sdml:u32 |
xsd:unsignedInt |
sdml:i32 |
xsd:int |
sdml:u64 |
xsd:unsignedLong |
sdml:i64 |
xsd:long |
2.4.1. Booleans
A boolean value in SDML is either a truth value, or a falsity value. Other types are not coerced into boolean values as they may be in other languages.
Boolean ::= Truth | Falsity
We denote a truth value with either the keyword true
or the Down Tack (“⊤”, U+22A4
) character.
Truth ::= ( “true” | “⊤” ) #+ENDSRC
We denote a falsity value with either the keyword false
or the Up Tack (“⊤”, U+22A5
) character.
Falsity ::= ( "false" | "⊥" )
2.4.2. Unsigned Integers
An unsigned value in SDML is a string of ASCII decimal digits, without leading zeros; zero, 0
, is a valid value however
00
and 01
are not. The unsigned type corresponds to a 64-bit unsigned integer number (values \([0, 2^{64}-1]\)).
Unsigned ::= Zero | NonZero ASCII_DIGIT* Zero ::= [0] NonZero ::= [1-9]
2.4.3. Signed Integers
A signed integer value in SDML consists of an optional a sign character (Hyphen Minus, “-”, U+002D
or Plus Sign, “+”,
U+002B
) followed by a string of ASCII decimal digits, without leading zeros; zero, 0
, is a valid value however 00
and 01
are not. The integer type corresponds to a 64-bit signed integer number (values \([−2^{63}, 2^{63} − 1]\)).
Integer ::= NumericSign? Unsigned NumericSign ::= [+-]
2.4.4. Decimal Numbers
A decimal (fixed point) value in SDML is an integer value, followed by the Full Stop (“.”, U+002E
) character and another
integer-like value although this second value allows any number of leading zeros. The decimal type corresponds to a
128-bit representation of a fixed-precision decimal number.
Decimal
::= Integer "." ASCII_DIGIT+
2.4.5. Floating-Point Numbers
A double value in SDML is a decimal-like value followed by a lower or upper case letter E (Latin Small Letter E, “e”,
U+0065
or Latin Capital Letter E, “E”, U+0045
), a sign character (Hyphen Minus, “-”, U+002D
or Plus Sign, “+”, U+002B
)
and an integer-like value. The double type is a 64-bit floating point number (specifically, the “binary64” type defined
in IEEE 754-2008).
Double ::= Decimal ExponentChar NumericSign? Integer ExponentChar ::= [eE]
2.4.6. Character Strings
A string value in SDML is a sequence of Unicode characters starting and ending with a Quotation Mark (’“‘, U+0022
)
character. While standard escape sequences allow for embedding non-printing characters.
String ::= QuotedString LanguageTag? QuotedString ::= '"' ( Escape | NotAllowed )* '"' NotAllowed ::= [^\"\#x00-#x08#x0B-#x1F#x7F]
Standard single-character escape forms are supported, described in table 4. The form
\u{XXXX}
, where X
is a single hex digit, allows for the inclusion of any Unicode characters by their code point. Note
that this form requires a minimum of 2 and a maximum of 6 such digits.
Escape ::= "\\" ( CharacterEscape | UnicodeEscape ) CharacterEscape ::= [\"\\/abefnrtv] UnicodeEscape ::= "u{" HexPair ( HexPair ( HexPair )? )? "}" HexPair ::= ASCII_HEX_DIGIT ASCII_HEX_DIGIT
Note also that strings allow literal newlines and do not require they be present in escaped form. This means that a string literal supports multiline forms.
Escape Character | Character Name (Common Name) | Unicode Equivalent |
---|---|---|
\" |
Quotation Mark | \u{0022} |
\/ |
Solidus (Forward Slash) | \u{002F} |
\\ |
Reverse Solidus (Backslash) | \u{005C} |
\a |
Bell | \u{0007} |
\b |
Backspace | \u{0008} |
\e |
Escape | \u{001B} |
\f |
Form Feed (Page Break) | \u{000C} |
\n |
Line Feed (New Line) | \u{000A} |
\r |
Carriage Return | \u{000D} |
\t |
Character Tabulation (Horizontal Tab) | \u{0009} |
\v |
Line Tabulation (Vertical Tab) | \u{000B} |
A language-tagged string value in SDML is a string as above but immediately followed by a Commercial At (“@”, U+0040
)
character and an unquoted string of characters that conform to a language identifier. Note that both components of such
a string contribute to equality tests, so that "abc"@en
is not equal to "abc"@fr
.
LanguageTag
::= "@"
LanguageTagPrimary
LanguageTagExtended?
LanguageTagScript?
LanguageTagRegion?
The primary language is one of:
LanguageTagPrimary ::= ASCII_LETTER_LOWER ASCII_LETTER_LOWER ASCII_LETTER_LOWER?
The extended language is zero to three selected three letter ISO 639-2 codes.
LanguageTagExtended
::= "-"
ASCII_LETTER_UPPER ASCII_LETTER_UPPER ASCII_LETTER_UPPER
The language script is an ISO 1592415 four letter code in title case.
LanguageTagScript
::= "-"
ASCII_LETTER_UPPER ASCII_LETTER_LOWER
ASCII_LETTER_LOWER ASCII_LETTER_LOWER
The language region is either a two letter ISO 3166-116 code or a three digit UN M4917 code.
LanguageTagRegion ::= "-" ( ( ASCII_LETTER_UPPER ASCII_LETTER_UPPER ) | ( ASCII_DIGIT ASCII_DIGIT ASCII_DIGIT ) )
2.4.7. IRI References
An IRI reference value in SDML is a value IRI value, either absolute or relative, between a Less-Than Sign (“<”, U+003C
)
character and a Greater-Than Sign (“>”, U+003E
) character. IRI references are more permissive in the SDML grammar than
the Turtle18 language.
Iri ::= "<" ( [^<>\"{}|^`\#x00-#x20] | UnicodeEscape )* ">"
2.4.8. Binary Strings
A binary value in SDML is a compact list of bytes that allows for the encoding of values that may not be possible with
the other built-in types. The representation of a binary string is a sequence of bytes in hex pairs; the opening bracket
of the sequence has a leading Number Sign (“#”, U+0023
) character to differentiate from other sequence values. Note that
whitespace is entirely optional, but recommended to layout the hex values.
Binary ::= "#[" HexPair* "]"
Here is an example showing a common layout with 16 bytes per row in two blocks of 8.
module example is import ex @ex:thing = #[ 52 32 39 76 5a 43 42 43 65 57 55 67 51 33 4a 31 5a 57 77 67 56 32 39 79 62 47 51 4b ] end
2.4.9. Value Constructors
While the value 101
is defined to be an Integer literal, in the presence of sub-types how do you specify the type of a
literal? To accomplish this a value constructor allows for specifying the precise type, or casting a value to a specific
type.
The syntax appears as a function call with a type reference followed by a valid simple value surrounded by the Left
Parenthesis (“(”, U+0028
) and Right Parenthesis (“)”, U+0029
) characters. The literal value MUST be valid for the
referenced type, or one of it’s super-types.
ValueConstructor ::= IdentifierReference "(" SimpleValue ")"
Here we assert that the value 1
is an unsigned rather than the default signed integer.
module example is import ex @ex:thing = xsd:unsigned(1) end
You can ignore the syntax of @ex:thing
which is an annotation, see section 2.6, used to ensure the syntax is
complete for all examples.
2.4.10. Mapping Values
A mapping value denotes a functional relationship between the domain (left-hand value) and range (right-hand value).
MappingValue ::= SimpleValue HasType Value
Mapping values are most often used within lists to create dictionary-like structures.
module example is import ex @ex:dictionary = [ "yes" -> 1 "no" -> 2 "maybe" -> 3 ] end
2.4.11. Sequence Values
As stated in section 2.3, sequences in SDML start with the Left Square Bracket (“[”, U+005B
)
character and end with the Right Square Bracket (“]”, U+005D
) character with no separator character between members of
the list. Sequence values are, as one might expect, sequences of values and specifically of simple values. Sequence
value may also by heterogeneous and therefore contain elements of different types.
SequenceOfValues ::= SequenceValueConstraint? "[" ( SimpleValue | ValueConstructor | IdentifierReference )+ "]" SequenceValueConstraint ::= "{" ( SequenceOrdering? SequenceUniqueness? ) "}"
module example is import ex @ex:thing = [ "yes" "no" "maybe" ] end
module example is import ex @ex:thing = {unique}[ "yes" "no" "maybe" ] end
2.4.12. Defining Data Types
A datatype definition introduces a new simple data type by restriction of some existing base type.
DataTypeDef ::= "datatype" Identifier TypeRestriction "opaque"? DataTypeBase AnnotationOnlyBody?
TypeRestriction ::= "<-" | "←" DataTypeBase ::= IdentifierReference | BuiltinSimpleType
Listing 13 shows the type restriction operator, <-
, defining a new type named name
as a restriction on
the existing XML Schema data type xsd:string
.
module example is import ex datatype Name <- string end
While such a type is useful for conveying semantic meaning with types it doesn’t provide any actual restriction on the value space of the type. This is accomplished by using a subset of the facets described in XML Schema part 2 to specify constraints on the new type.
module example is import ex datatype Currency <- decimal is @xsd:fractionDigits = 3 end datatype Price <- Currency is @xsd:totalDigits = 7 end end
Also, in listing 15 we now see that the Name type is a string whose length is between 5 and 25 characters only.
module example is import ex datatype Name <- string is @xsd:minLength = 5 @xsd:maxLength = 25 end end
As we defined the new datatype Name
as a kind of string it has all the properties (length, substring, etc.) of a string.
However, in some cases we may want the underlying type to be inaccessible to clients and so we mark the datatype as
opaque. The only operations that can be performed on an opaque type are equality and strict inequality.
module example is import ex datatype Name <- opaque string is @xsd:minLength = 5 @xsd:maxLength = 25 end end
From OWL 2 Web Ontology Language Quick Reference Guide19:
Facet | Value | Applicable Datatypes | Explanation |
---|---|---|---|
xsd:minInclusive , xsd:maxInclusive , xsd:minExclusive , xsd:maxExclusive |
literal in the corresponding datatype | Numbers, Time Instants | Restricts the value-space to greater than (equal to) or lesser than (equal to) a value |
xsd:minLength , xsd:maxLength , xsd:length |
Non-negative integer | Strings, Binary Data, IRIs | Restricts the value-space based on the lengths of the literals |
xsd:pattern |
xsd:string literal as a regular expression |
Strings, IRIs | Restricts the value space to literals that match the regular expression |
rdf:langRange |
xsd:string literal as a regular expression |
rdf:PlainLiteral |
Restricts the value space to literals with language tags that match the regular expression |
2.5. Sequence Types
When a structure member has a cardinality other than 1..1
it has to be represented by a sequence type. SDML bases it’s
sequence types on cardinality constraints rather than the direct specification of a collection type. This allows you to
specify the semantic intent of a member rather than picking a type and then worrying about the mapping of that to a
programming language types.
The current SDML cardinality constraints are:
- Ordering
- determines whether the sequence maintains the order of it’s elements; one of
ordered
orunordered
(default). - Uniqueness
- determines whether the sequence allows duplicate elements or not; one of
ordered
ornonunique
(default).
Table 6 shows how these constraints are combined to determine the corresponding SDML sequence type. See also section 9.4.6 which describes the standard library operations on sequence types.
Ordering | Uniqueness | Logical Type | Example Specification |
---|---|---|---|
unordered |
nonunique |
Bag (default) |
{0..} |
ordered |
nonunique |
List |
{ordered 0..} |
n/a | n/a | Maybe |
{0..1} |
ordered |
unique |
OrderedSet |
{ordered unique 0..} |
unordered |
unique |
Set |
{unique 0..} |
Note, any ordering or uniqueness constraint on the cardinality 0..1
is effectively ignored.
A dictionary or map structure can be described in SDML as a sequence of mapping values (see section 2.4.10).
Specifically the sequence type {unique 0..} (string -> string)
is mapping from string values to other strings where each mapping
is unique, and for mapping types only the keys are considered in comparisons. The type {0..} (string -> string)
does not
enforce uniqueness and thus is a multi-map.
2.6. Annotations
Annotations allow for the assertion of facts attached to model elements that go beyond what is possible to describe in the core syntax. Annotations are available in two forms, annotation properties, and constraints.
Annotation ::= AnnotationProperty | Constraint
A number of model elements described in the following sections have a body that may only contain annotations and so the following common production is used.
AnnotationOnlyBody ::= "is" Annotation+ "end"
2.6.1. Annotation Properties
Annotation Properties are an extension mechanism that interacts directly with the underlying RDF representation of the subject model element. While these may look like Java annotations, Python decorators, or Rust attributes it is more powerful in that it can express arbitrary statements about the model element. An SDML annotation starts with the symbol “@” and then has an identifier that resolves to an OWL annotation property (see section 2.6.3 for the detailed rules), and a value for the corresponding property range.
AnnotationProperty ::= "@" IdentiferReference "=" Value
The following example demonstrates a common annotation property attached to a module.
module example is import skos @skos:note = "This is an example annotation" end
For descriptive annotation properties it is valuable to use the language specific string format so allow for localization of labels and descriptions. The following example shows a simple label specified in multiple languages.
module example is import skos @skos:prefLabel = [ "example"@en "exemple"@fr "例子"@zh-CH ] end
A model element may have any number of annotation properties, especially the module itself which often includes annotations for the domain itself.
module example is import [ dc skos xml ] @skos:prefLabel = "Example Module"@en @dc:description = "This is an example module, with two annotations."@en @dc:created = @xsd:date("2023-08-17 Thu") @dc:creator = <http://github.com/sdm-lang> @dc:license = <https://opensource.org/license/mit/> end
From OWL Web Ontology Language Reference, Appendix E: Rules of Thumb for OWL DL ontologies:
If a property
a
is used where an annotation property is expected then it should either be one of the built in annotation properties (owl:versionInfo
,rdfs:label
,rdfs:comment
,rdfs:seeAlso
, andrdfs:isDefinedBy
) or there should be a triple:
a rdf:type owl:AnnotationProperty
2.6.2. Constraints
This section will only briefly introduce constraints as section 4 provides a detailed description. The first example, in listing 20, provides an informal constraint in that it is simply a string which is useful to readers but cannot be verified.
module rental is assert name_form = "All definition names MUST start with 'Rental'" end
While informal constraints are not validated in any way, given the ambiguities of human language a controlled language is a useful alternative.
A formal constraint on the other hand may be verified, but takes more work to structure correctly. The example in listing 21 example above by methodically checking every definition and any members therein.
module rental is assert name_form is forall d in definitions(self), d.name.has_prefix("Rental") end assert name_form_alternate is ∀ d ∈ definitions(self) has_prefix(name(d) "Rental") end end
2.6.3. Defining RDF Classes and Properties
In general only properties that have an RDF type of owl:AnnotationProperty
, or are sub-properties of such a property,
may appear as annotations. This maintains the assertion that they are annotations of their subject and allows the use of
a range of pre-defined annotation properties from RDF, RDF Schema, OWL, and the Dublin Core Metadata Initiative
(DCMI)20.
For example the annotation property rdfs:comment
is defined in the following manner allowing it’s use on any resource
and with a value that may be any literal.
rdfs:comment rdf:type rdf:Property, owl:AnnotationProperty ; rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ; rdfs:label "comment" ; rdfs:comment "A description of the subject resource." ; rdfs:domain rdfs:Resource ; rdfs:range rdfs:Literal .
To allow the standard library to define SDML equivalents of such properties it is necessary to provide a mechanism to
use non-annotation properties such as rdf:type
, rdfs:domain
, and rdfs:range
as demonstrated in the example above. To
allow this, if a model element has the property rdf:type
then the transformation from that element to RDF does not use
any of the transformations described here but an explicit mapping from only the provided properties.
RdfThingDef ::= "rdf" Identifier RdfTypes? AnnotationOnlyBody RdfTypes ::= "type" ( IdentifierReference | "[" IdentiferReference "]" )
Rewriting the RDF from listing 22 into SDML results in the definition in listing 23.
module rdf_schema base <http://www.w3.org/2000/01/rdf-schema#> is import [ rdf owl ] rdf Resource is ;; rdf:type -- rdf:Class is automatically implied ;; rdfs:label -- structure name is automatically implied ;; @isDefinedBy -- module base URL is automatically implied @comment = "The class resource, everything." end rdf comment type [ rdf:Property owl:AnnotationProperty ] is ;; rdf:type -- rdf:Property is automatically implied ;; rdfs:label -- property name is automatically implied ;; @isDefinedBy -- module base URL is automatically implied @comment = "A description of the subject resource." @domain = Resource @range = Literal end end
The following are commonly used RDF and OWL definition properties.
Property | Applies To | Type | Comments |
---|---|---|---|
rdf:type |
Either | Type Reference | |
rdfs:domain |
Property | Type Reference | |
rdfs:range |
Property | Type Reference | |
rdfs:subClassOf |
Structure | Type Reference | |
rdfs:subPropertyOf |
Property | Type Reference | |
owl:equivalentClass |
Structure | Type Reference | |
owl:equivalentProperty |
Property | Type Reference | |
owl:inverseOf |
Property | Type Reference | |
owl:disjointWith |
Structure | Type Reference | |
Commonly used annotation super-types
owl:AnnotationProperty
owl:FunctionalProperty
owl:InverseFunctionalProperty
owl:SymmetricProperty
owl:TransitiveProperty
2.7. Structured Types
Structured types fall into two primary categories, Product types and Sum types. Product types have named and typed
Members and in SDML these are structures, entities and events. Sum types on the other hand have Variants and in SDML
these are enumerations and disjoint unions. In the surface syntax product type bodies are introduced by the keyword is
whereas sum type bodies are introduced by the keyword of
.
2.7.1. Structures
A structure is a product type that is composed of named and typed members. A structure is therefore akin to a record type, a table in data modeling, or a class in object modeling.
StructureDef
::= "structure" Identifier StructuredBody?
As only the keyword structure
and the identifier are required, the listing 24 is therefore a
valid model.
module example is structure Length end
The structure Length
in listing 24 is valid but considered incomplete. Adding a body to the
structure, between is
and end
, makes it complete even if it has no actual members. In listing
25 the structure Length is now complete.
StructuredBody ::= "is" Annotation* Member+ "end"
module example is structure Length is @skos:prefLabel = "Length"@en end end
Listing 26 adds the members value
and unit
and their corresponding types.
module example is structure Length is @skos:prefLabel = "Length"@en value -> Decimal unit -> DistanceUnit end end
2.7.2. Entities
EntityDef
::= "entity" Identifier EntityBody?
module example is entity Person end
The entity Person
in listing 27 is valid but incomplete.
EntityBody ::= "is" Annotation* EntityIdentity Member* "end"
module example is entity Person is identity id -> PersonId end end
The identity
member is a required part of the entity definition, it not only tells us what type represents the unique
identifier for this entity but is also used in event sources (see section 2.7.3).
module example is entity Person is identity id -> PersonId emergency_contact (emergency_contact_for) -> {0..2} Person is @dc:description = "Emergency contact person" end end end
2.7.3. Entity Events
Entity Events, or simply Events, model notifications generated by an entity most often representing a state change in the entity.
As such a source
reference denotes the entity which generates this event. Any expansion of the event structure must include the
identifiers of the source entity.
EventDef ::= "event" Identifier "source" IdentifierReference StructuredBody?
module example is event PersonNameChanged source Person end
With the expansion of the source
entity the event definition above is logically equivalent to the following structure.
module example is structure PersonNameChanged is id -> PersonId is @sdml:identifies = Person end end end
As we have seen before, the event PersonNameChanged
in listing 30 is valid but incomplete.
The following is a valid and complete event definition with two structure members.
module example is event PersonNameChanged source Person is fromValue -> Name toValue -> Name end end
2.7.4. Enumerations
An enumeration defines a set of unique constant values. The representation of these values is not defined by SDML, allowing users of the model to choose a scheme that matches their need.
Note that the keyword of
and not is
starts an enumeration body.
EnumDef
::= "enum" Identifier EnumBody?
module example is enum DistanceUnit end
The enumeration DistanceUnit
in 32 is valid but incomplete. Completion of the enumeration requires
the addition of a body with enumeration variants.
EnumBody ::= "of" Annotation* ValueVariant+ "end"
ValueVariant ::= Identifier AnnotationOnlyBody?
The following demonstrates a valid and complete enumeration.
module example is enum DistanceUnit of Meter Foot end end
While SDML only guarantees that a unique value is assigned to each variant within an enumeration it does not say what the type of this value is, or how it is represented. In effect the datatype of a variant’s value is opaque and therefore supports only equality tests.
Where it is useful to assign specific values to variants this can be accomplished with a pair of annotations,
owl:equivalentClass
and rdf:value
. The first identifies a specific datatype to be used to represent the
enumeration. Listing 34 shows our previous enum declaration with a representation type of
sdml:integer
, however the specific values for each variant are still assigned by the model. Only types derived from
the types boolean
, decimal
, integer
, iri
, string
or unsigned
are allowed as enum representation types.
module example is import owl enum DistanceUnit of @owl:equivalentClass = sdml:integer Meter Foot end end
It is possible for the model author to set specific values for each variant, if they have provided an equivalent class
annotation to the declaration for the enum itself. To accomplish this, the author adds an rdf:value
attribute property
to each variant with a unique value with the datatype specified.
module example is import [ owl rdf ] enum DistanceUnit of @owl:equivalentClass = sdml:integer Meter is @rdf:value = 1 end Foot is @rdf:value = 2 end end end
Specifically, to assign values to enumerations the following constraints must be met:
- The value of an
owl:equivalentClass
annotation on an enum declaration must be one of the supported datatypes, or a type derived from one. - If more than one
owl:equivalentClass
annotation is present they must have distinct base data types. See listing 36. - The presence of an
rdf:value
annotation on a variant requires the presence of a correspondingowl:equivalentClass
annotation on the enclosing enum declaration. - If no
rdf:value
is specified for a variant the model will assign a unique value.- It is recommended that you either specify all values, or leave all to the model to assign.
- Each variant’s
rdf:value
must be unique. - Where the literal syntax of a type overlaps, specifically for
signed
andunsigned
integers, type constructors may be used to disambiguate the value type. See listing 37.
module example is import [ owl rdf ] enum DistanceUnit of @owl:equivalentClass = sdml:integer @owl:equivalentClass = sdml:string Meter is @rdf:value = 1 end Foot is @rdf:value = 2 end end end
module example is import [ owl rdf ] enum DistanceUnit of @owl:equivalentClass = sdml:integer @owl:equivalentClass = sdml:unsigned Meter is @rdf:value = xsd:integer(1) end Foot is @rdf:value = xsd:integer(2) end end end
2.7.5. Disjoint Unions
A disjoint, or discriminated, union is a mechanism to allow for a selection of disjoint types to be treated as a single
type. As such the variants of the union are simply type references, although they do allow annotations. Note also that
the keyword of
and not is
starts a union body.
UnionDef
::= "union" Identifier UnionBody?
module example is union VehicleClass end
The union VehicleClass
in 38 is valid but incomplete. Completion of the union requires the addition
of a body with type variants.
UnionBody ::= "of" Annotation* TypeVariant+ "end"
TypeVariant ::= IdentifierReference TypeVariantRename? AnnotationOnlyBody?
TypeVariantRename
::= "as" Identifier
The following is a valid and complete example with three type variants and one that is renamed from Van
to LittleTruck
.
module example is union VehicleClass of Car Truck Van as LittleTruck end end
2.8. Structure Members
Product type members should be familiar to anyone exposed to either programming languages or data definition languages such as SQL. A name is associated with a type, an optional inverse name, and may have annotations.
Member ::= MemberDef | PropertyRef
MemberDef ::= Identifier TypeExpressionTo AnnotationOnlyBody?
module accounts is structure Account is name -> string end end
2.8.1. Member Types
To allow for the capture of member names before the elaboration of all types the language allows for the
target type to be replaced with the keyword unknown
. This marks the member, and by extension it’s owning type, as
incomplete. Note the grammar allows ASCII and Unicode representations of the has type operator.
TypeExpressionTo ::= HasType Cardinality? "features"? TypeReference HasType ::= "->" | "→" TypeReference ::= UnknownType | IdentifierReference | BuiltinSimpleType | MappingType UnknownType ::= "unknown"
A mapping type denotes a functional relationship between the domain (left-hand type) and range (right-hand type).
MappingType ::= "(" TypeReference HasType TypeReference ")"
The syntax for mapping values was previously described in section 2.4.10.
2.8.2. Member Cardinality
The cardinality of a member is specified as a range operation with a minimum and maximum number of occurrences specified. Additionally cardinality constraints determine the ordering and uniqueness of the collection of member values in a sequence type (see section 2.5).
- The form
{1..3}
specifies a cardinality of 1 to 3 inclusive, or \([1,3]\) in interval notation. - The form
{1..}
specifies a minimum of 1 occurrences an unbounded maximum, or \([1,\infty]\) in interval notation. - The form
{1}
specifies that 1 is both the minimum and maximum value, or \([1,1]\) in interval notation, commonly termed a degenerate interval.
Cardinality ::= "{" SequenceOrdering? SequenceUniqueness? CardinalityInner "}" CardinalityInner ::= Unsigned CardinalityRange? CardinalityRange ::= ".." Unsigned?
SequenceOrdering ::= ( "ordered" | "unordered" ) SequenceUniqueness ::= ( "unique" | "nonunique" )
2.8.3. Identity Members
EntityIdentity
::= "identity" Member
module accounts is datatype AccountId <- opaque string entity Account is identity id -> AccountId name -> string end end
2.8.4. Property Reference
PropertyRef
::= "ref" IdentifierReference
In the following example (42) we see two members, default
and name
. The former is actually a reference to a property role,
specifically the role default
within the property definition AccountId
in module account
. The latter is a defined member
with the name name
and the type string
and default cardinality.
module example is import accounts structure Record is ref accounts:accountId name -> string end end
2.8.5. Feature Members
…
TypeExpressionTo
::= HasType Cardinality? "features"? TypeReference
module rentals is import vehicles entity Rental is identity id -> unknown options -> features vehicles:Options end end
module rentals is import vehicles entity Rental is identity id -> unknown options -> vehicles:Options is assert is_exclusive_feature is forall self ( self.type_of_type = sdml:Union and is_unique({ t | forall t in X, type_of(t) }) ) end end end end
2.9. Property Definitions
SDML provides for a data dictionary, or element-first, style of model capture where key properties of the domain are defined and annotated in a stand-alone form. These are often key identifiers and other shared properties
PropertyDef
::= "property" MemberDef
module example is property account_id -> unknown end
The property account_id
in 45 is valid but incomplete. Completion of the property requires the
addition of a body with roles defined.
The following is a valid and complete example.
module account is datatype Accountid <- unsigned property accountId -> {1} AccountId is @skos:prefLabel = "account identifier"@en @skos:definition = "The identifier that denotes that this entity has a one-to-one relationship with a customer account."@en end entity Account end
3. Common Modeling Questions
3.1. Why are Enumerations and Unions distinct
TBD
3.2. How to model Polymorphism
The term polymorphism is, in itself, polymorphic; the intent in software to create a structure that may be of more than one type is useful enough that it has been invented a number of times.
In programming language theory and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types. — Wikipedia21
- Pointers, and especially the C
void*
- Operator overloading or dispatching, including methods and multi-methods
- Discriminated Unions, also known as disjoint unions, variant records (Pascal family), tagged types (Ada)
- Inheritance, of both interface and implementation
- Interface, or protocol, implementation
As SDML does not support pointers, inheritance, or interface definitions, polymorphism is provided only via union types.
3.3. Modeling Features, Sets and Extensions
In software development, a feature model is a compact representation of all the products of a Software Product Line in terms of features. Feature models are visually represented by means of feature diagrams. Feature models were first introduced in the Feature-Oriented Domain Analysis (FODA) method 1990. Since then, feature modeling has been widely adopted by the software product line community and a number of extensions have been proposed.
The following explanation of figure 7 demonstrates the major components of a feature diagram
by modeling the enumeration AdType
above as a feature extension point. Figure 8 contains specific
details on feature diagrams and their elements.
Figure 7: Feature Diagram
The following explanation of figure 7 demonstrates the major components of a feature diagram, more details are shown in figure 8.
- The filled top-level element is the feature host, the structure that contains the feature extension point.
- The element named Ad Type is the feature extension point; the line from host to extension point has a filled circle which denotes a mandatory requirement, one of the possible features must be present.
- The extension point has an attached triangle which denotes how the features in the attached feature set are present;
- An open triangle represents the disjunction (or) operator and so any combination of features may be present, but each may only be present once.
- A filled triangle represents the conjunction (and) operator and so all features must be present.
- An open triangle with the letter x inside represents the exclusive disjunction (xor) operator and so only one of the features may be present.
- A dependency relationship, not shown in figure 7, allows for the expression of feature to
feature dependencies within and across feature sets. Specific forms of dependency are often expressed, such as:
- Requires, the inclusion of the one feature requires that the other feature be present.
- Excludes, the inclusion of the one feature requires that the other feature not be present.
Figure 8: Feature Diagram Elements
While figure 8 shows the common elements of a feature diagram, these have been extended in different implementations. Figure 9 shows some of common extensions.
- Some implementations extend the notion of mandatory and optional relationships between hosts and extension points by replacing them with cardinality expressions.
- Less commonly the relationship from extension point to feature may be annotated with a cardinality expression.
- It is not clear in some implementations whether an extension point may have one or more than one feature sets. Multiple feature sets of this form get confusing if the same feature is present in both sets.
- Alternatively the idea of feature sets directly owning other feature sets is present in some literature but again can be hard to control complexity.
Figure 9: Feature Diagram Extended Elements
In the cases of multiple feature sets it is …
module ad is structure Ad is adType -> AdType end features AdType of Product as ProductAd is @dc:description = "implies SD or SP" end Product as Image is @dc:description = "implies SD" end Product as Video is @dc:description = "implies SD" end retail:Asin as ProductCollection is @dc:description = "implies SB" end retail:Asin as SponsoredTv is @dc:description = "implies SPOT" end retail:BrandId as BrandVideo is @dc:description = "implies SB" end StoreSpotlight is @dc:description = "implies SB" end Post as PromotedPost is @dc:description = "implies SB" end end end
3.4. When to use Property Definitions
module example is import [ skos xsd ] datatype AccountId <- xsd:long datatype ExternalAccountId <- uri structure ChildAccountId is parent -> AccountId child -> AccountId end union AllAccountIds of AccountId, ExternalAccountId, ChildAccountId end end
module example is import [ skos xsd ] property account_id is internal -> {1} xsd:long is @skos:definition = "This is our internal representation."@en end external -> {1} url is @skos:definition = "This is our external representation for customers."@en end child -> {2} xsd:long end entity Account is identity account_id as internal end end
4. Capturing Model Constraints
In developing any data, domain, or information model there are certain constraints between model elements that are either difficult to represent in the model language itself. This is particularly true of visual languages or those with a visual representation. For example, in UML it is not possible to define cross-attribute constraints (if this attribute is true, that attribute must be false), or setting cardinality on transitive associations. To address this UML uses Object Constraint Language22, 23, and similar languages have been proposed for parametric modeling languages.
SDML allows constraints to be added to any model element where annotations are also allowed. Constraints are named, and MAY be either informally, or formally specified.
Constraint ::= "assert" Identifier ( InformalConstraint | FormalConstraint )
Informal constraints may be simple free-form strings, these are a common place to start even if you intend to make a formal version later. An example informal constraint is shown in listing 50. However, they may also be written using controlled natural languages for an additional level of specificity.
module example is assert must_have_preferred_label = "All definitions in this module MUST have a skos:prefLabel annotation property." end
4.1. Informal Constraints
Informal constraints, much like regular string values, have an optional language specifier. However, unlike regular strings this specifier has a more limited range as it’s purpose is to identify a controlled natural language used for specification.
InformalConstraint
::= "=" QuotedString ControlledLanguageTag?
The controlled language tag only includes the primary language component of a regular SDML string but adds a scheme identifier.
ControlledLanguageTag ::= "@" LanguageTagPrimary ControlledLanguageScheme? ControlledLanguageScheme ::= "-" ASCII_LETTER ASCII_LETTER ASCII_LETTER ASCII_LETTER? ASCII_LETTER? ASCII_LETTER? ASCII_LETTER? ASCII_LETTER? ASCII_LETTER?
module example is assert must_have_preferred_label = "..."@en end
For example, Common Logic Controlled English24 (or Attempto Controlled English25) are well-known controlled forms of English with formal structure and defined transformations into logical forms.
module example is assert must_have_preferred_label = "..."@en-CLCE end
While no registry exists for scheme identifiers, the following identifiers must be used for the schemes listed in table 7.
Name | Identifier | Alternate | Formalism |
---|---|---|---|
Attempto Controlled English | ACE |
Attempto |
|
Common Logic Controlled English | CLCE |
ISO Common Logic |
4.2. Formal Constraints
Formal constraints have their own embedded constraint language whose semantics are a subset of ISO Common Logic26. ISO Common Logic is most easily understood as an abstract language that allows for the interchange of logical expressions based in first-order predicate logic between systems using standardized representations (Common Logic Interchange Format (CLIF), Conceptual Graph Interchange Format (CGIF), and XML notation for Common Logic (XCL)).
FormalConstraint ::= "is" ConstraintEnvironment? ConstraintSentence "end"
Listing 53 is another informal constraint.
module tagged is datatype Tag <- string structure Target is tags -> {0..} Tag ;; plus more interesing things... end structure Thing is tags -> {0..} Tag inner -> Target is assert tag_subset = "All of target's tags must exist in my tags." end end end
Listing 54 is a formal version of listing 53 which iterates over the set
of tags on the type Target
which is the type of the member inner
which is aliased to self
(self.tags
) within the constraint body.
For each element in the sequence of tags an existential statement ensures there is an equal tag within the enclosing
structure’s tag collection (self.container.tags
).
module tagged is structure Thing is tags -> {0..} Tag inner -> Target is assert tag_subset is ∀ tag ∈ self.tags ( ∃ ptag ∈ self.container.tags ( ptag = tag ) ) end end end
Alternatively, listing 55 turns each sequence of tags into a set with the library function
into_set
and can then perform a simple subset check with the library function is_subset
.
module tagged is structure Thing is tags -> {0..} Tag inner -> Target is assert tag_subset is ∀ self ( is_subset( into_set(self.tags) into_set(self.container.tags)) ) end end end end
Using the terminology of Common Logic a formal constraint consists of a constraint sentence which is evaluated in the
context of the model element the constraint is attached to. This contextual element is bound to the identifier self
and
the type of self
is bound to the identifier Self
. Additionally, an optional environment allows for the definition of
temporary values and functions for use in the constraint sentence.
ConstraintSentence ::= SimpleSentence | BooleanSentence | QuantifiedSentence | "(" ConstraintSentence ")"
4.2.1. Simple Sentences
SimpleSentence ::= Atomic | Equation | Inequation
In the case of an atomic sentence the first term is the name of a predicate and the remaining terms are arguments to the predicate invocation.
Atomic ::= Term ActualArguments ActualArguments ::= "(" Term* ")"
An equation asserts the equality of it’s left and right operand.
Equation
::= Term "=" Term
An inequation asserts the inequality of it’s left and right operand according to some relation.
Inequation ::= Term InequationRelation Term
The following are the recognized inequality relations, note the inclusion of both ASCII and Unicode versions of some relation symbols.
InequationRelation ::= ( '/=' | '≠' | '<' | '>' | '<=' | '≤' | '>=' | '≥' )
4.2.2. Boolean Sentences
Boolean sentences allow for the logical composition of sentences.
BooleanSentence ::= UnaryBooleanSentence | BinaryBooleanSentence
UnaryBooleanSentence ::= NegationOperator ConstraintSentence Negation ::= ( "not" | "¬" ) ConstraintSentence
BinaryBooleanSentence ::= ConstraintSentence LogicalConnective ConstraintSentence LogicalConnective ::= ConjunctionOperator | DisjunctionOperator | ExclusiveDisjunctionOperator | ImplicationOperator | BiconditionalOperator
ConjunctionOperator ::= ( "and" | "∧" ) ConstraintSentence DisjunctionOperator ::= ( "or" | "∨" ) ConstraintSentence ExclusiveDisjunctionOperator ::= ( "xor" | "⊻" ) ConstraintSentence ImplicationOperator ::= ( "implies" | "⇒" ) ConstraintSentence BiconditionalOperator ::= ( "iff" | "⇔" ) ConstraintSentence
4.2.3. Quantified Sentences
SDML supports, as shown in listing 54 sentences that range over values using the universal (\(\forall\)) or existential (\(\exists\)) quantifiers. A quantified sentence is therefore a sentence body with a set of variables bound to these quantified values.
QuantifiedSentence
::= QuantifiedVariableBinding+ ","? ConstraintSentence
Each variable binding specifies a quantifier, one or more names, and a source for variable values.
QuantifiedVariableBinding ::= ( UniversalQuantifier | ExistentialQuantifier ) QuantifiedVariable UniversalQuantifier ::= "forall" | "∀" ExistentialQuantifier ::= "exists" | "∃"
The special variable self
is a shortcut for the set of values from the type of the element to which the constraint is
attached.
QuantifiedVariable ::= ReservedSelf | Identifer MembershipOperator Term MembershipOperator ::= ( "in" | "∈" )
4.2.4. Terms
Term ::= SequenceBuilder | FunctionalTerm | FunctionComposition | IdentifierReference | ReservedSelf | PredicateValue
FunctionalTerm ::= Term ActualArguments
FunctionComposition ::= ( "self" | Identifier ) ( "." Identifier )+
This notation is effectively syntactic sugar as it can be systematically transformed into the form above. The transformation rules are:
- The reserved words
self
andSelf
may only appear as the first element. - The list of names after the first are reversed and applied in turn as functions.
name.suffix
becomessuffix(name(_))
- The first element will become the first argument to the inner-most function.
name.suffix
becomessuffix(name(def))
- If the path ends in an argument list the arguments are added to the corresponding function call.
suffix(name(def)) + ("Rental")
becomessuffix(name(def) "Rental")
Similarly the quantifier collection self.definitions
becomes definitions(self)
.
PredicateValue ::= SimpleValue | SequenceOfPredicateValues SequenceOfPredicateValues ::= "∅" | SequenceValueConstraint? "[" ( SimpleValue | IdentifierReference )* "]"
Note that the keyword ∅
denotes the empty set, or an empty sequence []
that is unique. Note that, like a normal sequence
of values, sequence constraints may be included.
ReservedSelf
::= "self"
4.2.5. Sequence Builders
A sequence builder uses set-builder notation to allow both specific selection of elements from sequences and values from types. A sequence builder expression describes a sequence as a selection of values from existing sequences filtered using predicates.
The simplest form of a builder expression is \(\bigl\{ x \mid P(x)\bigr\}\), where \(x\) is a variable and \(P\) is a predicate that
evaluates to true
to select the value of \(x\). In natural language this expression read as follows “construct a sequence
of values of \(x\) such that the predicate \(f\) holds true for \(x\)”.
However, this simple form tells us nothing about the domain of the variable \(x\), and in fact \(x\) is a free variable in the expression because of this lack of binding. The expression \(\bigl\{ x \mid \forall x \in X, P(x)\bigr\}\) binds the variable \(x\) to the elements of the sequence, or the values of the type, named \(X\). SDML does not allow free variables in sequence builders and so variable bindings must be present for all specified variables.
SequenceBuilder ::= "{" ( NamedVariableSet | MappingVariable ) "|" SequenceBuilderBody "}"
The variables returned to the caller are specified before the |
character and may either be a list of identifiers or a
single mapping of identifer to identifer.
NamedVariableSet ::= Identifier+ MappingVariable ::= "(" Identifier HasType Identifier ")"
With a named variables set the builder returns a single value and a set of relations named for the variable identifiers.
\[t ≔ \bigl\{ x, y \mid x \in X, y \in Y, P(x) \land Q(y) \land x = y \bigr\}\]
On the other hand, a mapping variable returns a sequence of mapping values which may comprise a lookup table.
\[t ≔ \bigl\{ \left(d \rightarrow r\right) \mid \forall d \in D, \exists r \in R, id_{D}(d) = id_{R}(r) \bigr\}\]
SequenceBuilderBody ::= QuantifiedSentence | "(" QuantifiedSentence ")"
4.2.6. Constraint Environment
A formal constraint may start with an environment that includes one or more definitions that are then used in the constraint body. A definition introduces a new function or constant – a constant is effectively a 0-arity function – although the function body may be a value or a constraint sentence and not specify side-effects.
ConstraintEnvironment
::= EnvironmentDef+ "in"
EnvironmentDef ::= "def" Identifier ( FunctionDef | ConstantDef )
A defined function has a signature that describes it’s domain, or parameters, and it’s range, or return type.
FunctionDef ::= FunctionSignature FunctionBody FunctionSignature ::= ( "(" FunctionParameter+ ")" )? FunctionType FunctionParameter ::= Identifier FunctionType
Function type specifications may use wildcard symbols to denote their application to undefined types.
FunctionType ::= HasType FunctionCardinality? FunctionTypeRef FunctionCardinality ::= "{" SequenceOrdering? SequenceUniqueness? CardinalityInner? "}" FunctionTypeRef ::= Optional? ( IdentiferReference | BuiltinSimpleType | MappingType ) Optional ::= "?"
FunctionBody ::= ByDefinition ConstraintSentence
ConstantDef ::= ByDefinition ( PredicateValue | ConstraintSentence )
The example in listing 56 is an expansion of the informal example in listing
50. Here we check for the presence of the annotation on every top-level definition and then any
members of that definition. Note that this example also demonstrates the ability to create new predicates as in
has_pref_label
which simplifies the body of the constraint.
module labelled is assert must_have_preferred_label is def has_pref_label(anns → {0..} Annotation) ≔ ∃ a ∈ anns ( a.name = skos:prefLabel ∧ ¬a.value.is_empty ) in has_pref_label(self.annotations) ∧ ∀ d ∈ self.definitions ( has_pref_label(d.annotations) ∧ Entity(d) ⇒ ∀ m ∈ d.flat_members ( has_pref_label(m.annotations) ) ∧ Enumeration(d) ⇒ ∀ m ∈ d.variants ( has_pref_label(m.annotations) ) ∧ Event(d) ⇒ ∀ m ∈ d.flat_members ( has_pref_label(m.annotations) ) ∧ Structure(d) ⇒ ∀ m ∈ d.flat_members ( has_pref_label(m.annotations) ) ∧ Union(d) ⇒ ∀ m ∈ d.variants ( has_pref_label(m.annotations) ) ∧ Property(d) ⇒ ∀ m ∈ d.roles ( has_pref_label(m.annotations) ) ) end end
4.2.7. Optional Types
The introduction of optional types, that it a type name preceded by the operator “?” may be considered a convenience
over the same type specified with a cardinality of {0..1}
. However, this greatly simplifies the implementation of
constraints over possibly missing values.
4.3. Type Classes
TBD
TypeClassDef
::= "class" Identifier TypeClassParameters TypeClassBody?
4.3.1. Type Parameters
TypeClassParameters ::= "(" TypeVariable+ ")" TypeVariable ::= FunctionCardinality? Identifier TypeVariableRestriction?
TypeVariableRestriction ::= HasType TypeClassReference ( "+" TypeClassReference )* TypeClassReference ::= IdentiferReference TypeClassArguments? TypeClassArguments ::= "(" ( Wildcard | TypeClassReference+ ) ")" Wildcard ::= "_"
TypeClassBody ::= "is" Annotation* MethodDef+ "end"
4.3.2. Methods
MethodDef
::= "def" Identifier FunctionSignature FunctionBody? AnnotationOnlyBody?
4.3.3. Library Functions
def predicate(Type) -> boolean def predicate(value -> Type) -> boolean
def relation(Type1, Type2) -> boolean def relation(value1 -> Type1, value2 -> Type2) -> boolean
def function(Type, ...) -> RType def function(value -> Type, ...) -> RType
5. Walk-Through Example
The following section walks through the process of model creation and iterative elaboration to add detail as domain experts provide more information. In our example we will build a domain model for a vehicle rental company.
5.1. Starting with Core Entities
So, as you might imagine we can start the model with some pretty obvious entities, but with no detail beyond their names. Listing 57 is this first-pass domain model.
module rentals is entity Booking entity Customer entity Location entity Vehicle end
The sdml
tool can be used to generate a concept diagram, a high-level view of only the entities in the model and their
relationships. The following command-line will generate figure 10 from listing
57.
sdml draw --diagram concepts \ --output-format svg \ --output-file docs/rentals-concept-1.svg \ docs/rental-1.sdm
Figure 10: Rentals Concepts (First Pass)
5.2. Adding Relationships
Figure 58 is the second-pass at our model, adding relationships from the Booking
entity to the
Customer
, Location
, and Vehicle
entities. Adding a body to the Booking
entity, between is
… end
, requires the entity
specify an identifier. To allow the addition of bookingCode
without spending time on the details of it’s type the
keyword unknown
is used to defer the type until later.
Additionally, the vehicle
relationship specifies a new cardinality overriding the usual 0..
with 0..1
.
module rentals is entity Booking is identity bookingCode -> unknown vehicle -> {0..1} Vehicle from -> Location customer -> Customer end entity Customer entity Vehicle entity Location end
Re-running the sdml
tool on listing 58 will result in the relationships added, as shown in figure
11.
Figure 11: Rentals Basic Relationships
5.3. Adding our first Properties
In the next edit of our rentals model we see some additions:
- The use of
import
to use theVehicleIdentificationNumber
data type from thecids
(Common Identifiers) model. - The new entity
Class
to model the rental class of a vehicle. Vehicle
now has an identifier, a relationship to the newClass
entity, and a relationship toLocation
.Booking
also has a relationship to the newClass
entity.- We demonstrate the non-range cardinality notation,
{1}
, which is a shortcut for the range1..1
.
module rentals is import cids:VehicleIdentificationNumber entity Booking is identity bookingCode -> unknown bookingClass -> {1} Class vehicle -> {0..1} Vehicle from -> Location customer -> {1} Customer end entity Vehicle is identity vin -> cids:VehicleIdentificationNumber rentalClass -> {1} Class onProperty -> {0..1} Location end entity Class entity Customer entity Location end
As usual we generate the concept diagram in figure 12 from listing 59.
Figure 12: Rentals More Detail
5.4. More detail, and a new diagram
module rentals is import [cids:VehicleIdentificationNumber cids:Uuid] import [xsd:date xsd:string] structure Address entity Booking is assert dropoff_after_pickup = "Ensure the dropoff date is greater than or equal to the pickup date." identity bookingCode -> unknown bookingClass -> {1} RentalClass vehicle -> {0..1} Vehicle pickup -> {1} Location pickupDate -> {1} xsd:date dropoff -> {1} Location dropoffDate -> {1} xsd:date customer -> {1} Customer end entity Customer is assert drivers_license_valid = "Ensure the drivers license date is in the future." identity id -> cids:Uuid contactAddress -> {1} Address driverLicense -> {0..1} LicenseInformation end structure LicenseInformation is number -> xsd:string state -> xsd:string expiration -> xsd:date end entity Location is identity id -> cids:Uuid address -> {1} Address end enum RentalClass entity Vehicle is identity vin -> cids:VehicleIdentificationNumber rentalClass -> {1} RentalClass end end
As usual we generate the concept diagram in figure 13 from listing 60.
Figure 13: Rentals Non-Entity Detail
sdml draw --diagram entity-relationship \ --output-format svg \ --output-file docs/rentals-concept-4.svg \ --input-file docs/rentals-4.sdm
Figure 14: Rentals ERD Diagram
5.5. More detail, and yet another diagram
module rentals base <https://example.org/v/rentals> is import [ dc sdml skos xsd ] ;; This is actually a US VIN import cids:VehicleIdentificationNumber @dc:version = 2 structure Address entity Booking is assert dropoff_after_pickup = "Ensure the dropoff date is greater than or equal to the pickup date." identity bookingCode -> BookingCode customer -> {1} Customer group @skos:prefLabel = "Rental Period"@en pickup_date -> xsd:date pickup_location -> Location dropoff_date -> xsd:date dropoff_location -> Location end group @skos:prefLabel = "Requested"@en bookingClass -> {1} RentalClass vehicle -> {0..1} Vehicle end end entity Customer is identity id -> UniqueId group @skos:prefLabel = "Personal Information" name -> string contactAddress -> {1} Address driverLicense -> {0..1} LicenseInformation end group @skos:prefLabel = "Membership" level -> 0..1 MembershipLevel since -> xsd:date end end structure LicenseInformation is assert drivers_license_valid = "Ensure the drivers license date is in the future." number -> xsd:string state -> xsd:string expiration -> xsd:date end entity Location is identity id -> cids:Uuid address -> {1} Address end enum MembershipLevel of Basic Silver Gold end enum RentalClass of SubCompact Compact FullSize CompactUtilityVehicle FullSizeUtilityVehicle Luxury Sports end datatype BookingCode <- xsd:string is @xsd:pattern = "[A-Za-z0-9_-]{22}" end entity Vehicle is @skos:prefLabel = "Vehicle"@en identity vin -> VehicleIdentificationNumber information -> VehicleInformation rentalClass -> {1} RentalClass end structure VehicleInformation is make -> xsd:string model -> xsd:string year -> xsd:integer is @xsd:minValue = 2000 @xsd:maxValue = 3000 end end event VehicleAssigned source Booking is vehicle -> VehicleIdentificationNumber end end
Figure 15: Rentals Structural Detail
6. Implementation and Tools
Figure 4 gave a high-level overview of transformations, this section will expand not only on transforms but also the other capabilities of the tooling for SDML.
One element that is not included in figure 1, as it is primarily an implementation decision, is that the reference implementation for SDML is provided as a Tree-Sitter27 package. While generated tree-sitter parsers are not intended to be fast, or to be part of a compilation processes they do provide a multitude of language bindings, and a concrete syntax tree allowing easy development of the relatively simple tools required for SDML usage.
As such the much expanded figure 16 shows the
Figure 16: Implementation Overview
6.1. Installation
While the following sections will provide more details on the components of figure 16 this section will cover the install script provided on this site. Because the script was developed on, and for, Mac systems, it may work on Linux, but at this time has no Windows counterpart.
The following is all that’s necessary to fetch and execute the installer.
$ curl https://sdml.io/install.sh | bash
Alternatively, you can download the script, saving it to a local file, which may be executed later.
$ curl https://sdml.io/install.sh > install-sdml.sh
The script performs the following:
- Install the Homebrew package manager, if not present.
- Using brew:
- Using cargo, install sdml-cli, for the sdml command.
- Prompt the user to install an editor and associated language support packages.
Table 8 lists the supported editors at the time of writing. Note that while Emacs has had the most testing it is probably the most complex install process and learning curve if you haven’t used Emacs in the past. For this reason alone we recommend Visual Studio Code as the easiest to configure and get started with.
Editor | See | Mechanism | Package | Status |
---|---|---|---|---|
Emacs (<29) | §6.3 | Tree-sitter | sdml-mode.el | Primary tool, tested |
VSCode | §6.4 | TextMate grammar | sdml extension | Secondary tool, tested |
TextMate | §6.5.1 | TextMate grammar | SDML.tmplugin | Some testing |
Sublime Text | §6.5.2 | TextMate grammar | SDML.tmplugin | Untested |
IntelliJ IDEA | §6.5.3 | TextMate grammar | SDML.tmplugin | Untried |
Vim (Neovim) | §6.5.4 | Tree-sitter | tree-sitter-sdml | Untried |
6.2. Command-Line Tool
The SDML command-line tool is used to validate module source files as well as generate different representations and
diagrams from the source. Once installed the option --help
or the command help
will guide you through the commands
available.
$ sdml --help Rust CLI for Simple Domain Modeling Language (SDML) Usage: sdml [OPTIONS] <COMMAND> Commands: highlight Highlight an SDML source file tags Extract tags from an SDML Module convert Convert SDML modules into other formats draw Draw diagrams from SDML modules help Print this message or the help of the given subcommand(s) Options: -v, --verbose... More output per occurrence -q, --quiet... Less output per occurrence -h, --help Print help -V, --version Print version
For most commands the tool accepts a pair of arguments that determine the format and location of the generated output.
These commands --output-format
/ -f
and --output-file
/ -o
can be used in the following manner.
--output-format svg --output-file rentals.svg -output-file rentals.svg
In the second example above the format is not specified and so the tool will use the extension of the output file as the format specifier. If the output file is not specified the output is written to stdout. If neither argument is provided the output is written to stdout in the default format.
Similarly most tools use the module resolution rules for loading a module and so the input file is actually specified
as a module name. To allow for searches in non-standard locations the argument --base-path
/ -b
can be used to
prepend a path to the standard search path. Thus the two examples below are identical as the current directory is always
a component of the search path.
--base-path . rentals rentals
If you wish to install the command-line tool on it’s own, without using the install script above, it is simple using the
cargo
command. If you do not have cargo installed you can follow the standard Rustup directions28 or use the
Homebrew29 or equivalent package manager.
$ cargo install sdml-cli
6.2.1. Highlighting
$ sdlml highlight --help Highlight an SDML source file Usage: sdml highlight [OPTIONS] [MODULE] Arguments: [MODULE] SDML module, loaded using the standard resolver Options: -f, --output-format <OUTPUT_FORMAT> Format to convert into [default: ansi] Possible values: - ansi: ANSI escape for console - html: HTML pre-formatted element - html-standalone: HTML stand-alone document -v, --verbose... More output per occurrence -o, --output-file <OUTPUT_FILE> File name to write to, if not provided will write to stdout -q, --quiet... Less output per occurrence -b, --base-path <BASE_PATH> A path to pre-pend to the resolver search path -i, --input-file <INPUT_FILE> SDML File name, load without resolver -h, --help Print help (see a summary with '-h') -V, --version Print version
For the console this uses ANSI escape sequences to format the text.
$ sdml highlight --output-format ansi rentals
To generate formatted and highlighted HTML the tool accepts two different format specifiers, html
for simply a block
of HTML that can be inserted into another document, or html-standalone
to generate a full document around the
highlighted code block.
$ sdml highlight --output-format html rentals $ sdml highlight --output-format html-standalone rentals
6.2.2. Draw Diagrams
To draw a high-level Concepts diagram, use the diagram specifier concepts
. To generate an image file you need to install
Graphviz30.
$ sdml draw --diagram concepts \ --output-format svg --output-file rentals.svg \ --base-path . rentals
For more detail an Entity-Relationship diagram can be generated with the diagram specifier entity-relationship
. To
generate an image file you need to install Graphviz30.
$ sdml draw --diagram entity-relationship \ --output-format svg --output-file rentals.svg \ --base-path . rentals
For the mose detail a UML Class diagram can be generated with the diagram specifier uml-class
. The generated diagram
applies the profile outlined in appendix 13. To generate an image file you need to install
PlantUML31.
$ sdml draw --diagram uml-class \ --output-format svg --output-file rentals.svg \ --base-path . rentals
6.2.3. Module Resolution
A particular module rentals
is resolved by looking for the module name first with the “.sdm” and then the “.sdml”
extension. If neither of these exist the same pair will be checked within a directory named rentals
. So in total the
following four file paths are checked.
{prefix}rentals.sdm {prefix}rentals.sdml {prefix}rentals/rentals.sdm {prefix}rentals/rentals.sdml
The purpose of the variable {prefix}
is to allow the resolver to use an environment variable, SDML_PATH
, to find and
load module files. Each path within the variable is used as a prefix in turn. The command line option base-path
allows
you to prepend a value to the SDML_PATH
list for just this command. In the following example:
SDML_PATH=lib/sdml sdml highlight --base-path ./examples rentals
The list of file paths to check are:
lib/sdml/rentals.sdm lib/sdml/rentals.sdml lib/sdml/rentals/rentals.sdm lib/sdml/rentals/rentals.sdml ./examples/rentals.sdm ./examples/rentals.sdml ./examples/rentals/rentals.sdm ./examples/rentals/rentals.sdml
This same resolution mechanism is used for all imported values, in the following example each module skos
, other
, and
more
. With one exception, the module skos
is known to the sdml command and it uses an internal representation.
module subDomain is import skos:prefLabel import other:Something import more end
6.3. Emacs
Emacs has been the primary playground for SDML and as such has some tools already built for editing and documentation.
The package sdml-mode (currently not in any package repository) is associated with any file ending in .sdm
or .sdml
with
highlighting and indentation support. Follow the installation instructions on the package’s own site.
Emacs standard customization allows for managing all package variables and can be accessed via:
M-x customize-group RET sdml RET
The installer script performs the following steps if you select Emacs as your editor:
- Install Emacs (emacs-plus@28) using brew.
- Create the Emacs init directory.
- Install Git using brew.
- Clone the
tree-sitter-sdml
repository into the Emacs init directory. - Install GNU Make using brew.
- Make the tree-sitter library and install for Emacs.
- Clone the
emacs-sdml-mode
repository into the Emacs init directory.
6.3.1. Highlighting
Syntax highlighting is enabled by the tree-sitter-hl-mode
minor mode based on the configuration in the constant
sdml-mode-tree-sitter-hl-patterns
. The highlighting (figure 17) also carries over into Org Babel source blocks and into generated
content such as this documentation.
Figure 17: Emacs Syntax Highlighting
The sdml-mode
also adds to the prettify-symbols-alist
list, the set of symbols is in the custom variable
sdml-prettify-symbols-alist
.
6.3.2. Indentation
Line indentation is enabled by the tree-sitter-indent-mode
minor mode based on the configuration in the constant
sdml-mode-folding-definitions
.
Default indentation is two spaces, although this can be overridden by the custom variable sdml-indent-offset
.
6.3.3. Code Folding
Block Folding is provided by the ts-fold-mode
minor mode based on the configuration in the constant
tree-sitter-indent-sdml-scopes
. Note that folding of groups of line comments is also supported.
C-c C-s -
– fold itemC-c C-s +
– unfold itemC-c C-s C--
– fold all items in bufferC-c C-s C-+
– unfold all items in bufferC-c C-s /
– unfold item and all childrenC-c C-s .
– toggle fold/unfold state
As well as the mechanics of folding, the ts-fold
package also has indicator support for windowed clients and this is
enabled by default with window-system
is non-nil.
6.3.4. Abbreviations and Skeletons
This package provides a number of useful abbreviations/skeletons to help editing. The built-in abbrev-mode
is enabled by
sdml-mode
and when typing one of the abbreviations below use a space to expand. For example, typing d t SPC
will prompt
for a name and expand into the SDML declaration where the underscore character represents the new cursor position. A
number of abbreviations will ask for values, check the minibuffer. A complete list of abbreviations set by sdml-mode
is
shown in table 9.
datatype MyName ← opaque _
Kind | Abbreviation | Expands to |
---|---|---|
Declaration | mo |
module |
Declaration | dt |
datatype |
Declaration | en |
enumeration |
Declaration | ev |
event |
Declaration | pr |
property |
Declaration | st |
structure |
Datatype | db |
boolean |
Datatype | dd |
decimal |
Datatype | df |
double |
Datatype | dh |
binary |
Datatype | di |
integer |
Datatype | ds |
string |
Datatype | du |
unsigned |
Annotation Properties | pal |
SKOS alternate label |
Annotation Properties | pdf |
SKOS definition |
Annotation Properties | ped |
SKOS editorial note |
Annotation Properties | ppl |
SKOS preferred label |
Annotation Properties | pco |
RDF Schema comment |
Constraint | ci |
informal constraint |
Constraint | cf |
formal constraint |
Constraint | all |
universally quantified sentence |
Constraint | any |
existentially quantified sentence |
Where an abbreviation expands a property whose value is a language string the current value of the Emacs variable
locale-language
is included as the string language.
6.3.5. Tree-Sitter Debugging
The following two comands are provided by the underlying tree-sitter
package, but exposed here with the common key
prefix.
C-c C-s d
– open the tree-sitter debug viewC-c C-s q
– open the tree-sitter query builder
6.3.6. Ispell
The additional package sdml-ispell
provides selective spell checking by only checking selected nodes in the tree.
C-c C-s s
– spell check the item at pointC-c C-s C-s
– spell check all items in the buffer
By default only strings and comments will be checked, although this can be overridden by the custom variable
tree-sitter-ispell-sdml-text-mapping
.
6.3.7. Flycheck
The additional package flycheck-sdml
provides on-the-fly linting for SDML buffers. To enable, simply ensure Flycheck
mode is enabled for your buffer. Rather than per-buffer, you can enable this by setting flycheck-mode
for all SDML files
with a hook. Figure 17 shows the flycheck buffer open with advice provided and a fringe indicator for
the reported issue.
The entire set of lint rules are stored in the custom variable sdml-lint-rules
with the form:
'(rule-id "Issue message" level "tree-sitter query")
For example, the following rule returns the name of the module, but only if the first character is an upper case letter. This is marked as a warning by Flycheck and provided with the necessary message.
'(module-name-case "Module names may not start with upper-case" warning "((module name: (identifier) @name) (#match? @name \"^[:upper:]\"))")
6.3.8. Org-Mode and Babel
Org-Babel support provides the ability to call the SDML command-line tool to produce diagrams and more. For example, the following source block calls the CLI to draw a concept diagram for the enclosed module. It is worth noting that this documentation has been generated, including all source highlighting and diagrams, from an Org-mode document with Babel.
#+NAME: lst:rentals-example #+CAPTION: Rentals Concepts #+BEGIN_SRC sdml :cmdline draw --diagram concepts :file ./rentals-concepts.svg :exports both module rentals is entity Vehicle entity Location entity Customer entity Booking end #+END_SRC
The results block then references the resulting image.
#+NAME: fig:rentals-example-concepts #+CAPTION: Rentals Concepts #+RESULTS: lst:rentals-example [[file:./rentals-concepts.svg]]
Figure 18 shows this experience in Emacs, note that each SRC
block has language-specific syntax
highlighting. Emacs provides a command, org-edit-special
, which opens the block in it’s own buffer for editing with all
the capabilities of a file-backed resource.
Figure 18: Emacs Org-Mode Editing
But, what if we want to produce more than one diagram from the same source? By using the built-in noweb syntax we can create a new source block, but reference the original content. This source block has different command-line parameters and has it’s own results block as well.
#+NAME: fig:rentals-example-erd #+BEGIN_SRC sdml :cmdline draw --diagram concepts :file ./rentals-erd.svg :exports results :noweb yes <<lst:rentals-example>> #+END_SRC
6.4. Visual Studio Code
Visual Studio Code is a modern, powerful, configurable, and cross-platform editor and IDE. It has excellent language support based on years of Microsoft IDE development experience. VSCode is also lightweight enough to use for quick tasks and launches from the command-line easily. Lastly, VSCode is free and you can download directly from Microsoft or from common package managers such as Homebrew.
The SDML extension is accessible from the Visual Studio Marketplace, as shown in figure 19. This makes installation easy, using the Extensions View, and VSCode will also keep the extension up to date with any published changes.
Figure 19: SDML Extension in the Marketplace
Alternatively, the extension can be installed manually from the command-line which the installer above uses to provide a single-step editor setup.
$ code --force --install-extension SimonJohnston.sdml
The extension provides syntax highlighting, seen in figure 20, as well as a number of snippets for type declarations, annotations, and idioms. The VSCode extension is derived from the TextMate bundle in that it uses the original grammar file as-is, and copies over a number of the snippets into a native format.
Figure 20: SDML Syntax Highlighting
Additionally, the plug-in can invoke the command-line tool to draw diagrams from the file in the current editor window. These commands are accessible via the command palette and all start with the text “draw” for searching. Figure 21 shows the command palette narrowed to the three supported drawing commands.
Figure 21: Drawing Commands in the Palette
These commands are executed in a dedicated Terminal window (figure 22), this has a few advantages to running the command in the background. First, you get to see the command-line used and so you can learn the parameters; secondly, you will see the errors as they are output by the tool without filtering; lastly, you can interact with the Terminal window to try out other commands.
Figure 22: Command-Line Usage in Terminal
The extension has a number of settings that are, of course, available for the user to override if necessary. Figure 23 shows the standard settings window with the SDML section selected. At this time you can change the name of the command-line tool, the name of the terminal window used for output, and the format of drawings generated by the tool.
Figure 23: Extension Settings
The installer script (section 6.1) performs the following steps if you select Visual Studio Code as your editor:
- Install the editor using brew.
- Install the extension from the marketplace.
6.5. Other Editors
6.5.1. TextMate
TextMate is another popular editor, and it’s in-built language descriptions have been adopted by other tools as a semi-standard mechanism for providing syntax highlighting. The SDML TextMate bundle includes a grammar file as well as a set of snippets for easy addition of language features in source files.
Figure 24 shows the bundle menu for SDML with the list of declaration snippets.
Figure 24: Declaration Snippets
Figure 24 shows the bundle menu for SDML with the list of annotation property snippets.
Figure 25: Annotation Snippets
Additionally, every TextMate window has an integrated Symbol List in the footer to easily navigate around a large file. SDML includes every type declaration as a symbol in the list with their corresponding members or variants shown indented as seen in figure 26.
Figure 26: Symbol List Integration
The installer script (section 6.1) performs the following steps if you select TextMate as your editor:
- Install the editor using brew.
- Clone the language bundle into the editor’s bundle folder.
- Have TextMate reload all bundles (on macos only).
6.5.2. Sublime Text
The installer script (section 6.1) performs the following steps if you select Sublime Text as your editor:
- Install the editor using brew.
- Clone the language bundle into the editor’s bundle folder.
6.5.3. IntelliJ IDEA
IntelliJ IDEA, and by extension most of the JetBrains tools, has a native plugin that acts as a bridge to use TextMate bundles as-is.
To enable this plugin go to the IDE settings and select the Plugins section and the Installed tab. Find the bundle named “TextMate Bundles” and set the checkbox next to it to enable. Click the Apply button to save the changes and close the dialog. Restart the IDE if prompted.
To add the SDML bundle either download the bundle from https://github.com/sdm-lang/SDML.tmbundle, or use Git to clone
onto your local file system. Go to the IDE settings and select the Editor section and TextMate Bundles sub-section.
Click the +
at the top of the list and locate the bundle you downloaded. You should see it now in the list of bundles
and you can enable/disable it with the checkbox next to it.
Opening SDML files should now provide syntax highlighting.
The installer script (section 6.1) performs the following steps if you select IDEA as your editor:
- install the editor using brew.
- Point the user to next-step instructions.
6.5.4. Neovim
Neovim has some great tree-sitter support with the nvim-tree-sitter and a nice in-editor playground. Firstly, add the tree-sitter plugin, but don’t install yet, as shown on the nvim-treesitter page (using vim-plug):
call plug#begin() Plug 'nvim-treesitter/nvim-treesitter', {'do': ':TSUpdate'} call plug#end()
Now, add the following Lua block, using content from the homepage, and then set the ensure_installed
to include the
query language and so highlight tree-sitter queries and set ignore_install
to include sdml before installing the
tree-sitter plugin. The builtin sdml grammar will unfortunately install queries that will then be a problem so we want
to avoid that.
require'nvim-treesitter.configs'.setup { ensure_installed = { "query" }, sync_install = false, auto_install = true, highlight = { enable = true, disable = {}, additional_vim_regex_highlighting = false, }, incremental_selection = { enable = true, keymaps = { init_selection = "gnn", node_incremental = "grn", scope_incremental = "grc", node_decremental = "grm", }, }, indent = { enable = true }, query_linter = { enable = true, use_virtual_text = true, lint_events = {"BufWrite", "CursorHold"}, }, }
To allow tree-sitter to do folding based on folds.scm
, add the following to init.vim
.
set foldmethod=expr set foldexpr=nvim_treesitter#foldexpr()
Once the core plugin has been installed you can add the following Lua block in init.vim
to install this grammar.
local parser_config = require "nvim-treesitter.parsers".get_parser_configs() parser_config.sdml = { install_info = { url = "https://github.com/sdm-lang/tree-sitter-sdml", files = {"src/parser.c"}, generate_requires_npm = true, requires_generate_from_grammar = true, }, filetype = "sdm", maintainers = { "@johnstonskj" }, }
Additionally, try the nvim-treesitter-context and nvim-treesitter-refactor plugins based on tree-sitter.
call plug#begin() Plug 'nvim-treesitter/nvim-treesitter', {'do': ':TSUpdate'} Plug 'nvim-treesitter/nvim-treesitter-context' Plug 'nvim-treesitter/playground' call plug#end()
Add the corresponding configuration:
require'nvim-treesitter.configs'.setup { // … playground = { enable = true, disable = {}, updatetime = 25, persist_queries = false, keybindings = { toggle_query_editor = 'o', toggle_hl_groups = 'i', toggle_injected_languages = 't', toggle_anonymous_nodes = 'a', toggle_language_display = 'I', focus_language = 'f', unfocus_language = 'F', update = 'R', goto_node = '<cr>', show_help = '?', }, } }
The installer script (section 6.1) performs the following steps if you select Neovim as your editor:
- install the editor using brew.
- Point the user to next-step instructions.
6.6. Rust Libraries and Command-Line
Crates:
- sdml-core
- The in-memory model of the SDML language.
- sdml-parse
- The parser that relies on the tree-sitter parser above to
create the in-memory model defined in
sdml-core
. - sdml-generate
- The set of generators and transformers using the
sdml-core
in-memory model as the source. - sdml-cli
- The command-line tool that parses existing models and executes generators.
The source is in the Github repository rust-sdml.
6.7. Tree-Sitter Parser Library
6.8. Resolver Catalog File
The SDML catalog format is based on the OASIS XML Catalogs for XML namespace resolution for offline development. This format has a number of features that make no sense for SDML and so a simpler set of properties is included. Listing 62 provides the grammar for the file, which contains items that describe a local module and provides URL information to allow for constructing module URLs.
Catalog ::= BaseUrl CatalogEntry* BaseUrl ::= Url CatalogEntry ::= Name ( Group | Item ) Group ::= RelativeIri? RelativePath? GroupEntry* GroupEntry ::= Name Item Item ::= RelativeIri RelativePath RelativeIri ::= String RelativePath ::= String
6.8.1. Locating the Catalog
As module files most likely exist as part of a larger project, or may be organized into folders, it is useful to be able to put the catalog file in the root of a project. Therefore, the resolver, if not provided an explicit location, will look in the current working directory, and then it’s parent directory and so on until either a catalog file is found or we reach the file-system root.
.. /parent /project /src vehicle-v1.sdm sdml-catalog.json
More TBD.
6.8.2. Resolving File Paths
The example in listing 63 shows how to map the module identifier vehicle
to a file and a base URL. The
root of the catalog has a mapping named entries which has identifiers as keys and either groups or items as values. The
item only has two properties, a relative URL and a relative file-system path.
{ "base": "https://example.org/rentals/", "entries": { "vehicle": { "item": { "relative_uri": "vehicle#", "relative_path": "src/vehicle-v1.sdm" } } } }
The relative path in an item is added to the directory in which the catalog was located, not relative to where the
resolver is executed. For example, if the resolver is executed in the project
directory the path to the catalog is
./sdml-catalog.json
. Removing the file name gives the path .
to which we add the relative path from the matching item to
result in ./src/vehicle-v1.sdm
.
6.8.3. Resolving Module Base URLs
Using the example from the previous section, if the resulting ./src/vehicle-v1.json
file contains a base
attribute it
will take precedent. If no base attribute is present, a URL will be constructed by resolving the relative URL from the
item against the base URL specified in the catalog root. In the case of our example this becomes:
<https://example.org/rentals/> + "vehicle#" = <https://example.org/rentals/vehicle#>
6.8.4. Catalog Groups
When a project becomes more complex it is useful to provide organization in the form of folder structures.
More TBD
.. /parent /project /src /inventory vehicle-v1.sdm /customer commercial-v2.sdm customer-v2.sdm retail-v3.sdm sdml-catalog.json
More TBD
{ "base": "https://example.org/rentals/", "entries": { "inventory": { "group": { "relative_path": "src/inventory/", "entries": { "vehicle": { "item": { "relative_uri": "vehicle#", "relative_path": "vehicle-v1.sdm" } } } } }, "customer": { "group": { "relative_path": "src/customer/", "entries": { "commercial": { "item": { "relative_uri": "commercial#", "relative_path": "commercial-v2.sdm" } }, "customer": { "item": { "relative_uri": "customer#", "relative_path": "customer-v2.sdm" } } "retail": { "item": { "relative_uri": "retail#", "relative_path": "retail-v3.sdm" } } } } } } }
This means that the path for the module vehicle
= .
+ src/inventory/
+ vehicle-v1.sdm
and the path for the module
customer
= .
+ src/customer/
+ customer-v2.sdm
.
7. The RDF Semantics
The W3C’s Resource Description Framework (RDF) is a mature standard for the open development of vocabularies and knowledge representation. Key characteristics are that it has a fundamentally simple model, was designed with an Open World Assumption (OWA), and because of this it is inherently composable. RDF’s core can be summarized with the following grammar, noting that at it’s core it comprises a set of statements about things.
Graph = GraphId, Statement* GraphId = Uri Statement = Subject, Predicate, Object Subject = Named | Anonymous Predicate = Named Object = Subject | Literal Named = Uri
The Closed or Open World Assumption of a given model or other representation determines how it copes with, or even acknowledges new information beyond it’s initial scope. For example, a database schema is a fixed structure determined by defining some scope which necessitates the ignoring of information outside the schema. Similarly, when object modeling in software development we start by defining a scope in the same manner. While both database schema and object models can change over time they may become unstable if new facts contradict the existing structure. RDF on the other hand was designed for the web and as such assumes that you cannot know what is known about anything as there are other datasets out there somewhere which may contain new information.
In the following example, expressed in the RDF Turtle syntax, there is a single statement whose subject is the value
:mytruck
, with a predicate rdf:type
, and object v:Truck
. Expressed in English this reads as There exists a thing named
:mytruck
whose type is Truck
(from the v
namespace).
:mytruck rdf:type v:Truck .
As the values above don’t look like URIs the Turtle processor uses namespace mappings from prefix (before the “:”) to URI to expand the values above into the following statement.
<https://example.org/p/simon#mytruck> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/v#Truck> .
RDF, in a similar manner to SDML, defines an abstract syntax that captures the structure and semantics of RDF. This abstract syntax is then described in a set of concrete syntax specifications, or representations, including JSON, JSON-LD (JSON for Linked Data), NT (N-Tripes), N3 (Notation3), RDFa (RDF annotations in HTML), TriG (graph-oriented representation), Turtle (Terse RDF Triple Language), and XML. We use Turtle only in this document as it tends to be the easier to read.
A more complete example adds a number of statements about the common subject :mytruck
.
:mytruck rdf:type v:Truck . :mytruck v:make "Dodge" . :mytruck v:model "Ram 3500" . :mytruck v:yearModel "2009"^^xsd:int .
I then consider adding the VIN32 to my statements above, in the following manner.
:mytruck v:hasVin "2G1FP22G522155049" .
However, I discover that my state Department of Transportation has an open dataset containing information recorded by VIN (just FYI, my state does not provide this).
dot:US_2G1FP22G522155049 rdf:type dot:VehicleRecord ; vso:engineDisplacement [ rdf:type gr:QuantitativeValueFloat ; gr:hasValueFloat "6.7"^^xsd:float ; gr:hasUnitOfMeasurement "LTR"^^xsd:string ] ; dot:engineConfiguration "Inline 6-cylinder" ; dot:engineName dbpedia:Cummins_B_Series_engine ; dot:engineManufacturer dbpedia:Cummins ; dot:fuelType dbpedia:GasolineDiesel_fuel .
Each VIN is an addressable resource and so I can assert that my resource
named :mytruck
is the same as the resource dot:US_2G1FP22G522155049
which has the effect of adding any statements made
by the DOT to my record as well.
:mytruck owl:sameAs dot:US_2G1FP22G522155049 .
If I now query my resource I should find that my handful of statements above now looks like this:
:mytruck rdf:type v:Truck, dot:VehicleRecord ; owl:sameAs dot:US_2G1FP22G522155049 ; v:make "Dodge" ; v:model "Ram 3500" ; v:yearModel "2009"^^xsd:int ; vso:engineDisplacement [ rdf:type gr:QuantitativeValueFloat ; gr:hasValueFloat "6.7"^^xsd:float ; gr:hasUnitOfMeasurement "LTR"^^xsd:string ] ; dot:engineConfiguration "Inline 6-cylinder" ; dot:engineName dbpedia:Cummins_B_Series_engine ; dot:engineManufacturer dbpedia:Cummins ; dot:fuelType dbpedia:GasolineDiesel_fuel .
Why is this valuable? Because it allows us easily to add new statements about things at any future point. We don’t have to worry about expanding a schema or changing table definitions we just add new statements and if their subjects are the same they will be combined. To manage the naming of things you will have noticed that names in the examples above have namespace prefixes and these namespaces correspond to definitions of vocabularies or ontologies. Figure 27 is an overview of the OWL Ontology for SDML. For clarity this figure elides the sub classes of Group and Member. Those classes in this diagram that are in a lighter color are abstract in that they exist only to be super-types of the classes actually referenced by other model elements. For more information on OWL itself, see OWL 2 Web Ontology Language Document Overview (Second Edition) and OWL References for Humans.
Figure 27: OWL Ontology
Figure 27 uses some notation elements from UML, although not a UML diagram itself. For a higher resolution version, click here.
- Open triangular arrow heads correspond to the RDF Schema
subClassOf
relationship. - Filled diamonds correspond to composite associations, or strong ownership.
- Open diamonds correspond to aggregate associations, or weak ownership.
7.1. What is an Ontology?
The term ontology was adopted from philosophy in around 1980 to describe logical theories developed to support work in artificial intelligence – for instance logical theories capturing the knowledge of the world of a robot. Since then the term has come to be widely used in information-driven science, journalism, industry, defense, intelligence, and government to describe computer artifacts created to support data retrieval, integration, reasoning and an expanding range of further goals.
An ontology is at its core a collection of general terms organized into a taxonomic hierarchy and associated with formal definitions and axioms. A simple example of how an ontology is used occurs where we have a number of heterogeneous data sources, each using different codes or labels to describe the same entities in reality – for instance proteins or weapons – and where we need to unify all the information in these different sources that pertains to each type of represented entity. Ontologies address this problem by providing a data source-neutral term for each type of entity which is then used to tag corresponding data entries in a consistent way. This enables enhanced retrieval and aggregation of the data. It also allows reasoning across aggregated data through appeal to the logical structure of the definitions and axioms of the corresponding ontology terms.
Such strategies have proved most successful where multiple ontologies covering different domains of entities are built in coordinated fashion to ensure interoperability and avoid overlap and redundancy. One method to achieve such coordination involves the application of a hub-and-spokes strategy, resting on a highly general domain-neutral hub, together with successive layers of spokes comprising domain ontologies built out of terms defined as specializations of terms contained in the hub. A top level ontology is an ontology designed to serve as hub in such an architecture.
7.2. SDML Ontology
Listing 65 is the ontology header that describes the Ontology itself. This shows the use of annotation properties from the Dublin Core DCMI Terms and the W3C Simple Knowledge Organization System (SKOS). Section 7.8 presents details on commonly used annotation properties found in the Ontology.
@prefix dc: <http://purl.org/dc/elements/1.1/> . @prefix owl: <http://www.w3.org/2002/07/owl#> . @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix skos: <http://www.w3.org/2004/02/skos/core#> . @base <https://sdml.io/sdml-owl.ttl#> . <https://sdml.io/sdml-owl.ttl#> rdf:type owl:Ontology ; skos:prefLabel "Simple Domain Modeling Language"@en ; skos:altLabel "SDML"@en ; dc:hasVersion "0.1.10" ; dc:creator "Simon Johnston <johntonskj@gmail.com>" ; dc:license <https://sdml.io/LICENSE> ; rdf:seeAlso <https://sdml.io/> .
7.3. Datatypes
7.4. Classes
All classes have an RDF type of owl:Class
.
Name | Sub-Class Of | Disjoint With |
---|---|---|
:ByReferenceMember |
:Member |
:IdentityMember , :ByValueMember |
:ByValueMember |
:Member |
:IdentityMember , :ByReferenceMember |
:Cardinality |
None. | |
:Classifier |
:ModelElement |
|
:Datatype |
:Classifier , rdfs:Datatype |
:Enumeration , :Structure , :Union , :Unknown |
:Entity |
:Structure |
:Event |
:EntityGroup |
:Group |
:StructureGroup |
:Enumeration |
:Classifier |
:Datatype , :Structure , :Union , :Unknown |
:Event |
:Structure |
:Entity |
:Group |
:ModelElement |
|
:IdentityMember |
:Member |
:ByValueMember , :ByReferenceMember |
:Member |
:ModelElement |
|
:ModelElement |
None. | |
:Module |
:ModelElement |
|
:Property |
:Classifier |
:Datatype , :Enumeration , :Structure , :Unknown |
:RefRole |
:Role |
|
:Role |
None. | |
:Structure |
:Classifier |
:Datatype , :Enumeration , :Union , :Unknown |
:StructureGroup |
:Group |
:EntityGroup |
:Union |
:Classifier |
:Datatype , :Enumeration , :Structure , :Unknown |
:Unknown |
:Classifier |
:Datatype , :Enumeration , :Structure , :Union |
:Variant |
None. |
7.5. Object Properties
All object properties have an RDF type of owl:ObjectProperty
.
Domain | Name | Range | Inverse Of |
---|---|---|---|
:ByReferenceMember ∪ :RefRole |
:sourceCardinality |
:Cardinality |
|
:ByReferenceMember ∪ :Role |
:targetCardinality |
:Cardinality |
|
:Classifier |
:isDefinedBy |
:Module |
:hasDefinition |
:Classifier |
:isTypeVariantOf |
:Union |
:hasTypeVariant |
:Datatype |
:baseDatatype |
:Datatype ∪ rdfs:Datatype |
|
:Entity |
:entityHasGroup |
:EntityGroup |
|
:Entity |
:emitsEvent |
:Event |
:eventSource |
:Entity |
:hasIdentityMember 1,2 |
:IdentityMember |
|
:Entity ∪ :EntityGroup |
:hasByReferenceMember 2 |
:ByReferenceMember |
|
:Entity ∪ :EntityGroup ∪ :Event ∪ :Structure ∪ :StructureGroup |
:hasByValueMember 2 |
:ByValueMember |
|
:Enumeration |
:hasVariant |
:Variant |
:isVariantOf |
:Event |
:eventSource |
:Entity |
:emitsEvent |
:Member |
:targetClassifier |
:Classifier |
|
:Member |
:isMemberOf |
:Structure ∪ :EntityGroup ∪ :StructureGroup |
:hasMember |
:Member |
:targetClassifier |
:Classifier ∪ :Unknown |
|
:Module |
:hasDefinition |
:Classifier |
:isDefinedBy |
:Structure |
:structureHasGroup |
:StructureGroup |
|
:Structure ∪ :EntityGroup ∪ :StructureGroup |
:hasMember |
:Member |
:isMemberOf |
:Union |
:hasTypeVariant |
:Classifier |
:isTypeVariantOf |
:Variant |
:isVariantOf |
:Enumeration |
:hasVariant |
Notes:
- This also has the rdf type
owl:FunctionalProperty
. - These are also sub-properties of
:hasMember
.
7.6. Datatype Properties
All datatype properties have an RDF type of owl:DatatypeProperty
.
Name | Sub Property Of | Domain | Range |
---|---|---|---|
:hasValue |
rdf:value |
:Variant |
xsd:nonNegativeInteger |
:maxOccurs |
:Cardinality |
xsd:nonNegativeInteger |
|
:minOccurs |
:Cardinality |
xsd:nonNegativeInteger |
7.7. Annotation Properties
All annotation properties have an RDF type of owl:AnnotationProperty
.
Name | Domain | Range |
---|---|---|
:defaultVariant |
:Enumeration |
xsd:NMTOKEN |
:deprecatedSince |
N/A | :versionString |
:flatten |
:Member |
Boolean |
:hasVersionIdType |
:Entity |
:Datatype ∪ :Structure |
:hasVersioningStyle |
:Entity |
VersioningStyle |
:introducedIn |
N/A | :versionString |
:rootEntity |
:Module |
:Entity |
The properties :deprecatedSince
and :introducedIn
both refer to a range type :versionString
, this type is a string that
conforms to the semver form of major.minor.path where each is an integer.
:versionString rdf:type rdfs:Datatype ; owl:onDatatype xsd:string ; owl:withRestrictions ( [ xsd:pattern "(0|[1-9][0-9]*)(\.0|[1-9][0-9]*){,2}" ] ) .
The property :hasVersioningStyle
, if present, denotes an entity as versioned and what form of versioning is to be applies.
The values for this property are:
- Immutable
- The user does not get to control the versioning, every mutation of the entity results in a new version and that version is therefore immutable and so version identifiers are maximally stable.
- Snapshot
- The user creates a new version explicitly with a specific operation. This implies that there are mutations of a specific version possible and so the version identifier is not necessarily stable.
- Mutable
- In this case the user has complete control over the versioning by mutation of the entity. In general this implies that there is some member of the entity that holds the version identifier. This results in an explicitly unstable identifier.
:VersioningStyle rdf:type rdfs:DataRange ; owl:oneOf ( "Immutable"^^xsd:string "Snapshot"^^xsd:string "Mutable"^^xsd:string ) .
The corresponding property :hasVersionIdType
references the type used to represent a specific version. The default for this
is xsd:string
.
7.8. Commonly Used Annotations
The following are annotation properties that are common in defining and using Ontologies and other forms of controlled vocabularies. These are all well-defined as well as commonly understood and so should be used before others in annotating a model.
Name | Usage | Domain | Range |
---|---|---|---|
:defaultVariant |
Denotes, if present, the default variant of an enumeration. | :ValueVariant |
xsd:NMTOKEN |
:deprecatedSince |
The specific version in which this model element was marked as deprecated. | N/A | :versionString |
:introducedIn |
The specific version this model element first appeared. | N/A | :versionString |
dc:conformsTo |
An established standard to which the described model element conforms. | N/A | xsd:AnyURI |
dc:contributor |
An entity responsible for making contributions to the model element. | N/A | xsd:string |
dc:creator |
An entity primarily responsible for making the model element. | N/A | xsd:string |
dc:license |
A legal document giving official permission to do something with the model element. | :Module |
xsd:AnyURI |
rdfs:seeAlso |
Used to indicate a resource that might provide additional information about the model element. | N/A | xsd:AnyURI |
skos:altLabel |
An alternative lexical label/name for the model element. | N/A | rdf:langString |
skos:definition |
A complete explanation of the intended meaning of the model element. | N/A | rdf:langString |
skos:hiddenLabel |
A lexical label/name for the element where it may be searchable but not commonly displayed. | N/A | rdf:langString |
skos:historyNote |
Describes significant changes to the meaning or the form of the model element. | N/A | rdf:langString |
skos:prefLabel |
The preferred lexical label/name of the model element. | N/A | rdf:langString |
8. Appendix: Unicode Usage
The grammar for SDML uses a number of Unicode alternative characters for operators, relations, and keywords. Table
15 lists these with their ASCII equivalents. In the case of the type-of relation there are two
Unicode choices where the character →
is usually used for expressing the type of members and function parameters and the
character ↦
is usually reserved for mapping types and mapping values.
Operator, Relation, or Keyword | ASCII | Unicode | Unicode Codepoint | LaTeX |
---|---|---|---|---|
Type-of | -> |
→ or ↦ |
U+2192 or U+21A6 |
rightarrow or mapsto |
Type Restriction | <- |
← |
U+2190 |
leftarrow |
Defined-as | := |
≔ |
U+2254 |
see notes. |
Universal Quantifier | forall |
∀ |
U+2200 |
forall |
Existential Quantifier | exists |
∃ |
U+2203 |
exists |
Sequence Membership | in |
∈ |
U+2208 |
in |
Inequation Relation | /= |
≠ |
U+2260 |
neq |
Less-than or Equal Relation | <= |
≤ |
U+2264 |
leq |
Greater-than or Equal Relation | >= |
≥ |
U+2265 |
geq |
Negation | not |
¬ |
U+00A5 |
neg |
Conjunction | and |
∧ |
U+2227 |
land |
Disjunction | or |
∨ |
U+2228 |
lor |
Exclusive Disjunction | xor |
⊻ |
U+22BB |
veebar |
Implication (Conditional) | implies or ==> |
⇒ |
U+21D2 |
implies |
Biconditional | iff or <==> |
⇔ |
U+21D4 |
iff |
Empty Sequence | [] |
∅ |
U+2205 |
emptyset or varnothing |
Truth Value | true |
⊤ |
U+22A4 |
top |
False Value | false |
⊥ |
U+22A5 |
bot |
Notes
In describing the type system for SDML the characters ⊤
and ⊥
are also used to describe the type of all things and the
type of no things respectively. The grammar does not use these distinctions and the values are only used for boolean
values.
For some entries in the table above there are notation options not supported by SDML. In the following discussion the value \(v\) represents some valid member of the type \(T\).
- The type-of relation uses the rightward arrow \(v \rightarrow T\), common in programming languages and type theory. SDML chose not
to use the colon \(v : T\) character which is heavily used in programming.
- Product types \(T {\,\mathrel{\mathop:}=\,} T_1 \times T_2 \cdots \times T_n\).
- Sum types \(T {\,\mathrel{\mathop:}=\,} T_1 + T_2 \cdots + T_n\).
- Functions \((T_1 \times T_2 \cdots \times T_n) → T\)
- The type-restriction relation uses the leftward arrow \(T_2 ← T_1\) to denote that a type is a sub-type of another by
restriction.
- In languages that support sub-type polymorphism the expression is commonly \(T_2 \sqsubseteq T_1\) or \(T_2 \leq: T_1\).
- The inequation operator uses the slashed equation symbol \(v_1 \neq v_2\), or it’s ASCII equivalent
/=
, while some programming languages use!=
,~=
, or<>
. - The unary logical connective negation does not introduce an ASCII symbol such as
!
or~
as in some programming languages, only the logic symbol \(\neg v\). - Similarly, the binary logical connectives \(\{\land,\lor,\veebar,\implies,\iff\}\) do not introduce ASCII symbols from programming languages.
- Rendering the equal by definition in LaTeX is best done by using the command
coloneqq
in themathtools
package.
9. Appendix: Standard Library
The standard library provided with SDML contains the language library itself, SDML modules representing commonly used external namespaces, and a set of predicates and functions that are available for writing constraints.
9.1. Type Classes
TBD
9.2. SDML Module
The sdml
module contains the set of types that represent the language itself, most of which are only used in the writing
of constraints. However, the pre-defined simple types boolean
, decimal
, etc. are also defined here.
module sdml base <http://sdml.io/sdml-owl.ttl#> is structure Datatype ;; ... structure Annotation ;; ... structure AnnotationProperty ;; ... structure ByReferenceMember ;; ... structure ByValueMember ;; ... structure Cardinality ;; ... structure Constraint ;; ... structure Definition ;; ... structure Entity ;; ... structure EntityGroup ;; ... structure EntityMember ;; ... structure Enumeration ;; ... structure Event ;; ... structure FormalConstraint ;; ... structure Identifier ;; ... structure IdentiferReference ;; ... structure IdentityMember ;; ... structure ImportStatement ;; ... structure Import ;; ... structure Member ;; ... structure MemberImport ;; ... structure Module ;; ... structure ModuleImport ;; ... structure Property ;; ... structure PropertyRole ;; ... structure QualifiedIdentifier ;; ... structure Structure ;; ... structure StructureGroup ;; ... structure TypeVariant ;; ... structure Union ;; ... structure ValueVariant ;; ... datatype boolean <- xsd:boolean ;; ... datatype unsigned <- xsd:unsignedLong ;; ... datatype integer <- xsd:long ;; ... datatype double <- xsd:double ;; ... datatype decimal <- xsd:decimal ;; ... datatype language <- xsd:language ;; ... datatype string <- xsd:string ;; ... datatype iri <- xsd:anyURI ;; ... datatype binary <- xsd:hexBinary ;; ... datatype u8 <- xsd:unsignedByte ;; ... datatype i8 <- xsd:byte ;; ... datatype u16 <- xsd:unsignedShort ;; ... datatype i16 <- xsd:short ;; ... datatype u32 <- xsd:unsignedInt ;; ... datatype i32 <- xsd:int ;; ... datatype u64 <- xsd:unsignedLong ;; ... datatype i64 <- xsd:long ;; ... end
9.3. Commonly Used Modules
9.3.1. Dublin Core Elements
This module
module dc base <http://purl.org/dc/elements/1.1/> is structure contributor ;; ... structure coverage ;; ... structure creator ;; ... structure date ;; ... structure description ;; ... structure format ;; ... structure identifier ;; ... structure language ;; ... structure publisher ;; ... structure relation ;; ... structure rights ;; ... structure source ;; ... structure subject ;; ... structure title ;; ... structure type ;; ... end
9.3.2. DCMI Abstract Model
This module
module dc_am base <http://purl.org/dc/dcam/> is structure VocabularyEncodingScheme ;; ... structure domainIncludes ;; ... structure memberOf ;; ... structure rangeIncludes ;; ... end
9.3.3. DCMI Type Vocabulary
This module
module dc_type base <http://purl.org/dc/dcmitype/> is structure Collection ;; ... structure Dataset ;; ... structure Event ;; ... structure Image ;; ... structure InteractiveResource ;; ... structure MovingImage ;; ... structure PhysicalObject ;; ... structure Service ;; ... structure Software ;; ... structure Sound ;; ... structure StillImage ;; ... structure Text ;; ... end
9.3.4. DCMI Metadata Terms
This module
module dc_terms base <http://purl.org/dc/terms/> is structure Agent ;; ... structure AgentClass ;; ... structure BibliographicResource ;; ... structure Box ;; ... structure DCMIType ;; ... structure DDC ;; ... structure FileFormat ;; ... structure Frequency ;; ... structure IMT ;; ... structure ISO3166 ;; ... structure ISO639_2 ;; ... structure ISO639_3 ;; ... structure Jurisdiction ;; ... structure LLC ;; ... structure LCSH ;; ... structure LicenseDocument ;; ... structure LinguisticSystem ;; ... structure Location ;; ... structure LocationPeriodOrJurisdiction ;; ... structure MESH ;; ... structure MediaType ;; ... structure MediaTypeOrExtent ;; ... structure MethodOfAccrual ;; ... structure MethodOfInstruction ;; ... structure NLM ;; ... structure Period ;; ... structure PeriodOfTime ;; ... structure PhysicalMedium ;; ... structure PhysicalResource ;; ... structure Point ;; ... structure Policy ;; ... structure ProvenanceStatement ;; ... structure RFC1766 ;; ... structure RFC3066 ;; ... structure RFC4646 ;; ... structure RFC5646 ;; ... structure RightsStatement ;; ... structure SizeOrDuration ;; ... structure Standard ;; ... structure TGN ;; ... structure UDC ;; ... structure URI ;; ... structure WthreeCDTF ;; ... structure abstract ;; ... structure accessRights ;; ... structure accrualMethod ;; ... structure accrualPeriodicity ;; ... structure accrualPolicy ;; ... structure alternative ;; ... structure audience ;; ... structure available ;; ... structure bibliographicCitation ;; ... structure conformsTo ;; ... structure contributor ;; ... structure coverage ;; ... structure created ;; ... structure creator ;; ... structure date ;; ... structure dateCopyrighted ;; ... structure dateSubmitted ;; ... structure description ;; ... structure educationLevel ;; ... structure extent ;; ... structure forma ;; ... structure hasFormat ;; ... structure hasPart ;; ... structure hasVersion ;; ... structure identifier ;; ... structure instructionalMethod ;; ... structure isFormatOf ;; ... structure isPartOf ;; ... structure isReferencedBy ;; ... structure isReplacedBy ;; ... structure isRequiredBy ;; ... structure isVersionOf ;; ... structure issued ;; ... structure language ;; ... structure license ;; ... structure mediator ;; ... structure medium ;; ... structure modified ;; ... structure provenance ;; ... structure publisher ;; ... structure references ;; ... structure relation ;; ... structure replaces ;; ... structure requires ;; ... structure rights ;; ... structure rightsHolder ;; ... structure source ;; ... structure spatial ;; ... structure subject ;; ... structure tableOfContents ;; ... structure temporal ;; ... structure title ;; ... structure type ;; ... structure valid ;; ... end
9.3.5. The OWL 2 Schema Vocabulary
This module
module owl base <http://www.w3.org/2002/07/owl#> is end
9.3.6. The RDF Concepts Vocabulary
This module
module rdf base <http://www.w3.org/1999/02/22-rdf-syntax-ns#> is structure Alt ;; ... structure Bag ;; ... structure CompoundLiteral ;; ... structure HTML ;; ... structure JSON ;; ... structure List ;; ... structure PlainLiteral ;; ... structure Property ;; ... structure Seq ;; ... structure Statement ;; ... structure XMLLiteral ;; ... structure direction structure first ;; ... structure langString ;; ... structure language ;; ... structure nil ;; ... structure object ;; ... structure predicate ;; ... structure rest ;; ... structure subject ;; ... structure type ;; ... structure value ;; ... end
9.3.7. The RDF Schema Vocabulary
This module
module rdf_schema base <http://www.w3.org/2000/01/rdf-schema#> is structure Class ;; ... structure Container ;; ... structure ContainerMembershipProperty ;; ... structure Datatype ;; ... structure Literal ;; ... structure Resource ;; ... structure comment ;; ... structure domain ;; ... structure isDefinedBy ;; ... structure label ;; ... structure member ;; ... structure range ;; ... structure seeAlso ;; ... structure subClassOf ;; ... structure subPropertyOf ;; ... end
9.3.8. Simple Knowledge Organization System Namespace
This module
module skos base <http://www.w3.org/2004/02/skos/core#> is structure Collection ;; ... structure Concept ;; ... structure ConceptScheme ;; ... structure OrderedCollection ;; ... structure altLabel ;; ... structure broadMatch ;; ... structure broader ;; ... structure broaderTransitive ;; ... structure changeNote ;; ... structure closeMatch ;; ... structure definition ;; ... structure editorialNote ;; ... structure exactMatch ;; ... structure example ;; ... structure hasTopConcept ;; ... structure hiddenLabel ;; ... structure historyNote ;; ... structure inScheme ;; ... structure mappingRelation ;; ... structure member ;; ... structure memberList ;; ... structure narrowMatch ;; ... structure narrower ;; ... structure narrowerTransitive ;; ... structure notation ;; ... structure note ;; ... structure prefLabel ;; ... structure related ;; ... structure relatedMatch ;; ... structure scopeNote ;; ... structure semanticRelation ;; ... structure topConceptOf ;; ... end
9.3.9. The xml Namespace
This module
module xml <http://www.w3.org/XML/1998/namespace> is end
9.3.10. XML Schema Datatypes
This module
module xsd base <http://www.w3.org/2001/XMLSchema#> is end
9.4. Constraint Language Library
Note that all of the predicates, relations, and functions in this section exist within the sdml
module scope but
qualification is optional unless the application is ambiguous in a particular context.
The standard library provides methods to constraints in the form of type classes (see section 9.1) described in the following sections.
9.4.1. Named
and Typed
module sdml_names is import skos class Named(T) is @skos:definition = "Implemented by model elements that have a distinct name within some namespace."@en def name(v -> T) -> Identifier := member(qualified_name(t)) is @skos:definition = "Return the name of the defined model element."@en end def qualified_name(v -> T) → QualifiedIdentifier is @skos:description = "The qualified identifier that names this type. In all cases the module portion of the name will be ~sdml~."@en @skos:example = "~type_of(rentals:Customer) = sdml:Entity~" end def iri(v -> T) -> iri is @skos:definition = "Return the IRI of the defined model element."@en end end class Namespace(T E -> Named) is def members(v -> T) -> {unique 0..} E is @skos:definition = "Return a set containing the members of this namespace."@en end def get_member(v -> T name -> Identifier) -> ?E is @skos:definition = "Return the member of this namespace with the name ~name~."@en end def has_member(v -> T id -> Identifier) -> boolean := has(get_member(v id)) is @skos:definition = "Return ~true~ if this namespace contains a member with the name ~name~."@en end end class Member(T -> Named N -> Namespace(_ T) M -> Module) is def container(v -> T) -> N is @skos:definition = "Return the container to which this member belongs."@en end def module(v -> T) -> M is @skos:definition = "Return the module to which this member (transitively) belongs."@en end end end
module sdml_types is import skos class Typed(T) is @skos:definition = "Implemented by model elements that have a type."@en def target_cardinality(v -> T) -> ?Cardinality def target_type(v -> T) → Type is @skos:definition = "Returns the SDML type of the passed value."@en @skos:example = "~type_of(rentals:Customer) = sdml:Entity~" end end class Cardinality(T) is def min_occurs(v -> T) -> unsigned def max_occurs(v -> T) -> ?unsigned def is_one(v -> T) -> boolean := is(max_occurs(v) min_occurs(v)) def is_range(v -> T) -> boolean := has_not(max_occurs(v)) or is_not(max_occurs(v) min_occurs(v)) def is_unbounded(v -> T) -> boolean := has_not(max_occurs(v)) end end
9.4.2. Equality and Inequality
module sdml_tests is import skos class Eq(T1 T2 -> T1) is @skos:definition = "This class provides support for equality tests between values."@en def eq(lhs -> T1 rhs -> T2) → boolean is @skos:definition = "Returns ~true~ if the two values are equal, else ~false~."@en end def ne(lhs → T1 rhs → T2) → boolean ≔ ¬eq(lhs rhs) is @skos:definition = "Returns ~true~ if the two values are not equal, else ~false~."@en end end class Ord(T1 -> Eq(T1 T2) T2 -> T1) is @skos:definition = "This class provides support for inequality tests between values of types that have a natural order."@en def gt(lhs -> T1 rhs -> T2) → boolean is @skos:definition = "Returns ~true~ if the first value is greater than the second, else ~false~."@en end def gte(lhs → T1 rhs → T2) → boolean ≔ gt(lhs rhs) ∨ eq(lhs rhs) is @skos:definition = "Returns ~true~ if the first value is greater than or equal to the second, else ~false~."@en end def lt(lhs → T1 rhs → T2) → boolean ≔ ¬gte(lhs rhs) is @skos:definition = "Returns ~true~ if the first value is less than the second, else ~false~."@en end def lte(lhs → T1 rhs → T2) → boolean ≔ ¬gt(lhs rhs) is @skos:definition = "Returns ~true~ if the first value is less than or equal to the second, else ~false~."@en end end end
9.4.3. Identifiers
module sdml_identifiers is import skos class Identifier(T) is def join(lhs -> T rhs -> T) → QualifiedIdentifier is @skos:definition = "Return a new qualified identifier with the first parameter as the module name and the second as the member name."@en end end class QualifiedIdentifier(T) is def member(v -> T) → Identifier is @skos:definition = "Return the member name part of the identifier."@en end def module(v -> T) → Identifier is @skos:definition = "Return the module name part of the identifier."@en end end class IdentifierReference(T) is def as_identifier(v -> T) → ?Identifier is @skos:definition = "Returns the current identifier reference as the identifier type variant."@en end def as_qualified_identifier(v -> T) → ?QualifiedIdentifier is @skos:definition = "Returns the current identifier reference as the reference type variant."@en end def is_identifier(v -> T) → boolean := ¬is_empty(as_identifer(v)) is @skos:definition = "Returns ~true~ if , else ~false~."@en end def is_qualified_identifier(v -> T) → boolean := ¬is_empty(as_qualified_identifer(v)) is @skos:definition = "Returns ~true~ if , else ~false~."@en end end end
9.4.4. Module Definition
module sdml_modules is import skos class Module(T -> Named + Namespace(Definition) + Annotated) is @skos:definition = "The interface for a ~module~ definition."@en def base(v -> T) -> ?iri is @skos:definition = "Returns the value of the module's ~base~ property, if present."@en end def absolute_iri(v -> T) -> iri is @skos:definition = "Returns the calculated, absolute, IRI for this module."@en end def imports(v -> T) -> {0..} IdentifierReference is @skos:definition = "Return a sequence of all imported modules and members."@en end def member_imports(v -> T) -> {0..} QualifiedIdentifier ≔ id({i | i ∈ imports(v) is_qualified_identifier(i)}) def module_imports(v -> T) -> {0..} Identifier ≔ id({i | i ∈ imports(i) is_identifier(i)}) def is_complete(v -> T) → boolean ≔ ∀ d ∈ members(v) is_complete(d) is @skos:definition = "Returns ~true~ if the module definition is /complete/, else ~false~."@en end end end
Union: Definition
def Definition(self → _) → boolean ≔ type_of(self) = sdml:Definition
Returns true
if the passed value is of type Definition
, else false
.
def as_datatype(Definition) → {0..1} Datatype
def as_entity(Definition) → {0..1} Entity
def as_enumeration(Definition) → {0..1} Enumeration
def as_event(Definition) → {0..1} Event
def as_structure(Definition) → {0..1} Structure
def as_property(Definition) → {0..1} Property
def is_datatype(Definition) → boolean
def is_entity(Definition) → boolean
def is_enumeration(Definition) → boolean
def is_event(Definition) → boolean
def is_structure(Definition) → boolean
def is_property(Definition) → boolean
9.4.5. Datatypes and Values
Class: Datatype
def Datatype(self → _) → boolean ≔ type_of(self) = sdml:Datatype
Returns true
if the passed value is of type Datatype
, else false
.
def annotations(Module) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def base_type(Datatype) → {0..1} Datatype
def container(Datatype) → Module
Returns the module that contains this model element’s definition.
def is_ordinal(Datatype) → boolean
Returns true
if the passed type is an ordinal type, else false
.
def name(Datatype) → IdentifierReference
Returns the identifier naming this model element.
def qualified_name(c → Constraint) → Identifier ≔ join(name(container(c)) name(c))
Return the qualified identifier naming this model element and it’s containing module.
Datatype: decimal
def decimal(_) → boolean
Returns true
if the passed value is of type decimal
, else false
.
def whole_part(decimal) → decimal
def decimal_part(decimal) → decimal
def round(decimal) → decimal
Datatype: double
def double(_) → boolean
Returns true
if the passed value is of type double
, else false
.
def whole_part(double) → double
def decimal_part(double) → double
def round(double) → double
Datatype: integer
def integer(_) → boolean
Returns true
if the passed value is of type integer
, else false
.
Datatype: iri
def iri(_) → boolean
Returns true
if the passed value is of type iri
, else false
.
def is_absolute(iri) → boolean
Datatype: language
def language(_) → boolean
Returns true
if the passed value is of type language
, else false
.
def broader(languageTag languageTag) → boolean
def language_extended_part(languageTag) → string
def language_part(languageTag) → string
def narrower(languageTag languageTag) → boolean
def region_part(languageTag) → string
def script_part(languageTag) → string
Datatype: string
def string(_) → boolean
Returns true
if the passed value is of type string
, else false
.
def contains(string sub → string) → boolean
Returns true
if the value of the second string is contained within the first, else false
.
def eq_ci(string string) → boolean
def into_list(string) → {ordered 0..} unsigned
Returns a new list where each element is the Unicode code-point of a character in the input string.
def join(string string) → string
Return a new string which is the concatenation of the two input strings.
def language_part(string) → Maybe of language
def length(string) → integer
def matches(string regex → string) → boolean
def ne_ci(string string) → boolean
def prefix(string prefix → string) → boolean
Returns true
if the first string starts with the value of the second, else false
.
def quoted_part(string) → string
def suffix(string suffix → string) → boolean
Returns true
if the first string ends with the value of the second, else false
.
Datatype: unsigned
def unsigned(_) → boolean
Returns true
if the passed value is of type unsigned
, else false
.
def into_integer(unsigned) → integer
9.4.6. Sequence Types
Class: Sequence
module sdml_sequences is import skos class Sequence({} S) is end end
def Sequence(self → {_}) → boolean ≔ type_of(self) = sdml:Sequence
Returns true
if the value is of type Sequence
or any sub-type of Sequence
, else false
.
def contains(s → {_} t → _) → boolean ≔ ¬is_empty({ e | e ∈ s ∧ e = t})
Returns true
if the sequence s
contains the element t
, else false
.
def count({_}) → unsigned
Returns the number of elements in the sequence.
def element_type({_}) → Type
Returns the type of the members within this sequence.
def into_bag(seq → {_}) → {0..} _
Transforms the input sequence into a Bag
, this may remove ordering and uniqueness constraints from the input sequence
but will not remove any elements.
def into_list(seq → {_}) → {ordered nonunique 0..} _
Transforms the input sequence into a List
, this may add ordering and remove uniqueness constraints from the input sequence
but will not remove any elements.
def into_ordered_set(seq → {_}) → {ordered unique 0..} _
Transforms the input sequence into a OrderedSet
, this may add both ordering and uniqueness constraints to the input sequence
and will remove duplicate elements.
def into_set({_}) → {unordered unique 0..} _
Transforms the input sequence into a Set
, this may remove ordering and add uniqueness constraints to the input sequence
and will remove duplicate elements.
def is_empty(seq → {_}) → boolean ≔ count(seq) = 0
Return true
if the sequence has no elements, else false
.
def is_ordered(t → {_}) → boolean ≔ fold_head(lte t)
Returns true
if the provided type t
is an ordered sequence, else false
.
def is_unique(t → {_}) → boolean ≔ count(t) == count(fold(∅ union t))
Returns true
if the provided type t
is a sequence that guarantees uniqueness of elements, else false
.
def join({_} sep → string) → string
Return a new string where the string form of each value in the sequence is concatenated with the value of the string placed between each.
Example: join([1 2 3] ",") = "1,2,3"
Class: Ordered
module sdml_sequences is import skos class Ordered({ordered nonunique 0..} S) is def drop(vs -> S count → unsigned) → S is @skos:definition = "Drop ~count~ items from the beginning of the sequence and return the rest."@en end def get(vs -> S index → unsigned) → {0..1} S is @skos:definition = "Return the element in this sequence at the position ~index~."@en end def reverse(vs -> S) → S is @skos:definition = "Return a new sequence with the order of elements reversed."@en end def slice(vs -> S start → unsigned count → unsigned) → S := take(drop(vs count) start) is @skos:definition = "Return a sub-sequence of ~count~ elements starting at ~start~."@en end def take(vs -> S count → unsigned) → S is @skos:definition = "Return ~count~ items from the beginning of the sequence and discard the rest."@en end end end
Class: Unique
module sdml_sequences is import skos class Unique({unordered unique 0..} S) is def intersection(lhs → S rhs → S) → S def is_strict_subset(lhs → S rhs → S) → boolean ≔ is_subset(lhs rhs) ∧ ¬eq(lhs rhs) def is_strict_superset(lhs → S rhs → S) → boolean ≔ is_superset(lhs rhs) ∧ ¬eq(lhs rhs) def is_subset(lhs → S rhs → S) → boolean def is_superset(lhs → S rhs → S) → boolean def minus(lhs → S rhs → S) → S def union(lhs → S rhs → S) → S end end
9.4.7. Annotations
module sdml_annotations is import skos class Annotated(T) is end end
Class Annotation
def Annotation(self → _) → boolean ≔ type_of(self) = sdml:Annotation
Returns true
if the passed value is of type Annotation
, else false
.
def as_annotation_property(Annotation) → {0..1} AnnotationProperty
def as_constraint(Annotation) → {0..1} Constraint
def is_annotation_property(Annotation) → boolean
def is_constraint(Annotation) → boolean
Class AnnotationProperty
def AnnotationProperty(self → _) → boolean ≔ type_of(self) = sdml:AnnotationProperty
Returns true
if the passed value is of type AnnotationProperty
, else false
.
def container(Member) → ModelElement
Returns the model element that contains this annotation assertion.
def name(AnnotationProperty) → IdentiferReference
Returns the identifier naming this model element.
def value(AnnotationProperty) → Value
Class Constraint
def Constraint(self → _) → boolean ≔ type_of(self) = sdml:Constraint
Returns true
if the passed value is of type Constraint
, else false
.
def as_formal(Constraint) → {0..1} FormalConstraint
def as_informal(Constraint) → {0..1} string
def container(Member) → ModelElement
Returns the model element that contains this annotation assertion.
def is_formal(Constraint) → boolean
def is_informal(Constraint) → boolean
def name(Constraint) → Identifier
Returns the identifier naming this model element.
Annotation Collections
def annotation_properties(as → {0..} Annotation) ≔ {a | a ∈ as ∧ AnnotationProperty(a)}
def constraints(as - > {0..} Annotation) ≔ {a | a ∈ as ∧ Constraint(a)}
9.4.8. Structured Type Members
Class: Member
def Member(self → _) → boolean ≔ type_of(self) = sdml:Member
Returns true
if the passed value is of type Member
, else false
.
def annotations(Member) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(Member) → Type
Returns the container type that contains this member’s definition.
def is_complete(self → Member) → boolean ≔ ¬Unknown(type_of(self))
def name(Member) → Identifier
Returns the identifier naming this model element.
def target_type(Member) → Type
Class: IdentityMember
def IdentityMember(self → _) → boolean ≔ type_of(self) = sdml:IdentityMember
Returns true
if the passed value is of type IdentityMember
, else false
.
Class: ByReferenceMember
def ByReferenceMember(self → _) → boolean ≔ type_of(self) = sdml:ByReferenceMember
Returns true
if the passed value is of type ByReferenceMember
, else false
.
Class: ByValueMember
def ByValueMember(self → _) → boolean ≔ type_of(self) = sdml:ByValueMember
Returns true
if the passed value is of type ByValueMember
, else false
.
Class: Cardinality
def Cardinality(self → _) → boolean ≔ type_of(self) = sdml:Cardinality
Returns true
if the passed value is of type Cardinality
, else false
.
def is_optional(c → Cardinality) → boolean ≔ min(c) = 0 ∧ eq(max(c) 1)
def is_range(Cardinality) → boolean
def is_range_unbounded(c → Cardinality) → boolean ≔ is_range(c) ∧ is_empty(max(c))
def max(Cardinality) → {0..} unsigned
def min(Cardinality) → unsigned
9.4.9. Entities
Class: Entity
def Entity(self → _) → boolean ≔ type_of(self) = sdml:Entity
Returns true
if the passed value is of type Entity
, else false
.
def annotations(Entity) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(Entity) → Module
Returns the module that contains this model element’s definition.
def flat_members(Entity) → {ordered unique 0..} EntityMember
Returns an unordered, unique, sequence of members contained within the entity.
def groups(Entity) → {ordered unique 0..} EntityGroup
Returns an unordered, unique, sequence of members contained within the entity.
def identity(Entity) → IdentityMember
Returns the identity member for this entity.
def is_complete(self → Structure) → boolean ≔ has_body(self) ∧ ∀ member ∈ flat_members(self) (is_complete(member))
Returns true
if the entity definition is complete, else false
.
def members(Entity) → {ordered unique 0..} EntityMember
Returns an unordered, unique, sequence of members contained within the entity.
def name(Entity) → Identifier
Returns the identifier naming this model element.
def qualified_name(self → Constraint) → Identifier ≔ join(name(container(self)) name(self))
Return the qualified identifier naming this model element and it’s containing module.
Class: EntityGroup
def EntityGroup(self → _) → boolean ≔ type_of(self) = sdml:EntityGroup
Returns true
if the passed value is of type EntityGroup
, else false
.
def annotations(EntityGroup) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(EntityGroup) → Entity
Returns the entity that contains group’s definition.
def is_complete(self → Structure) → boolean ∀ member ∈ members(self) (is_complete(member))
Returns true
if the entity group is complete, else false
.
def members(EntityGroup) → {ordered unique 0..} EntityMember
Returns an unordered, unique, sequence of variants contained within the union.
Union: EntityMember
def EntityMember(self → _) → boolean ≔ type_of(self) = sdml:EntityMember
Returns true
if the passed value is of type EntityMember
, else false
.
def as_by_reference(EntityMember) → {0..1} ByReferenceMember
def as_by_value(EntityMember) → {0..1} ByValueMember
def is_by_reference(EntityMember) → boolean
def is_by_value(EntityMember) → boolean
9.4.10. Entity Events
Class: Event
def Event(self → _) → boolean ≔ type_of(self) = sdml:Event
Returns true
if the passed value is of type Event
, else false
.
def annotations(Event) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(Event) → Module
Returns the module that contains this model element’s definition.
def flat_members(Event) → {ordered unique 0..} ByValueMember
Returns an unordered, unique, sequence of members contained within the event.
def groups(Event) → {ordered unique 0..} StructureGroup
Returns an unordered, unique, sequence of groups contained within the event.
def is_complete(self → Event) → boolean ≔ has_body(self) ∧ ∀ member ∈ flat_members(self) (is_complete(member))
Returns true
if the structure definition is complete, else false
.
def members(Event) → {ordered unique 0..} ByValueMember
Returns an unordered, unique, sequence of members contained within the event.
def name(Event) → Identifier
Returns the identifier naming this model element.
def qualified_name(c → Constraint) → Identifier ≔ join(name(container(c)) name(c))
Return the qualified identifier naming this model element and it’s containing module.
def source(Event) → IdentifierReference
9.4.11. Enumerations
Class: Enumeration
def Enumeration(self → _) → boolean ≔ type_of(self) = sdml:Enumeration
Returns true
if the passed value is of type Enumeration
, else false
.
def annotations(Enumeration) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(Enumeration) → Module
Returns the module that contains this model element’s definition.
def is_complete(self → Enumeration) → boolean ≔ has_body(self)
Returns true
if the enumeration definition is complete, else false
.
def name(Enumeration) → Identifier
Returns the identifier naming this model element.
def qualified_name(self → Constraint) → Identifier ≔ join(name(container(self)) name(self))
Return the qualified identifier naming this model element and it’s containing module.
def variants(Enumeration) → {ordered unique 1..} ValueVariant
Returns an ordered, unique, sequence of variants contained within the enumeration.
Class: ValueVariant
def ValueVariant(self → _) → boolean ≔ type_of(self) = sdml:ValueVariant
Returns true
if the passed value is of type ValueVariant
, else false
.
def annotations(ValueVariant) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(ValueVariant) → Enumeration
Returns the enumeration that contains this variant’s definition.
def name(ValueVariant) → Identifier
Returns the identifier naming this model element.
def value(ValueVariant) → integer
9.4.12. Structures
Class: Structure
def Structure(self → _) → boolean ≔ type_of(self) = sdml:Structure
Returns true
if the passed value is of type Structure
, else false
.
def annotations(Structure) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(Structure) → Module
Returns the module that contains this model element’s definition.
def flat_members(Structure) → {unique 0..} ByValueMember
Returns an unordered, unique, sequence of members contained within the structure and all of it’s groups.
def groups(Structure) → {ordered unique 0..} StructureGroup
Returns an unordered, unique, sequence of groups contained within the structure.
def is_complete(self → Structure) → boolean ≔ has_body(self) ∧ ∀ member ∈ flat_members(self) (is_complete(member))
Returns true
if the structure definition is complete, else false
.
def members(Structure) → {ordered unique 0..} ByValueMember
Returns an unordered, unique, sequence of members contained within the structure.
def name(Structure) → Identifier
Returns the identifier naming this model element.
def qualified_name(self → Structure) → Identifier ≔ join(name(container(self)) name(self))
Return the qualified identifier naming this model element and it’s containing module.
Class: StructureGroup
def StructureGroup(self → _) → boolean ≔ type_of(self) = sdml:StructureGroup
Returns true
if the passed value is of type StructureGroup
, else false
.
def annotations(StructureGroup) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(StructureGroup) → Structure
Returns the structure that contains this group’s definition.
def is_complete(self → StructureGroup) → boolean ≔ ∀ member ∈ members(self) (is_complete(member))
Returns true
if the structure group is complete, else false
.
def members(StructureGroup) → {ordered unique 0..} ByValueMember
Returns an unordered, unique, sequence of members contained within the structure group.
9.4.13. Disjoint Unions
Class: Union
def Union(self → _) → boolean ≔ type_of(self) = sdml:Union
Returns true
if the passed value is of type Union
, else false
.
def annotations(Union) → Bag of Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(Union) → Module
Returns the module that contains this model element’s definition.
def is_complete(self → Union) → boolean ≔ has_body(self)
Returns true
if the union definition is complete, else false
.
def name(Union) → Identifier
Returns the identifier naming this model element.
def qualified_name(self → Union) → Identifier ≔ join(name(container(self)) name(self))
Return the qualified identifier naming this model element and it’s containing module.
def variants(Union) → {unique 0..} TypeVariant
Returns an unordered, unique, sequence of variants contained within the union.
Class: TypeVariant
def TypeVariant(self → _) → boolean ≔ type_of(self) = sdml:TypeVariant
Returns true
if the passed value is of type TypeVariant
, else false
.
def annotations(TypeVariant) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(TypeVariant) → Union
Returns the union that contains this variant’s definition.
def is_renamed(PropertyRole) → boolean
Returns true
if variant has a rename clause, else false
.
def name(TypeVariant) → Identifier
Returns the identifier naming this model element, this identifier must refer to a type definition.
def renamed_as(PropertyRole) → {0..1} Identifier
Returns the identifier representing the new name of the variant.
9.4.14. Property Definitions
Class: Property
def Property(self → _) → boolean ≔ type_of(self) = sdml:Property
Returns true
if the passed value is of type Property
, else false
.
def annotations(Property) → {0..} Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(Property) → Module
Returns the module that contains this model element’s definition.
def is_complete(self → Property) → boolean ≔ self.body.present ∧ ∀ role in self.roles ( role.is_complete )
Returns true
if the property definition is complete, else false
.
def name(Property) → Identifier
Returns the identifier naming this model element.
def qualified_name(self → Property) → Identifier ≔ join(name(container(self)) name(self))
Return the qualified identifier naming this model element and it’s containing module.
def roles(Property) → {unique 0..} PropertyRole
Returns an unordered set of roles defined within this property.
Class: Property Role
def PropertyRole(self → _) → boolean ≔ type_of(self) = sdml:PropertyRole
Returns true
if the passed value is of type PropertyRole
, else false
.
def annotations(PropertyRole) → Bag of Annotation
Returns an unordered sequence of annotations attached to this model element.
def container(PropertyRole) → Property
Returns the property that contains this role’s definition.
def is_complete(self → PropertyRole) → boolean ≔ ¬(type_of(self) = Unknown)
Returns true
if the property role definition is complete, else false
.
def name(PropertyRole) → Identifier
Returns the identifier naming this model element.
def source_cardinality(PropertyRole) → {0..1} Cardinality
def target_type(PropertyRole) → Type
def target_cardinality(PropertyRole) → Cardinality
9.4.15. Formal Constraints
TBD
Class: FormalConstraint
def FormalConstraint(_) → boolean ≔ type_of(self) = sdml:FormalConstraint
Returns true
if the passed value is of type FormalConstraint
, else false
.
def environment(FormalConstraint) → {0..1} ConstraintEnvironment
def sentence(FormalConstraint) → ConstraintSentence
9.4.16. Constraint Sentences
Class: AtomicSentence
TBD
def predicate(AtomicSentence) → Term
def arguments(AtomicSentence) → {ordered 0..} Term
def arity(AtomicSentence) → unsigned
Union: BindingFromSequence
TBD
Class: BindingFromType
TBD
Union: BindingTarget
TBD
Enumeration: BinaryOperation
TBD
BinaryOperation::Conjunction = 1
BinaryOperation::Disjunction = 2
BinaryOperation::ExclusiveDisjunction = 3
BinaryOperation::Implication = 4
BinaryOperation::Biconditional = 5
Class: BinarySentence
TBD
Union: BooleanSentence
TBD
Union: ConstraintSentence
TBD
Class: EquationSentence
TBD
def left_hand_side(EquationSentence) → Term
def right_hand_side(EquationSentence) → Term
Class: Negation
TBD
Union: QuantifiedSentence
TBD
Class: QuantifierBinding
TBD
Enumeration: QuantifierKind
QuantifierKind::Existential = 1
QuantifierKind::Universal = 2
Union: SimpleSentence
TBD
9.4.17. Constraint Terms
Class: BinaryExpression
TBD
Union: BooleanExpression
TBD
Union: Expression
TBD
Class: ExpressionBinding
TBD
Class: ExpressionNegation
TBD
Class: FunctionalTerm
TBD
def arity(FunctionalTerm) → unsigned
Class: ListOfPredicateValues
TBD
Class: NamePath
TBD
Union: PredicateValue
TBD
Union: ReturnValues
TBD
Class: SequenceComprehension
TBD
Union: Term
TBD
9.4.18. Constraint Environment
Class: ConstraintEnvironment
def ConstraintEnvironment(_) → boolean ≔ type_of(self) = sdml:ConstraintEnvironment
Returns true
if the passed value is of type ConstraintEnvironment
, else false
.
def bindings(ConstraintEnvironment) → {ordered 0..} EnvironmentBinding
Class: CollectionType
def CollectionType(_) → boolean ≔ type_of(self) = sdml:CollectionType
Returns true
if the passed value is of type CollectionType
, else false
.
def sequence(CollectionType) → Type
def element(CollectionType) → {0..1} Type
Class: EnvironmentBinding
def EnvironmentBinding(_) → boolean ≔ type_of(self) = sdml:EnvironmentBinding
Returns true
if the passed value is of type EnvironmentBinding
, else false
.
def arity(EnvironmentBinding) → unsigned
def name(EnvironmentBinding) → Identifer
def signature(EnvironmentBinding) → {0..1} FunctionSignature
def body(EnvironmentBinding) → EnvironmentBindingValue
Union: EnvironmentBindingValue
def EnvironmentBindingValue(_) → boolean ≔ type_of(self) = sdml:EnvironmentBindingValue
Returns true
if the passed value is of type EnvironmentBindingValue
, else false
.
TBD
Class: FunctionParameter
def FunctionParameter(_) → boolean ≔ type_of(self) = sdml:FunctionParameter
Returns true
if the passed value is of type FunctionParameter
, else false
.
def name(FunctionParameter) → {0..1} Identifier
def type(FunctionParameter) → FunctionType
Class: FunctionSignature
def FunctionSignature(_) → boolean ≔ type_of(self) = sdml:FunctionSignature
Returns true
if the passed value is of type FunctionSignature
, else false
.
def arity(FunctionSignature) → unsigned
def parameters(FunctionSignature) → {ordered 1..} FunctionParameter
def type(FunctionSignature) → FunctionType
Union: FunctionType
def FunctionType(_) → boolean ≔ type_of(self) = sdml:FunctionType
Returns true
if the passed value is of type FunctionType
, else false
.
TBD
10. Appendix: Surface Syntax Grammar
The grammar of the surface syntax is presented in Extended Backus–Naur form (EBNF)33. The addition of a character literal
range operation ..
reduces the number of single character alternatives for clarity. There are also a set of pre-defined
terminals such as ASCII_DIGIT
, ASCII_HEX_DIGIT
, UC_LETTER_UPPER
, and UC_LETTER_LOWER
used to simplify character ranges.
While there are many variations34 of EBNF in existence, including multiple standardized forms, the specific form
used here follows the notation described the W3C Extensible Markup Language (XML) 1.0 (Fifth Edition), section 6
Notation.
Module ::= "module" Identifier ModuleBase? ModuleBody ModuleBase ::= "base" IriReference ModuleBody ::= "is" ImportStatement* Annotation* Definition* "end" MemberImport ::= QualifiedIdentifier ModuleImport ::= Identifer Import ::= MemberImport | ModuleImport ImportStatement ::= "import" ( Import | "[" Import+ "]" ) Annotation ::= AnnotationProperty | Constraint AnnotationProperty ::= "@" IdentiferReference "=" Value Definition ::= DataTypeDef | EntityDef | EnumDef | EventDef | PropertyDef | StructureDef | UnionDef DataTypeDef ::= "datatype" Identifier TypeRestriction "opaque"? DataTypeBase AnnotationOnlyBody? TypeRestriction ::= "<-" | "←" DataTypeBase ::= IdentifierReference | BuiltinSimpleType AnnotationOnlyBody ::= "is" Annotation+ "end" EntityDef ::= "entity" Identifier EntityBody? EntityBody ::= "is" Annotation* EntityIdentity StructuredBodyInner "end" EnumDef ::= "enum" Identifier EnumBody? EnumBody ::= "of" Annotation* ValueVariant+ "end" ValueVariant ::= Identifier AnnotationOnlyBody? EventDef ::= "event" Identifier "source" IdentifierReference StructuredBody? StructureDef ::= "structure" Identifier StructuredBody? StructuredBody ::= "is" Annotation* StructuredBodyInner "end" StructuredBodyInner ::= ( MemberGroup | Member )+ MemberGroup ::= "group" Annotation* Member+ "end" UnionDef ::= "union" Identifier UnionBody? UnionBody ::= "of" Annotation* TypeVariant+ "end" TypeVariant ::= IdentifierReference TypeVariantRename? AnnotationOnlyBody? TypeVariantRename ::= "as" Identifier PropertyDef ::= "property" Identifier PropertyBody? PropertyBody ::= "is" Annotation* ( IdentityRole | MemberRole )+ "end" IdentityRole ::= "identity" Identifier TypeExpression AnnotationOnlyBody? MemberRole ::= Identifier InverseName? TypeExpressionTo AnnotationOnlyBody? TypeClassDef ::= "class" Identifier TypeClassParameters TypeClassBody? TypeClassParameters ::= "(" TypeVariable+ ")" TypeVariable ::= FunctionCardinality? Identifier TypeVariableRestriction? TypeVariableRestriction ::= HasType TypeClassReference ( "+" TypeClassReference )* TypeClassReference ::= IdentiferReference TypeClassArguments? TypeClassArguments ::= "(" ( Wildcard | TypeClassReference+ ) ")" Wildcard ::= "_" TypeClassBody ::= "is" Annotation* MethodDef+ "end" MethodDef ::= "def" Identifier FunctionSignature FunctionBody? AnnotationOnlyBody? PropertyReference ::= "in" IdentifierReference EntityIdentity ::= "identity" Identifier ( PropertyReference | ( TypeExpression AnnotationOnlyBody? ) ) Member ::= Identifier ( PropertyReference | ( InverseName? TypeExpressionTo AnnotationOnlyBody? ) ) InverseName ::= "(" Identifier ")" TypeExpression ::= HasType TypeReference TypeExpressionTo ::= HasType Cardinality? "features"? TypeReference HasType ::= "->" | "→" TypeReference ::= UnknownType | IdentifierReference | BuiltinSimpleType | MappingType UnknownType ::= "unknown" MappingType ::= "(" TypeReference HasType TypeReference ")" Cardinality ::= "{" SequenceOrdering? SequenceUniqueness? CardinalityInner "}" CardinalityInner ::= Unsigned CardinalityRange? CardinalityRange ::= ".." Unsigned? SequenceOrdering ::= ( "ordered" | "unordered" ) SequenceUniqueness ::= ( "unique" | "nonunique" ) Value ::= SimpleValue | ValueConstructor | IdentifierReference | MappingValue | SequenceOfValues BuiltinSimpleType ::= "string" | "double" | "decimal" | "integer" | "boolean" | "iri" SimpleValue ::= String | Double | Decimal | Integer | Boolean | IriReference Boolean ::= Truth | Falsity Truth ::= ( "true" | "⊤" ) Falsity ::= ( "false" | "⊥" ) NumericSign ::= [+-] Zero ::= [0] NonZero ::= [1-9] Unsigned ::= Zero | NonZero ASCII_DIGIT* Integer ::= NumericSign? Unsigned Decimal ::= Integer "." ASCII_DIGIT+ ExponentChar ::= [eE] Double ::= Decimal ExponentChar NumericSign? Integer HexPair ::= ASCII_HEX_DIGIT ASCII_HEX_DIGIT Escape ::= "\\" ( CharacterEscape | UnicodeEscape ) CharacterEscape ::= [\"\\/abefnrtv] UnicodeEscape ::= "u{" HexPair ( HexPair ( HexPair )? )? "}" NotAllowed ::= [^\"\#x00-#x08#x0B-#x1F#x7F] QuotedString ::= '"' ( Escape | NotAllowed )* '"' LanguageTag ::= "@" LanguageTagPrimary LanguageTagExtended? LanguageTagScript? LanguageTagRegion? LanguageTagPrimary ::= ASCII_LETTER_LOWER ASCII_LETTER_LOWER ASCII_LETTER_LOWER? LanguageTagExtended ::= "-" ASCII_LETTER_UPPER ASCII_LETTER_UPPER ASCII_LETTER_UPPER LanguageTagScript ::= "-" ASCII_LETTER_UPPER ASCII_LETTER_LOWER ASCII_LETTER_LOWER ASCII_LETTER_LOWER LanguageTagRegion ::= "-" ( ( ASCII_LETTER_UPPER ASCII_LETTER_UPPER ) | ( ASCII_DIGIT ASCII_DIGIT ASCII_DIGIT ) ) String ::= QuotedString LanguageTag? IriReference ::= "<" ( [^<>\"{}|^`\#x00-#x20] | UnicodeEscape )* ">" ValueConstructor ::= IdentifierReference "(" SimpleValue ")" MappingValue ::= SimpleValue HasType Value SequenceOfValues ::= SequenceValueConstraint? "[" ( SimpleValue | ValueConstructor | IdentifierReference )+ "]" SequenceValueConstraint ::= "{" ( SequenceOrdering? SequenceUniqueness? ) "}" IdentifierInitial ::= UC_LETTER_UPPER | UC_LETTER_LOWER IdentifierChar ::= IdentifierInitial | UC_NUMBER_DECIMAL Identifier ::= IdentifierInitial IdentifierChar* ( "_" IdentifierChar+ )* QualifiedIdentifier ::= Identifier ":" Identifier IdentifierReference ::= QualifiedIdentifier | Identifier LineComment ::= ";" [^\r\n]* Whitespace ::= [^\f\n\r\t\v#x20#xa0#x1680#x2000#x200a#x2028#x2029#x202f#x205f#x3000#xfeff]
10.0.1. The Embedded Constraint Language
Given that the formal constraint language component of SDML is likely to be used less frequently and the grammar is fairly long we have made it a separate section.
Constraint ::= "assert" Identifier ( InformalConstraint | FormalConstraint ) InformalConstraint ::= "=" QuotedString ControlledLanguageTag? ControlledLanguageTag ::= "@" LanguageTagPrimary ControlledLanguageScheme? ControlledLanguageScheme ::= "-" ASCII_LETTER ASCII_LETTER ASCII_LETTER ASCII_LETTER? ASCII_LETTER? ASCII_LETTER? ASCII_LETTER? ASCII_LETTER? ASCII_LETTER? FormalConstraint ::= "is" ConstraintEnvironment? ConstraintSentence "end" ConstraintSentence ::= SimpleSentence | BooleanSentence | QuantifiedSentence | "(" ConstraintSentence ")" SimpleSentence ::= Atomic | Equation | Inequation Atomic ::= Term ActualArguments ActualArguments ::= "(" Term* ")" Equation ::= Term "=" Term Inequation ::= Term InequationRelation Term InequationRelation ::= ( '/=' | '≠' | '<' | '>' | '<=' | '≤' | '>=' | '≥' ) BooleanSentence ::= UnaryBooleanSentence | BinaryBooleanSentence UnaryBooleanSentence ::= NegationOperator ConstraintSentence BinaryBooleanSentence ::= ConstraintSentence LogicalConnective ConstraintSentence LogicalConnective ::= ConjunctionOperator | DisjunctionOperator | ExclusiveDisjunctionOperator | ImplicationOperator | BiconditionalOperator NegationOperator ::= ( "not" | "¬" ) ConjunctionOperator ::= ( "and" | "∧" ) DisjunctionOperator ::= ( "or" | "∨" ) ExclusiveDisjunctionOperator ::= ( "xor" | "⊻" ) ImplicationOperator ::= ( "implies" | "⇒" ) BiconditionalOperator ::= ( "iff" | "⇔" ) QuantifiedSentence ::= QuantifiedVariableBinding+ ","? ConstraintSentence QuantifiedVariableBinding ::= ( UniversalQuantifier | ExistentialQuantifier ) QuantifiedVariable UniversalQuantifier ::= "forall" | "∀" ExistentialQuantifier ::= "exists" | "∃" QuantifiedVariable ::= ReservedSelf | Identifer MembershipOperator Term MembershipOperator ::= ( "in" | "∈" ) Term ::= SequenceBuilder | FunctionalTerm | FunctionComposition | IdentifierReference | ReservedSelf | PredicateValue FunctionalTerm ::= Term ActualArguments FunctionComposition ::= ( "self" | Identifier ) ( "." Identifier )+ PredicateValue ::= SimpleValue | SequenceOfPredicateValues SequenceOfPredicateValues ::= "∅" | SequenceValueConstraint? "[" ( SimpleValue | IdentifierReference )* "]" ReservedSelf ::= "self"
10.0.2. Constraint Environment Definitions
ConstraintEnvironment ::= EnvironmentDef+ "in" EnvironmentDef ::= "def" Identifier ( FunctionDef | ConstantDef ) FunctionDef ::= FunctionSignature FunctionBody FunctionSignature ::= ( "(" FunctionParameter+ ")" )? FunctionType FunctionParameter ::= Identifier FunctionType FunctionType ::= HasType FunctionCardinality? FunctionTypeRef FunctionCardinality ::= "{" SequenceOrdering? SequenceUniqueness? CardinalityInner? "}" FunctionTypeRef ::= Optional? ( IdentiferReference | BuiltinSimpleType | MappingType ) Optional ::= "?" FunctionBody ::= ByDefinition ConstraintSentence ConstantDef ::= ByDefinition ( PredicateValue | ConstraintSentence ) ByDefinition ::= ( ":=" | "≔" )
10.0.3. Sequence Builders
SequenceBuilder ::= "{" ( NamedVariableSet | MappingVariable ) "|" SequenceBuilderBody "}" NamedVariableSet ::= Identifier+ MappingVariable ::= "(" Identifier HasType Identifier ")" SequenceBuilderBody ::= QuantifiedSentence | "(" QuantifiedSentence ")"
11. Appendix: Surface Syntax to RDF
This appendix describes the normative mapping from the SDML surface syntax into RDF and OWL.
11.1. Whitespace
Whitespace present in the surface syntax has no effect on the corresponding RDF.
11.2. Comments
Comments present in the surface syntax are not translated into the corresponding RDF.
11.3. Identifiers
Identifiers for modules and type definitions are appended to the base IRI of the module resource, making individual types within a module directly addressable.
Example:
Given a module resource retrieved from https://example.com/v/example/
and containing the following:
module example is end
The module within has the IRI identifier https://example.com/v/example/example
. However, if the module overrides the
retrieved location using the base
specifier, as follows:
module example base <https://example.org/langs/Example#> is end
The module named example
within this resource now has the absolute IRI identifier https://example.org/langs/Example#example
.
11.4. Values
Value Literals are necessary for the specification of annotation values. Literals fall into two categories, simple values and lists with an additional non-literal form the value constructor.
11.4.1. Simple Value Literals
SDML Type | Underlying Type | Value Space | Lexical Space |
---|---|---|---|
boolean |
xsd:boolean |
The values true and false . |
true and false |
decimal |
xsd:decimal |
A 128-bit representation of a fixed-precision decimal number. | XML Schema |
double |
xsd:double |
An IEEE double-precision 64-bit floating point type. | XML Schema |
integer |
xsd:integer |
A signed 64-bit integer. | |
iri |
xsd:AnyURI |
Represents a Uniform Resource Identifier Reference (URI). | XML Schema |
string |
rdf:langString |
A string value with optional language-tag. | RDF |
11.4.2. List Literals
Example:
Given the following list literal:
module example is import skos @skos:altLabel = [ "yes" "no" "maybe" ] end
The generated RDF consists of separate statements for each value in the list.
:thing skos:altLabel "yes" . :thing skos:altLabel "no" . :thing skos:altLabel "maybe" .
For clarity and brevity this is usually written in Turtle as an object list35:
:thing skos:altLabel "yes", "no", "maybe" .
Currently there is no mapping from the surface syntax to list values, that is an RDF Collection36
which constructs a list using the rdf:first
and rdf:rest
predicates. This would result in the following RDF:
:thing skos:altLabel ( "yes" "no" "maybe" ) .
11.4.3. Value Constructors
A value constructor allows for the precise specification of a datatype for a literal.
module example is import sdml @sdml:defaultVariant = xsd:nonNegativeInteger(0) end
:example sdml:defaultVariant "0"^^xsd:nonNegativeInteger .
11.5. Modules
Each module is transformed as follows:
- A
@base
declaration is created with the calculated base IRI for the module. - A
@prefix
declaration, for the empty prefix, is created with the same value as@base
. - Add
@prefix
declaration for the known prefixesowl
,rdf
, andsdml
. - Add the following statements with the subject
:
,rdf:type
isowl:Ontology
,rdf:type
issdml:Module
,sdml:srcLabel
is the identifier for the module.
Example with Implied Base:
module example is end
@base <file:///users/me/sdml/modules/example#> . @prefix : <file:///users/me/sdml/modules/example#> . @prefix owl: <http://www.w3.org/2002/07/owl#> . @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix sdml: <https://sdml.io/sdml-owl.ttl#> . : rdf:type owl:Ontology, sdml:Module ; sdml:srcLabel "example" .
Example with Explicit Base:
The only difference here is that the @base
and @prefix
for the current module have been set explicitly.
module example base <https://example.org/langs/Example#> is end
@base <https://example.org/langs/Example#> . @prefix : <https://example.org/langs/Example#> . @prefix owl: <http://www.w3.org/2002/07/owl#> . @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix sdml: <https://sdml.io/sdml-owl.ttl#> . : rdf:type owl:Ontology, sdml:Module ; sdml:srcLabel "example" .
11.6. Imports
Each imported module is transformed as follows:
- The imported module name is used to calculate an IRI relative to the current base.
- Add
@prefix
declaration with the imported module name as prefix. Add an
owl:imports
property to the current ontology with the imported module’s calculate IRI.Any mechanism for the mapping of module names to IRI for off-line resolution is out of scope for this
Example:
module example base <https://example.org/langs/Example#> is import other end
@base <https://example.org/langs/Example#> . @prefix : <https://example.org/langs/Example#> . @prefix owl: <http://www.w3.org/2002/07/owl#> . @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix sdml: <https://sdml.io/sdml-owl.ttl#> . @prefix other: <https://example.org/langs/other#> . : rdf:type owl:Ontology, sdml:Module ; owl:imports <https://example.org/langs/other#> ; sdml:srcLabel "example" .
11.7. Annotations
11.8. Constraints
11.9. Datatype Definitions
Example:
module example is datatype name <- string end
:name rdf:type rdfs:Datatype ; owl:onDatatype sdml:string ; sdml:srcLabel "name" ; rdfs:isDefinedBy : .
Example with Restrictions:
module example is datatype name <- xsd:string is @xsd:minLength = 5 @xsd:maxLength = 25 end end
:name rdf:type rdfs:Datatype ; owl:onDatatype sdml:string ; owl:withRestrictions ( [ xsd:minLength "5"^^xsd:nonNegativeInteger ] [ xsd:maxLength "25"^^xsd:nonNegativeInteger ] ) ; sdml:srcLabel "name" ; rdfs:isDefinedBy : .
11.10. Entity Definitions
Example Empty Entity:
module example is entity Person end
:Person rdf:type owl:Class, sdml:Entity ; sdml:srcLabel "Person" ; rdfs:isDefinedBy : .
Example Entity with Identity:
module example is entity Person is identity id -> PersonId end end
:Person rdf:type owl:Class, sdml:Entity ; sdml:srcLabel "Person" ; rdfs:isDefinedBy :example . :Person__id rdf:type owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :Person ; rdfs:range :PersonId ; sdml:srcLabel "id" ; rdfs:isDefinedBy : .
Example Entity with By-Value Member:
TBD
Example Entity with By-Reference Member:
module example is entity Person is identity id -> PersonId contact {0..} -> {0..2} Person is @dc:description = "Emergency contact person" end end end
:Person rdf:type owl:Class, sdml:Entity ; sdml:srcLabel "Person" ; rdfs:isDefinedBy : . :Person__id rdf:type owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :Person ; rdfs:range :PersonId ; sdml:srcLabel "id" . :Person__contact rdf:type owl:ObjectProperty ; rdfs:domain :Person ; rdfs:range :PersonId ; owl:minCardinality 0 ; owl:maxCardinality 2 ; owl:inverseOf :Person__contact__inverse ; dc::description "Emergency contact person" ; sdml:srcLabel "contact" . :Person__contact__inverse rdf:type owl:ObjectProperty ; rdfs:domain :PersonId ; rdfs:range :Person ; owl:minCardinality 0 ; owl:inverseOf :Person__contact ; sdml:srcLabel "contact-inverse" .
Example Entity with Group:
TBD
module example is entity Person is identity id -> PersonId group @skos:prefLabel = "Identification" pref_name -> xsd:string alt_name -> xsd:string end end end
:Person rdf:type owl:Class, sdml:Entity ; sdml:srcLabel "Person" ; rdfs:isDefinedBy : . _:G01 rdf:type sdml:Group ; skos:prefLabel = "Identification" sdml:inClassifier :Person. :Person__pref_name rdf:type owl:ObjectProperty ; rdfs:domain :Person ; rdfs:range xsd:string ; sdml:srcLabel "pref_name" ; sdml:inGroup _:G01 . :Person__alt_name rdf:type owl:ObjectProperty ; rdfs:domain :Person ; rdfs:range xsd:string ; sdml:srcLabel "alt_name" ; sdml:inGroup _:G01 .
11.11. Enumeration Definitions
Example Empty Enumeration:
module example is enum DistanceUnit end
:DistanceUnit rdf:type owl:Class, sdml:Enumeration ; sdml:srcLabel "DistanceUnit" ; rdfs:isDefinedBy :example .
Example Enumeration with Variants:
module example is enum DistanceUnit is Meter = 1 Foot = 2 end end
:DistanceUnit rdf:type owl:Class, sdml:Enumeration ; :hasValueVariant [ rdf:type sdml:EnumerationVariant ; sdml:srcLabel "Meter" ; rdf:value 1 ] , [ rdf:type sdml:EnumerationVariant ; sdml:srcLabel "Foot" ; rdf:value 2 ] . sdml:srcLabel "DistanceUnit" ; rdfs:isDefinedBy :example .
Example Enumeration with Named Variants:
Alternatively if the enumeration sets the SDML property variantTransform
to the value “named” it will alter the
transformation (from the default “anonymous”) to RDF such that all variants become named individuals within the
enclosing module.
module example is import sdml enum DistanceUnit is @sdml:variantTransform = "named" Meter = 1 Foot = 2 end end
:Meter rdf:type owl:Class, sdml:EnumerationVariant ; sdml:srcLabel "Meter" ; rdf:value 1 . :Foot rdf:type owl:Class, sdml:EnumerationVariant ; sdml:srcLabel "Foot" ; rdf:value 2 . :DistanceUnit rdf:type owl:Class, sdml:Enumeration ; :hasValueVariant :Meter ; :hasValueVariant :Foot ; sdml:srcLabel "DistanceUnit" ; rdfs:isDefinedBy :example .
11.12. Event Definitions
Example Empty Event:
module example is event PersonNameChanged source Person end
:PersonNameChanged rdf:type owl:Class, sdml:Event ; sdml:eventSource :Person ; sdml:srcLabel "PersonNameChanged" ; rdfs:isDefinedBy :example.;
Example Event with Members:
module example is event PersonNameChanged source Person is ;; identifier members will be copied from Person fromValue -> Name toValue -> Name end end
:PersonNameChanged rdf:type owl:Class, sdml:Event ; sdml:eventSource :Person ; :hasMember [ sdml:srcLabel "fromValue" ; :targetClassifier :Name ] , [ sdml:srcLabel "toValue" ; :targetClassifier :Name ] . sdml:srcLabel "PersonNameChanged" ; rdfs:isDefinedBy :example .
11.13. Structure Definitions
Example Empty Structure:
module example is structure Length end
:Length rdf:type owl:Class, sdml:Structure ; sdml:srcLabel "Length" ; rdfs:isDefinedBy :example .
Example Structure with Annotation:
module example is structure Length is @skos:prefLabel = "Length"@en end end
:Length rdf:type owl:Class, sdml:Structure ; skos:prefLabel "Length"@en ; sdml:srcLabel "Length" ; rdfs:isDefinedBy :example .
Example Structure with Members:
module example is structure Length is @skos:prefLabel = "Length"@en value -> Decimal unit -> DistanceUnit end end
:Length rdf:type owl:Class, sdml:Structure ; skos:prefLabel "Length"@en . :hasMember [ sdml:srcLabel "value" ; :targetClassifier :Decimal ; ] , [ sdml:srcLabel "unit" ; :targetClassifier :DistanceUnit ; ] ; sdml:srcLabel "Length" ; rdfs:isDefinedBy :example .
Example Structure with Groups:
module example is structure Account is @skos:prefLabel = "Customer Account"@en group @skos:prefLabel = "Metadata"@en created -> xsd:dateTime end group @skos:prefLabel = "Customer Information"@en customer -> {1..1} Customer end end end
11.14. Union Definitions
Example Empty Union:
module example is union Vehicle end
:Vehicle rdf:type owl:Class, sdml:Union ; sdml:srcLabel "Vehicle" ; rdfs:isDefinedBy : .
Example Union with Types:
module example is union Vehicle of Car Truck Boat end end
:Vehicle rdf:type owl:Class, sdml:Union ; sdml:hasTypeVariant :Car ; sdml:hasTypeVariant :Truck ; sdml:hasTypeVariant :Boat ; sdml:srcLabel "Vehicle" ; rdfs:isDefinedBy : .
Example Union with Annotation:
module example is union Vehicle of @skos:prefLabel = "Vehicle"@en Car Truck Boat end end
:Vehicle rdf:type owl:Class, sdml:Union ; skos:prefLabel "Vehicle"@en ; sdml:hasTypeVariant :Car ; sdml:hasTypeVariant :Truck ; sdml:hasTypeVariant :Boat . sdml:srcLabel "Vehicle" ; rdfs:isDefinedBy :example .
Example Union with Rename:
module example is union Vehicle of Car Truck Boat as WaterCar end end
:Vehicle rdf:type owl:Class, sdml:Union ; sdml:hasTypeVariant :Car ; sdml:hasTypeVariant :Truck ; sdml:hasTypeVariant [ sdml:srcLabel "WaterCar" ; owl:equivalentClass :Boat ] ; sdml:srcLabel "Vehicle" ; rdfs:isDefinedBy :example .
11.15. Property Definitions
TBD
12. Appendix: Constraint Semantics
The embedded constraint language is purely functional and constraint assertions MUST return a single boolean value determining correctness. The constraint language follows the naming convention of Common Logic, and so a constraint corresponds to a Common Logic sentence which may be one of the following forms.
Notation
- We use the symbol \(\mathcal{D}\) to denote the domain of discourse which is defined as the set \(\{\mathcal{F}, \mathcal{P}, \mathcal{T}, \mathcal{V}\}\) of functions, predicates, types, and concrete values.
- We use the letters \(f, g, h, \cdots\) to denote functions in the set \(\mathcal{F}\).
- We use the letters \(P, Q, R, \cdots\) to denote predicates in the set \(\mathcal{P}\).
- We use the letters \(\tau, \delta, \rho, \cdots\) to denote types in the set \(\mathcal{T}\).
- Functions map a domain type \(\rho\) to a range type \(\rho\) of values in \(\mathcal{V}\).
- We also associate with each function or predicate symbol a non-negative integer, called its arity, \(|f|\), which indicates the
number of arguments a function or predicate takes.
- A function of arity 0 is called a constant.
- Sometimes we use superscripts to indicate the arity of a symbol, e.g., we may write \(f^2\) for a binary function.
- Predicates are functions over a domain of relations and have a range of boolean values.
- We use the letters \(x, y, z, \cdots\) to denote (individual) variable in the set \(\mathcal{X}\) of all variables, ranging over values in \(\mathcal{V}\).
General Semantics
- The set \(T(\mathcal{F}, \mathcal{V})\) of terms (over \(\mathcal{F}\) and \(\mathcal{X}\)) is defined inductively
by:
- every variable \(x\) in \(\mathcal{X}\) is a term, and
- if \(f\) is a function in \(\mathcal{F}\) of arity \(n\), and \(t_1, \cdots, t_n\) are terms, then \(f(t_1,\cdots, t_n)\) is also a term.
- For example, if \(f\) is a binary function, \(a\) is a constant, and \(x\) is a variable, then \(a\), \(f(a, x)\), and \(f(a, a)\) are all terms.
- Similarly, we define:
- if \(P\) is a predicate symbol in \(\mathcal{P}\) of arity \(n\), and \(t1, \cdots, t_n\) are terms, then \(P(t_1, \cdots, t_n)\) is an atomic formula, or atom for short.
- Thus, if \(P\) is a binary predicate symbol, then \(P(x, x)\) and \(P(a, f(x, a))\) are atomic formulas.
- If a term or atom contains no variables, it is said to be variable-free or ground.
- The same variable may occur several times in a formula. We distinguish between free and bound occurrences of variables.
- Each occurrence of a variable \(x\) that is in the scope of a quantifier expression \(∀x\) or \(∃x\) is said to be bound. An occurrence of \(x\) that is not bound is said to be free.
- For example, in \(∀x∃y P(x, y)\) all variable occurrences are bound, whereas in \(∃y P(x, y)\) the occurrence of \(x\) is free.
- The same variable may have both free and bound occurrences in a formula, e.g., the variable \(x\) in \(Q(x) ∨ ∃x ¬R(x)\).
- Formulas without free occurrences of variables are called sentences. Thus, \(∀x∃y P(x, y)\) is a sentence but \(∃y P(x, y)\) is not.
Semantically, sentences are formulas that can be true (⊤) or false (⊥), whereas the truth value of a formula with free occurrences of variables depends on the assignment of values to these variables.
12.1. Simple Sentences
An atomic sentence takes the form of a predicate with a set of zero or more arguments.
predicate(a1, ..., an)
An equation sentence asserts the equality of two terms.
rhs = rhs
An inequation sentence asserts an inequality relation between two terms.
lhs /= rhs lhs ≠ rhs lhs < rhs lhs <= rhs lhs ≤ rhs lhs > rhs lhs >= rhs lhs ≥ rhs
12.2. Boolean Sentences
The negation of a sentence takes the form of a preceding logical not operator.
not rhs ¬rhs
The conjunction of two sentences takes the form of the application of the logical and operator.
lhs and rhs lhs ∧ rhs
The disjunction of two sentences takes the form of the application of the logical or operator.
lhs or rhs lhs ∧ rhs
The exclusive disjunction of two sentences takes the form of the application of the logical xor operator.
lhs xor rhs lhs ⊻ rhs
An implication relation between two sentences holds true when the right-hand proposition is a logical consequence of the left-hand. In effect it says that “if the left-hand side is true, then the right-hand side is also true”; however, it does not say what one can deduce if the left-hand side is not true, or under what other conditions the right-hand side may be true.
Note that the left-hand side is often named the antecedent and the right-hand side is the consequent. The entire expression is often termed a conditional.
lhs implies rhs lhs ==> rhs lhs ⇒ rhs
A biconditional relation between two sentences holds true when the both sides are either true or false. In effect, “the left-hand side implies the right-hand side and the right-hand side implies the left-hand-side”.
lhs iff rhs lhs <==> rhs lhs ⇔ rhs\begin{equation} L \iff R \equiv (L \implies R) \land (R \implies L) \end{equation}
12.3. Quantified Sentences
The universal quantification over a sequence says that “for all elements \(e\) in the sequence \(S\) the sentence \(s\) must hold true”.
forall e in Sequence s ∀ e ∈ Sequence s
The existential quantification over a sequence says that “there exists an element \(e\) in the sequence \(S\) where the sentence \(s\) holds true”.
exists e in Sequence s ∃ e ∈ Sequence s
12.4. Sequence Builders
Notation
- We use the symbol \(\mathbb{S}\) to denote an arbitrary sequence of values such that \(\mathbb{S} ⊆ \mathcal{V}\).
- We use \(\tau(x)\) to denote the type a variable \(x\).
- Given that a function or predicate with an arity of \(0\) is a constant value:
- for functions the following equivalence holds \(\forall f \in \mathcal{F} \left(|f| = 0 \implies \mathcal{r}_f = \tau_f\right)\), and
- for predicates the following equivalence holds \(\forall p \in \mathcal{P} \left(|p| = 0 \implies \mathcal{r}_p = \tau_p\right)\).
The value of a sequence builder of the form \(t ≔ \bigl\{ x, y \mid \ldots\}\) provides a relation for each variable of the form \(x(\tau_t) → \tau_x\) and \(y(\tau_t) \rightarrow \tau_y\). This can be expressed in the following:
\begin{equation} t ≔ \bigl\{ x_1, \cdots, x_n \mid \ldots \bigr\} \implies \forall x \in x_1, \cdots, x_n, \exists f \in \mathcal{F} \bigl(|f| = 1 \land \mathcal{d}_f = \tau_t \land \mathcal{r}_f = \tau_x\bigr) \end{equation} \begin{equation} \bigl\{ x \mid P(x)\bigr\} \equiv \bigl\{ x \mid x \in \mathcal{D} \land P(x)\bigr\} \end{equation}In natural language this transformed expression read as follows “construct a sequence of values of \(x\) such that \(x\) is in the sequence \(\mathcal{D}\) and the predicate \(P\) holds true for \(x\)”.
Predicates may also be combined with the usual boolean operations; negation \(\bigl\{ x \mid ¬P(x)\bigr\}\), conjunction \(\bigl\{ x \mid P(x) \wedge Q(x)\bigr\}\), disjunction \(\bigl\{ x \mid P(x) \vee Q(x)\bigr\}\), and exclusive disjunction \(\{ x \mid P(x) \veebar Q(x)\bigr\}\).
The domain for each variable \(x\) may either be described as the set of all elements in a sequence value \(\mathbb{S}\), or the set of all values for a type \(\mathbb{T}\).
- \(\bigl\{ x \mid x \in \mathbb{S} \land P(x)\bigr\}\) returns a sequence of all values \(x\) in the collection \(\mathbb{S}\) for which the
predicate \(P\) holds (evaluates to
true
).- Example: \(\bigl\{ m \mid x \in self.members \land optional(m)\bigr\}\)
- \(\bigl\{ x \mid x \in \mathbb{T} \land P(x)\bigr\}\) returns a sequence of all values \(x\) in the type \(\mathbb{T}\) for which the
predicate \(P\) holds (evaluates to
true
).- Example: \(\bigl\{ v \mid v \in FutureDate \land gt(v, today)\bigr\}\).
- Example: \(\bigl\{ v \mid v → FutureDate \land gt(v, today)\bigr\}\).
It can be shown that this is a universal quantification, all values from the type \(\mathbb{T}\) or sequence
\(\mathbb{S}\) will be considered. A variable can be identified as existentially quantified by prefacing it’s definition
with either the keyword exists
or the operator ∃
.
The following definitions are taken from the standard library and act as filters over a sequence of Annotation
union
values to provide sequences which only contain one or other of the types in the union.
def annotation_properties(anns -> {} Annotation) -> {} AnnotationProperty ≔ {a | a ∈ anns is_annotation_property(a)} def constraints(anns - > {} Annotation) -> {} Constraint ≔ {a | a ∈ anns is_constraint(a)}
12.5. Quantified Sentences as Sequence Builders
The existential qualifier specifically determines that there is at least one element in the type or sequence where the sentence provided holds true. If you need to ensure a specific cardinality, for example that exactly three elements in type or sequence are selected, use a sequence comprehension.
count({ e | e in Sequence s}) = 3
By this definition we can show that the following are equivalent.
exists e in Sequence s not is_empty({ e | e in Sequence s})
More formally, the following equivalence holds true.
\begin{equation} ∃ e \in \mathbb{S} \bigl(P\left(e\right)\bigr) ≡ ¬\bigl\{e \mid e \in \mathbb{S} ∧ P\left(e\right)\bigr\}=∅ \end{equation}We can also show that the following are equivalent for universal quantification.
forall e in Sequence s is_empty({ e | e in Sequence not s}\begin{equation} ∀ e \in \mathbb{S} \bigl(P\left(e\right)\bigr) ≡ \bigl\{e \mid e \in \mathbb{S} ∧ ¬P(e)\bigr\}=∅ \end{equation}
In this manner we can treat the quantified sentences as syntactic sugar over specific forms of sequence comprehensions.
12.6. Constraints to ISO/IEC Common Logic
This appendix describes the normative mapping from SDML/Constraints to the ISO/IEC Common Logic standard.
TBD
13. Appendix: UML Profile for SDML
This section describes a non-normative UML Profile for SDML that allows the diagrammatic presentation of an SDML model.
Based upon the Unified Modeling Language 2.5.1.
13.1. The Profile
A UML profile creates a specific sub-language that utilizes the structure and semantics of UML but constrained and tailored to a specific domain. In this case the profile creates a way to use UML to visualize the structure of an SDML module as well as the dependencies between modules. Figure 28 it the top-level of the profile and shows the set of stereotypes used to represent the elements of SDML.
Figure 28: The «sdml» Profile Package
Example: Applying the Profile
Figure 29 shows how the SDML profile is applied to a model; note that some tools have alternative methods for applying profiles to a model.
Figure 29: Applying the Profile
13.2. Modules
The SDML profile introduces a stereotype module that may then be applied to a package within the model to denote a named module.
Figure 30: The «module» Stereotype
Constraints
A module with the stereotype «module»
applied:
- MUST contain ONLY the datatype, enumeration, entity, event, structure, union, and property elements of the SDML profile.
- MUST NOT contain a nested module.
The constraint below the module in the previous figure defines a set of functions used to construct other constraints in this section.
Example: An Empty Module
The following is our example empty module.
module rentals is end
This empty module above will transform into the following UML.
Figure 31: An Empty Module
13.3. Imports
Import statements in SDML target either a module, or members within a module by qualified name. UML on the other hand
has two separate import relations, ImportElement
and ImportPackage
. Importing a package in UML brings all the public
elements of the package into the current namespace unqualified. In SDML there is no way to reference an external
definition unqualified, so this profile only uses element import.
Figure 32: The «import» Stereotype
Constraints
TBD
Example: Importing a Module
The following demonstrates the importing of the module named sdml
into the current module. Following the notation in
section 7.4.4 of the UML specification, imports are represented with a dashed line and the keyword «import»
.
module rentals is import sdml end
Figure 33: Importing a Module
Example: Importing Module Members
The following demonstrates the importing of the members named dc:description
and skos:prefLabel
into the current module.
module rentals is import skos:prefLabel import dc:description end
Figure 34: Importing Module Members
13.4. Annotations
TBD
13.5. Datatype Definitions
The profile element «datatype»
is a specialization of the UML PrimitiveType
(section 10.2.3.2 of the UML specification)
classifier.
Figure 35: The «datatype» Stereotype
Example: A Datatype Example
The following simple datatype definition restricts the type sdml:string
.
module rentals is datatype VehicleIdNumber <- string end
Figure 36: A Datatype Example
13.6. Entity Definitions
Figure 37: The «entity» Stereotype
Figure 38: The «identity» Stereotypes
!!!
Constraints
- The
«identity»
stereotype renames the meta-associationclass
toowningEntity
, with type«entity»
and multiplicity1..1
. - The
«identity»
stereotype - The
«ref»
stereotype
Example: An Entity Example
module rentals is import uuid datatype VehicleIdNumber <- string entity Vehicle is identity vin -> VehicleIdNumber end entity Booking is identity id -> uuid::Uuid vehicle -> {0..1} Vehicle end end
Figure 39: An Entity Example
13.7. Enumeration Definitions
Figure 40: The «enum» Stereotype
The standard Enumeration
classifier is used to model enum definitions.
The standard EnumerationLiteral
instance is used to model value variants.
Constraints
Example: An Example Enumeration
module rentals is enum VehicleClass of SubCompact = 0 Compact = 1 MidSize = 2 FullSize = 3 CompactSuv = 4 FullSizeSuv = 5 end end
Figure 41: An Example Enumeration
13.8. Event Definitions
The standard Signal
(10.3.3.1) and Dependency
Figure 42: The «event» and «source» Stereotypes
For details on by-value members and groups see section 13.9 below.
Example: An Example Event
module rentals is import uuid entity Booking is identity id -> uuid:Uuid end event VehicleAssigned source Booking end
Figure 43: An Example Event
13.9. Structure Definitions
TBD
Example:
TBD
13.10. Union Definitions
Generalization Sets (9.7)
Figure 44: The «union» and «variant» Stereotypes
Constraints
Example: An Example Union
module rentals is import uuid structure Car structure Van structure Truck union RentalClass of Car Van Truck end end
Figure 45: An Example Union
13.11. Property Definitions
TBD
13.12. Member Cardinality
UML
(section 7.5 – Types and Multiplicities)
Figure 46: UML Multiplicity Element
While the type Integer
is clear in the UML above, the type UnlimitedNatural
is best defined by the text from the UML
specification itself:
An instance of
UnlimitedNatural
is a value in the (infinite) set of natural numbers (0, 1, 2…) plus unlimited. The value of unlimited is shown using an asterisk (‘*’).UnlimitedNatural
values are typically used to denote the upper bound of a range, such as a multiplicity; unlimited is used whenever the range is specified to have no upper bound.
Using the following example from the UML specification, actually combining the two diagrams from section 7.5.5 of the specification into one.
Figure 47: UML Multiplicity Example
Either of these representations corresponds to the following SDML structure.
module customer is structure Purchase structure Account structure Customer is purchase -> {ordered unique 0..} Purchase account -> {unique 0..5} Account end end
14. License
Copyright 2023 Simon Johnston <johnstonskj@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
15. Tables of Other Content
List of Tables
- Table 1: Domain-Related Model Summary
- Table 2: Built-in Simple Type Mapping
- Table 3: Additional Simple Types in SDML
- Table 4: String Escape Characters
- Table 5: OWL Built-in Datatype Facets
- Table 6: Logical Sequence Types
- Table 7: Informal Constraint Schemes
- Table 8: Editor Support Packages
- Table 9: Abbreviation Expansions
- Table 10: OWL: Classes
- Table 11: OWL: Object Properties
- Table 12: OWL: Datatype Properties
- Table 13: OWL: Annotation Properties
- Table 14: Commonly Used Annotation Properties
- Table 15: Unicode Characters in Grammar
- Table 16: Summary of Value Literals
List of Listings
- Listing 1: Comments
- Listing 2: Example: Annotation Comments
- Listing 3: Example: Empty Module
- Listing 4: Example: Empty Module with Base
- Listing 5: Example: Importing Module
- Listing 6: Example: Importing Member
- Listing 7: Example: Importing a List
- Listing 8: Binary Value Example
- Listing 9: Example: Value Constructor
- Listing 10: Example: Mapping List
- Listing 11: Example: Sequence Value
- Listing 12: Example: Sequence Value
- Listing 13: Example: New Datatype
- Listing 14: Example: New Currency Datatype
- Listing 15: Example: New Datatype with Restrictions
- Listing 16: Example: New Opaque Datatype
- Listing 17: Example: Annotation Property
- Listing 18: Example: Annotation Property List
- Listing 19: Example: Annotated Module
- Listing 20: Example: Informal Constraint on Module
- Listing 21: Example: Formal Constraint on Module
- Listing 22: The definition of rdfs:comment
- Listing 23: The SDML definition of rdfs:comment
- Listing 24: Example: Empty Structure
- Listing 25: Example: Annotated Structure
- Listing 26: Example: Structure Members
- Listing 27: Example: Empty Entity
- Listing 28: Example: Entity with Identifying Member
- Listing 29: Entity with Reference Member
- Listing 30: Example: Empty Event
- Listing 31: Example: Event
- Listing 32: Example: Empty Enum
- Listing 33: Example: Enum With Variants
- Listing 34: Example: Enum With Equivalent Datatype
- Listing 35: Example: Enum With Variant Values
- Listing 36: Example: Enum With Multiple Variant Values
- Listing 37: Example: Enum With Conflicting Variant Values
- Listing 38: Example: Empty Union
- Listing 39: Example: Union With Variants
- Listing 40: Example: Minimal Structure Member
- Listing 41: Example: Entity Identity
- Listing 42: Example: Using a Property Reference
- Listing 43: Example: Declaring Feature Member
- Listing 44: Example: Feature Member Constraint
- Listing 45: Example: Empty Property Definition
- Listing 46: Example: Property Definition
- Listing 47: Feature Sets
- Listing 48: Union Type Definition
- Listing 49: Union Type As Property
- Listing 50: Example: Informal Constraint
- Listing 51: Example: Informal Constraint with Natural Language
- Listing 52: Example: Informal Constraint with Controlled Language
- Listing 53: Example: Formal Constraint (1 of 3)
- Listing 54: Example: Formal Constraint (2 of 3)
- Listing 55: Example: Formal Constraint (3 of 3)
- Listing 56: Example: Formal Constraint
- Listing 57: Rentals Concepts (First Pass)
- Listing 58: Rentals Basic Relationships
- Listing 59: Rentals More Detail
- Listing 60: Rentals Non-Entity Detail
- Listing 61: Rentals Structural Detail
- Listing 62: SDML Catalog Grammar
- Listing 63: Example SDML Catalog File
- Listing 64: Example SDML Catalog File with Groups
- Listing 65: OWL: Ontology Header
Footnotes:
Home at sdmi.io, source at github.com
Human-Usable Textual Notation (HUTN), OMG
Domain-Driven Design, Wikipedia
…, ?
Simple Knowledge Organization System (SKOS), W3C.
Model-Driven Development, Wikipedia
Thrift interface description language, Apache Software Foundation
Unicode General Category, Unicode.org
SPARQL 1.1 Overview, W3C
Codes for the representation of names of languages — Part 1: Alpha-2 code (ISO 639-1:2002), ISO
Codes for the representation of names of languages — Part 2: Alpha-3 code (ISO 639-2:1998), ISO
Information and documentation — Codes for the representation of names of scripts (ISO 15924:2022), ISO
RDF 1.1 Turtle – Terse RDF Triple Language, §6.3 IRI References, W3C
DublinCoreDCMI Metadata Terms, DublinCore
Common Logic Controlled English, John F. Sowa, 2004.
Attempto Controlled English (ACE), University of Zurich
Home at Tree Sitter, source at github.com
Installing Cargo from the Cargo book
Vehicle identification number, Wikipedia
Extended Backus-Naur Form (EBNF), Wikipedia
RDF 1.1 Turtle – Terse RDF Triple Language, §2.3 Object Lists, W3C
RDF 1.1 Turtle – Terse RDF Triple Language, §2.8 Collections, W3C