Appendix: EBNF

The EBNF used here is defined in XML 1.0 EBNF-NOTATION with the addition that each rule in the grammar ends with a semicolon character ;.

program ::= processing-instruction* ( fact | rule | query )* ;

/* ************************************************************************* */

fact    ::= assertion | retraction ;

assertion
        ::= fact-body "." ;

retraction
        ::= fact-body "~" ;

fact-body
        ::= predicate ( "(" constant ( "," constant )* ")" )? ;
        
predicate
        ::= LC_ALPHA ( ALPHA | DIGIT | UNDERSCORE )* ;

/* ************************************************************************* */

constant
        ::= string | number | boolean ;

string  ::= identifier-string | literal-string ;

identifier-string
        ::= predicate ( ":" ALPHA ( ALPHA | DIGIT | UNDERSCORE )* )? ;

literal-string
        ::= DQUOTE ( string-escape | [^#x22] )* DQUOTE ;

string-escape
        ::= "\\" ( DQUOTE | [tnr] )
        | "\u{" HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT
              ( HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT )? "}" ;

number  ::= float   # Optional
        | decimal   # Optional
        | integer ; # Required

integer ::= ( "+" | "-" )? DIGIT+ ;

decimal ::= integer "." DIGIT+ ;

float   ::= ( decimal ( "e" | "E" ) integer )
            | "+inf.0"
            | "-inf.0"
            | "+nan.0" ;

boolean ::= "true" | "false" ;

/* ************************************************************************* */

rule    ::= head? ( ":-" | "<-" | "⟵" ) body "." ;

head    ::= ( atom ( ( ";" | "|" | "OR" | "∨" ) atom )* )
            | "⊥" ;

body    ::= literal ( ( "," | "&" | "AND" | "∧" ) literal )* ;

/* ************************************************************************* */

atom    ::= predicate "(" term ( "," term )* ")" ;

term    ::= variable | constant ;

variable
        ::= named-variable | anon-variable ;

named-variable
        ::= UC_ALPHA ( ALPHA | DIGIT | UNDERSCORE )* ;

anon-variable
        ::= UNDERSCORE ;

/* ************************************************************************* */

literal ::= ( "!" | "NOT" | "¬" )?
            ( relational-literal
            | arithmetic-literal ) ;

/* ************************************************************************* */

relational-literal
        ::= atom ;

arithmetic-literal
        ::= operand operator operand ;

operand ::= ( named-variable | constant ) ;

operator
        ::= "="
            | ("!=" | "/=" | "≠")
            | "<"
            | ("<=" | "≤")
            | ">"
            | (">=" | "≥")
            | ("*=" | "≛" | "MATCHES") ;

/* ************************************************************************* */

query   ::= ( "?-" atom "." )
        |   ( atom "?" ) ;

/* ************************************************************************* */

processing-instruction
        ::= "."
            ( pi-pragma
            | pi-assert
            | pi-infer
            | pi-input
            | pi-output )
            "." ;

pi-pragma
        ::= "pragma" predicate ( "=" constant )? ;

pi-assert
        ::= "assert" relation-decl functional-dependency-list? ;

relation-decl
        ::= predicate attribute-decl-list ;

attribute-decl-list
        ::= "(" attribute-decl ( "," attribute-decl )* ")" ;

attribute-decl
        ::= ( predicate ":" )?
            ( "boolean" | "float" | "decimal" | "integer" | "string" ) ;

functional-dependency-list
        ::= ":" functional-dependency ( ";" functional-dependency )* ;

functional-dependency
        ::= attribute-index-list ( "-->" | "⟶" ) attribute-index-list ;

attribute-index-list
        ::= attribute-index ( "," attribute-index )* ;

attribute-index
        ::= integer | predicate ;

pi-infer
        ::= "infer" ( relation-decl | "from" predicate ) ;

pi-input
        ::= "input" io-details ;

io-details
        ::= predicate parameter-list ;

parameter-list
        ::= "(" parameter-assignment ( "," parameter-assignment )* ")" ;

parameter-assignment
        ::= predicate "=" constant ;

/*
Known Parameter types:

uri     ::=  URI ;
type    ::=  predicate | media-type ;
headers ::= "present" | "absent" ;
columns ::= column ( "," column )* ;
column  ::= integer |
            "[" integer? ":" integer? "]" ;
table   ::= identifier-string ;
db-columns
        ::= identifier-string ( "," identifier-string )* ;
*/

pi-output
        ::= "output" parameter-list ;

/* ************************************************************************* */

comment ::= line-comment | block-comment ;

line-comment
        ::= "%" [^\r\n]* EOL ;

block-comment
        ::= '/*' ( [^*] | '*'+ [^*/] )* '*'* '*/' ;

/* ************************************************************************* */

EOL     ::= "\n" | "\r" "\n"? ;

DQUOTE  ::= #x22 ;

UNDERSCORE
        ::= "_" ;

SPACE_SEP
        ::= ? corresponds to the Unicode category 'Zs' ? ;

WHITESPACE
        ::= SPACE_SEP | "\t" | EOL ;

LC_ALPHA
        ::= ? corresponds to the Unicode category 'Ll' ? ;

UC_ALPHA
        ::= ? corresponds to the Unicode category 'Lu' ? ;

TC_ALPHA
        ::= ? corresponds to the Unicode category 'Lt' ? ;

ALPHA   ::= LC_ALPHA | UC_ALPHA | TC_ALPHA ;

DIGIT   ::= ? corresponds to the Unicode category 'Nd' (decimal number) ? ;

HEXDIGIT
        ::= [0-9a-fA-F] ;