Types & functors

Before any logic, Nelumbo lets you define syntax: which phrases exist and how they are built. Person :: Object declares a type and Male :: Person a subtype. Male ::= Piet, Jan declares constants of a type. A functor declares a new phrase with ::=: Person ::= father(<Person>) says that father(a person) is itself a person, where each <T> is a hole for an expression of type T. A phrase whose type extends Root can stand on its own line as a sentence; give such a functor the precedence #0. These sections have no queries and no meaning yet: a sentence is correct when it parses, and the editor underlines it in red when it does not.

import nelumbo.lang Person :: Object Male :: Person Female :: Person Male ::= Piet, Jan Female ::= Marie Person ::= father(<Person>) Sentence :: Root Sentence ::= <Person> knows <Person> #0 Piet knows Marie father(Marie) knows Jan

Marie knows Klaas does not parse: Klaas is declared as an Animal, and knows takes a Person. Klaas is a man: fix the declarations so the sentence parses.

import nelumbo.lang Person :: Object Male :: Person Female :: Person Animal :: Object Male ::= Piet, Jan Female ::= Marie Animal ::= Klaas Sentence :: Root Sentence ::= <Person> knows <Person> #0 Marie knows Klaas
import nelumbo.lang
Person   :: Object
Male     :: Person
Female   :: Person
Animal   :: Object
Male     ::= Piet, Jan, Klaas
Female   ::= Marie
Sentence :: Root
Sentence ::= <Person> knows <Person> #0
Marie knows Klaas

Declare a type Thing with the constants flowers and a book, and a functor, so that both sentences parse.

import nelumbo.lang Person :: Object Person ::= Piet, Marie Sentence :: Root // declare a type Thing, its constants and a sentence functor so both sentences parse Marie gives flowers to Piet Piet gives a book to Marie
import nelumbo.lang
Person   :: Object
Person   ::= Piet, Marie
Thing    :: Object
Thing    ::= flowers, a book
Sentence :: Root
Sentence ::= <Person> gives <Thing> to <Person> #0
Marie gives flowers to Piet
Piet gives a book to Marie

Patterns

A functor's pattern is more than words and holes. <(> really <)?> is optional, <(> likes <|> loves <)> is a choice, and <(> <Person> <,> , <)+> repeats a hole one or more times, separated by commas (<)*> also allows none). The token types <NUMBER>, <STRING> and <NAME> accept a number, a quoted string or a name. Several alternatives of one type are separated by commas, each with its own precedence.

import nelumbo.lang Person :: Object Person ::= Piet, Jan, Marie, Klaas Sentence :: Root Sentence ::= <Person> <(> really <)?> <(> likes <|> loves <)> <Person> #0, <Person> is <NUMBER> years old #0, <Person> says <STRING> #0, party with <(> <Person> <,> , <)+> #0 Piet likes Marie Piet really loves Marie Klaas is 42 years old Marie says "hello" party with Piet, Jan, Marie

Piet can invite any number of guests, with and before the last one. Change the pattern so all three sentences parse.

import nelumbo.lang Person :: Object Person ::= Piet, Jan, Marie, Klaas Sentence :: Root // change the pattern so all three sentences parse Sentence ::= <Person> invites <Person> #0 Piet invites Jan Piet invites Jan and Marie Piet invites Jan, Marie and Klaas
import nelumbo.lang
Person   :: Object
Person   ::= Piet, Jan, Marie, Klaas
Sentence :: Root
Sentence ::= <Person> invites <(> <Person> <,> , <)+> <(> and <Person> <)?> #0
Piet invites Jan
Piet invites Jan and Marie
Piet invites Jan, Marie and Klaas

Change the pattern so that Jan does not know Piet parses as well as Jan knows Piet.

import nelumbo.lang Person :: Object Person ::= Piet, Jan Sentence :: Root // change the pattern so both sentences parse Sentence ::= <Person> knows <Person> #0 Jan knows Piet Jan does not know Piet
import nelumbo.lang
Person   :: Object
Person   ::= Piet, Jan
Sentence :: Root
Sentence ::= <Person> <(> knows <|> does not know <)> <Person> #0
Jan knows Piet
Jan does not know Piet

Precedence

An operator is a functor whose pattern starts with a hole, like <Expr> plus <Expr>. When a sentence contains several operators, their precedence #N decides how they group: the higher one binds tighter. With plus at #40 and times at #50, one plus two times three is one plus (two times three); operators of equal precedence group from the left. An operator without a precedence does not parse at all. A hole can carry a precedence too: <Expr#45> takes only operators above 45, so times but not plus. A hole without one takes the precedence of its functor; that is why the #0 of a sentence lets every operator in. The standard operators range from #16 (<->) to #80 (unary -).

import nelumbo.lang Expr :: Object Expr ::= one, two, three Expr ::= <Expr> plus <Expr> #40, <Expr> times <Expr> #50 Sentence :: Root Sentence ::= compute <Expr> #0, twice <Expr#45> #0 // one plus (two times three) compute one plus two times three // (one times two) plus three compute one times two plus three twice two times three

minus has no precedence, so the last sentence does not parse. Give it the precedence of plus.

import nelumbo.lang Expr :: Object Expr ::= one, two, three // give minus a precedence, the same as plus Expr ::= <Expr> plus <Expr> #40, <Expr> minus <Expr>, <Expr> times <Expr> #50 Sentence :: Root Sentence ::= compute <Expr> #0 compute three minus one times two
import nelumbo.lang
Expr     :: Object
Expr     ::= one, two, three
Expr     ::= <Expr> plus <Expr>  #40,
             <Expr> minus <Expr> #40,
             <Expr> times <Expr> #50
Sentence :: Root
Sentence ::= compute <Expr> #0
compute three minus one times two

twice one plus two does not parse: the hole <Expr#45> rejects plus. Change only the precedence of the hole so both sentences parse.

import nelumbo.lang Expr :: Object Expr ::= one, two, three Expr ::= <Expr> plus <Expr> #40, <Expr> times <Expr> #50 Sentence :: Root // change only the precedence of the hole so both sentences parse Sentence ::= twice <Expr#45> #0 twice two times three twice one plus two
import nelumbo.lang
Expr     :: Object
Expr     ::= one, two, three
Expr     ::= <Expr> plus <Expr>  #40,
             <Expr> times <Expr> #50
Sentence :: Root
Sentence ::= twice <Expr#35> #0
twice two times three
twice one plus two

Parentheses

Parentheses override precedence: (one plus two) times three. You never declare them: nelumbo.lang ends with Type P and P ::= (<P>) @nelumbo.lang.Parenthesized, one generic functor (see the next section) that gives every type its parentheses. The @ binds a functor to a Java class; Parenthesized returns the expression inside, so (one) is just one, not a new phrase. A parenthesized expression is complete in itself, so it fits a hole of any precedence: twice (one plus two) parses where twice one plus two did not.

import nelumbo.lang Expr :: Object Expr ::= one, two, three Expr ::= <Expr> plus <Expr> #40, <Expr> times <Expr> #50 Person :: Object Person ::= Piet, Marie Person ::= father(<Person>) Sentence :: Root Sentence ::= compute <Expr> #0, twice <Expr#45> #0, <Person> knows <Person> #0 compute (one plus two) times three twice (one plus two) twice ((one)) (father(Marie)) knows (Piet)

Add square brackets as a second way to group expressions so that the last sentence parses. Bind them to @nelumbo.lang.Parenthesized too, so that [one] is just one.

import nelumbo.lang Expr :: Object Expr ::= one, two, three Expr ::= <Expr> plus <Expr> #40, <Expr> times <Expr> #50 // add square brackets as a second way to group expressions Sentence :: Root Sentence ::= compute <Expr> #0 compute (one plus two) times three compute [one plus two] times three
import nelumbo.lang
Expr     :: Object
Expr     ::= one, two, three
Expr     ::= <Expr> plus <Expr>  #40,
             <Expr> times <Expr> #50
Expr     ::= [<Expr>] @nelumbo.lang.Parenthesized
Sentence :: Root
Sentence ::= compute <Expr> #0
compute (one plus two) times three
compute [one plus two] times three

Generics

Type E declares a type parameter. With it, Team<E> :: Object declares a generic type and Team<E> ::= team of <(> <E> <,> , <)+> a functor for it: team of Piet, Jan is a Team<Person> and team of Rex, Tom a Team<Animal>. A hole names the kind of team it takes: <Team<Person>> rejects a team of animals, <Team<E>> takes a team of anything. Write a generic type without spaces: Team<E>, not Team < E >. The parentheses of the previous section are declared this way, and so are the collection types List<E> and Set<E>.

import nelumbo.lang Person :: Object Person ::= Piet, Jan, Marie Animal :: Object Animal ::= Rex, Tom, Bello Type E Team<E> :: Object Team<E> ::= team of <(> <E> <,> , <)+> Sentence :: Root Sentence ::= <Team<Person>> plays against <Team<Person>> #0, <Team<Animal>> chases <Team<Animal>> #0 team of Piet, Jan plays against team of Marie team of Rex chases team of Tom, Bello

A team can only hold people, so the last sentence does not parse. Make Team generic so that animals can form teams too.

import nelumbo.lang Person :: Object Person ::= Piet, Jan, Marie Animal :: Object Animal ::= Rex, Tom, Bello // make Team generic, so that animals can form teams too Team :: Object Team ::= team of <(> <Person> <,> , <)+> Sentence :: Root Sentence ::= <Team> plays against <Team> #0 team of Piet, Jan plays against team of Marie team of Rex plays against team of Tom, Bello
import nelumbo.lang
Person   :: Object
Person   ::= Piet, Jan, Marie
Animal   :: Object
Animal   ::= Rex, Tom, Bello
Type E
Team<E>  :: Object
Team<E>  ::= team of <(> <E> <,> , <)+>
Sentence :: Root
Sentence ::= <Team<E>> plays against <Team<E>> #0
team of Piet, Jan plays against team of Marie
team of Rex plays against team of Tom, Bello

Connected tokens

