Syntax Overview
Dolfin uses indentation-sensitive syntax, like Python. Blocks are opened with a colon (:) and delimited by consistent indentation, tabs or spaces, but never mixed. All lines inside a block must be at the same indentation level.
Comments begin with # and run to the end of the line.
# This is a comment
concept Foo:
has bar: string # inline comment
File types
A Dolfin project uses two kinds of files.
package.dlf: one per project, declares the package identity:
package <http://example.com/my-ontology>:
dolfin_version "1"
version "0.3.0"
author "Alice"
description "A short description"
| Field | Required | Description |
|---|---|---|
dolfin_version | yes | Language version to use (currently "1") |
version | yes | Ontology version (semver string) |
author | no | Author name |
description | no | Human-readable description of the ontology |
*.dlf ontology files: one or more files containing declarations (concepts, enums, properties, rules).
Prefixes
Prefixes bind short aliases to IRI namespaces, enabling interoperability with external vocabularies.
prefix <http://schema.org/> as schema
prefix <http://purl.org/dc/elements/1.1/> as dc
Once declared, a prefix can be used in qualified names:
has address: optional schema.PostalAddress
Prefixes can also be declared in a hierarchical block to share a common path:
prefix com.example:
Person
Organization as Org
This is equivalent to:
prefix com.example.Person
prefix com.example.Organization as Org
Concepts
A concept defines a category of things and the attributes they can carry.
concept Person:
has first_name: one string
has last_name: one string
has email: optional string
has age: optional int
An empty concept (no body) is also valid, useful as a flag or tag:
concept FlaggedForReview
Inheritance
A concept can inherit from one or more parents using sub:
concept Employee:
sub Person
has employee_id: one string
has department: one string
concept Manager:
sub Employee
has reports: Employee
Multiple parents are comma-separated:
concept PartTimeEmployee:
sub Employee, Contractor
Attributes
Each attribute is declared with has:
has <name>: [cardinality] <type>
The type can be a primitive or another concept name:
has count: one int
has owner: optional Person
has tags: string # zero or more (default cardinality)
Cardinality
Cardinality constrains how many values an attribute can hold.
| Keyword | Meaning |
|---|---|
| (none) | Any number (zero or more) |
any | Any number (zero or more, explicit) |
one | Exactly one (required) |
optional | Zero or one |
some | One or more |
N | Exactly N (integer literal) |
N..M | Between N and M (inclusive) |
N..* | At least N |
has name: one string # required, single value
has nickname: optional string # may be absent
has tags: some string # at least one
has aliases: string # any number (default)
has lucky_numbers: 3 int # exactly three
has scores: 1..5 float # between one and five
has comments: 0..* string # zero or more (explicit range)
Primitive types
| Type | Description | Examples |
|---|---|---|
string | Text | "hello", "" |
int | Integer | 0, 42, -7 |
float | Floating-point | 3.14, -0.5 |
boolean | Boolean | true, false |
Closed concepts
An closed concept defines a closed set of named values. Only the declared variants are valid:
concept Status:
one of:
Pending
Active
Archived
Enumurated values are referenced by name in attributes and rules:
has status: one Status
Properties
A property is a named relationship declared independently of any concept, rather than inside one. It connects a domain type to a range type:
property worksFor: Employee -> Organization
Cardinality can be specified on either side:
property manages: one Manager -> Employee
Properties are used in rule patterns and assertions like any attribute.
Facts
A fact declares a named instance of a concept. Facts use the same indentation-based style as every other Dolfin construct.
fact alice a Person
first_name "Alice"
last_name "Chen"
age 34
The general form is:
fact <id> a <ConceptName>
<property-name> <value>
...
<id>is the instance identifier. It can be referenced elsewhere as:<id>.a <ConceptName>asserts the type. Multiple types are comma-separated:fact x a Dog, Neutered.- Each property line is
<name> <value>, where the name matches ahasdeclaration (or inherited one) on the concept.
Value forms
| Form | Example | Meaning |
|---|---|---|
| String literal | "Alice" | Text value |
| Integer literal | 42 | Integer value |
| Float literal | 3.14 | Floating-point value |
| Boolean literal | true | Boolean value |
| Enum value | Active | A one of member, unquoted |
| Reference | :alice | Another named fact in this module |
| Anonymous block | [ ... ] | Inline instance (blank node) |
| List | v1, v2 | Multiple values for multi-valued properties |
References
A reference points to another fact declaration by its identifier, prefixed with ::
fact acme a Organization
name "Acme Corp"
fact alice a Person
first_name "Alice"
employer :acme
Forward references are allowed; all identifiers are resolved after the full file is parsed.
Anonymous blocks
When a property’s value is a structured object with no need for a global identifier, use an inline block:
fact alice a Person
address [
street "12 Main St"
city "Paris"
country "France"
]
The block’s type is inferred from the property’s declared range. To specify a subtype explicitly, add a SubType as the first line of the block:
fact rex a Animal
owner [
a LegalGuardian
first_name "Bob"
]
Multi-valued properties
For properties with cardinality any, some, or at least N, repeat the property name on multiple lines:
fact alice a Person
phone_numbers "555-0001"
phone_numbers "555-0002"
Or use a comma-separated list:
fact alice a Person
phone_numbers "555-0001", "555-0002"
Both forms are equivalent.
Multiple anonymous blocks
Repeating a property with block values works the same way:
fact rex a Dog
vaccinations [
vaccine_name "Rabies"
date_administered "2024-03-15"
]
vaccinations [
vaccine_name "Distemper"
date_administered "2024-03-15"
]
Scoping and identity
All fact declarations in a module share a flat namespace. An id must be unique within the module. Facts from imported modules are referenced with module.id syntax.
Rules
A rule defines an if-then inference. When all patterns in the match: block hold, the assertions in the then: block are applied.
rule classify_senior:
match:
?p a Person
?p age [ >= 65 ]
then:
?p a SeniorPerson
Variables
Variables begin with ?. They bind to values during matching and can be referenced in assertions:
rule link_preferred_contact:
match:
?org a Organization
?org primary_contact ?person
then:
?person worksFor ?org
Match patterns
Each line in match: is a pattern. All patterns are combined with implicit AND.
Type pattern: checks that a variable is an instance of a concept:
?x a SomeConcept
Triple pattern: checks that a subject has a property with a given value:
?x someProperty someValue
?x someProperty ?y
?x someProperty "literal"
?x someProperty 42
Constraint block: inline conditions on a value using [...]:
?x age [ > 18 ]
?x status [ = Active ]
?x address [ city "Paris" ]
?x manager [ a Director ]
Multiple constraints in a block are AND-combined:
?x score [ >= 50, <= 100 ]
Constraint blocks can be nested:
?x owner [ address [ country "France" ] ]
Comparison operators
Used inside constraint blocks:
| Operator | Meaning |
|---|---|
= | Equal |
!= | Not equal |
< | Less than |
<= | Less than or equal |
> | Greater than |
>= | Greater than or equal |
Quantifiers
Quantifiers express conditions over collections of matching bindings:
| Quantifier | Meaning |
|---|---|
all | Every binding must satisfy the sub-patterns |
none | No binding may satisfy the sub-patterns |
at_least N | At least N bindings must satisfy the sub-patterns |
at_most N | At most N bindings may satisfy the sub-patterns |
exactly N | Exactly N bindings must satisfy the sub-patterns |
between N, M | Between N and M bindings (inclusive) |
rule require_manager_approval:
match:
?req a Request
none ?approver:
?approver a Manager
?req approvedBy ?approver
then:
?req a UnapprovedRequest
An optional constraint block on the quantifier variable filters the set being quantified:
at_least 2 ?member [ a SeniorEmployee ]:
?member worksIn ?dept
Then assertions
Each line in then: asserts a new fact:
Type assertion: classifies a variable as an instance of a concept:
?x a SomeConcept
Triple assertion: asserts a property relationship:
?x someProperty ?y
?x someProperty "value"
Nested rules
A then: block can contain a nested match:/then: block for conditional sub-inferences:
rule complex_inference:
match:
?x a Foo
then:
match:
?x bar ?y
then:
?y a Baz
Names and identifiers
Simple names are alphanumeric identifiers (with underscores): Person, first_name, status.
Qualified names use dot-notation to reference names in a namespace: schema.Person, com.example.Thing.
IRIs are enclosed in angle brackets: <http://example.com/Thing>. They can appear as package names and prefix targets.
Variables begin with ?: ?person, ?count.
The @iri_name annotation
The @iri_name annotation overrides the IRI segment derived from a file’s name. It appears at the top of an ontology file, before any declarations:
@iri_name "custom-segment"
concept Foo:
has bar: string
This is useful when the file name doesn’t match the IRI fragment expected by external systems.