A User-Friendly Syntax for Core SHACL This is a user-friendly syntax for the core of SHACL that tries to align directly with my SHACL RDF syntax. Example Description ∈ ex:Person ⊩ |≤27| # there are at most 27 people ¹ex:child ⊩ ∋ ex:john # child subjects include John ¹ex:child ⊩ ∈ ex:Person ∧ IRI # ... are people and not blank ex:child² ⊩ ∈ ex:Person # child objects are people ∈ ex:Patriot ⊩ ⋹ ex:Citizen # patriots are directly citizens ex:password² ⊩ ^^xs:string ∧ ℓ≤24 ∧ ℓ≥8 # passwords are between 8 and 24 long ex:age² ⊩ ^^xs:integer ∧ ≥0 # ages are non-negative integers ex:john ⊩ ex:name ∝ "^John.*" ★ # John's name starts with "John" ex:mstatus² ⊩ ∈ { ex:single ex:married ex:divorced } # three marital statuses only ex:Person ⊩ ex:mstatus ∝ |=1| # people have one marital status ex:Person ⊩ ex:mstatus ∝ ∋ ex:married → ex:spouse ∝ |≥1| # married people have a spouse ex:Person ⊩ ex:mstatus ∝ ∋ ex:single → ex:spouse ∝ |≤0| # single people don't have a spouse ∈ ex:Person ⊩ ex:spouse ∅ ex:child ∧ # people can't marry their children ex:child ex:age ≤ ex:age ∧ # ... are older than their children ex:age ≤ ex:child⁻¹ ex:age ∧ # ... are younger than their parents ex:child⁻¹ ex:child ∝ |≤9| ∧ # ... have at most 8 siblings ex:name ∝ ∈ rdf:langString ∧ # ... names are lang-tagged strings ex:name ∝ ➀ # ... have only one name per language ∈ ex:Person ⊩ ( ( ex:spouse ∝ |≤0| → ex:mstatus ∝ ( |≥1| ∧ ∈{ex:single ex:divorced} ) ) ∖ ( ex:spouse ∝ |≤1| → ex:mstatus ∝ ( |≥1| ∧ ∈{ex:married} ) ) ∖ |≤3| ∖ ) # people with no spouse are single or divorced # people with one spouse are married # and there are at most three people left over ∈ ex:Isolated ⊩ ⟦ rdf:type ⟧ # isolated nodes have only types ∈ ex:nonIsolated ⊩ ¬ ⟦ rdf:type ⟧ # non-isolated nodes have other values sh:partShape ≡ (IRI ∨ sh:inverse ∝ IRI) # parts are properties or inverses sh:pathShape ≡ ∈ sh:path ⊩ ( sh:partShape ∨ ⦇ sh:partShape ⦈ ) # paths are parts or lists of parts Grammar Meaning shaclDoc ::= ( prefixID | definition ) * prefixID ::= '@prefix' prefixName ':' IRI definition::= name '≡' shape # refer to shape by name | name '≡' scopedShape # refer to shape by name scopedShape ::= scope ( '∪' scope )* '⊩' shape # set of all nodes in any scope validates against shape scope ::= value # the value | '∈' class # SHACL instances of class | '¹' property # subjects of property | property '²' # objects of property | '¹' '?' # all subjects | '?' '²' # all objects shape ::= ( filter ( '∧' filter )* '→' )? component ( '∧' component )* # set of nodes that validate against all filters # validates against each component filter ::= component # nodes that validate vs shape component ::= name # validate against named shape | '¬' component # doesn't validate ag. comp. | '∈' class ( '∪' class )* # SHACL instance of some class | '^^' datatype ( '∪' datatype )* # has one of datatypes | '∈' '{' value* '}' # is one of values | '⋹' class # has rdf:type of class | 'ℓ' '≤' nonnegativeInteger # maximum string length | 'ℓ' '≥' nonnegativeInteger # minimum string length | '>' literal # exclusive minimum | '≥' literal # inclusive minimum | '<' literal # exclusive maximum | '≤' literal # inclusive maximum | 'IRI' | 'Literal' | 'BlankNode' # kind of node | regex '★' ( string )? # matches pattern (with flags) | path '=' path # path values the same | path '∅' path # path values disjoint | path '<' path # path1 values < path2 values | path '≤' path # path1 values ≤ path2 values | path '∝' component # path values in shape | '⦇' shape '⦈' # list members in shape | '⟦' pathpart * '⟧' # no other property has values | '∋' value # set contains value | '|' '≥' nonnegativeInteger '|' # minimum size of set | '|' '≤' nonnegativeInteger '|' # maximum size of set | '|' '=' nonnegativeInteger '|' # exact size of set | '➀' # only one value per language | '(' shape ')' # validate against shape | '(' component ('∨'component)+ ')' # validate against one or more | '(' ( component '∖' ) + ')' # partition - see below # The initial remnant is the entire set being validated. # The next remnant is the subset of the current one that fails # to validate against the filter of the respective component. # The final remnant is empty. # Each remnant validates against the respective component. ⦅ path ::= pathpart + # composition pathpart ::= property | property '⁻¹' # inverse of property class ::= name # a class datatype ::= name # a datatype property ::= name # a property value ::= name | literal # object regex ::= string # regular expression name ::= qname # NOTE: no here, just qnames qname ::= prefixName ':' name IRI ::= '<' ucharacter '>' prefixName as per Turtle name as per Turtle literal as per Turtle string as per usual nonnegativeInteger as per usual Possible Tweaks It would be possible to modify the syntax to come up with a more standard treatment of conjunction, disjunction, and negation but that syntax would not align as closely with the RDF syntax.