Whitespace between tokens does not matter: the pattern <NUMBER> : <NUMBER> accepts 9:30 and 9 : 30 alike. Between <[> and <]> the tokens must touch: <[> <NUMBER> : <NUMBER> <]> accepts only 9:30. Use it for a literal written as several tokens, such as a time, a date or a decimal number, so that it cannot be mistaken for an expression with an operator. <[> and <]> themselves do not appear in the sentence. The standard library writes 3.14, 2025-01-01 and 16#FF this way, and nelumbo.lang the head of a generic type like Team<E>.

import nelumbo.lang Person :: Object Person ::= Piet, Marie Time :: Object Time ::= <[> <NUMBER> : <NUMBER> <]> Sentence :: Root Sentence ::= <Person> arrives at <Time> #0 Piet arrives at 9:30 Marie arrives at 14:05 // Piet arrives at 9 : 30 does not parse

Declare a type Date for dates like 1990-05-17, written without spaces, so that both sentences parse.

import nelumbo.lang Person :: Object Person ::= Piet, Marie Time :: Object Time ::= <[> <NUMBER> : <NUMBER> <]> // declare the type Date, with connected tokens like Time Sentence :: Root Sentence ::= <Person> arrives at <Time> #0, <Person> was born on <Date> #0 Piet arrives at 9:30 Marie was born on 1990-05-17
import nelumbo.lang
Person   :: Object
Person   ::= Piet, Marie
Time     :: Object
Time     ::= <[> <NUMBER> : <NUMBER> <]>
Date     :: Object
Date     ::= <[> <NUMBER> - <NUMBER> - <NUMBER> <]>
Sentence :: Root
Sentence ::= <Person> arrives at <Time>   #0,
             <Person> was born on <Date> #0
Piet arrives at 9:30
Marie was born on 1990-05-17

Named patterns

A recurring piece of a pattern can be given a name: pattern GUESTS ::= ... declares it, and <GUESTS> stands for its body wherever it is used after the declaration. A named pattern is just an abbreviation: unlike a functor, it declares no type and builds no value. Here GUESTS is the guest list of the Patterns exercise, now shared by two sentences. nelumbo.lang uses named patterns itself, for instance pattern QNAME ::= <[> <(> <NAME> <,> . <)+> <]> for the dotted name after import.

import nelumbo.lang Person :: Object Person ::= Piet, Jan, Marie, Klaas pattern GUESTS ::= <(> <Person> <,> , <)+> <(> and <Person> <)?> Sentence :: Root Sentence ::= <Person> invites <GUESTS> #0, party with <GUESTS> #0 Piet invites Jan, Marie and Klaas party with Jan and Marie

Both sentences use <TIME>, which is not declared yet. Declare it as a named pattern for a time like 9:30, above the functor that uses it.

import nelumbo.lang Person :: Object Person ::= Piet, Marie, Klaas // declare the named pattern TIME, for a time like 9:30 Sentence :: Root Sentence ::= <Person> arrives at <TIME> #0, <Person> works from <TIME> to <TIME> #0 Marie arrives at 9:30 Klaas works from 8:00 to 17:30
import nelumbo.lang
Person   :: Object
Person   ::= Piet, Marie, Klaas
pattern TIME ::= <[> <NUMBER> : <NUMBER> <]>
Sentence :: Root
Sentence ::= <Person> arrives at <TIME>          #0,
             <Person> works from <TIME> to <TIME> #0
Marie arrives at 9:30
Klaas works from 8:00 to 17:30

Scoping

A { } block is a scope. A functor declared private in a block exists only inside that block, including nested blocks. Without private, a declaration in a block stays visible after the block: the block alone hides nothing. Below, Max is private to the block and Piet is not. private is also how two functors with the same pattern are kept apart: if they are declared side by side, the parser cannot tell them apart and reports a non deterministic pattern merge.

import nelumbo.lang Person :: Object Sentence :: Root Sentence ::= <Person> reads #0 { private Person ::= Max Person ::= Piet Max reads { Max reads } } Piet reads // Max reads does not parse here: Max is private to the block

Both functors are written friend(<Person>), one for a person and one for a dog, so they clash. Restrict each one to the sentence that uses it so that both sentences parse.

import nelumbo.lang Person :: Object Dog :: Object Person ::= Piet, Marie Sentence :: Root Sentence ::= <Person> reads #0, <Dog> barks #0 // these two functors clash: restrict each to the sentence that uses it Person ::= friend(<Person>) Dog ::= friend(<Person>) friend(Marie) reads friend(Piet) barks
import nelumbo.lang
Person   :: Object
Dog      :: Object
Person   ::= Piet, Marie
Sentence :: Root
Sentence ::= <Person> reads #0,
             <Dog> barks    #0
{
    private Person ::= friend(<Person>)
    friend(Marie) reads
}
{
    private Dog ::= friend(<Person>)
    friend(Piet) barks
}

Rex is private to its block, so the last sentence does not parse. Make it parse without moving it.

import nelumbo.lang Person :: Object Dog :: Object Sentence :: Root Sentence ::= <Person> reads #0, <Dog> barks #0 { private Person ::= Max Max reads } { private Dog ::= Rex Rex barks } // make this sentence parse, without moving it Rex barks
import nelumbo.lang
Person   :: Object
Dog      :: Object
Sentence :: Root
Sentence ::= <Person> reads #0,
             <Dog> barks    #0
{
    private Person ::= Max
    Max reads
}
{
    Dog ::= Rex
    Rex barks
}
Rex barks

Propositional logic

This is the first section with queries. A query is an expression followed by ?: it asks Nelumbo whether the expression holds. import nelumbo.logic provides the values true, false and unknown, and the operators & (and), | (or) and ! (not).

There is no Run button: Nelumbo evaluates every query as you type and shows its result at the end of the line, in a purple label. A long result is cut short; hover over it to see all of it.

A result has two sides, [facts][falsehoods]: on the left the answers for which the query is true, on the right those for which it is false. An answer is written between parentheses. These queries have no variables, so an answer is just (). .. means that a side may contain more than it shows. In the results below:

  • [()][] means "true",
  • [][()] means "false",
  • [..][..] means "unknown": nothing is known on either side.

Nelumbo is three-valued: a query is true, false or unknown. unknown & true stays unknown, but unknown | true is true, whatever the unknown part turns out to be. Try changing the queries below.

import nelumbo.logic true & true ? true & false ? true | false ? !false ? unknown & true ? unknown | true ?

After the ? you may write the result you expect, in the same notation as the label: true & false ? [][()] expects false. The editor then checks it instead of showing the result: a green check mark at the end of the line when the result matches; otherwise the expected result is underlined in red and the actual result follows a red cross. Without an expected result you just get the purple label. The exercises use this to show when you are done.

Make the query below come out true by changing only the operator.

import nelumbo.logic false & true ? [()][]
import nelumbo.logic

false | true ? [()][]

Facts & queries

Ground truth is asserted with fact. Objects and their functors are declared first (Person :: Object introduces a type, Boolean ::= ... a relation), then a query binds a variable and enumerates every matching answer. Facts are open: what is not asserted is not false, so the trailing .. in [(X=Jan),..] says more answers may exist.

import nelumbo.logic Person :: Object Boolean ::= friends(<Person>,<Person>) Person ::= Piet, Jan, Klaas fact friends(Piet, Jan), friends(Jan, Klaas) Person X friends(Piet, X) ?

Add a fact so that friends(Klaas, Kees) holds.

import nelumbo.logic Person :: Object Boolean ::= friends(<Person>,<Person>) Person ::= Piet, Jan, Klaas, Kees // add a fact here friends(Klaas, Kees) ? [()][]
import nelumbo.logic
Person :: Object
Boolean ::= friends(<Person>,<Person>)
Person ::= Piet, Jan, Klaas, Kees
fact friends(Klaas, Kees)
friends(Klaas, Kees) ? [()][]

A function value can be asserted too. A function has exactly one value, so its fact is closed: after fact age(Piet)=40 the answer is [(a=40)] without .., and every other age is false. Statements run top to bottom, and each fact or rule only adds knowledge: unknown answers become true or false, open answers grow or close, and nothing already proven is withdrawn. Nelumbo is monotonic, and it checks this. A fact can never be taken back further down: after fact age(Piet)=40, a later fact age(Piet)=22 is an inconsistency, and nothing after it is evaluated. In the same way, when a query is asked again further down, its answer must keep everything the earlier answer showed and may only add to it. If not, the query reports an inconsistency. Only relations and function values can be asserted: fact !friends(Piet, Klaas) is an error.

import nelumbo.integers Person :: Object Person ::= Piet, Jan Integer ::= age(<Person>) Integer a age(Piet)=a ? fact age(Piet)=40 age(Piet)=a ? age(Piet)=41 ?

Give Jan an age so that both queries below hold.

import nelumbo.integers Person :: Object Person ::= Piet, Jan Integer ::= age(<Person>) Integer a // add a fact here age(Jan)=a ? [(a=35)][..] age(Jan)=40 ? [][()]
import nelumbo.integers
Person :: Object
Person ::= Piet, Jan
Integer ::= age(<Person>)
Integer a
fact age(Jan)=35
age(Jan)=a  ? [(a=35)][..]
age(Jan)=40 ? [][()]

Unique values

Facts are open, but a hole can be declared unique with one: <Person> lives in <one City> says that a person lives in exactly one city. A fact then closes the hole for that person: Piet lives in c ? answers Amsterdam without .., and every other city is false. Nothing is asserted for Jan, so his city stays unknown, and the other direction stays open: more people may live in Amsterdam. likes has no one, so Piet may like other cities too and Utrecht is unknown. A second city for Piet would be rejected as an inconsistency. This is also why a function value is closed: both sides of = are unique holes.

import nelumbo.logic Person :: Object Person ::= Piet, Jan City :: Object City ::= Amsterdam, Utrecht Boolean ::= <Person> lives in <one City> #30, <Person> likes <City> #30 City c Person p fact Piet lives in Amsterdam fact Piet likes Amsterdam Piet lives in c ? Piet lives in Utrecht ? Jan lives in c ? p lives in Amsterdam ? Piet likes c ? Piet likes Utrecht ?

Everybody was born in exactly one city. Change the pattern so that the expected results hold.

