From 2d2b925227260f5eb394a63bb4730058b6f0a4af Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Mon, 14 Jun 2021 17:20:34 -0500 Subject: [PATCH] go back to the single-construction API --- sample-apps/src/FamilyTree.elm | 84 +++++---- src/Datalog.elm | 92 +++++----- tests/DatalogTests.elm | 307 ++++++++++++++++++++------------- 3 files changed, 289 insertions(+), 194 deletions(-) diff --git a/sample-apps/src/FamilyTree.elm b/sample-apps/src/FamilyTree.elm index 0d633b9..ebb8add 100644 --- a/sample-apps/src/FamilyTree.elm +++ b/sample-apps/src/FamilyTree.elm @@ -327,17 +327,23 @@ viewRelationships model personId = derived = Datalog.derive [ {- me(id, name) :- person(id, name), id = personId -} - Datalog.rule "me" [ "id", "name" ] - |> Datalog.with "person" [ Datalog.var "id", Datalog.var "name" ] - |> Datalog.filter (Datalog.eq "id" (Datalog.int personId)) + Datalog.rule "me" + [ "id", "name" ] + [ Datalog.with "person" [ Datalog.var "id", Datalog.var "name" ] + , Datalog.filter (Datalog.eq "id" (Datalog.int personId)) + ] , {- parents(id, name) :- person(id, name), parent(id, personId). -} - Datalog.rule "parents" [ "id", "name" ] - |> Datalog.with "person" [ Datalog.var "id", Datalog.var "name" ] - |> Datalog.with "parent" [ Datalog.var "id", Datalog.int personId ] + Datalog.rule "parents" + [ "id", "name" ] + [ Datalog.with "person" [ Datalog.var "id", Datalog.var "name" ] + , Datalog.with "parent" [ Datalog.var "id", Datalog.int personId ] + ] , {- children(id, name) :- person(id, name), parent(personId, id). -} - Datalog.rule "children" [ "id", "name" ] - |> Datalog.with "person" [ Datalog.var "id", Datalog.var "name" ] - |> Datalog.with "parent" [ Datalog.int personId, Datalog.var "id" ] + Datalog.rule "children" + [ "id", "name" ] + [ Datalog.with "person" [ Datalog.var "id", Datalog.var "name" ] + , Datalog.with "parent" [ Datalog.int personId, Datalog.var "id" ] + ] , {- siblings(siblingId, siblingName) :- person(siblingId, siblingName), @@ -345,25 +351,31 @@ viewRelationships model personId = parent(parentId, siblingId), siblingId /= personId. -} - Datalog.rule "siblings" [ "siblingId", "siblingName" ] - |> Datalog.with "person" [ Datalog.var "siblingId", Datalog.var "siblingName" ] - |> Datalog.with "parent" [ Datalog.var "parentId", Datalog.int personId ] - |> Datalog.with "parent" [ Datalog.var "parentId", Datalog.var "siblingId" ] - |> Datalog.filter (Datalog.not_ (Datalog.eq "siblingId" (Datalog.int personId))) + Datalog.rule "siblings" + [ "siblingId", "siblingName" ] + [ Datalog.with "person" [ Datalog.var "siblingId", Datalog.var "siblingName" ] + , Datalog.with "parent" [ Datalog.var "parentId", Datalog.int personId ] + , Datalog.with "parent" [ Datalog.var "parentId", Datalog.var "siblingId" ] + , Datalog.filter (Datalog.not_ (Datalog.eq "siblingId" (Datalog.int personId))) + ] , {- grandparents(grandparentId, grandparentName) :- person(grandparentId, grandparentName), person(parentId, personId), person(grandparentId, parentId). -} - Datalog.rule "grandparents" [ "grandparentId", "grandparentName" ] - |> Datalog.with "person" [ Datalog.var "grandparentId", Datalog.var "grandparentName" ] - |> Datalog.with "parent" [ Datalog.var "parentId", Datalog.int personId ] - |> Datalog.with "parent" [ Datalog.var "grandparentId", Datalog.var "parentId" ] - , Datalog.rule "grandchildren" [ "grandchildId", "grandchildName" ] - |> Datalog.with "person" [ Datalog.var "grandchildId", Datalog.var "grandchildName" ] - |> Datalog.with "parent" [ Datalog.var "childId", Datalog.var "grandchildId" ] - |> Datalog.with "parent" [ Datalog.int personId, Datalog.var "childId" ] + Datalog.rule "grandparents" + [ "grandparentId", "grandparentName" ] + [ Datalog.with "person" [ Datalog.var "grandparentId", Datalog.var "grandparentName" ] + , Datalog.with "parent" [ Datalog.var "parentId", Datalog.int personId ] + , Datalog.with "parent" [ Datalog.var "grandparentId", Datalog.var "parentId" ] + ] + , Datalog.rule "grandchildren" + [ "grandchildId", "grandchildName" ] + [ Datalog.with "person" [ Datalog.var "grandchildId", Datalog.var "grandchildName" ] + , Datalog.with "parent" [ Datalog.var "childId", Datalog.var "grandchildId" ] + , Datalog.with "parent" [ Datalog.int personId, Datalog.var "childId" ] + ] , {- auntsAndUncles(auId, auName) :- person(auId, auName), @@ -372,18 +384,22 @@ viewRelationships model personId = parent(parentId, personId), auId /= parentId. -} - Datalog.rule "auntsAndUncles" [ "auId", "auName" ] - |> Datalog.with "person" [ Datalog.var "auId", Datalog.var "auName" ] - |> Datalog.with "parent" [ Datalog.var "grandparentId", Datalog.var "auId" ] - |> Datalog.with "parent" [ Datalog.var "grandparentId", Datalog.var "parentId" ] - |> Datalog.with "parent" [ Datalog.var "parentId", Datalog.int personId ] - |> Datalog.filter (Datalog.not_ (Datalog.eq "auId" (Datalog.var "parentId"))) - , Datalog.rule "niecesAndNephews" [ "nnId", "nnName" ] - |> Datalog.with "person" [ Datalog.var "nnId", Datalog.var "nnName" ] - |> Datalog.with "parent" [ Datalog.var "siblingId", Datalog.var "nnId" ] - |> Datalog.with "parent" [ Datalog.var "parentId", Datalog.var "siblingId" ] - |> Datalog.with "parent" [ Datalog.var "parentId", Datalog.int personId ] - |> Datalog.filter (Datalog.not_ (Datalog.eq "siblingId" (Datalog.int personId))) + Datalog.rule "auntsAndUncles" + [ "auId", "auName" ] + [ Datalog.with "person" [ Datalog.var "auId", Datalog.var "auName" ] + , Datalog.with "parent" [ Datalog.var "grandparentId", Datalog.var "auId" ] + , Datalog.with "parent" [ Datalog.var "grandparentId", Datalog.var "parentId" ] + , Datalog.with "parent" [ Datalog.var "parentId", Datalog.int personId ] + , Datalog.filter (Datalog.not_ (Datalog.eq "auId" (Datalog.var "parentId"))) + ] + , Datalog.rule "niecesAndNephews" + [ "nnId", "nnName" ] + [ Datalog.with "person" [ Datalog.var "nnId", Datalog.var "nnName" ] + , Datalog.with "parent" [ Datalog.var "siblingId", Datalog.var "nnId" ] + , Datalog.with "parent" [ Datalog.var "parentId", Datalog.var "siblingId" ] + , Datalog.with "parent" [ Datalog.var "parentId", Datalog.int personId ] + , Datalog.filter (Datalog.not_ (Datalog.eq "siblingId" (Datalog.int personId))) + ] ] model.db diff --git a/src/Datalog.elm b/src/Datalog.elm index 8c18430..9f7b700 100644 --- a/src/Datalog.elm +++ b/src/Datalog.elm @@ -392,9 +392,9 @@ If you have multiple rules with the same name, they'll be merged together (for an example, see the docs for [`with`](#with).) -} -rule : String -> List String -> Rule -rule name headVars = - Rule (Atom name (List.map Variable headVars)) [] +rule : String -> List String -> List BodyAtom -> Rule +rule name headVars atoms = + Rule (Atom name (List.map Variable headVars)) atoms {-| Add matches from the given name (TODO: table? rule? named tuple store?) @@ -402,25 +402,29 @@ rule name headVars = For example, if you have some greeks (Socrates, say) you can write a rule like this to see which of them are mortal: - rule "mortal" [ "name" ] - |> with "greek" [ var "name" ] + rule "mortal" + [ "name" ] + [ with "greek" [ var "name" ] ] It's fine to use this to set up recursive queries. For example, you could compute reachability for all nodes in a graph using two rules like this: - [ rule "reachable" [ "a", "b" ] - |> with "link" [ var "a", var "b" ] - , rule "reachable" [ "a", "c" ] - |> with "link" [ var "a", var "b" ] - |> with "reachable" [ var "b", var "c" ] + [ rule "reachable" + [ "a", "b" ] + [ with "link" [ var "a", var "b" ] ] + , rule "reachable" + [ "a", "c" ] + [ with "link" [ var "a", var "b" ] + , with "reachable" [ var "b", var "c" ] + ] ] If you introduce a variable in a `with` like that above, it's also fine! -} -with : String -> List Term -> Rule -> Rule -with name terms (Rule head body) = - Rule head (BodyAtom Positive (Atom name terms) :: body) +with : String -> List Term -> BodyAtom +with name terms = + BodyAtom Positive (Atom name terms) {-| The opposite of [`with`](#with): remove any matching tuples based on @@ -441,30 +445,40 @@ Here's an example of computing all the nodes in a graph that _aren't_ reachable from each other: [ -- first, define `reachable` as in the example in `with`: - rule "reachable" [ "a", "b" ] - |> with "link" [ var "a", var "b" ] - , rule "reachable" [ "a", "c" ] - |> with "link" [ var "a", var "b" ] - |> with "reachable" [ var "b", var "c" ] + rule "reachable" + [ "a", "b" ] + [ with "link" [ var "a", var "b" ] ] + , rule "reachable" + [ "a", "c" ] + [ with "link" + [ var "a", var "b" ] + with + "reachable" + [ var "b", var "c" ] + ] -- next, we need to know what is a valid node so we can - , rule "node" [ "a" ] - |> with "link" [ var "a", var "b" ] - , rule "node" [ "b" ] - |> with "link" [ var "a", var "b" ] + , rule "node" + [ "a" ] + [ with "link" [ var "a", var "b" ] ] + , rule "node" + [ "b" ] + [ with "link" [ var "a", var "b" ] ] -- finally, we just say "a set of two nodes is unreachable if they're -- individually in `node` but not together in `reachable`" - , rule "unreachable" [ "a", "b" ] - |> with "node" [ var "a" ] - |> with "node" [ var "b" ] - |> without "reachable" [ var "a", var "b" ] + , rule "unreachable" + [ "a", "b" ] + [ with "node" [ var "a" ] + , with "node" [ var "b" ] + , without "reachable" [ var "a", var "b" ] + ] ] -} -without : String -> List Term -> Rule -> Rule -without name terms (Rule head body) = - Rule head (BodyAtom Negative (Atom name terms) :: body) +without : String -> List Term -> BodyAtom +without name terms = + BodyAtom Negative (Atom name terms) planRule : Rule -> Result Problem Database.QueryPlan @@ -689,9 +703,9 @@ type Op | Lt -filter : Filter -> Rule -> Rule -filter filter_ (Rule head body) = - Rule head (Filter filter_ :: body) +filter : Filter -> BodyAtom +filter = + Filter eq : String -> Term -> Filter @@ -899,15 +913,15 @@ parserHelp soFar = ruleParser : Parser Rule ruleParser = - Parser.succeed (List.foldl (\with_ rule_ -> with_ rule_)) + Parser.succeed (\( name, headTerms ) bodyTerms -> rule name headTerms bodyTerms) |= ruleHeadParser |. spacesAndComments |= ruleBodyParser -ruleHeadParser : Parser Rule +ruleHeadParser : Parser ( String, List String ) ruleHeadParser = - Parser.succeed rule + Parser.succeed Tuple.pair |= Parser.inContext NameOfRule nameParser |. spacesAndComments |= Parser.sequence @@ -921,7 +935,7 @@ ruleHeadParser = |> Parser.inContext RuleHead -ruleBodyParser : Parser (List (Rule -> Rule)) +ruleBodyParser : Parser (List BodyAtom) ruleBodyParser = Parser.sequence { start = hornToken @@ -937,7 +951,7 @@ ruleBodyParser = } -bodyAtomParser : Parser (Rule -> Rule) +bodyAtomParser : Parser BodyAtom bodyAtomParser = Parser.succeed (\negative name body -> @@ -970,14 +984,14 @@ notParser = ] -filterParser : Parser (Rule -> Rule) +filterParser : Parser BodyAtom filterParser = Parser.andThen (\firstFilter -> Parser.loop firstFilter filterParserHelp) filterClauseParser -filterParserHelp : Filter -> Parser (Parser.Step Filter (Rule -> Rule)) +filterParserHelp : Filter -> Parser (Parser.Step Filter BodyAtom) filterParserHelp lastFilter = Parser.oneOf [ Parser.succeed (\nextFilter -> Parser.Loop (or lastFilter nextFilter)) diff --git a/tests/DatalogTests.elm b/tests/DatalogTests.elm index 44a16af..a88b888 100644 --- a/tests/DatalogTests.elm +++ b/tests/DatalogTests.elm @@ -12,14 +12,16 @@ datalogTests = [ describe "planRule" [ test "a simple read turns into a Read -> Project" <| \_ -> - rule "mortal" [ "who" ] - |> with "greek" [ var "who" ] + rule "mortal" + [ "who" ] + [ with "greek" [ var "who" ] ] |> planRule |> Expect.equal (Ok (Database.Project [ 0 ] (Database.Read "greek"))) , test "a filtered read turns into a Select" <| \_ -> - rule "mortal" [ "first name" ] - |> with "greek" [ var "first name", string "of Athens" ] + rule "mortal" + [ "first name" ] + [ with "greek" [ var "first name", string "of Athens" ] ] |> planRule |> Expect.equal (Database.Read "greek" @@ -29,24 +31,28 @@ datalogTests = ) , test "sharing a variable between two atoms results in a join" <| \_ -> - rule "reachable" [ "a", "c" ] - |> with "link" [ var "a", var "b" ] - |> with "reachable" [ var "b", var "c" ] + rule "reachable" + [ "a", "c" ] + [ with "link" [ var "a", var "b" ] + , with "reachable" [ var "b", var "c" ] + ] |> planRule |> Expect.equal (Database.JoinOn - { left = Database.Read "reachable" - , right = Database.Read "link" - , fields = [ ( 0, 1 ) ] + { left = Database.Read "link" + , right = Database.Read "reachable" + , fields = [ ( 1, 0 ) ] } - |> Database.Project [ 2, 1 ] + |> Database.Project [ 0, 3 ] |> Ok ) , test "filtering adds a predicate" <| \_ -> - rule "adult" [ "name" ] - |> with "person" [ var "name", var "age" ] - |> filter (gt "age" (int 20)) + rule "adult" + [ "name" ] + [ with "person" [ var "name", var "age" ] + , filter (gt "age" (int 20)) + ] |> planRule |> Expect.equal (Database.Read "person" @@ -56,10 +62,12 @@ datalogTests = ) , test "filtering using negation adds a predicate" <| \_ -> - rule "sibling" [ "a", "b" ] - |> with "parent" [ var "parent", var "a" ] - |> with "parent" [ var "parent", var "b" ] - |> filter (not_ (eq "a" (var "b"))) + rule "sibling" + [ "a", "b" ] + [ with "parent" [ var "parent", var "a" ] + , with "parent" [ var "parent", var "b" ] + , filter (not_ (eq "a" (var "b"))) + ] |> planRule |> Expect.equal (Database.JoinOn @@ -67,19 +75,21 @@ datalogTests = , right = Database.Read "parent" , fields = [ ( 0, 0 ) ] } - |> Database.Select (Database.Not (Database.Predicate 3 Database.Eq (Database.Field 1))) - |> Database.Project [ 3, 1 ] + |> Database.Select (Database.Not (Database.Predicate 1 Database.Eq (Database.Field 3))) + |> Database.Project [ 1, 3 ] |> Ok ) , test "filtering using or adds a predicate" <| \_ -> - rule "teen" [ "name" ] - |> with "person" [ var "name", var "age" ] - |> filter + rule "teen" + [ "name" ] + [ with "person" [ var "name", var "age" ] + , filter (or (gt "age" (int 12)) (lt "age" (int 20)) ) + ] |> planRule |> Expect.equal (Database.Read "person" @@ -93,24 +103,28 @@ datalogTests = ) , test "filtering using separate filters adds two filters" <| \_ -> - rule "oldHockeyTeam" [ "name" ] - |> with "team" [ var "name", var "league", var "age" ] - |> filter (eq "league" (string "NHL")) - |> filter (gt "age" (int 50)) + rule "oldHockeyTeam" + [ "name" ] + [ with "team" [ var "name", var "league", var "age" ] + , filter (eq "league" (string "NHL")) + , filter (gt "age" (int 50)) + ] |> planRule |> Expect.equal (Database.Read "team" - |> Database.Select (Database.Predicate 2 Database.Gt (Database.Constant (Database.Int 50))) |> Database.Select (Database.Predicate 1 Database.Eq (Database.Constant (Database.String "NHL"))) + |> Database.Select (Database.Predicate 2 Database.Gt (Database.Constant (Database.Int 50))) |> Database.Project [ 0 ] |> Ok ) , test "negation adds an outer join" <| \_ -> - rule "unreachable" [ "a", "b" ] - |> with "node" [ var "a" ] - |> with "node" [ var "b" ] - |> without "reachable" [ var "a", var "b" ] + rule "unreachable" + [ "a", "b" ] + [ with "node" [ var "a" ] + , with "node" [ var "b" ] + , without "reachable" [ var "a", var "b" ] + ] |> planRule |> Expect.equal (Database.OuterJoinOn @@ -121,47 +135,54 @@ datalogTests = , right = Database.Read "node" } , drop = Database.Read "reachable" - , fields = [ ( 1, 0 ), ( 0, 1 ) ] + , fields = [ ( 0, 0 ), ( 1, 1 ) ] } - |> Database.Project [ 1, 0 ] + |> Database.Project [ 0, 1 ] |> Ok ) , describe "safety" [ test "rules are required to have bodies" <| \_ -> - rule "noBody" [ "a" ] + rule "noBody" [ "a" ] [] |> planRule |> Expect.equal (Err NeedAtLeastOnePositiveAtom) , test "rules are required to have at least one name" <| \_ -> - rule "noNames" [] - |> with "what" [ var "a" ] + rule "noNames" + [] + [ with "what" [ var "a" ] ] |> planRule |> Expect.equal (Err NeedAtLeastOneName) , test "all terms in the head must appear in the body" <| \_ -> - rule "bad" [ "a", "b" ] - |> with "fine" [ var "a" ] + rule "bad" + [ "a", "b" ] + [ with "fine" [ var "a" ] ] |> planRule |> Expect.equal (Err (VariableDoesNotAppearInBody "b")) , test "you can't have just filters" <| \_ -> - rule "bad" [ "a" ] - |> filter (eq "a" (string "no")) + rule "bad" + [ "a" ] + [ filter (eq "a" (string "no")) ] |> planRule |> Expect.equal (Err NeedAtLeastOnePositiveAtom) , test "you can't filter on an unbound name" <| \_ -> - rule "bad" [ "a" ] - |> with "fine" [ var "a" ] - |> filter (eq "b" (string "no")) + rule "bad" + [ "a" ] + [ with "fine" [ var "a" ] + , filter (eq "b" (string "no")) + ] |> planRule |> Expect.equal (Err (VariableDoesNotAppearInBody "b")) , test "every name appearing in a negative atom must also appear in a positive atom" <| \_ -> - rule "bad" [ "a" ] - |> with "an atom to avoid the must-have-one-positive-atom rule" [ var "b" ] - |> without "here's the problem" [ var "a" ] + rule "bad" + [ "a" ] + [ with "an atom to avoid the must-have-one-positive-atom rule" [ var "b" ] + , without "here's the problem" [ var "a" ] + ] |> planRule |> Expect.equal (Err (VariableMustAppearInPositiveAtom "a")) ] @@ -242,8 +263,9 @@ datalogTests = |> insert "greek" [ string "Socrates" ] |> Result.andThen (derive - [ rule "mortal" [ "name" ] - |> with "greek" [ var "name" ] + [ rule "mortal" + [ "name" ] + [ with "greek" [ var "name" ] ] ] ) |> Result.andThen @@ -259,10 +281,12 @@ datalogTests = |> Result.andThen (insert "french" [ string "Simone de Beauvoir" ]) |> Result.andThen (derive - [ rule "mortal" [ "name" ] - |> with "greek" [ var "name" ] - , rule "mortal" [ "name" ] - |> with "french" [ var "name" ] + [ rule "mortal" + [ "name" ] + [ with "greek" [ var "name" ] ] + , rule "mortal" + [ "name" ] + [ with "french" [ var "name" ] ] ] ) |> Result.andThen @@ -278,8 +302,9 @@ datalogTests = |> Result.andThen (insert "person" [ string "Barbara Liskov", string "computer scientist" ]) |> Result.andThen (derive - [ rule "philosopher" [ "name" ] - |> with "person" [ var "name", string "philosopher" ] + [ rule "philosopher" + [ "name" ] + [ with "person" [ var "name", string "philosopher" ] ] ] ) |> Result.andThen @@ -297,11 +322,14 @@ datalogTests = |> Result.andThen (insert "link" [ int 3, int 4 ]) |> Result.andThen (derive - [ rule "reachable" [ "a", "b" ] - |> with "link" [ var "a", var "b" ] - , rule "reachable" [ "a", "c" ] - |> with "link" [ var "a", var "b" ] - |> with "reachable" [ var "b", var "c" ] + [ rule "reachable" + [ "a", "b" ] + [ with "link" [ var "a", var "b" ] ] + , rule "reachable" + [ "a", "c" ] + [ with "link" [ var "a", var "b" ] + , with "reachable" [ var "b", var "c" ] + ] ] ) |> Result.andThen @@ -332,9 +360,11 @@ datalogTests = |> Result.andThen (insert "team" [ string "Flyers", string "Philadelphia" ]) |> Result.andThen (derive - [ rule "hometown" [ "name", "hometown" ] - |> with "mascot" [ var "name", var "team" ] - |> with "team" [ var "team", var "hometown" ] + [ rule "hometown" + [ "name", "hometown" ] + [ with "mascot" [ var "name", var "team" ] + , with "team" [ var "team", var "hometown" ] + ] ] ) |> Result.andThen @@ -359,19 +389,26 @@ datalogTests = |> Result.andThen (insert "link" [ int 3, int 4 ]) |> Result.andThen (derive - [ rule "reachable" [ "a", "b" ] - |> with "link" [ var "a", var "b" ] - , rule "reachable" [ "a", "c" ] - |> with "link" [ var "a", var "b" ] - |> with "reachable" [ var "b", var "c" ] - , rule "node" [ "a" ] - |> with "link" [ var "a", var "b" ] - , rule "node" [ "b" ] - |> with "link" [ var "a", var "b" ] - , rule "unreachable" [ "a", "b" ] - |> with "node" [ var "a" ] - |> with "node" [ var "b" ] - |> without "reachable" [ var "a", var "b" ] + [ rule "reachable" + [ "a", "b" ] + [ with "link" [ var "a", var "b" ] ] + , rule "reachable" + [ "a", "c" ] + [ with "link" [ var "a", var "b" ] + , with "reachable" [ var "b", var "c" ] + ] + , rule "node" + [ "a" ] + [ with "link" [ var "a", var "b" ] ] + , rule "node" + [ "b" ] + [ with "link" [ var "a", var "b" ] ] + , rule "unreachable" + [ "a", "b" ] + [ with "node" [ var "a" ] + , with "node" [ var "b" ] + , without "reachable" [ var "a", var "b" ] + ] ] ) |> Result.andThen @@ -399,12 +436,16 @@ datalogTests = |> insert "x" [ string "this doesn't matter, we just need it to trigger the rule under test" ] |> Result.andThen (derive - [ rule "p" [ "x" ] - |> with "x" [ var "x" ] - |> without "q" [ var "x" ] - , rule "q" [ "x" ] - |> with "x" [ var "x" ] - |> without "p" [ var "x" ] + [ rule "p" + [ "x" ] + [ with "x" [ var "x" ] + , without "q" [ var "x" ] + ] + , rule "q" + [ "x" ] + [ with "x" [ var "x" ] + , without "p" [ var "x" ] + ] ] ) |> Expect.equal (Err CannotHaveNegationInRecursiveQuery) @@ -416,8 +457,9 @@ datalogTests = \_ -> expectParses "mortal(thing) :- greek(thing)." - [ rule "mortal" [ "thing" ] - |> with "greek" [ var "thing" ] + [ rule "mortal" + [ "thing" ] + [ with "greek" [ var "thing" ] ] ] , test "recursive rule" <| \_ -> @@ -426,18 +468,22 @@ datalogTests = reachable(a, b) :- link(a, b). reachable(a, c) :- link(a, b), reachable(b, c). """ - [ rule "reachable" [ "a", "b" ] - |> with "link" [ var "a", var "b" ] - , rule "reachable" [ "a", "c" ] - |> with "link" [ var "a", var "b" ] - |> with "reachable" [ var "b", var "c" ] + [ rule "reachable" + [ "a", "b" ] + [ with "link" [ var "a", var "b" ] ] + , rule "reachable" + [ "a", "c" ] + [ with "link" [ var "a", var "b" ] + , with "reachable" [ var "b", var "c" ] + ] ] , test "an atom with an string" <| \_ -> expectParses """baseballTeam(name) :- team(name, "MLB").""" - [ rule "baseballTeam" [ "name" ] - |> with "team" [ var "name", string "MLB" ] + [ rule "baseballTeam" + [ "name" ] + [ with "team" [ var "name", string "MLB" ] ] ] , test "an atom with an integer" <| \_ -> @@ -445,8 +491,9 @@ datalogTests = """ luckyBuilding(name) :- building(name, 7). """ - [ rule "luckyBuilding" [ "name" ] - |> with "building" [ var "name", int 7 ] + [ rule "luckyBuilding" + [ "name" ] + [ with "building" [ var "name", int 7 ] ] ] , describe "filters" [ test "less than" <| @@ -457,9 +504,11 @@ datalogTests = person(name, age), age < 18. """ - [ rule "child" [ "name" ] - |> with "person" [ var "name", var "age" ] - |> filter (lt "age" (int 18)) + [ rule "child" + [ "name" ] + [ with "person" [ var "name", var "age" ] + , filter (lt "age" (int 18)) + ] ] , test "greater than" <| \_ -> @@ -469,9 +518,11 @@ datalogTests = person(name, age), age > 17. """ - [ rule "adult" [ "name" ] - |> with "person" [ var "name", var "age" ] - |> filter (gt "age" (int 17)) + [ rule "adult" + [ "name" ] + [ with "person" [ var "name", var "age" ] + , filter (gt "age" (int 17)) + ] ] , test "equal to" <| \_ -> @@ -481,9 +532,11 @@ datalogTests = thing(name, legs), legs = 8. """ - [ rule "spider" [ "name" ] - |> with "thing" [ var "name", var "legs" ] - |> filter (eq "legs" (int 8)) + [ rule "spider" + [ "name" ] + [ with "thing" [ var "name", var "legs" ] + , filter (eq "legs" (int 8)) + ] ] , test "not equal to" <| \_ -> @@ -493,9 +546,11 @@ datalogTests = thing(name, legs), legs != 8. """ - [ rule "notASpider" [ "name" ] - |> with "thing" [ var "name", var "legs" ] - |> filter (not_ (eq "legs" (int 8))) + [ rule "notASpider" + [ "name" ] + [ with "thing" [ var "name", var "legs" ] + , filter (not_ (eq "legs" (int 8))) + ] ] , test "or" <| \_ -> @@ -505,13 +560,15 @@ datalogTests = person(name, age), age = 18 || age > 18. """ - [ rule "adult" [ "name" ] - |> with "person" [ var "name", var "age" ] - |> filter + [ rule "adult" + [ "name" ] + [ with "person" [ var "name", var "age" ] + , filter (or (eq "age" (int 18)) (gt "age" (int 18)) ) + ] ] , test "greater than or equal to" <| \_ -> @@ -521,13 +578,15 @@ datalogTests = person(name, age), age >= 18. """ - [ rule "adult" [ "name" ] - |> with "person" [ var "name", var "age" ] - |> filter + [ rule "adult" + [ "name" ] + [ with "person" [ var "name", var "age" ] + , filter (or (gt "age" (int 18)) (eq "age" (int 18)) ) + ] ] , test "less than or equal to" <| \_ -> @@ -537,13 +596,15 @@ datalogTests = person(name, age), age <= 17. """ - [ rule "child" [ "name" ] - |> with "person" [ var "name", var "age" ] - |> filter + [ rule "child" + [ "name" ] + [ with "person" [ var "name", var "age" ] + , filter (or (lt "age" (int 17)) (eq "age" (int 17)) ) + ] ] ] , test "rule with negation" <| @@ -555,10 +616,12 @@ datalogTests = node(b), not reachable(a, b). """ - [ rule "unreachable" [ "a", "b" ] - |> with "node" [ var "a" ] - |> with "node" [ var "b" ] - |> without "reachable" [ var "a", var "b" ] + [ rule "unreachable" + [ "a", "b" ] + [ with "node" [ var "a" ] + , with "node" [ var "b" ] + , without "reachable" [ var "a", var "b" ] + ] ] , test "comments" <| \_ -> @@ -570,9 +633,11 @@ datalogTests = age > 17 -- before close . -- after """ - [ rule "adult" [ "name" ] - |> with "person" [ var "name", var "age" ] - |> filter (gt "age" (int 17)) + [ rule "adult" + [ "name" ] + [ with "person" [ var "name", var "age" ] + , filter (gt "age" (int 17)) + ] ] ] ]