Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Full Grammar

There are two kind of files in a dolfin package. The package.dlf placed at the root of a package folder, it serves as a manifest of the whole package. We give an exemple of such a file here and let the grammar being deduced.


package <http://my.special.iri/of-ontology>:
  dolfin_version: 1
  version: 1.2.3
  author: not only me
  author: but also you
  description: This package is an example

There are regular dolfin files. The folder in which they are, are used to compose the iri of each ontology and then the iri of each element.

ontology ::= 
    iri_name_annotation?
    header_item*
    declaration*
    EOF

iri_name_annotation ::=
    "@iri_name" ( String | IRI ) NEWLINE

(* Prefixes and file-level directives may appear in any order at the top of
   the file, before the first declaration. *)
header_item ::=
    prefix_statement
  | locale_directive
  | timezone_directive

locale_directive ::=
    "@locale" date_field_order NEWLINE

timezone_directive ::=
    "@timezone" timezone_spec NEWLINE

date_field_order ::= field date_sep field date_sep field   (* e.g. d/m/y *)
field             ::= "y" | "m" | "d"
date_sep          ::= "/" | "-" | "."
timezone_spec     ::= iana_name        (* e.g. Europe/Brussels *)
                    | utc_offset       (* e.g. +02:00, -05:00 *)
                    | tz_abbrev        (* e.g. UTC, Z, CET *)

prefix_statement ::=
    "prefix" prefix_target

prefix_target ::=
    qualified_name_or_iri ":" NEWLINE
    INDENT
      prefix_target+
    DEDENT
  | qualified_name_or_iri "as" Name NEWLINE
  | qualified_name_or_iri NEWLINE

declaration ::=
    concept
  | property
  | rule
  | query
  | fact
  | mapping
  | export

(* A concept with no members is written without the colon: `concept Foo` *)
concept ::=
    "concept" Name NEWLINE
  | "concept" Name ":" NEWLINE
    INDENT
      concept_member+
    DEDENT

concept_member ::=
    concept_iri_name
  | sub_concept
  | equivalent_concept
  | concept_condition
  | has_property
  | key_block
  | "one" "of" ":" NEWLINE
    INDENT
      enum_value+
    DEDENT

(* An enum value may fix the concept's key fields:
     one of:
       black [red 0.0, green 0.0, blue 0.0]
       red   [red 1.0, green 0.0, blue 0.0]
   Its IRI is prefixed with the concept (Color_red). *)
enum_value ::= Name no_comp_constraint_block? NEWLINE

(* Two instances with equal keys are the same individual (owl:sameAs /
   owl:hasKey). `key:` + fields is the same as `has key` on each field. A key
   field implies `one`: any other cardinality is an error. A multi-valued key
   is typed `set of T` or `list of T` (see has_property). *)
key_block ::= "key" ":" NEWLINE INDENT has_property+ DEDENT

concept_iri_name ::= "@iri_name" IRI NEWLINE

sub_concept ::= "sub" type_ref ("," type_ref)* NEWLINE with_block?
(* A with_block needs a single type: `sub T` + block is (T and line and …). *)

equivalent_concept ::= "equivalent to" type_ref ("," type_ref)* NEWLINE with_block?
(* owl:equivalentClass, one per type and per line; same with_block rule as sub. *)

(* Concept conditions (SHACL side). `equivalent to T` + a condition block
   defines the concept as T narrowed by the block: members are inferred and
   asserted members validated. `check` + a block validates only. The block is
   one line, indented under the keyword line (no line continuation inside
   brackets); it cannot be mixed with a with_block. `check` is matched
   contextually and stays usable as a name; its ":" is optional. *)
concept_condition ::=
    "equivalent to" type_ref ("," type_ref)* NEWLINE
    INDENT condition_block NEWLINE DEDENT
  | "check" ":"? NEWLINE
    INDENT condition_block NEWLINE DEDENT

condition_block ::= "[" condition ("," condition)* "]"