import nelumbo.logic Person :: Object Person ::= Piet, Jan City :: Object City ::= Amsterdam, Utrecht // everybody was born in exactly one city: change the pattern so the expected results hold Boolean ::= <Person> was born in <City> #30 City c fact Jan was born in Utrecht Jan was born in c ? [(c=Utrecht)][..] Jan was born in Amsterdam ? [][()]
import nelumbo.logic
Person  :: Object
Person  ::= Piet, Jan
City    :: Object
City    ::= Amsterdam, Utrecht
Boolean ::= <Person> was born in <one City> #30
City   c
fact Jan was born in Utrecht
Jan was born in c         ? [(c=Utrecht)][..]
Jan was born in Amsterdam ? [][()]

Rules & recursion

A rule <=> defines how a functor is derived. For a function, == gives its value directly: fib(n) == e is short for fib(n)=f <=> f=e. if attaches a guard to each alternative, and a rule may reference itself to recurse. The classic Fibonacci rule has a base case for n<=1 and a recursive step for n>1. Ask for a function's value with a function query: fib(7) ? introduces its own result variable, so the answer lists the value without a name, [(13)][..]. It is the same as fib(7)=r ? with a declared Integer r.

import nelumbo.integers Integer ::= fib(<Integer>) Integer n fib(n) == n if n<=1, fib(n-1)+fib(n-2) if n>1 fib(7) ?

Define factorial so that factorial(5) = 120.

import nelumbo.integers Integer ::= factorial(<Integer>) Integer n // fix this rule so factorial(5)=120 (hint: 1 if n<=0, else n*factorial(n-1)) factorial(n) == n factorial(5) ? [(120)][..]
import nelumbo.integers
Integer ::= factorial(<Integer>)
Integer n
factorial(n) == 1 if n<=0, n*factorial(n-1) if n>0
factorial(5) ? [(120)][..]

Adding a rule adds knowledge just like adding a fact. It can even add only falsehoods: below, adult(Jan) is unknown until a rule says that nobody younger than 18 is an adult. After that rule adult(Jan) is false, while the fact adult(Piet) still holds. The rule is silent where its guard fails, so it takes nothing away. Had Piet been 16, the query adult(Piet) ? after the rule would report an inconsistency with the fact.

import nelumbo.integers Person :: Object Person ::= Piet, Jan Integer ::= age(<Person>) Boolean ::= adult(<Person>) Person p fact age(Piet)=40, age(Jan)=12 fact adult(Piet) adult(Piet) ? [()][] adult(Jan) ? [..][..] // unknown: no fact, no rule adult(p) <=> false if age(p)<18 adult(Piet) ? [()][] // unchanged adult(Jan) ? [][()] // now false: only the falsehood side grew

Combining rules

Several rules can define one relation; their answers are combined side by side. The first rule says that Piet eats what he likes. All its answers are on the facts side: [(f=Apple),(f=Pear),..][..]. Its falsehood side is empty but open (..): nothing is known to be false yet. The second rule only adds falsehoods: Piet does not eat what he is allergic to. It is silent where its guard fails, so it leaves the facts side alone and fills the open falsehood side: [(f=Apple),(f=Pear),..][(f=Fish),(f=Nut),..]. Bread is on neither side, so it stays unknown. The rules must agree: had Piet also liked nuts, the query would report an inconsistency.

import nelumbo.logic Person :: Object Person ::= Piet Food :: Object Food ::= Apple, Pear, Nut, Fish, Bread Boolean ::= likes(<Person>,<Food>), allergic(<Person>,<Food>), eats(<Person>,<Food>) Person p Food f fact likes(Piet, Apple), likes(Piet, Pear) fact allergic(Piet, Nut), allergic(Piet, Fish) eats(p,f) <=> likes(p,f) eats(Piet,f) ? eats(p,f) <=> false if allergic(p,f) eats(Piet,f) ? eats(Piet,Bread) ?

Piet invites his friends, but nothing says whom he does not invite. Add a second rule so that nobody invites someone they quarrel with, and the falsehood side fills up.

import nelumbo.logic Person :: Object Person ::= Piet, Jan, Klaas, Kees, Bob Boolean ::= friends(<Person>,<Person>), quarrels(<Person>,<Person>), invites(<Person>,<Person>) Person p, q fact friends(Piet, Jan), friends(Piet, Klaas) fact quarrels(Piet, Kees), quarrels(Piet, Bob) invites(p,q) <=> friends(p,q) // add a rule: nobody invites someone they quarrel with invites(Piet,q) ? [(q=Jan),(q=Klaas),..][(q=Bob),(q=Kees),..]
import nelumbo.logic
Person  :: Object
Person  ::= Piet, Jan, Klaas, Kees, Bob
Boolean ::= friends(<Person>,<Person>), quarrels(<Person>,<Person>), invites(<Person>,<Person>)
Person  p, q
fact friends(Piet, Jan), friends(Piet, Klaas)
fact quarrels(Piet, Kees), quarrels(Piet, Bob)
invites(p,q) <=> friends(p,q)
invites(p,q) <=> false if quarrels(p,q)
invites(Piet,q) ? [(q=Jan),(q=Klaas),..][(q=Bob),(q=Kees),..]

Cyclic rules

A rule may depend on itself in a cycle. The recursion of fib ends because n gets smaller; here nothing gets smaller: a friend of a friend of mine is a friend of mine, so to find my friends, Nelumbo needs my friends. It solves such a cycle by iteration. It starts with the facts (Jan is a friend of Piet), applies the rule and finds Klaas, a friend of Jan, applies it again and finds Kees, and stops when a round adds nothing new. That end point is the fixpoint, and its friends are the answer. The rule need not mention the facts: the facts and rules of one relation are combined. What the fixpoint does not reach stays unknown, not false: no fact mentions Bob, but the facts are open, so Bob is a friend of Piet ? is [..][..].

import nelumbo.logic Person :: Object Person ::= Piet, Jan, Klaas, Kees, Bob Boolean ::= <Person> is a friend of <Person> #30 Person me, f, ff, Who // a friend of a friend of mine is a friend of mine ff is a friend of me <=> E[f](ff is a friend of f & f is a friend of me) fact Jan is a friend of Piet fact Klaas is a friend of Jan fact Kees is a friend of Klaas Who is a friend of Piet ? Kees is a friend of Piet ? Bob is a friend of Piet ?

Friendship goes both ways: if Jan is a friend of Piet, Piet is a friend of Jan. Extend the rule so the expected results hold. Through Jan, Piet then turns out to be a friend of himself.

import nelumbo.logic Person :: Object Person ::= Piet, Jan, Klaas, Kees Boolean ::= <Person> is a friend of <Person> #30 Person me, f, ff, Who // friendship goes both ways: extend the rule ff is a friend of me <=> E[f](ff is a friend of f & f is a friend of me) fact Jan is a friend of Piet fact Klaas is a friend of Jan fact Kees is a friend of Klaas Piet is a friend of Kees ? [()][] Piet is a friend of Piet ? [()][] Who is a friend of Piet ? [(Who=Jan),(Who=Kees),(Who=Klaas),(Who=Piet),..][..]
import nelumbo.logic
Person  :: Object
Person  ::= Piet, Jan, Klaas, Kees
Boolean ::= <Person> is a friend of <Person> #30
Person  me, f, ff, Who
ff is a friend of me <=> me is a friend of ff | E[f](ff is a friend of f & f is a friend of me)
fact Jan is a friend of Piet
fact Klaas is a friend of Jan
fact Kees is a friend of Klaas
Piet is a friend of Kees ? [()][]
Piet is a friend of Piet ? [()][]
Who is a friend of Piet  ? [(Who=Jan),(Who=Kees),(Who=Klaas),(Who=Piet),..][..]

Data types

Nelumbo ships with built-in types - integers, rationals, strings, collections, datetime - each importable, with its own operators and functions. Declare a variable of a type, then let a query compute and bind it. Operators like + and functions like len come from the imported module.

import nelumbo.integers import nelumbo.strings Integer i String s 2+3=i ? "foo"+"bar"=s ? len("hello")=i ?
import nelumbo.collections Set<Integer> s Integer i s={1,2,3} ? |{1,2,3}|=i ?

Fix the concatenation so that a = "lotus" and the length check (len(a) = 5) passes.

import nelumbo.strings String a Integer d // fix the concatenation so a = "lotus" and its length is 5 "lo"+"xus"=a & len(a)=d ? [(a="lotus",d=5)][..]
import nelumbo.strings
String  a
Integer d
"lo"+"tus"=a & len(a)=d ? [(a="lotus",d=5)][..]

Equality

What = means depends on what a phrase is. A functor with arguments, declared for an Object type, is a function: double(2) stands for the value a rule or fact gives it, so double(2)=4 is true and double(2)=5 is false. Without such a rule or fact the value is unknown, so even point(1,2)=point(1,2) is unknown. A functor declared struct builds values instead: pos(1,2) is the value itself, equal to another one exactly when it is built the same way, and it can be the answer to a query (q=pos(1,2)). Constants such as Piet are literals too.

import nelumbo.integers Integer ::= double(<Integer>) Integer n double(n) == n*2 double(2)=4 ? double(2)=5 ? Point :: Object Point ::= point(<Integer>,<Integer>) point(1,2)=point(1,2) ? Pos :: Object struct Pos ::= pos(<Integer>,<Integer>) Pos q pos(1,2)=pos(1,2) ? pos(1,2)=pos(2,1) ? q=pos(1,2) ?

A card is its number, nothing more. Change the declaration of Card so that cards are compared by how they are built.

import nelumbo.integers // change the declaration of Card so the expected results hold Card :: Object Card ::= card(<Integer>) card(7)=card(7) ? [()][] card(7)=card(8) ? [][()]
import nelumbo.integers
Card        :: Object
struct Card ::= card(<Integer>)
card(7)=card(7) ? [()][]
card(7)=card(8) ? [][()]

Relational KB

A knowledge base is facts plus rules over relations. Base facts (here pc, parent-child) are asserted directly; rules with quantifiers such as E[x] (there exists) and A[y] (for all) derive new relations from them. Below, the mother relation m is derived from pc via an existential over female parents. Because pc facts are open, Beatrix may have parents not asserted here: the answer ends in ...

import nelumbo.logic Person :: Object Male :: Person Female :: Person Boolean ::= pc(<Person>,<Person>), m(<Person>,<Person>) Person a, b Female x m(a,b) <=> E[x](pc(x,a) & b=x) Female ::= Juliana, Beatrix fact pc(Juliana, Beatrix) Person who m(Beatrix,who) ?