condition ::=
    "a" type_ref
  | comparison_op (field_expr | focus_ref) (* `<= now() - $birthdate` *)
  | condition_quantifier? qualified_name variable? condition_block?
  | "$" Name variable?                      (* binds the focus node's field *)

(* Default quantifier: all. `some` = at least one. "at least"/"at most" also
   take the underscore spelling. No `between`; a count is an integer or a
   variable bound earlier in the block (`[ $ quota ?q, at_least ?q item ]`). *)
condition_quantifier ::=
    "all" | "some" | "none"
  | ("at least" | "at most" | "exactly") (Integer | variable)

(* `$` is the focus node (SHACL $this), always the outermost one at any
   nesting depth. `$age` is shorthand for `$ age ?age`; `$age` and `$ age` lex
   the same. Name an intermediate node with a variable:
   `[ some of_country ?c [ … ] ]`. `$` only appears in condition blocks. *)
focus_ref ::= "$" | "$" Name | "$" Name variable

(* Example:
     concept CanVote:
       equivalent to Citizen
         [ some of_country [ legal_age_of_vote [ <= $age ]]]
     concept Adult:
       check
         [ $ birthdate ?b, some of_country [ legal_age_of_vote [ <= $ age ?a ]]] *)

with_block ::= INDENT with_line+ DEDENT
with_line ::= with_clause NEWLINE with_block?   (* a nested block narrows the filler *)

has_property ::=
    "has" "key"? Name ":" (cardinality? | collection) type_ref field_block? NEWLINE
    (INDENT has_line+ DEDENT)?

(* A `has` body: property axioms, and `@iri_name <IRI>` (the field's property
   IRI, shared by every `has` of that name in the file), in any order. No
   domain / range lines. *)
has_line ::=
    "@iri_name" IRI NEWLINE
  | "sub" qualified_name NEWLINE
  | "inverse of" qualified_name NEWLINE
  | "transitive" NEWLINE | "symmetric" NEWLINE | "reflexive" NEWLINE
  | "equivalent to" property_path NEWLINE

(* `has tags: set of string`: any number of values, no order (a plain
   multi-valued property; a set key compares as a set). `has steps: list of
   Step`: one rdf:List node (rdfs:range rdf:List), its rdf:first values the
   Steps in order. A collection takes no cardinality; type_ref is the element
   type, and a field_block constrains the elements. `set` and `list` are
   matched contextually (before `of`) and stay usable as names. *)
collection ::= "set" "of" | "list" "of"

(* Value constraints on the field (SHACL side): `has red: float [ <= 1, >= 0 ]`.
   An `=` makes a derived field, computed rather than stored:
   `has age: duration [ = now() - $birthdate ]`. `now()` (`now` is matched
   contextually) and `$name` (the focus node's field) appear in field_block
   and condition comparisons only. *)
field_block ::= "[" comparison_op field_expr ("," comparison_op field_expr)* "]"

field_expr ::= expr                (* arithmetic over literals, variables, and: *)
  | "now" "(" ")"
  | "$" Name

property ::=
    "property" Name (":" cardinality? type_ref "->" cardinality? type_ref)? NEWLINE
    (INDENT property_line+ DEDENT)?

property_line ::=
    "@iri_name" IRI NEWLINE                    (* this property's IRI *)
  | "sub" qualified_name NEWLINE
  | "inverse of" qualified_name NEWLINE
  | "transitive" NEWLINE | "symmetric" NEWLINE | "reflexive" NEWLINE
  | "equivalent to" property_path NEWLINE
  | "domain" type_ref NEWLINE with_block?     (* rdfs:domain *)
  | "range" type_ref NEWLINE with_block?      (* rdfs:range *)
(* `domain` and `range` are matched contextually: they stay usable as ordinary
   names. Body lines come in any order. A side given twice (by the one-line
   `D -> R` and a line, or by two lines) is error S014. *)

(* `fact` declares one instance; `mapping` declares a template expanded once
   per row of a CSV file or a spreadsheet sheet, or once per `for each` match of
   a JSON or XML document. `mapping` is a reserved word, but `from`, `for`,
   `each`, `sheet`, `header`, `row`, `map`, `where`, `starts`, `case`, `else`,
   `slug` and `to` are matched contextually and stay usable as ordinary names. *)
fact ::=
    "fact" Name "a" type_ref ("," type_ref)* NEWLINE
    INDENT
      fact_assertion+
    DEDENT

fact_assertion ::=
    qualified_name fact_value ("," fact_value)* NEWLINE
  | "is" qualified_name "of" fact_value NEWLINE
  | "a" type_ref NEWLINE

fact_value ::=
    literal
  | ":" Name                      (* reference to another fact *)
  | Name                          (* enum value *)
  | "[" NEWLINE INDENT fact_assertion+ DEDENT "]"

mapping ::=
    "mapping" "from" IRI "as" Name NEWLINE
    INDENT
      mapping_header_line*
      "map" ":" NEWLINE
      INDENT
        source_binding+
      DEDENT
      where_block?
      "to" ":" NEWLINE
      INDENT
        mapping_template+
      DEDENT
    DEDENT

(* At most one of each. `for each` is for JSONPath and XPath only; `sheet`
   and `header row` (an integer >= 1) are for XLSX and ODS only. *)
mapping_header_line ::=
    "for" "each" String NEWLINE   (* iteration root *)
  | "sheet" String NEWLINE        (* worksheet name, matched exactly *)
  | "header" "row" Integer NEWLINE

(* A String is a column name under CSV, XLSX and ODS, a JSONPath expression under
   JSONPath and an XPath expression under XPath; under the last two a bare
   Name is an error. *)
source_binding ::= (Name | String) "as" variable NEWLINE

(* A row failing any condition is dropped silently. *)
where_block ::=
    "where" ":" NEWLINE
    INDENT
      (variable "[" where_condition ("," where_condition)* "]" NEWLINE)+
    DEDENT

where_condition ::=
    comparison_op (String | Integer | Float)
  | "starts" "with" String

mapping_template ::=
    "fact" subject_template "a" type_ref ("," type_ref)* NEWLINE
    (INDENT mapping_assertion+ DEDENT)?

(* A `{?var}` hole may appear in the subject; a cast there is an error. *)
subject_template ::=
    Name ":" template_tail
  | prefixed_name template_tail?
  | IRI                           (* holes are written inside the IRI text *)
  | Name

template_tail ::= template_hole template_chunk*

(* `{slug(?x)}`: the cell lowercased, accents dropped, other runs of
   characters than a-z0-9 replaced by `_`. Also allowed inside an IRI. *)
template_hole ::= "{" variable "}" | "{" "slug" "(" variable ")" "}"

template_chunk ::= template_hole | Name | Integer

mapping_assertion ::=
    qualified_name mapping_value ("," mapping_value)* NEWLINE
  | "is" qualified_name "of" mapping_value NEWLINE
  | "a" type_ref NEWLINE
  | qualified_name "case" variable ":" NEWLINE
    INDENT
      (String "->" fact_value NEWLINE)+
      ("else" "->" fact_value NEWLINE)?
    DEDENT

mapping_value ::=
    "{" variable "}" cast?
  | "{" variable cast "}"         (* same as "{" variable "}" cast *)
  | variable cast?                (* shorthand for the braced form *)
  | subject_template              (* an IRI or prefixed name built from cells *)
  | "[" NEWLINE INDENT mapping_assertion+ DEDENT "]"
  | fact_value                    (* a constant, identical on every row *)

cast ::= "as" ("string" | "int" | "float" | "boolean" | "date" | quantity_unit)

(* `quantity(kW)`: the cell is the number, the unit is written here. *)
quantity_unit ::= "quantity" "(" unit_expr ("as" convert_target)? ")"

(* `export` writes the rows of a query to a file. Unlike `mapping`, `export`
   is not a reserved word: it is matched contextually at the start of a
   top-level declaration, so a concept or property named `export` keeps
   working. `from`, `map`, `limit`, `to`, `sheet`, `for` and `each` are
   contextual too; `query` is the reserved word of `query`. `map:` may be
   left out only after `from query`. `header row` is refused under `to:`. *)
export ::=
    "export" NEWLINE
    INDENT
      export_source
      export_map?                 (* required with `from:` *)
      "to" ":" NEWLINE
      INDENT
        IRI "as" Name NEWLINE     (* <export://path> as CSV | XLSX | JSONPath | ... *)
        mapping_header_line*      (* `sheet` and `for each` only *)
      DEDENT
    DEDENT

export_source ::=
    "from" "query" qualified_name NEWLINE
  | "from" ":" NEWLINE
    INDENT
      query_clause*
      group_by_block?             (* a query body without `return` *)
    DEDENT

export_map ::=
    "map" ":" NEWLINE
    INDENT
      export_column+
      ("limit" Integer NEWLINE)?
    DEDENT

export_column ::=
    (Name | String) "as" variable cast? NEWLINE
    (INDENT column_modifier+ DEDENT)?

rule ::=
    "rule" Name ":" NEWLINE
    INDENT
      match_block
      then_block
    DEDENT

match_block ::=
    "match" ":" NEWLINE
    INDENT
      match_pattern+
    DEDENT

then_block ::=
    "then" ":" NEWLINE
    INDENT
      then_item+
    DEDENT

match_pattern ::=
    subject qualified_name object NEWLINE
  | subject qualified_name contraint_block NEWLINE
  | subject "a" tyep_ref NEWLINE
  | "among" ":" NEWLINE
    INDENT
      match_pattern+
    DEDENT quantifier ":" NEWLINE
    INDENT
      match_pattern+
    DEDENT
  | quantifier ":" NEWLINE
    INDENT
      match_pattern+
    DEDENT
  | variable constraint_block NEWLINE                 (* condition on a bound value *)
  | aggregation variable "as" variable NEWLINE        (* aggregate, same line as in queries *)
    INDENT
      match_pattern+                                  (* no aggregate inside *)
    DEDENT

quantifier ::=
    "all"
  | "none"
  | "at least" Integer
  | "at most" Integer
  | "exactly" Integer
  | "between" Integer "," Integer

then_item ::=
    assertion NEWLINE
  | nested_rule

assertion ::=
    subject qualified_name object
  | subject qualified_name no_comp_contraint_block
  | subject "a" type_ref

nested_rule ::= match_block then_block

(* ----------------------------------------------------------------------- *)
(* Queries                                                                  *)
(* ----------------------------------------------------------------------- *)

query ::=
    "query" Name ":" NEWLINE
    INDENT
      query_clause*
      group_by_block?
      return_block?
    DEDENT

query_clause ::=
    query_subject_pattern
  | existence_block
  | query_composition
  | variable comparison_op (variable | literal) NEWLINE     (* filter *)
  | aggregation variable "as" variable NEWLINE              (* aggregate sub-query *)
    INDENT
      query_clause+
    DEDENT
  | "is" qualified_name "of" object NEWLINE

query_subject_pattern ::=
    variable "a" type_ref NEWLINE (INDENT property_pattern+ DEDENT)?
  | "a" type_ref NEWLINE (INDENT property_pattern+ DEDENT)?
  | variable NEWLINE INDENT property_pattern+ DEDENT
  | variable qualified_name object NEWLINE (INDENT property_pattern+ DEDENT)?
  | variable qualified_name constraint_block NEWLINE
  | variable qualified_name subject_block NEWLINE
  | variable "is" qualified_name "of" (variable | constraint_block) NEWLINE

property_pattern ::=
    qualified_name object NEWLINE
  | qualified_name constraint_block NEWLINE
  | qualified_name subject_block NEWLINE
  | "optional" qualified_name object NEWLINE
  | "is" qualified_name "of" (variable | constraint_block) NEWLINE
  | "either" qualified_name constraint_block NEWLINE
    ("or" qualified_name constraint_block NEWLINE)+

subject_block ::= "[" NEWLINE INDENT property_pattern+ DEDENT "]"

existence_block ::=
    ("some" | "none") ":" NEWLINE
    INDENT
      query_clause+
    DEDENT

(* The field names are the return columns of the called query. *)
query_composition ::=
    qualified_name "as" variable NEWLINE
  | qualified_name "as" "[" NEWLINE
    INDENT
      (Name variable NEWLINE)+
    DEDENT
    "]" NEWLINE?

aggregation ::= "count" | "count" "distinct" | "sum" | "average" | "min" | "max"

(* Each line is an aggregate or a HAVING filter, in any order. *)
group_by_block ::=
    "group by" variable NEWLINE
    INDENT
      ( aggregation variable "as" variable NEWLINE
      | variable comparison_op (variable | literal) NEWLINE )+
    DEDENT

return_block ::=
    "return" NEWLINE
    INDENT
      return_line*
    DEDENT

return_line ::=
    Name? variable NEWLINE
    (INDENT column_modifier+ DEDENT)?
  | "limit" Integer NEWLINE

column_modifier ::=
    "order" ("asc" | "desc") NEWLINE
  | "distinct" NEWLINE

subject ::=
    variable
  | qualified_name
  | constraint_block

object ::=
    variable
  | literal
  | qualified_name

constraint_block ::= "[" constraint ("," constraint)* "]"

no_comp_constraint_block ::= "[" no_comp_constraint ("," no_comp_constraint)* "]"

no_comp_constraint ::=
    "a" type_ref
  | qualified_name object
  | qualified_name constraint_block

(* In rule and query blocks, a comparison takes a literal, a
   variable or an arithmetic expression over them; quantifiers and `$` are
   condition_block only. *)
constraint ::=
    no_comp_constraint
  | variable? comparison_op expr
  | "is" qualified_name "of" (object | constraint_block)

comparison_op ::=
    "="
  | "!="
  | "<"
  | "<="
  | ">"
  | ">="

type_ref ::=
    type_primary
  | type_primary with_clause+                       (* T and (with …) and … *)
  | with_clause+

type_primary ::=
    type_atom
  | "(" type_ref ")"
  | "not" type_primary                              (* owl:complementOf *)
  | "(" type_ref ("or" type_ref)+ ")"               (* owl:unionOf *)
  | "(" type_ref ("and" type_ref)+ ")"              (* owl:intersectionOf *)
(* Mixing "and" and "or" in one group is an error. "and"/"not"/"with"
   operands must not contain primitives. *)

with_clause ::= "with" "^"? qualified_name ":" with_filler   (* owl:Restriction *)
with_filler ::=
    type_primary                                    (* owl:someValuesFrom *)
  | "only" type_primary                             (* owl:allValuesFrom; `only` is contextual *)
  | cardinality type_primary                        (* qualified cardinalities + owl:onClass *)
  | "=" (literal | qualified_name)                  (* owl:hasValue *)

type_atom ::=
    "string"
  | "int"
  | "float"
  | "boolean"
  | "date"
  | "time"
  | "date_time"
  | "duration"
  | quantity_type
  | qualified_name

(* Built-in quantity datatypes. Contextual, not keywords: only a type in type
   position, so `has length: length` is valid. A capitalised `Mass` or
   `unit.Mass` is a qualified_name, linted towards `mass`. "duration" (above)
   is the time dimension. *)
quantity_type ::=
    "length" | "mass" | "current" | "temperature" | "amount"
  | "luminosity" | "speed" | "acceleration" | "force" | "pressure" | "energy"
  | "power" | "frequency" | "area" | "volume" | "density" | "momentum"

qualified_name ::=
  

(* ----------------------------------------------------------------------- *)
(* Literals                                                                 *)
(* ----------------------------------------------------------------------- *)

literal ::=
    String
  | Integer
  | Float
  | Boolean
  | IRI
  | temporal_literal
  | quantity_literal

(* Temporal smart literals. The keyword names the XSD type; the parenthesised
   content is human-friendly notation, parsed by the dolfin-datetime library.
   The content must NOT contain a ")". *)
temporal_literal ::=
    "date"      "(" date_content     ")"
  | "time"      "(" time_content     ")"
  | "date_time" "(" datetime_content ")"
  | "duration"  "(" duration_content ")"

date_content ::= natural_date | numeric_date ("as" date_field_order)?

natural_date ::= month_name day_ordinal year
               | day_ordinal month_name year
month_name   ::= "January" | "Jan" | "February" | "Feb" | ... | "December" | "Dec"  (* case-insensitive *)
day_ordinal  ::= Integer ("st" | "nd" | "rd" | "th")?   (* suffix is cosmetic *)
year         ::= Integer
numeric_date ::= Integer date_sep Integer date_sep Integer
(* The separator in the value and in the "as" mask must match. Without a mask
   the file-level @locale supplies the field order; with neither, an ambiguous
   numeric date is an error. *)

time_content ::= hour ":" minute (":" second)? am_pm? timezone_spec?
hour   ::= Integer          (* 0-23, or 1-12 with AM/PM *)
minute ::= Integer          (* 00-59 *)
second ::= Integer          (* 00-59 *)
am_pm  ::= "AM" | "PM"      (* case-insensitive; switches to 12-hour clock *)

datetime_content ::= date_content ("," )? time_content   (* comma or space separated *)

duration_content ::= duration_term+
duration_term    ::= Integer duration_unit
duration_unit    ::= "y" | "mo" | "w" | "d" | "h" | "min" | "s"

(* Physical-quantity smart literal. The parenthesised content is a value and a
   unit expression, parsed by the dolfin-units library. The content must NOT
   contain a ")". See the Units & Quantities reference. *)
quantity_literal ::= "quantity" "(" quantity_content ")"
quantity_content ::= simple_quantity (arith_op simple_quantity)* ("as" convert_target)?
simple_quantity  ::= Number unit_expr | Number       (* bare number = dimensionless *)
arith_op         ::= "+" | "-" | "*" | "/"
convert_target   ::= unit_expr | "SI"
unit_expr        ::= unit_term (("." | "/") unit_term)*
unit_term        ::= unit_name ("^" power)?
power            ::= Integer | "(" Integer ")"        (* e.g. s^(-1) *)
unit_name        ::= (* a unit token, optionally SI-prefixed, e.g. m, kg, km, N, km/h components *)

cardinality ::=
    "one"
  | "any"
  | "some"
  | "optional"
  | Integer
  | "at least" Integer
  | "at most" Integer
  | Integer ".." Integer
  | Integer ".." "*"

(* "at least"/"at most" (and the "at_least"/"at_most" underscore aliases) are
   shared with the rule quantifiers above. *)