Add a father relation f that mirrors the mother rule, using E[y] over male parents, so f(Beatrix,who) finds Bernhard.

import nelumbo.logic Person :: Object Male :: Person Female :: Person Boolean ::= pc(<Person>,<Person>), m(<Person>,<Person>), f(<Person>,<Person>) Person a, b Female x Male y m(a,b) <=> E[x](pc(x,a) & b=x) // fix this rule so f(Beatrix,who) finds Bernhard (mirror the mother rule using Male y) f(a,b) <=> E[y](pc(a,y) & b=y) Female ::= Juliana, Beatrix Male ::= Bernhard fact pc(Juliana, Beatrix), pc(Bernhard, Beatrix) Person who f(Beatrix,who) ? [(who=Bernhard),..][..]
import nelumbo.logic
Person   :: Object
Male     :: Person
Female   :: Person
Boolean ::= pc(<Person>,<Person>), m(<Person>,<Person>), f(<Person>,<Person>)
Person a, b
Female x
Male   y
m(a,b) <=> E[x](pc(x,a) & b=x)
f(a,b) <=> E[y](pc(y,a) & b=y)
Female ::= Juliana, Beatrix
Male   ::= Bernhard
fact pc(Juliana, Beatrix),
     pc(Bernhard, Beatrix)
Person who
f(Beatrix,who) ? [(who=Bernhard),..][..]

Quantifiers

E[c](...) (there exists) and A[c](...) (for all) never try every member of a type: c only gets values by navigation, from something already known, here from Juliana to her children. Without navigation a quantifier gives no answer, even though four people are declared. That is a design choice: Person ::= Juliana, ... names some people, not all of them. If A[c](age(c)>10) were decided by trying the declared people, declaring one more could turn true into false, and Nelumbo never withdraws an answer. So navigate: through a fact or rule (pc(Juliana,c)), through =, or through a known collection (c in children(Juliana)). Over a closed set such as Juliana's children, A can be true and E false; over open facts more children may exist, so only finding an example decides. Put the membership in parentheses before ->.

import nelumbo.collections Person :: Object Person ::= Juliana, Beatrix, Irene, Margriet Integer ::= age(<Person>) Set<Person> ::= children(<Person>) Person c fact age(Beatrix)=20, age(Irene)=15, age(Margriet)=30 fact children(Juliana)={Beatrix, Irene, Margriet} // no navigation: c gets no values E[c](age(c)>18) ? A[c](age(c)>10) ? // navigate from Juliana to her children E[c](c in children(Juliana) & age(c)>18) ? E[c](c in children(Juliana) & age(c)>40) ? A[c]((c in children(Juliana)) -> age(c)>10) ? A[c]((c in children(Juliana)) -> age(c)>18) ?

Both questions are about Juliana's children, but the quantifiers do not navigate there, so they give no answer. Make them navigate so the expected results hold.

import nelumbo.collections Person :: Object Person ::= Juliana, Beatrix, Irene, Margriet Integer ::= age(<Person>) Set<Person> ::= children(<Person>) Person c fact age(Beatrix)=20, age(Irene)=15, age(Margriet)=30 fact children(Juliana)={Beatrix, Irene, Margriet} // is every child of Juliana older than 12? A[c](age(c)>12) ? [()][] // does Juliana have a child younger than 18? E[c](age(c)<18) ? [()][]
import nelumbo.collections
Person      :: Object
Person      ::= Juliana, Beatrix, Irene, Margriet
Integer     ::= age(<Person>)
Set<Person> ::= children(<Person>)
Person      c
fact age(Beatrix)=20, age(Irene)=15, age(Margriet)=30
fact children(Juliana)={Beatrix, Irene, Margriet}
// is every child of Juliana older than 12?
A[c]((c in children(Juliana)) -> age(c)>12) ? [()][]
// does Juliana have a child younger than 18?
E[c](c in children(Juliana) & age(c)<18) ? [()][]

Custom DSL

Nelumbo is a meta-language: declare your own functors and patterns with an explicit precedence (#N) and natural-language statements and queries become first-class syntax. Here a rule makes the friends facts symmetric and transitive, then the phrase X is a friend of Y is declared as a Boolean pattern that reads as ordinary English.

import nelumbo.logic Person :: Object Boolean ::= friends(<Person>,<Person>) Person ::= Piet, Jan, Klaas Person a, b, c, Who friends(a,c) <=> friends(a,c) | friends(c,a) | E[b](friends(a,b) & friends(b,c)) Boolean ::= <Person> is a friend of <Person> #30 Person X, Y X is a friend of Y <=> friends(Y,X) fact friends(Piet, Jan), friends(Jan, Klaas) Who is a friend of Piet ?

A synonym phrase X knows Y has been declared but its rule is wrong (X knows Y <=> X=Y). Fix the rule so Who knows Piet lists the same friends as the phrase above.

import nelumbo.logic Person :: Object Boolean ::= friends(<Person>,<Person>) Person ::= Piet, Jan, Klaas Person a, b, c, Who friends(a,c) <=> friends(a,c) | friends(c,a) | E[b](friends(a,b) & friends(b,c)) Boolean ::= <Person> knows <Person> #30 Person X, Y X knows Y <=> X=Y fact friends(Piet, Jan), friends(Jan, Klaas) Who knows Piet ? [(Who=Jan),(Who=Klaas),(Who=Piet),..][..]
import nelumbo.logic
Person   :: Object
Boolean ::= friends(<Person>,<Person>)
Person   ::= Piet, Jan, Klaas
Person a, b, c, Who
friends(a,c) <=> friends(a,c) | friends(c,a) | E[b](friends(a,b) & friends(b,c))
Boolean ::= <Person> knows <Person> #30
Person X, Y
X knows Y <=> friends(Y,X)
fact friends(Piet, Jan), friends(Jan, Klaas)
Who knows Piet ? [(Who=Jan),(Who=Klaas),(Who=Piet),..][..]

Transformations

A transformation rule ::> rewrites one construct into several. The attr declaration below expands - at declaration time - into a getter function o.name and a := setter that itself transforms into a fact about that function. Declaring attr Person name String then behaves as if both were written by hand.

import nelumbo.strings Root ::= attr <Type> <NAME> <Type> #100 Type OT, AT NAME AN attr OT AN AT ::> { AT ::= <OT>.AN #30 Root ::= <{OT,Literal}>.AN := <{AT,Literal}> #30 {OT,Literal} ol {AT,Literal} al ol.AN := al ::> { fact ol.AN = al } } Person :: Object attr Person name String Person ::= Piet Piet.name := "Piet" String n Piet.name=n ?

An address attribute has been added to Person, but Piet's address is set to the wrong value. Set it to "Kalverstraat" so the query matches.

import nelumbo.strings Root ::= attr <Type> <NAME> <Type> #100 Type OT, AT NAME AN attr OT AN AT ::> { AT ::= <OT>.AN #30 Root ::= <{OT,Literal}>.AN := <{AT,Literal}> #30 {OT,Literal} ol {AT,Literal} al ol.AN := al ::> { fact ol.AN = al } } Person :: Object attr Person name String attr Person address String Person ::= Piet Piet.name := "Piet" Piet.address := "Dam" String a Piet.address=a ? [(a="Kalverstraat")][..]
import nelumbo.strings
Root ::= attr <Type> <NAME> <Type> #100
Type OT, AT
NAME AN
attr OT AN AT ::> {
    AT   ::= <OT>.AN                             #30
    Root ::= <{OT,Literal}>.AN := <{AT,Literal}> #30
    {OT,Literal} ol
    {AT,Literal} al
    ol.AN := al ::> {
        fact ol.AN = al
    }
}
Person :: Object
attr Person name String
attr Person address String
Person ::= Piet
Piet.name := "Piet"
Piet.address := "Kalverstraat"
String a
Piet.address=a ? [(a="Kalverstraat")][..]

Controlled English

A transformation can turn a dictionary into a language. A controlled natural language is a fixed set of sentence shapes, filled with words from a dictionary, where each word's entry decides how the word may be used. Here noun a dog and noun an owl record every noun with its own article, and the transformation gives each entry its own sentences: Rex is a dog and is Hoot an owl ?. So Hoot is a owl does not parse: after a only dog and cat fit. verb chases adds Rex chases Tom and who chases Hoot ?. A sentence becomes a fact and a question becomes a query. Nothing says that Tom is not a dog, so is Tom a dog ? is unknown. The Dutch example deHet does the same with the articles de and het.

import nelumbo.logic Article :: Object Article ::= a, an Root ::= noun <Article> <NAME> #100, verb <NAME> #100 Thing :: Object { Article ar NAME n, v noun ar n ::> { Root ::= <{Thing,Literal}> is <ar> n #0 Root ::= is <Thing> <ar> n ? #0 private Boolean ::= n(<Thing>) Thing x is x ar n ? ::> { n(x) ? } {Thing,Literal} t t is ar n ::> { fact n(t) } } verb v ::> { Root ::= <{Thing,Literal}> v <{Thing,Literal}> #0 Root ::= who v <Thing> ? #0 private Boolean ::= v(<Thing>,<Thing>) Thing x, y who v y ? ::> { v(x,y) ? } {Thing,Literal} s, o s v o ::> { fact v(s,o) } } } noun a dog noun an owl noun a cat verb chases Thing ::= Rex, Hoot, Tom Rex is a dog Hoot is an owl Tom is a cat Rex chases Tom Tom chases Hoot is Rex a dog ? is Hoot an owl ? is Tom a dog ? who chases Hoot ?

Dumbo is an elephant and scares Rex, but elephant and scares are not in the dictionary yet. Add them so that all sentences and questions parse.

import nelumbo.logic Article :: Object Article ::= a, an Root ::= noun <Article> <NAME> #100, verb <NAME> #100 Thing :: Object { Article ar NAME n, v noun ar n ::> { Root ::= <{Thing,Literal}> is <ar> n #0 Root ::= is <Thing> <ar> n ? #0 private Boolean ::= n(<Thing>) Thing x is x ar n ? ::> { n(x) ? } {Thing,Literal} t t is ar n ::> { fact n(t) } } verb v ::> { Root ::= <{Thing,Literal}> v <{Thing,Literal}> #0 Root ::= who v <Thing> ? #0 private Boolean ::= v(<Thing>,<Thing>) Thing x, y who v y ? ::> { v(x,y) ? } {Thing,Literal} s, o s v o ::> { fact v(s,o) } } } noun a dog noun an owl // add the missing words to the dictionary Thing ::= Rex, Hoot, Dumbo Rex is a dog Hoot is an owl Dumbo is an elephant Dumbo scares Rex is Dumbo an elephant ? who scares Rex ?
import nelumbo.logic
Article :: Object
Article ::= a, an
Root    ::= noun <Article> <NAME> #100,
            verb <NAME>           #100
Thing   :: Object
{
    Article ar
    NAME    n, v
    noun ar n ::> {
        Root            ::= <{Thing,Literal}> is <ar> n #0
        Root            ::= is <Thing> <ar> n ?         #0
        private Boolean ::= n(<Thing>)
        Thing x
        is x ar n ? ::> {
            n(x) ?
        }
        {Thing,Literal} t
        t is ar n ::> {
            fact n(t)
        }
    }
    verb v ::> {
        Root            ::= <{Thing,Literal}> v <{Thing,Literal}> #0
        Root            ::= who v <Thing> ?                       #0
        private Boolean ::= v(<Thing>,<Thing>)
        Thing x, y
        who v y ? ::> {
            v(x,y) ?
        }
        {Thing,Literal} s, o
        s v o ::> {
            fact v(s,o)
        }
    }
}

noun a dog
noun an owl
noun an elephant
verb scares

Thing ::= Rex, Hoot, Dumbo
Rex is a dog
Hoot is an owl
Dumbo is an elephant
Dumbo scares Rex

is Dumbo an elephant ?
who scares Rex ?

Family tree

A project brings the previous sections together. Only the end of the file is given: facts and questions, each with the answer it should get. You design and write everything above it. Here you build a small English language about the Dutch royal family. Some answers are closed: Beatrix has exactly two parents. Others are open: Amalia's mother is not given, so she may have more grandparents than the two that are known.

Hints
  1. Declare Person with the subtypes Male and Female, and the nine people as constants of those.
  2. Declare the sentences as Boolean phrases with precedence #30. A person has one mother: <one Person> is the mother of <Person> makes Irene is the mother of Beatrix false. The same goes for the father.
  3. Declare the variables, including Who.
  4. A parent is the mother or the father. A grandparent is a parent of a parent (E[p](...)), a grandchild the other way around.
  5. An ancestor is a parent, or a parent of an ancestor: a cyclic rule.
  6. Sisters and brothers share a parent and are not the same person (!=). To tell them apart, add <Person> is female with two rules: true for a Female variable, false for a Male one.
  7. An aunt is a sister of a parent.

Write the language above the given part so that the facts parse and every question gets the expected answer.

import nelumbo.logic // build your language here: types, people, sentences, variables and rules // ---- given: make these facts parse and these questions give the expected answers ---- fact Juliana is the mother of Beatrix, Juliana is the mother of Irene, Juliana is the mother of Margriet fact Bernhard is the father of Beatrix, Bernhard is the father of Irene, Bernhard is the father of Margriet fact Beatrix is the mother of Alexander, Beatrix is the mother of Friso fact Claus is the father of Alexander, Claus is the father of Friso fact Alexander is the father of Amalia Who is the mother of Beatrix ? [(Who=Juliana)][..] Irene is the mother of Beatrix ? [][()] Who is a parent of Beatrix ? [(Who=Bernhard),(Who=Juliana)][..] Who is a grandparent of Amalia ? [(Who=Beatrix),(Who=Claus),..][..] Who is a grandchild of Juliana ? [(Who=Alexander),(Who=Friso),..][..] Who is an ancestor of Amalia ? [(Who=Alexander),(Who=Beatrix),(Who=Bernhard),(Who=Claus),(Who=Juliana),..][..] Who is a sister of Beatrix ? [(Who=Irene),(Who=Margriet),..][(Who=Beatrix),..] Who is a brother of Alexander ? [(Who=Friso),..][(Who=Alexander),..] Friso is a sister of Alexander ? [][()] Who is an aunt of Alexander ? [(Who=Irene),(Who=Margriet),..][..]
import nelumbo.logic
Person  :: Object
Male    :: Person
Female  :: Person
Male    ::= Bernhard, Claus, Alexander, Friso
Female  ::= Juliana, Beatrix, Irene, Margriet, Amalia
Boolean ::= <one Person> is the mother of <Person> #30,
            <one Person> is the father of <Person> #30,
            <Person> is female                     #30,
            <Person> is a parent of <Person>       #30,
            <Person> is a grandparent of <Person>  #30,
            <Person> is a grandchild of <Person>   #30,
            <Person> is an ancestor of <Person>    #30,
            <Person> is a sister of <Person>       #30,
            <Person> is a brother of <Person>      #30,
            <Person> is an aunt of <Person>        #30
Person  p, c, g, a, s, x, Who
Female  f
Male    m
f is female             <=> true
m is female             <=> false
p is a parent of c      <=> p is the mother of c | p is the father of c
g is a grandparent of c <=> E[p](g is a parent of p & p is a parent of c)
c is a grandchild of g  <=> g is a grandparent of c
a is an ancestor of c   <=> a is a parent of c | E[p](a is a parent of p & p is an ancestor of c)
s is a sister of x      <=> s is female & s != x & E[p](p is a parent of x & p is a parent of s)
s is a brother of x     <=> !(s is female) & s != x & E[p](p is a parent of x & p is a parent of s)
s is an aunt of c       <=> E[p](p is a parent of c & s is a sister of p)

// ---- given: make these facts parse and these questions give the expected answers ----
fact Juliana   is the mother of Beatrix, Juliana  is the mother of Irene, Juliana  is the mother of Margriet
fact Bernhard  is the father of Beatrix, Bernhard is the father of Irene, Bernhard is the father of Margriet
fact Beatrix   is the mother of Alexander, Beatrix is the mother of Friso
fact Claus     is the father of Alexander, Claus   is the father of Friso
fact Alexander is the father of Amalia

Who is the mother of Beatrix   ? [(Who=Juliana)][..]
Irene is the mother of Beatrix ? [][()]
Who is a parent of Beatrix     ? [(Who=Bernhard),(Who=Juliana)][..]
Who is a grandparent of Amalia ? [(Who=Beatrix),(Who=Claus),..][..]
Who is a grandchild of Juliana ? [(Who=Alexander),(Who=Friso),..][..]
Who is an ancestor of Amalia   ? [(Who=Alexander),(Who=Beatrix),(Who=Bernhard),(Who=Claus),(Who=Juliana),..][..]
Who is a sister of Beatrix     ? [(Who=Irene),(Who=Margriet),..][(Who=Beatrix),..]
Who is a brother of Alexander  ? [(Who=Friso),..][(Who=Alexander),..]
Friso is a sister of Alexander ? [][()]
Who is an aunt of Alexander    ? [(Who=Irene),(Who=Margriet),..][..]

Club fees

The rules of a sports club are given in English, as comments, with its members and the fee each one should pay. Turn them into a small language that computes every fee. Some answers come from what is not possible: Henk is not a junior because he has exactly one age, and it is 70. And a rule only needs what it uses: Piet's age is unknown, so his fee is too, but Bram is a volunteer, and a volunteer pays nothing at any age.

Hints
  1. Declare Person with the nine members, and a type Membership with the constants regular, weekday and volunteer.
  2. Declare the sentences as Boolean phrases with precedence #30. A person has one age and one membership: <Person> is <one Integer> years old. Without one, Henk is a junior cannot be false.
  3. Define junior, adult and senior with E[n](p is n years old & ...). Mind the boundaries: at 18 you are an adult, at 65 a senior.
  4. Declare the fees as functions, Integer ::= <Person>'s fee #60. The pattern starts with a hole, so it is an operator and needs a precedence.
  5. Write two function rules with == and if: a base fee per age group, and the fee per membership, which uses the base fee.

Write the language above the given part so that the facts parse and every question gets the expected answer.

import nelumbo.integers // build your language here: types, members, sentences, variables and rules // ---- given: the club's rules, its members, and the answers they should get ---- // Juniors (younger than 18) pay 50 euros, adults 120 euros, seniors (65 or older) 84 euros. // Weekday members pay 20 euros less. Volunteers pay nothing. fact Anna is 30 years old, Lisa is 16 years old, Henk is 70 years old, Kees is 12 years old, Marie is 45 years old, Sven is 18 years old, Eva is 65 years old fact Anna has a regular membership, Lisa has a weekday membership, Henk has a weekday membership, Kees has a regular membership, Marie has a volunteer membership, Sven has a regular membership, Eva has a regular membership, Piet has a weekday membership, Bram has a volunteer membership Lisa is a junior ? [()][] Henk is a junior ? [][()] Sven is an adult ? [()][] Eva is a senior ? [()][] Anna is 31 years old ? [][()] Anna has a weekday membership ? [][()] Anna's fee ? [(120)][..] Lisa's fee ? [(30)][..] Henk's fee ? [(64)][..] Kees's fee ? [(50)][..] Marie's fee ? [(0)][..] Sven's fee ? [(120)][..] Eva's fee ? [(84)][..] Piet's fee ? [..][..] Bram's fee ? [(0)][..]
import nelumbo.integers
Person     :: Object
Person     ::= Anna, Lisa, Henk, Kees, Marie, Sven, Eva, Piet, Bram
Membership :: Object
Membership ::= regular, weekday, volunteer
Boolean    ::= <Person> is <one Integer> years old        #30,
               <Person> has a <one Membership> membership #30,
               <Person> is a junior                       #30,
               <Person> is an adult                       #30,
               <Person> is a senior                       #30
Integer    ::= <Person>'s base fee #60,
               <Person>'s fee      #60
Person     p
Integer    n
p is a junior <=> E[n](p is n years old & n<18)
p is an adult <=> E[n](p is n years old & n>=18 & n<65)
p is a senior <=> E[n](p is n years old & n>=65)
p's base fee == 50 if p is a junior, 120 if p is an adult, 84 if p is a senior
p's fee == p's base fee      if p has a regular membership,
           p's base fee - 20 if p has a weekday membership,
           0                 if p has a volunteer membership

// ---- given: the club's rules, its members, and the answers they should get ----
// Juniors (younger than 18) pay 50 euros, adults 120 euros, seniors (65 or older) 84 euros.
// Weekday members pay 20 euros less. Volunteers pay nothing.
fact Anna is 30 years old, Lisa is 16 years old, Henk is 70 years old, Kees is 12 years old,
     Marie is 45 years old, Sven is 18 years old, Eva is 65 years old
fact Anna has a regular membership, Lisa has a weekday membership, Henk has a weekday membership,
     Kees has a regular membership, Marie has a volunteer membership, Sven has a regular membership,
     Eva has a regular membership, Piet has a weekday membership, Bram has a volunteer membership

Lisa is a junior              ? [()][]
Henk is a junior              ? [][()]
Sven is an adult              ? [()][]
Eva is a senior               ? [()][]
Anna is 31 years old          ? [][()]
Anna has a weekday membership ? [][()]
Anna's fee                    ? [(120)][..]
Lisa's fee                    ? [(30)][..]
Henk's fee                    ? [(64)][..]
Kees's fee                    ? [(50)][..]
Marie's fee                   ? [(0)][..]
Sven's fee                    ? [(120)][..]
Eva's fee                     ? [(84)][..]
Piet's fee                    ? [..][..]
Bram's fee                    ? [(0)][..]

Train network

The timetable of a small rail network says which cities have a train between them. Turn it into a language that answers travel questions. A train runs both ways, a journey can go round the ring of cities, and "in at most 3 steps" counts down, so it ends. Groningen cannot be reached from Utrecht with these trains, but that is unknown, not false: the timetable may not list every train. Only "in at most 0 steps" is false: no journey takes zero steps.

Hints
  1. Declare City with the nine cities.
  2. Declare the sentences as Boolean phrases with precedence #30: the fact sentence, <City> is connected to <City>, I can travel from <City> to <City>, and the same with in at most <Integer> steps.
  3. Declare the variables, including Which.
  4. A train runs both ways: two cities are connected when there is a train between them, in either order.
  5. You can travel from a to b when a is connected to b, or when a city c connected to a can reach b: a cyclic rule.
  6. In at most n steps: n is more than 0, and a city connected to a is b or reaches b in at most n-1 steps. Write n>0 & ..., not ... if n>0: a guard that fails makes a rule silent, so 0 steps would be unknown instead of false.

Write the language above the given part so that the facts parse and every question gets the expected answer.

import nelumbo.integers // build your language here: cities, sentences, variables and rules // ---- given: the timetable and the answers it should give ---- fact there is a train between Utrecht and Amsterdam, there is a train between Amsterdam and Haarlem, there is a train between Haarlem and Leiden, there is a train between Leiden and Delft, there is a train between Delft and Rotterdam, there is a train between Rotterdam and Gouda, there is a train between Gouda and Utrecht, there is a train between Zwolle and Groningen Utrecht is connected to Amsterdam ? [()][] Amsterdam is connected to Utrecht ? [()][] Which is connected to Utrecht ? [(Which=Amsterdam),(Which=Gouda),..][..] Utrecht is connected to Leiden ? [..][..] I can travel from Utrecht to Leiden ? [()][] I can travel from Utrecht to Groningen ? [..][..] I can travel from Utrecht to Which ? [(Which=Amsterdam),(Which=Delft),(Which=Gouda),(Which=Haarlem),(Which=Leiden),(Which=Rotterdam),(Which=Utrecht),..][..] I can travel from Utrecht to Leiden in at most 3 steps ? [()][] I can travel from Utrecht to Leiden in at most 2 steps ? [..][..] I can travel from Utrecht to Delft in at most 3 steps ? [()][] I can travel from Utrecht to Delft in at most 0 steps ? [][()]
import nelumbo.integers
City    :: Object
City    ::= Utrecht, Amsterdam, Haarlem, Leiden, Delft, Rotterdam, Gouda, Zwolle, Groningen
Boolean ::= there is a train between <City> and <City>                #30,
            <City> is connected to <City>                             #30,
            I can travel from <City> to <City>                        #30,
            I can travel from <City> to <City> in at most <Integer> steps #30
City    a, b, c, Which
Integer n
a is connected to b <=> there is a train between a and b | there is a train between b and a
I can travel from a to b <=> a is connected to b | E[c](a is connected to c & I can travel from c to b)
I can travel from a to b in at most n steps <=> n>0 & E[c](a is connected to c & (c=b | I can travel from c to b in at most n-1 steps))

// ---- given: the timetable and the answers it should give ----
fact there is a train between Utrecht and Amsterdam, there is a train between Amsterdam and Haarlem,
     there is a train between Haarlem and Leiden,    there is a train between Leiden and Delft,
     there is a train between Delft and Rotterdam,   there is a train between Rotterdam and Gouda,
     there is a train between Gouda and Utrecht,     there is a train between Zwolle and Groningen

Utrecht is connected to Amsterdam                      ? [()][]
Amsterdam is connected to Utrecht                      ? [()][]
Which is connected to Utrecht                          ? [(Which=Amsterdam),(Which=Gouda),..][..]
Utrecht is connected to Leiden                         ? [..][..]
I can travel from Utrecht to Leiden                    ? [()][]
I can travel from Utrecht to Groningen                 ? [..][..]
I can travel from Utrecht to Which                     ? [(Which=Amsterdam),(Which=Delft),(Which=Gouda),(Which=Haarlem),(Which=Leiden),(Which=Rotterdam),(Which=Utrecht),..][..]
I can travel from Utrecht to Leiden in at most 3 steps ? [()][]
I can travel from Utrecht to Leiden in at most 2 steps ? [..][..]
I can travel from Utrecht to Delft in at most 3 steps  ? [()][]
I can travel from Utrecht to Delft in at most 0 steps  ? [][()]

Number toolkit

The projects so far read like English, but a Nelumbo language can just as well use mathematical notation. Here you extend the integers with operators: factorial, power, remainder, greatest common divisor, divisibility and primality. Precedence decides how a question groups: 2 * 3! is 12 and 2 + 10 mod 4 is 4, because ! binds tighter than *, and mod as tightly as *. Power groups to the right, as in mathematics: 2^3^2 is 2^(3^2), so 512, not 64.

Hints
  1. Declare the operators on Integer: the postfix <Integer> ! with #70, <Integer> mod <Integer> with #50 like *, and gcd(<Integer>, <Integer>), which needs no precedence because it does not start with a hole.
  2. An operator groups to the left by default, so 2^3^2 would be (2^3)^2. Hole precedences make it group to the right: <Integer#61> ^ <Integer#59> #60. The left operand cannot be a power itself, the right one can.
  3. Write each function as a recursive function rule with == and if: n! is 1 for 0 and n*(n-1)! above it, and power works the same way. a mod b subtracts b until the rest is smaller than b, and gcd is Euclid's algorithm: gcd(a,b) is gcd(b, a mod b) until b is 0.
  4. d divides n is a Boolean operator with #30: the remainder is 0.
  5. A number above 1 is prime when no number from n-1 down to 2 divides it. Write a relation no divisor of <Integer> up to <Integer> that counts down to 1. It needs an explicit false branch for a divisor: a guard that fails makes its branch silent, not false. Count down to the constant 1: a recursive relation whose guards compare two of its variables (d<n) runs into a known engine bug.

Write the language above the given part so that every question gets the expected answer.

import nelumbo.integers // build your language here: operators, variables and rules // ---- given: the questions and the answers they should get ---- 5! ? [(120)][..] 0! ? [(1)][..] 2 * 3! ? [(12)][..] 2^10 ? [(1024)][..] 2^3^2 ? [(512)][..] 2^3 * 2 ? [(16)][..] 17 mod 5 ? [(2)][..] 2 + 10 mod 4 ? [(4)][..] (2 + 10) mod 4 ? [(0)][..] gcd(12, 18) ? [(6)][..] gcd(17, 5) ? [(1)][..] 3 divides 12 ? [()][] 5 divides 12 ? [][()] prime 2 ? [()][] prime 97 ? [()][] prime 91 ? [][()] prime 1 ? [][()]
import nelumbo.integers
Integer ::= <Integer> !                 #70,
            <Integer#61> ^ <Integer#59> #60,
            <Integer> mod <Integer>     #50,
            gcd(<Integer>, <Integer>)
Boolean ::= <Integer> divides <Integer>          #30,
            prime <Integer>                      #30,
            no divisor of <Integer> up to <Integer> #30
Integer n, k, a, b, d
n!       == 1 if n=0, n*(n-1)! if n>0
a^k      == 1 if k=0, a*a^(k-1) if k>0
a mod b  == a if a<b, (a-b) mod b if a>=b
gcd(a,b) == a if b=0, gcd(b, a mod b) if b>0
d divides n             <=> n mod d = 0
no divisor of n up to d <=> true  if d<=1,
                            false if d>1 & d divides n,
                            no divisor of n up to d-1 if d>1 & !(d divides n)
prime n                 <=> n>1 & no divisor of n up to n-1

// ---- given: the questions and the answers they should get ----
5!             ? [(120)][..]
0!             ? [(1)][..]
2 * 3!         ? [(12)][..]
2^10           ? [(1024)][..]
2^3^2          ? [(512)][..]
2^3 * 2        ? [(16)][..]
17 mod 5       ? [(2)][..]
2 + 10 mod 4   ? [(4)][..]
(2 + 10) mod 4 ? [(0)][..]
gcd(12, 18)    ? [(6)][..]
gcd(17, 5)     ? [(1)][..]
3 divides 12   ? [()][]
5 divides 12   ? [][()]
prime 2        ? [()][]
prime 97       ? [()][]
prime 91       ? [][()]
prime 1        ? [][()]

Set algebra

The sets of nelumbo.collections come with intersection &&, union || and difference -. Build new set operators on top of them: the symmetric difference xor (the elements in exactly one of the two sets), disjoint, multiplying every element by a number, and the sum set s + t: every sum of an element of s and an element of t. One symbol can mean different things on different types: + adds integers but builds the sum set of two sets, and the types of the operands decide which. An open set stays open: {1,2,..} xor {2} contains 1, and perhaps more.

Hints
  1. Declare the operators on Set<Integer>, each with a precedence: xor with #60 like && and ||, * with #50 and + with #40, as on integers. So {1,2} + {10} * 2 is {1,2} + {20}.
  2. Declare the variables: two sets and a few integers.
  3. s xor t is a function rule over the built-in operators: what is in s but not in t, together with what is in t but not in s.
  4. The set-builder {[n](...)} collects every n for which the condition holds. s * k is the set of every n that is i*k for an element i of s: {[n](E[i](i in s & n = i*k))}. The sum set works the same way with two elements.
  5. Two sets are disjoint when their intersection is the empty set {}.

Write the language above the given part so that every question gets the expected answer.

import nelumbo.collections // build your language here: operators, variables and rules // ---- given: the questions and the answers they should get ---- {1,2,3} xor {2,3,4} ? [({1,4})][..] {1,2} xor {2,1} ? [({})][..] {1,2} disjoint {3,4} ? [()][] {1,2} disjoint {2,3} ? [][()] {1,2,3} * 10 ? [({10,20,30})][..] {1,2} + {10,20} ? [({11,12,21,22})][..] |{1,2,3} + {1,2,3}| ? [(5)][..] {1,2} + {10} * 2 ? [({21,22})][..] {1,2} + {0} = {1,2} ? [()][] {1,2,..} xor {2} ? [({1,..})][..]
import nelumbo.collections
Set<Integer> ::= <Set<Integer>> xor <Set<Integer>> #60,
                 <Set<Integer>> + <Set<Integer>>   #40,
                 <Set<Integer>> * <Integer>        #50
Boolean      ::= <Set<Integer>> disjoint <Set<Integer>> #30
Set<Integer> s, t
Integer      i, j, k, n
s xor t      == (s - t) || (t - s)
s * k        == {[n](E[i](i in s & n = i*k))}
s + t        == {[n](E[i,j](i in s & j in t & n = i+j))}
s disjoint t <=> s && t = {}

// ---- given: the questions and the answers they should get ----
{1,2,3} xor {2,3,4}   ? [({1,4})][..]
{1,2} xor {2,1}       ? [({})][..]
{1,2} disjoint {3,4}  ? [()][]
{1,2} disjoint {2,3}  ? [][()]
{1,2,3} * 10          ? [({10,20,30})][..]
{1,2} + {10,20}       ? [({11,12,21,22})][..]
|{1,2,3} + {1,2,3}|   ? [(5)][..]
{1,2} + {10} * 2      ? [({21,22})][..]
{1,2} + {0} = {1,2}   ? [()][]
{1,2,..} xor {2}      ? [({1,..})][..]

List statistics

Lists of numbers, the way a programmer handles them: the sum, maximum, mean and median of a list, and the dot product of two lists. You build them from list indexing and recursion, and the questions combine them with the library's map, where and sort, which take a lambda such as [i](i*i). Division is exact, so mean [1,2] has no value: 3/2 is not an integer, and the facts side stays empty.

Hints
  1. Declare the functions on Integer with #45: sum <List<Integer>>, and the same for max, mean and median. The dot product is an operator, <List<Integer>> dot <List<Integer>> with #50.
  2. Declare Integer i, j: the given questions use i in their lambdas.
  3. Recurse over an index with a helper such as sum <List<Integer>> from <Integer>: 0 past the end (i >= |l|), otherwise the element at i in l plus the sum from the next index. Write from (i+1): the helper binds tighter than +, so sum l from i+1 is (sum l from i)+1.
  4. At the last index (|l|-1) the maximum is the element itself, before that the larger of the element and the maximum of the rest: (x > y then x else y).
  5. The mean is the sum divided by the length. The median of a list of odd length is the middle element of the sorted list: l sort [i,j](i<j), at index (|l|-1)/2.
  6. The dot product multiplies the elements at the same index and adds up the products: one more helper that recurses over an index.

Write the language above the given part so that every question gets the expected answer.

import nelumbo.collections // build your language here: functions, variables and rules // ---- given: the questions and the answers they should get ---- sum [3,4,5] ? [(12)][..] sum [] ? [(0)][..] max [3,9,4] ? [(9)][..] mean [2,4,9] ? [(5)][..] mean [1,2] ? [][..] median [7,1,5] ? [(5)][..] [1,2,3] dot [4,5,6] ? [(32)][..] sum ([1,2,3] map [i](i*i)) ? [(14)][..] max ([3,9,4] where [i](i<5)) ? [(4)][..]
import nelumbo.collections
Integer ::= sum <List<Integer>>                     #45,
            sum <List<Integer>> from <Integer>      #45,
            max <List<Integer>>                     #45,
            max <List<Integer>> from <Integer>      #45,
            mean <List<Integer>>                    #45,
            median <List<Integer>>                  #45,
            <List<Integer>> dot <List<Integer>>     #50,
            products of <List<Integer>> and <List<Integer>> from <Integer> #45
List<Integer> l, m
Integer       i, j
sum l from i == 0 if i >= |l|, (at i in l) + sum l from (i+1) if i < |l|
sum l        == sum l from 0
max l from i == at i in l if i = |l|-1,
                (at i in l > max l from (i+1) then at i in l else max l from (i+1)) if i < |l|-1
max l        == max l from 0
mean l       == (sum l) / (|l|)
median l     == at ((|l|-1)/2) in (l sort [i,j](i<j))
products of l and m from i == 0 if i >= |l|, (at i in l) * (at i in m) + products of l and m from (i+1) if i < |l|
l dot m      == products of l and m from 0

// ---- given: the questions and the answers they should get ----
sum [3,4,5]                  ? [(12)][..]
sum []                       ? [(0)][..]
max [3,9,4]                  ? [(9)][..]
mean [2,4,9]                 ? [(5)][..]
mean [1,2]                   ? [][..]
median [7,1,5]               ? [(5)][..]
[1,2,3] dot [4,5,6]          ? [(32)][..]
sum ([1,2,3] map [i](i*i))   ? [(14)][..]
max ([3,9,4] where [i](i<5)) ? [(4)][..]

Vectors

Not every value is a number or a name. Here a vector such as vec(1,2) is a value of its own: two vectors are equal exactly when their parts are, and a vector can be an answer. Its operators are functions that compute a new vector: vec(1,2) + vec(3,4) is vec(4,6). That takes two kinds of declarations: the constructor vec is a struct, the operators are functions. A vector with a computed part is computed first, so vec(1+1, 2) equals vec(2, 2).

Hints
  1. Declare Vec :: Object, and only the constructor as a struct: struct Vec ::= vec(<Integer>, <Integer>), the way the library declares its set and list literals. The operators are declared without struct, so they are functions that compute a vector.
  2. Declare the operators with the precedences of their integer counterparts: Vec ::= <Vec> + <Vec> #40, the same for -, and <Integer> * <Vec> #50. The dot product is Integer ::= <Vec> . <Vec> #50, and <Vec> is perpendicular to <Vec> is a Boolean phrase with #30. The types of the operands tell 2 * vec(1,2) apart from 2 * 3.
  3. Write a function rule per operator, with a head that takes the vectors apart: vec(a,b) + vec(c,d) == vec(a+c, b+d).
  4. Two vectors are perpendicular when their dot product is 0.

Write the language above the given part so that every question gets the expected answer.

import nelumbo.integers // build your language here: the vector type, operators, variables and rules // ---- given: the questions and the answers they should get ---- vec(1,2) + vec(3,4) ? [(vec(4,6))][..] vec(1,2) - vec(3,4) ? [(vec(-2,-2))][..] 2 * vec(1,2) ? [(vec(2,4))][..] vec(1,2) + 2 * vec(1,1) ? [(vec(3,4))][..] vec(1,2) . vec(3,4) ? [(11)][..] vec(1+1, 2) = vec(2, 2) ? [()][] vec(1,2) + vec(3,4) = vec(4,6) ? [()][] vec(1,2) = vec(2,1) ? [][()] vec(2,1) is perpendicular to vec(-1,2) ? [()][] vec(2,1) is perpendicular to vec(1,2) ? [][()]
import nelumbo.integers
Vec        :: Object
struct Vec ::= vec(<Integer>, <Integer>)
Vec        ::= <Vec> + <Vec>     #40,
               <Vec> - <Vec>     #40,
               <Integer> * <Vec> #50
Integer    ::= <Vec> . <Vec>      #50
Boolean    ::= <Vec> is perpendicular to <Vec> #30
Vec     u, v
Integer a, b, c, d, k
vec(a,b) + vec(c,d)     ==  vec(a+c, b+d)
vec(a,b) - vec(c,d)     ==  vec(a-c, b-d)
k * vec(a,b)            ==  vec(k*a, k*b)
vec(a,b) . vec(c,d)     ==  a*c + b*d
u is perpendicular to v <=>  u . v = 0

// ---- given: the questions and the answers they should get ----
vec(1,2) + vec(3,4)                    ? [(vec(4,6))][..]
vec(1,2) - vec(3,4)                    ? [(vec(-2,-2))][..]
2 * vec(1,2)                           ? [(vec(2,4))][..]
vec(1,2) + 2 * vec(1,1)                ? [(vec(3,4))][..]
vec(1,2) . vec(3,4)                    ? [(11)][..]
vec(1+1, 2) = vec(2, 2)                ? [()][]
vec(1,2) + vec(3,4) = vec(4,6)         ? [()][]
vec(1,2) = vec(2,1)                    ? [][()]
vec(2,1) is perpendicular to vec(-1,2) ? [()][]
vec(2,1) is perpendicular to vec(1,2)  ? [][()]

Expression interpreter

A language inside your language: arithmetic expressions with variables, and a function eval that computes their value. An expression is a value, a tree of parts: x + #2 equals x + #2 but not #2 + x, although both evaluate to 5. eval is a function over those trees, with one rule per kind of expression. The variables get their values from facts, and a variable without a value gives an unknown result, not an error.

Hints
  1. Declare Expr :: Object, and Var :: Expr with the constants x, y and z.
  2. Declare the other expressions as structs so that an expression is a value: struct Expr ::= # <Integer> #90, <Expr> + <Expr> #40, <Expr> * <Expr> #50. The types of the operands tell x + #2 apart from 3 + 2.
  3. Declare eval <Expr> as an Integer function with #80, and <Var> := <one Integer> as a Boolean phrase with #20: a variable has one value.
  4. Write one rule per kind of expression, with a head that takes it apart: eval (e + f) == eval e + eval f. A variable's value comes from its fact: eval v = i <=> v := i.

Write the language above the given part so that the facts parse and every question gets the expected answer.

import nelumbo.integers // build your language here: expressions, eval, variables and rules // ---- given: the variables, and the questions with the answers they should get ---- fact x := 3, y := 4 eval #5 ? [(5)][..] eval x ? [(3)][..] eval (x + #2) ? [(5)][..] eval (x + y * #2) ? [(11)][..] eval ((x + y) * #2) ? [(14)][..] eval (z + #1) ? [..][..] x + #2 = x + #2 ? [()][] x + #2 = #2 + x ? [][()]
import nelumbo.integers
Expr        :: Object
Var         :: Expr
Var         ::= x, y, z
struct Expr ::= # <Integer>     #90,
                <Expr> + <Expr> #40,
                <Expr> * <Expr> #50
Integer     ::= eval <Expr>      #80
Boolean     ::= <Var> := <one Integer> #20
Expr    e, f
Var     v
Integer i
eval #i      ==  i
eval v = i   <=>  v := i
eval (e + f) ==  eval e + eval f
eval (e * f) ==  eval e * eval f

// ---- given: the variables, and the questions with the answers they should get ----
fact x := 3, y := 4

eval #5             ? [(5)][..]
eval x              ? [(3)][..]
eval (x + #2)       ? [(5)][..]
eval (x + y * #2)   ? [(11)][..]
eval ((x + y) * #2) ? [(14)][..]
eval (z + #1)       ? [..][..]
x + #2 = x + #2     ? [()][]
x + #2 = #2 + x     ? [][()]

Room booking

Today's bookings for a few meeting rooms are given with times like 9:30. Answer whether a room is free, at a time or between two times, and whether a room's bookings clash. "Free" is the hard part. In an open world you can prove that a room is taken: you find the booking. Proving that it is free requires every booking for the room, so each room's bookings are given as one complete set. Yellow has no set, so whether it is free stays unknown.

Hints
  1. A time is a named pattern of connected tokens, so 9:30 parses and 9 : 30 does not: pattern TIME ::= <[> <Integer#100> : <Integer#100> <]>. In a phrase, <TIME> gives two Integer arguments, hours and minutes; compare times in minutes, h*60+m.
  2. A booking is a value, not a fact: declare Slot :: Object and a struct Slot with the pattern <TIME> to <TIME> (precedence #35), and the bookings for <Room> as a function that returns a Set<Slot> (precedence #60).
  3. Because each set is complete, A[b]((b in the bookings for r) -> ...) can be true.
  4. For relations about a slot, such as <Integer> falls in <Slot>, let the rule head take the slot apart: n falls in h1:m1 to h2:m2 <=> ....
  5. A room is free between two times when it is free at the first, and no booking starts at or after the first and before the second.
  6. Bookings clash when one starts during another. A booking does not clash with itself (!=).

Write the language above the given part so that the facts parse and every question gets the expected answer.

import nelumbo.collections // build your language here: times, slots, rooms, sentences, variables and rules // ---- given: today's bookings, complete per room, and the answers they should give ---- fact the bookings for Blue = {9:30 to 11:00, 13:00 to 14:30} fact the bookings for Red = {9:00 to 10:30, 10:00 to 11:00} fact the bookings for Green = {} Blue is free at 10:00 ? [][()] Blue is free at 12:00 ? [()][] Blue is free at 11:00 ? [()][] Blue is free between 11:00 and 13:00 ? [()][] Blue is free between 10:30 and 12:00 ? [][()] Blue is free between 12:00 and 15:00 ? [][()] Green is free at 10:00 ? [()][] Yellow is free at 10:00 ? [..][..] Red has clashing bookings ? [()][] Blue has clashing bookings ? [][()]
import nelumbo.collections
pattern TIME ::= <[> <Integer#100> : <Integer#100> <]>
Slot         :: Object
struct Slot  ::= <TIME> to <TIME> #35
Room         :: Object
Room         ::= Blue, Red, Green, Yellow
Set<Slot>    ::= the bookings for <Room> #60
Boolean      ::= <Integer> falls in <Slot>                #30,
                 <Integer> is the start of <Slot>         #30,
                 <Room> is free at <TIME>                 #30,
                 <Room> is free between <TIME> and <TIME> #30,
                 <Room> has clashing bookings             #30
Integer h1, m1, h2, m2, n
Slot    b, c
Room    r
n falls in h1:m1 to h2:m2         <=>  h1*60+m1 <= n & n < h2*60+m2
n is the start of h1:m1 to h2:m2  <=>  n = h1*60+m1
r is free at h1:m1                <=>  A[b]((b in the bookings for r) -> !(h1*60+m1 falls in b))
r is free between h1:m1 and h2:m2 <=>  r is free at h1:m1 &
                                       A[b]((b in the bookings for r) -> !E[n](n is the start of b & h1*60+m1 <= n & n < h2*60+m2))
r has clashing bookings           <=>  E[b,c](b in the bookings for r & c in the bookings for r & b != c &
                                       E[n](n is the start of c & n falls in b))

// ---- given: today's bookings, complete per room, and the answers they should give ----
fact the bookings for Blue  = {9:30 to 11:00, 13:00 to 14:30}
fact the bookings for Red   = {9:00 to 10:30, 10:00 to 11:00}
fact the bookings for Green = {}

Blue is free at 10:00                ? [][()]
Blue is free at 12:00                ? [()][]
Blue is free at 11:00                ? [()][]
Blue is free between 11:00 and 13:00 ? [()][]
Blue is free between 10:30 and 12:00 ? [][()]
Blue is free between 12:00 and 15:00 ? [][()]
Green is free at 10:00               ? [()][]
Yellow is free at 10:00              ? [..][..]
Red has clashing bookings            ? [()][]
Blue has clashing bookings           ? [][()]

Access policy

A policy language for who may do what. A dictionary lists every role with its article, and the policy is written as plain statements: every manager may approve invoices, no intern may approve invoices. Transformations turn every statement into a fact or a rule. Allow rules fill the facts side of an answer, deny rules the falsehood side. Where no policy applies, the answer is unknown, not denied: nothing says whether Eva, an auditor, may approve invoices. When you are done, add Ann is a manager: she is then both allowed and denied, and Nelumbo reports the inconsistency instead of picking one.

Hints
  1. Declare the types Person, Role, Action and Item with their constants, and an Article type with a and an.
  2. Only <Person> may <Action> <Item> is a question: a Boolean phrase with precedence #30. The other lines are statements: Root phrases with precedence #0, and role <Article> <Role> with #100.
  3. Store a person's roles in a relation of your own, for example a private <Person> has role <Role>.
  4. A dictionary entry such as role an intern is transformed, as in Controlled English: it declares the sentences with that article, <Person> is an intern and every <Role> is an intern, and transformations for them. The first adds a fact, the second a rule: whoever has the one role has the other.
  5. A policy statement is transformed into a rule: every r may x i into q may x i <=> true if q has role r, no r may x i into the same with false.
  6. In a transformation, use variables such as {Role,Literal} r in the heads, and give every functor declared in a transformation body at least one hole (a known engine bug).

Write the language above the given part so that the dictionary, the policy and the staff parse and every question gets the expected answer.

import nelumbo.logic // build your language here: types, the dictionary, statements, questions and transformations // ---- given: the dictionary, the policy, the staff, and the answers they should give ---- role a manager role an intern role an employee role an auditor every manager is an employee every intern is an employee every employee may read contracts every manager may approve invoices no intern may approve invoices no intern may sign contracts every auditor may read invoices Jan is a manager Ann is an intern Eva is an auditor Jan may approve invoices ? [()][] Ann may approve invoices ? [][()] Eva may approve invoices ? [..][..] Ann may read contracts ? [()][] Eva may read contracts ? [..][..] Ann may sign contracts ? [][()] Jan may sign contracts ? [..][..] Who may approve invoices ? [(Who=Jan),..][(Who=Ann),..] Who may read contracts ? [(Who=Ann),(Who=Jan),..][..]
import nelumbo.logic
Person  :: Object
Person  ::= Jan, Ann, Eva, Bob
Article :: Object
Article ::= a, an
Role    :: Object
Role    ::= manager, intern, employee, auditor
Action  :: Object
Action  ::= approve, read, sign
Item    :: Object
Item    ::= invoices, contracts
Boolean ::= <Person> may <Action> <Item> #30
private Boolean ::= <Person> has role <Role> #30
Root    ::= role <Article> <Role>             #100,
            every <Role> may <Action> <Item>  #0,
            no <Role> may <Action> <Item>     #0
{
    Article          ar
    {Role,Literal}   r, s
    {Action,Literal} x
    {Item,Literal}   i
    {Person,Literal} p
    Person           q
    role ar r ::> {
        Root ::= <{Person,Literal}> is <ar> <r>     #0
        Root ::= every <{Role,Literal}> is <ar> <r> #0
        p is ar r ::> {
            fact p has role r
        }
        every s is ar r ::> {
            q has role r <=> true if q has role s
        }
    }
    every r may x i ::> {
        q may x i <=> true if q has role r
    }
    no r may x i ::> {
        q may x i <=> false if q has role r
    }
}
Person Who

// ---- given: the dictionary, the policy, the staff, and the answers they should give ----
role a manager
role an intern
role an employee
role an auditor

every manager is an employee
every intern is an employee
every employee may read contracts
every manager may approve invoices
no intern may approve invoices
no intern may sign contracts
every auditor may read invoices

Jan is a manager
Ann is an intern
Eva is an auditor

Jan may approve invoices ? [()][]
Ann may approve invoices ? [][()]
Eva may approve invoices ? [..][..]
Ann may read contracts   ? [()][]
Eva may read contracts   ? [..][..]
Ann may sign contracts   ? [][()]
Jan may sign contracts   ? [..][..]
Who may approve invoices ? [(Who=Jan),..][(Who=Ann),..]
Who may read contracts   ? [(Who=Ann),(Who=Jan),..][..]