From 4aad207a384ee0f7d55cfcc4c12fd3a88ff383bd Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Apr 2021 20:30:15 -0500 Subject: [PATCH] clear out old project --- .envrc | 1 - .gitignore | 3 - Makefile | 6 - default.nix | 29 --- elm.json | 33 --- nix/elm-srcs.nix | 82 -------- nix/registry.dat | Bin 103387 -> 0 bytes nix/sources.json | 38 ---- nix/sources.nix | 174 ---------------- playground/elm.json | 36 ---- playground/src/Main.elm | 253 ---------------------- shell.nix | 23 -- solving-by-hand.md | 98 --------- src/Datalog.elm | 249 ---------------------- src/Datalog/Atom.elm | 143 ------------- src/Datalog/Negatable.elm | 40 ---- src/Datalog/Parser.elm | 381 ---------------------------------- src/Datalog/Rule.elm | 128 ------------ src/Datalog/Term.elm | 83 -------- tests/Datalog/AtomTests.elm | 219 ------------------- tests/Datalog/ParserTests.elm | 176 ---------------- tests/Datalog/RuleTests.elm | 73 ------- tests/Datalog/TermTests.elm | 17 -- tests/DatalogTests.elm | 249 ---------------------- 24 files changed, 2534 deletions(-) delete mode 100644 .envrc delete mode 100644 .gitignore delete mode 100644 Makefile delete mode 100644 default.nix delete mode 100644 elm.json delete mode 100644 nix/elm-srcs.nix delete mode 100644 nix/registry.dat delete mode 100644 nix/sources.json delete mode 100644 nix/sources.nix delete mode 100644 playground/elm.json delete mode 100644 playground/src/Main.elm delete mode 100644 shell.nix delete mode 100644 solving-by-hand.md delete mode 100644 src/Datalog.elm delete mode 100644 src/Datalog/Atom.elm delete mode 100644 src/Datalog/Negatable.elm delete mode 100644 src/Datalog/Parser.elm delete mode 100644 src/Datalog/Rule.elm delete mode 100644 src/Datalog/Term.elm delete mode 100644 tests/Datalog/AtomTests.elm delete mode 100644 tests/Datalog/ParserTests.elm delete mode 100644 tests/Datalog/RuleTests.elm delete mode 100644 tests/Datalog/TermTests.elm delete mode 100644 tests/DatalogTests.elm diff --git a/.envrc b/.envrc deleted file mode 100644 index 1d953f4..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use nix diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b23893f..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/result -elm-stuff -index.html diff --git a/Makefile b/Makefile deleted file mode 100644 index ff5b374..0000000 --- a/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -nix/elm-srcs.nix: playground/elm.json - cd playground && elm2nix convert > ../$@ - -nix/registry.dat: playground/elm.json - cd playground && elm2nix snapshot - mv playground/registry.dat $@ diff --git a/default.nix b/default.nix deleted file mode 100644 index 275ac7d..0000000 --- a/default.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ ... }: -let - sources = import ./nix/sources.nix; - pkgs = import sources.nixpkgs { }; - gitignore = import sources.gitignore { }; -in rec { - datalog = pkgs.stdenv.mkDerivation { - name = "datalog"; - src = gitignore.gitignoreSource ./.; - - buildInputs = [ pkgs.elmPackages.elm pkgs.elmPackages.elm-test ]; - buildPhase = pkgs.elmPackages.fetchElmDeps { - elmPackages = import ./nix/elm-srcs.nix; - elmVersion = "0.19.1"; - registryDat = ./nix/registry.dat; - }; - - doCheck = true; - checkPhase = '' - elm-test - ''; - - installPhase = '' - mkdir -p $out/share/datalog - cd playground - elm make --optimize --output $out/share/datalog/index.html src/Main.elm - ''; - }; -} diff --git a/elm.json b/elm.json deleted file mode 100644 index 9bd4206..0000000 --- a/elm.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "type": "application", - "source-directories": [ - "src" - ], - "elm-version": "0.19.1", - "dependencies": { - "direct": { - "elm/browser": "1.0.2", - "elm/core": "1.0.5", - "elm/html": "1.0.0", - "elm/parser": "1.1.0", - "elm-community/graph": "6.0.0", - "rtfeldman/elm-sorter-experiment": "2.1.1" - }, - "indirect": { - "avh4/elm-fifo": "1.0.4", - "elm/json": "1.1.3", - "elm/time": "1.0.0", - "elm/url": "1.0.0", - "elm/virtual-dom": "1.0.2", - "elm-community/intdict": "3.0.0" - } - }, - "test-dependencies": { - "direct": { - "elm-explorations/test": "1.2.2" - }, - "indirect": { - "elm/random": "1.0.0" - } - } -} diff --git a/nix/elm-srcs.nix b/nix/elm-srcs.nix deleted file mode 100644 index 5c41e9c..0000000 --- a/nix/elm-srcs.nix +++ /dev/null @@ -1,82 +0,0 @@ -{ - - "rtfeldman/elm-sorter-experiment" = { - sha256 = "1vvvsg2axss25f7ilwk2pyhznv026bq3kj2fr8h2107g1lbgyabq"; - version = "2.1.1"; - }; - - "elm/html" = { - sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k"; - version = "1.0.0"; - }; - - "elm/parser" = { - sha256 = "0a3cxrvbm7mwg9ykynhp7vjid58zsw03r63qxipxp3z09qks7512"; - version = "1.1.0"; - }; - - "elm/browser" = { - sha256 = "0nagb9ajacxbbg985r4k9h0jadqpp0gp84nm94kcgbr5sf8i9x13"; - version = "1.0.2"; - }; - - "rtfeldman/elm-css" = { - sha256 = "0nxiyxyw3kw55whkpwhrcgc0dr6a8zlm2nqvsaqdw6mzkykg0ba6"; - version = "16.1.0"; - }; - - "elm/core" = { - sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf"; - version = "1.0.5"; - }; - - "elm/url" = { - sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4"; - version = "1.0.0"; - }; - - "elm-community/graph" = { - sha256 = "1rwsq2126q0rb4vmy95ajxfm3m063d6lw0p90d510nzcrbm9bxbc"; - version = "6.0.0"; - }; - - "elm/json" = { - sha256 = "0kjwrz195z84kwywaxhhlnpl3p251qlbm5iz6byd6jky2crmyqyh"; - version = "1.1.3"; - }; - - "avh4/elm-fifo" = { - sha256 = "1ka0iz2psr75h4qz7hh5z1prclah1nais9aaycaxapfd7inqmrrc"; - version = "1.0.4"; - }; - - "rtfeldman/elm-hex" = { - sha256 = "1y0aa16asvwdqmgbskh5iba6psp43lkcjjw9mgzj3gsrg33lp00d"; - version = "1.0.0"; - }; - - "elm-community/intdict" = { - sha256 = "09i1fk63gp6sr6kc6ccs8g0kxvqhw5czghi9cl8flizanrgcmva1"; - version = "3.0.0"; - }; - - "elm/time" = { - sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1"; - version = "1.0.0"; - }; - - "elm/virtual-dom" = { - sha256 = "0q1v5gi4g336bzz1lgwpn5b1639lrn63d8y6k6pimcyismp2i1yg"; - version = "1.0.2"; - }; - - "elm-explorations/test" = { - sha256 = "1fsd7bajm7qa93r5pn3mdafqh3blpzya601jbs9l238p0hmvh576"; - version = "1.2.2"; - }; - - "elm/random" = { - sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl"; - version = "1.0.0"; - }; -} diff --git a/nix/registry.dat b/nix/registry.dat deleted file mode 100644 index 819aeb2d05cb8c8a8dfd4104b7623d015040c0bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103387 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⪼^v!rs#Y`tm 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 diff --git a/nix/sources.json b/nix/sources.json deleted file mode 100644 index 92b5a51..0000000 --- a/nix/sources.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "gitignore": { - "branch": "master", - "description": "Nix function for filtering local git sources", - "homepage": "", - "owner": "hercules-ci", - "repo": "gitignore", - "rev": "c4662e662462e7bf3c2a968483478a665d00e717", - "sha256": "1npnx0h6bd0d7ql93ka7azhj40zgjp815fw2r6smg8ch9p7mzdlx", - "type": "tarball", - "url": "https://github.com/hercules-ci/gitignore/archive/c4662e662462e7bf3c2a968483478a665d00e717.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - }, - "niv": { - "branch": "master", - "description": "Easy dependency management for Nix projects", - "homepage": "https://github.com/nmattia/niv", - "owner": "nmattia", - "repo": "niv", - "rev": "62fcf7d0859628f1834d84a7a0706ace0223c27e", - "sha256": "06ghvcsarvi32awxvgdxivaji8fsdhv46p49as8xx8whwia9d3rh", - "type": "tarball", - "url": "https://github.com/nmattia/niv/archive/62fcf7d0859628f1834d84a7a0706ace0223c27e.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - }, - "nixpkgs": { - "branch": "release-20.03", - "description": "Nix Packages collection", - "homepage": null, - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "929768261a3ede470eafb58d5b819e1a848aa8bf", - "sha256": "0zi54vbfi6i6i5hdd4v0l144y1c8rg6hq6818jjbbcnm182ygyfa", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/929768261a3ede470eafb58d5b819e1a848aa8bf.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - } -} diff --git a/nix/sources.nix b/nix/sources.nix deleted file mode 100644 index 1938409..0000000 --- a/nix/sources.nix +++ /dev/null @@ -1,174 +0,0 @@ -# This file has been generated by Niv. - -let - - # - # The fetchers. fetch_ fetches specs of type . - # - - fetch_file = pkgs: name: spec: - let - name' = sanitizeName name + "-src"; - in - if spec.builtin or true then - builtins_fetchurl { inherit (spec) url sha256; name = name'; } - else - pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; - - fetch_tarball = pkgs: name: spec: - let - name' = sanitizeName name + "-src"; - in - if spec.builtin or true then - builtins_fetchTarball { name = name'; inherit (spec) url sha256; } - else - pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; - - fetch_git = name: spec: - let - ref = - if spec ? ref then spec.ref else - if spec ? branch then "refs/heads/${spec.branch}" else - if spec ? tag then "refs/tags/${spec.tag}" else - abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; - in - builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; - - fetch_local = spec: spec.path; - - fetch_builtin-tarball = name: throw - ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. - $ niv modify ${name} -a type=tarball -a builtin=true''; - - fetch_builtin-url = name: throw - ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. - $ niv modify ${name} -a type=file -a builtin=true''; - - # - # Various helpers - # - - # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 - sanitizeName = name: - ( - concatMapStrings (s: if builtins.isList s then "-" else s) - ( - builtins.split "[^[:alnum:]+._?=-]+" - ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) - ) - ); - - # The set of packages used when specs are fetched using non-builtins. - mkPkgs = sources: system: - let - sourcesNixpkgs = - import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; - hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; - hasThisAsNixpkgsPath = == ./.; - in - if builtins.hasAttr "nixpkgs" sources - then sourcesNixpkgs - else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then - import {} - else - abort - '' - Please specify either (through -I or NIX_PATH=nixpkgs=...) or - add a package called "nixpkgs" to your sources.json. - ''; - - # The actual fetching function. - fetch = pkgs: name: spec: - - if ! builtins.hasAttr "type" spec then - abort "ERROR: niv spec ${name} does not have a 'type' attribute" - else if spec.type == "file" then fetch_file pkgs name spec - else if spec.type == "tarball" then fetch_tarball pkgs name spec - else if spec.type == "git" then fetch_git name spec - else if spec.type == "local" then fetch_local spec - else if spec.type == "builtin-tarball" then fetch_builtin-tarball name - else if spec.type == "builtin-url" then fetch_builtin-url name - else - abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; - - # If the environment variable NIV_OVERRIDE_${name} is set, then use - # the path directly as opposed to the fetched source. - replace = name: drv: - let - saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; - ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; - in - if ersatz == "" then drv else - # this turns the string into an actual Nix path (for both absolute and - # relative paths) - if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; - - # Ports of functions for older nix versions - - # a Nix version of mapAttrs if the built-in doesn't exist - mapAttrs = builtins.mapAttrs or ( - f: set: with builtins; - listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) - ); - - # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 - range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); - - # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 - stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); - - # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 - stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); - concatMapStrings = f: list: concatStrings (map f list); - concatStrings = builtins.concatStringsSep ""; - - # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 - optionalAttrs = cond: as: if cond then as else {}; - - # fetchTarball version that is compatible between all the versions of Nix - builtins_fetchTarball = { url, name ? null, sha256 }@attrs: - let - inherit (builtins) lessThan nixVersion fetchTarball; - in - if lessThan nixVersion "1.12" then - fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) - else - fetchTarball attrs; - - # fetchurl version that is compatible between all the versions of Nix - builtins_fetchurl = { url, name ? null, sha256 }@attrs: - let - inherit (builtins) lessThan nixVersion fetchurl; - in - if lessThan nixVersion "1.12" then - fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) - else - fetchurl attrs; - - # Create the final "sources" from the config - mkSources = config: - mapAttrs ( - name: spec: - if builtins.hasAttr "outPath" spec - then abort - "The values in sources.json should not have an 'outPath' attribute" - else - spec // { outPath = replace name (fetch config.pkgs name spec); } - ) config.sources; - - # The "config" used by the fetchers - mkConfig = - { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null - , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) - , system ? builtins.currentSystem - , pkgs ? mkPkgs sources system - }: rec { - # The sources, i.e. the attribute set of spec name to spec - inherit sources; - - # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers - inherit pkgs; - }; - -in -mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/playground/elm.json b/playground/elm.json deleted file mode 100644 index 6580b5b..0000000 --- a/playground/elm.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "type": "application", - "source-directories": [ - "src", - "../src" - ], - "elm-version": "0.19.1", - "dependencies": { - "direct": { - "elm/browser": "1.0.2", - "elm/core": "1.0.5", - "elm/html": "1.0.0", - "elm/parser": "1.1.0", - "elm/url": "1.0.0", - "elm-community/graph": "6.0.0", - "rtfeldman/elm-css": "16.1.0", - "rtfeldman/elm-sorter-experiment": "2.1.1" - }, - "indirect": { - "avh4/elm-fifo": "1.0.4", - "elm/json": "1.1.3", - "elm/time": "1.0.0", - "elm/virtual-dom": "1.0.2", - "elm-community/intdict": "3.0.0", - "rtfeldman/elm-hex": "1.0.0" - } - }, - "test-dependencies": { - "direct": { - "elm-explorations/test": "1.2.2" - }, - "indirect": { - "elm/random": "1.0.0" - } - } -} diff --git a/playground/src/Main.elm b/playground/src/Main.elm deleted file mode 100644 index 43cf266..0000000 --- a/playground/src/Main.elm +++ /dev/null @@ -1,253 +0,0 @@ -module Main exposing (..) - -import Browser -import Browser.Navigation as Navigation exposing (Key) -import Css -import Css.Global -import Datalog -import Datalog.Atom as Atom -import Datalog.Parser -import Dict -import Html.Styled as Html exposing (Html) -import Html.Styled.Attributes as Attributes exposing (css) -import Html.Styled.Events as Events -import Url exposing (Url) -import Url.Builder -import Url.Parser -import Url.Parser.Query - - -type Evaluation - = Blank - | Error (List String) - | Unsolved Datalog.Program - | Solved Datalog.Database - - -type alias Model = - { source : String - , evaluation : Evaluation - , autoSolve : Bool - - -- routing stuff - , key : Key - , originalPath : List String - } - - -type Msg - = NewSource String - | SetAutoSolve Bool - | Solve - | Save - | OnUrlChange Url - | OnUrlRequest Browser.UrlRequest - - -init : () -> Url -> Key -> ( Model, Cmd Msg ) -init _ url key = - let - initialSource = - sourceFromUrl url - in - ( { source = initialSource - , evaluation = - case Datalog.Parser.parse initialSource of - Ok program -> - Solved (Datalog.solve program) - - Err errs -> - Error errs - , autoSolve = True - , key = key - , originalPath = - url.path - |> String.split "/" - |> List.filter ((/=) "") - } - , Cmd.none - ) - - -sourceFromUrl : Url -> String -sourceFromUrl url = - -- https://github.com/elm/url/issues/17 - { url | path = "" } - |> Url.Parser.parse (Url.Parser.query (Url.Parser.Query.string "program")) - |> Maybe.andThen identity - |> Maybe.withDefault "" - - -update : Msg -> Model -> ( Model, Cmd Msg ) -update msg model = - case msg of - NewSource source -> - ( { model - | source = source - , evaluation = - case Datalog.Parser.parse source of - Err problems -> - Error problems - - Ok program -> - if model.autoSolve then - Solved (Datalog.solve program) - - else - Unsolved program - } - , Cmd.none - ) - - Save -> - ( model - , Navigation.pushUrl model.key - (Url.Builder.absolute - model.originalPath - [ Url.Builder.string "program" model.source ] - ) - ) - - SetAutoSolve True -> - ( { model - | autoSolve = True - , evaluation = - case model.evaluation of - Unsolved program -> - Solved (Datalog.solve program) - - _ -> - model.evaluation - } - , Cmd.none - ) - - SetAutoSolve False -> - ( { model | autoSolve = False }, Cmd.none ) - - Solve -> - ( { model - | evaluation = - case model.evaluation of - Unsolved program -> - Solved (Datalog.solve program) - - _ -> - model.evaluation - } - , Cmd.none - ) - - OnUrlChange url -> - update - (NewSource (sourceFromUrl url)) - { model - | originalPath = - url.path - |> String.split "/" - |> List.filter ((/=) "") - } - - OnUrlRequest (Browser.External url) -> - ( model, Navigation.load url ) - - OnUrlRequest (Browser.Internal _) -> - ( { model | source = "I got an internal URL request but I'm not set up to handle those." }, Cmd.none ) - - -view : Model -> Browser.Document Msg -view model = - { title = "Datalog Time!" - , body = - [ Css.Global.global [ Css.Global.html [ Css.backgroundColor (Css.hex "B0E0E6") ] ] - |> Html.toUnstyled - , Html.main_ - [ css - [ Css.maxWidth (Css.px 800) - , Css.minHeight (Css.vh 100) - , Css.margin2 Css.zero Css.auto - , Css.padding (Css.px 20) - , Css.boxSizing Css.borderBox - , Css.boxShadow5 Css.zero Css.zero (Css.px 10) (Css.px 1) (Css.rgba 0 0 0 0.25) - , Css.fontFamily Css.sansSerif - , Css.backgroundColor (Css.hex "FFF") - ] - ] - [ Html.h1 [] - [ Html.text "Datalog Time! (" - , Html.a [ Attributes.href "https://git.bytes.zone/brian/bad-datalog" ] [ Html.text "source" ] - , Html.text ")" - ] - , Html.label [] - [ Html.input - [ Attributes.type_ "checkbox" - , Attributes.checked model.autoSolve - , Events.onCheck SetAutoSolve - ] - [] - , Html.text "automatically solve" - ] - , Html.button - [ Events.onClick Save ] - [ Html.text "Save" ] - , Html.textarea - [ Events.onInput NewSource - , Attributes.value model.source - , css - [ Css.width (Css.pct 100) - , Css.height (Css.px 300) - , Css.padding (Css.px 20) - , Css.boxSizing Css.borderBox - , Css.border3 (Css.px 1) Css.solid (Css.hex "AAA") - , Css.borderRadius (Css.px 10) - , Css.fontFamily Css.monospace - ] - ] - [] - , case model.evaluation of - Blank -> - Html.p [] [ Html.text "Enter a program above!" ] - - Error errors -> - Html.ul [] - (List.map - (\err -> Html.li [] [ Html.pre [] [ Html.text err ] ]) - errors - ) - - Unsolved program -> - Html.p [] - [ Html.text "Sweet, it parses! Now " - , Html.button [ Events.onClick Solve ] [ Html.text "solve" ] - , Html.text " this bad boy!" - ] - - Solved database -> - Html.dl [] - (Dict.foldr - (\( name, _ ) ( first, rest ) soFar -> - Html.dt [] [ Html.pre [] [ Html.text name ] ] - :: List.map - (\atom -> Html.dd [] [ Html.pre [] [ Html.text (Atom.toString atom) ] ]) - (first :: rest) - ++ soFar - ) - [] - database - ) - ] - |> Html.toUnstyled - ] - } - - -main : Program () Model Msg -main = - Browser.application - { init = init - , update = update - , view = view - , onUrlChange = OnUrlChange - , onUrlRequest = OnUrlRequest - , subscriptions = \_ -> Sub.none - } diff --git a/shell.nix b/shell.nix deleted file mode 100644 index fa8d245..0000000 --- a/shell.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ ... }: -let - sources = import ./nix/sources.nix; - - nixpkgs = import sources.nixpkgs { }; - - niv = import sources.niv { }; -in with nixpkgs; -stdenv.mkDerivation { - name = "bad-datalog"; - buildInputs = [ - niv.niv - git - gnumake - - # elm - elmPackages.elm - elmPackages.elm-format - elmPackages.elm-test - elmPackages.elm-live - elm2nix - ]; -} diff --git a/solving-by-hand.md b/solving-by-hand.md deleted file mode 100644 index 544d441..0000000 --- a/solving-by-hand.md +++ /dev/null @@ -1,98 +0,0 @@ -Say I had the following program computing which parts of a graph were reachable from `a`: - -``` -link(a, b) -link(b, c) - -reachable(From, To) :- link(From, To) -reachable(From, To) :- link(From, Middle), reachable(Middle, To) - -query(To) :- reachable(a, To) -``` - -How would I solve this? - -I guess I'd look at the rules as a first pass. Anything that doesn't have a body I'd just hold on to. - -Then I guess I'd look at the first definition of `reachable`... - -``` -reachable(From, To) :- link(From, Middle) -``` - -I'd see what `link` rules I had. All of these will unify, so I guess I'd have a bunch of substitutions that looked like this: - -``` -From: a, To: b -From: b, To: c -``` - -I'd apply my substitutions to `reachable` to get two more rules: - -``` -reachable(a, b) -reachable(b, c) -``` - -My old implementation just kind of hangs onto those things until the next iteration. I'm not sure why they can't enter the IDB immediately. - ---- - -Moving on to the second definition of `reachable`: - -``` -reachable(From, To) :- link(From, Middle), reachable(Middle, To) -``` - -First I'm looking at `link` again. I already found that this gives me the following substitutions: - -``` -From: a, Middle: b -From: b, Middle: c -``` - -Now for the recursive call to `reachable`. Since I already have substitutions, I can make the following partial replacements for the atom: - -``` -reachable(b, To) -reachable(c, To) -``` - -Now for each of those, I'll try to unify with all the facts I know about `reachable`. To start with, I don't know anything so this unification fails (which is not the implementation I may have arrived at by default, so I'm glad I'm thinking this through.) - -Assuming the opposite (e.g. because I'm in the next iteration or whatever) I can unify with each fact. For the first partial derivation above, that looks like: - -``` -reachable(b, To) + reachable(a, b) -> doesn't unify -reachable(b, To) + reachable(b, c) -> From: a, Middle: b, To: c -``` - -For the other, it looks like: - -``` -reachable(c, To) + reachable(a, b) -> doesn't unify -reachable(c, To) + reachable(b, c) -> doesn't unify -``` - -So I get one sucessful unification. That's all the rules here, so I can do substitution on the head to get: - -``` -reachable(a, c) -``` - ---- - -Now on to `query`... - -``` -query(To) :- reachable(a, To) -``` - -Assuming that I have all the previously-derived facts, I've just got to look up substituions for `reachable` and then apply them to `query`. So I get: - -``` -query(b) -query(c) -``` - -Which is exactly what I want! diff --git a/src/Datalog.elm b/src/Datalog.elm deleted file mode 100644 index 78b31d2..0000000 --- a/src/Datalog.elm +++ /dev/null @@ -1,249 +0,0 @@ -module Datalog exposing (Database, Problem(..), Program, program, solve) - -import Datalog.Atom as Atom exposing (Atom, Substitutions) -import Datalog.Negatable as Negatable exposing (Direction(..), Negatable(..)) -import Datalog.Rule as Rule exposing (Rule) -import Datalog.Term as Term exposing (Term(..)) -import Dict exposing (Dict) -import Graph - - -type Program - = Program (List (List Rule)) - - -program : List Rule -> Result Problem Program -program rules = - Result.map Program (stratify rules) - - -type Problem - = CycleWithNegation - - - --- STRATIFICATION - - -stratify : List Rule -> Result Problem (List (List Rule)) -stratify rules = - -- some future version of this function may want to create the entire graph - -- in a single pass over the rules. That'd be fine, of course, and faster, - -- but potentially way harder to work with. Keep it simple for now! - let - namesToIds = - rules - |> List.concatMap - (\rule -> - Atom.key (Rule.head rule) - :: List.map - (\atom -> Atom.key (Negatable.value atom)) - (Rule.body rule) - ) - |> List.foldl - (\key soFar -> - Dict.update key - (\value -> - case value of - Just id -> - Just id - - Nothing -> - Just (Dict.size soFar) - ) - soFar - ) - Dict.empty - - nodes = - Dict.foldl - (\key id soFar -> - Graph.Node id key :: soFar - ) - [] - namesToIds - - edges = - rules - |> List.concatMap - (\rule -> - let - headId = - namesToIds - |> Dict.get (Atom.key (Rule.head rule)) - |> Maybe.withDefault -1 - in - List.map - (\(Negatable direction bodyAtom) -> - Graph.Edge - (namesToIds - |> Dict.get (Atom.key bodyAtom) - |> Maybe.withDefault -1 - ) - headId - direction - ) - (Rule.body rule) - ) - - precedenceGraph = - Graph.fromNodesAndEdges nodes edges - - rulesByName = - List.foldr - (\rule soFar -> - Dict.update (Atom.key (Rule.head rule)) - (\maybeRules -> - case maybeRules of - Nothing -> - Just [ rule ] - - Just already -> - Just (rule :: already) - ) - soFar - ) - Dict.empty - rules - - scc = - Graph.stronglyConnectedComponents precedenceGraph - |> Result.mapError (List.map Graph.edges) - in - case Graph.stronglyConnectedComponents precedenceGraph of - -- pretty unlikely, but ok fine we'll handle it - Ok acyclic -> - Ok [ rules ] - - Err condensation -> - if List.any (Graph.edges >> List.map .label >> List.member Negative) condensation then - Err CycleWithNegation - - else - condensation - |> List.concatMap - (Graph.nodes - >> List.filterMap (\{ label } -> Dict.get label rulesByName |> Maybe.map (Tuple.pair label)) - >> Dict.fromList - >> Dict.values - ) - |> Ok - - - --- EVALUATION - - -{-| This is cheating a bit. A database is only ground atoms--that is, atoms -whose terms are all constants. --} -type alias Database = - Dict ( String, Int ) ( Atom, List Atom ) - - -insertAtom : Atom -> Database -> Database -insertAtom atom database = - Dict.update (Atom.key atom) - (\maybeExisting -> - case maybeExisting of - Just ( first, rest ) -> - if atom == first || List.member atom rest then - Just ( first, rest ) - - else - Just ( atom, first :: rest ) - - Nothing -> - Just ( atom, [] ) - ) - database - - -solve : Program -> Database -solve (Program rules) = - List.foldl solveHelp Dict.empty rules - - -solveHelp : List Rule -> Database -> Database -solveHelp rules database = - let - expanded = - List.foldl evaluateRule database rules - in - if expanded == database then - database - - else - solveHelp rules expanded - - -evaluateRule : Rule -> Database -> Database -evaluateRule rule database = - if Rule.isFact rule then - insertAtom (Rule.head rule) database - - else - Rule.body rule - |> List.foldl - (\bodyAtom substitutions -> - List.concatMap - (evaluateAtom database bodyAtom) - substitutions - ) - [ Atom.emptySubstitutions ] - |> List.foldl - (\substitution dbProgress -> - let - possiblyGround = - Atom.substitute (Rule.head rule) substitution - in - if Atom.isGround possiblyGround then - insertAtom possiblyGround dbProgress - - else - dbProgress - ) - database - - -evaluateAtom : Database -> Negatable Atom -> Substitutions -> List Substitutions -evaluateAtom database negatableAtom substitutions = - let - bound = - Negatable.map (\atom -> Atom.substitute atom substitutions) negatableAtom - in - case Dict.get (Atom.key (Negatable.value bound)) database of - Nothing -> - [] - - Just ( first, rest ) -> - evaluateAtomHelp bound substitutions (first :: rest) [] - - -evaluateAtomHelp : Negatable Atom -> Substitutions -> List Atom -> List Substitutions -> List Substitutions -evaluateAtomHelp bound substitutions facts soFar = - case facts of - [] -> - soFar - - fact :: rest -> - case Negatable.map (Atom.unify fact) bound of - Negatable Positive (Just outcome) -> - evaluateAtomHelp - bound - substitutions - rest - (Atom.mergeSubstitutions substitutions outcome :: soFar) - - Negatable Positive Nothing -> - evaluateAtomHelp bound substitutions rest soFar - - Negatable Negative (Just _) -> - [] - - Negatable Negative Nothing -> - evaluateAtomHelp - bound - substitutions - rest - (substitutions :: soFar) diff --git a/src/Datalog/Atom.elm b/src/Datalog/Atom.elm deleted file mode 100644 index 50b82eb..0000000 --- a/src/Datalog/Atom.elm +++ /dev/null @@ -1,143 +0,0 @@ -module Datalog.Atom exposing - ( Atom, atom, key, isGround, variables - , Substitutions, emptySubstitutions, unify, substitute, mergeSubstitutions - , toString - ) - -{-| - -@docs Atom, atom, key, isGround, variables - -@docs Substitutions, emptySubstitutions, unify, substitute, mergeSubstitutions - -@docs toString - --} - -import Datalog.Term as Term exposing (Term(..)) -import Sort.Dict as Dict exposing (Dict) - - -type Atom - = Atom String Int (List Term) - - -atom : String -> List Term -> Atom -atom name terms = - Atom name (List.length terms) terms - - -{-| TODO: this is useful but kind of internal. Should it move somewhere else, -or split into `name` and `size`? --} -key : Atom -> ( String, Int ) -key (Atom name arity _) = - ( name, arity ) - - -isGround : Atom -> Bool -isGround (Atom _ _ terms) = - not (List.isEmpty terms) && List.all Term.isGround terms - - -variables : Atom -> List Term.Variable -variables (Atom _ _ terms) = - List.filterMap - (\term -> - case term of - Term.Constant _ -> - Nothing - - Term.Variable var -> - Just var - ) - terms - - -type alias Substitutions = - Dict Term.Variable Term.Constant - - -emptySubstitutions : Substitutions -emptySubstitutions = - Dict.empty Term.variableSorter - - -unify : Atom -> Atom -> Maybe Substitutions -unify (Atom aName aArity aTerms) (Atom bName bArity bTerms) = - if aName == bName && aArity == bArity then - unifyHelp (List.map2 Tuple.pair aTerms bTerms) emptySubstitutions - - else - Nothing - - -unifyHelp : List ( Term, Term ) -> Substitutions -> Maybe Substitutions -unifyHelp termPairs substitutions = - let - variableToConstant : Term.Variable -> Term.Constant -> List ( Term, Term ) -> Maybe Substitutions - variableToConstant var constant rest = - if Term.isAnonymous var then - unifyHelp rest substitutions - - else - case Dict.get var substitutions of - Nothing -> - unifyHelp rest (Dict.insert var constant substitutions) - - Just alreadyBound -> - if alreadyBound == constant then - unifyHelp rest substitutions - - else - Nothing - in - case termPairs of - [] -> - Just substitutions - - ( Constant a, Constant b ) :: rest -> - if a == b then - unifyHelp rest substitutions - - else - Nothing - - ( Variable _, Variable _ ) :: rest -> - unifyHelp rest substitutions - - ( Variable var, Constant constant ) :: rest -> - variableToConstant var constant rest - - ( Constant constant, Variable var ) :: rest -> - variableToConstant var constant rest - - -substitute : Atom -> Substitutions -> Atom -substitute (Atom name arity terms) substitutions = - terms - |> List.map - (\term -> - case term of - Constant _ -> - term - - Variable var -> - case Dict.get var substitutions of - Just boundTerm -> - Constant boundTerm - - Nothing -> - term - ) - |> Atom name arity - - -mergeSubstitutions : Substitutions -> Substitutions -> Substitutions -mergeSubstitutions a b = - Dict.insertAll a b - - -toString : Atom -> String -toString (Atom name _ terms) = - name ++ "(" ++ String.join ", " (List.map Term.toString terms) ++ ")" diff --git a/src/Datalog/Negatable.elm b/src/Datalog/Negatable.elm deleted file mode 100644 index f3ff2c0..0000000 --- a/src/Datalog/Negatable.elm +++ /dev/null @@ -1,40 +0,0 @@ -module Datalog.Negatable exposing - ( Negatable(..), Direction(..), positive, negative - , value, map - ) - -{-| - -@docs Negatable, Direction, positive, negative -@docs value, map - --} - - -type Negatable a - = Negatable Direction a - - -type Direction - = Positive - | Negative - - -positive : a -> Negatable a -positive = - Negatable Positive - - -negative : a -> Negatable a -negative = - Negatable Negative - - -value : Negatable a -> a -value (Negatable _ a) = - a - - -map : (a -> b) -> Negatable a -> Negatable b -map fn (Negatable direction_ a) = - Negatable direction_ (fn a) diff --git a/src/Datalog/Parser.elm b/src/Datalog/Parser.elm deleted file mode 100644 index 098b434..0000000 --- a/src/Datalog/Parser.elm +++ /dev/null @@ -1,381 +0,0 @@ -module Datalog.Parser exposing (parse) - -import Datalog exposing (Program, program) -import Datalog.Atom as Atom exposing (Atom) -import Datalog.Negatable as Negatable exposing (Negatable) -import Datalog.Rule as Rule exposing (Rule) -import Datalog.Term as Term exposing (Term) -import Dict -import Parser.Advanced as Parser exposing ((|.), (|=), Parser) -import Set - - -parse : String -> Result (List String) Datalog.Program -parse source = - source - |> Parser.run parser - |> Result.mapError (niceErrors source) - - -niceErrors : String -> List (Parser.DeadEnd Context Problem) -> List String -niceErrors source deadEnds = - deadEnds - |> List.foldr - (\deadEnd groups -> - Dict.update - ( deadEndSpan deadEnd, niceContextStack deadEnd.contextStack ) - (\maybeExisting -> - case maybeExisting of - Just ( first, rest ) -> - Just ( deadEnd, first.problem :: rest ) - - Nothing -> - Just ( deadEnd, [] ) - ) - groups - ) - Dict.empty - |> Dict.values - |> List.map (niceError source) - - -deadEndSpan : Parser.DeadEnd Context Problem -> ( ( Int, Int ), ( Int, Int ) ) -deadEndSpan deadEnd = - let - locations = - ( deadEnd.row, deadEnd.col ) - :: List.map (\{ row, col } -> ( row, col )) deadEnd.contextStack - in - Maybe.map2 Tuple.pair - (List.minimum locations) - (List.maximum locations) - |> Maybe.withDefault ( ( 0, 0 ), ( 0, 0 ) ) - - -niceContextStack : List { otherStuff | context : Context } -> String -niceContextStack = - List.foldr - (\before after -> - niceContext before.context - ++ (if after == "" then - after - - else - " inside " ++ after - ) - ) - "" - - -niceError : String -> ( Parser.DeadEnd Context Problem, List Problem ) -> String -niceError source ( deadEnd, moreProblems ) = - let - ( ( startRow, _ ), ( endRow, problemCol ) ) = - deadEndSpan deadEnd - - lines = - String.lines source - |> List.drop (startRow - 1) - |> List.take (endRow - startRow + 1) - |> String.join "\n" - - pointer = - List.concat - [ List.repeat (problemCol - 1) ' ' - , [ '^' ] - ] - |> String.fromList - - problems = - case deadEnd.problem :: moreProblems of - [] -> - "This shouldn't be possible, and indicates a bug. Ask for help!" - - [ only ] -> - "I was expecting " ++ niceProblem only - - many -> - many - |> List.map (\problem -> " - " ++ niceProblem problem) - |> String.join "\n" - |> (++) "I was expecting one of these things:\n\n" - in - "I ran into a problem while parsing " - ++ niceContextStack deadEnd.contextStack - ++ " at line " - ++ String.fromInt endRow - ++ ", column " - ++ String.fromInt problemCol - ++ ":\n\n" - ++ lines - ++ "\n" - ++ pointer - ++ "\n\n" - ++ problems - - -type Context - = Rule - | Atom (Maybe String) - - -niceContext : Context -> String -niceContext context = - case context of - Rule -> - "a rule" - - Atom Nothing -> - "an atom" - - Atom (Just name) -> - "an atom named " ++ name - - -type Problem - = ExpectingAtomName - | ExpectingStartOfTerms - | ExpectingEndOfTerms - | ExpectingComma - | ExpectingOpeningQuote - | ExpectingClosingQuote - | ExpectingNumber - | InvalidNumber - | ExpectingVariable - | ExpectingImplies - | ExpectingNewline - | ExpectingEnd - | ExpectingPeriod - | ExpectingUnderscore - | ExpectingNot - | ExpectingComment - | InvalidRule Rule.Problem - | InvalidProgram Datalog.Problem - - -niceProblem : Problem -> String -niceProblem problem = - case problem of - ExpectingAtomName -> - "an atom name" - - ExpectingStartOfTerms -> - "an opening parenthesis to start the list of terms in the atom" - - ExpectingEndOfTerms -> - "a closing parenthesis to end the list of terms in the atom" - - ExpectingComma -> - "a comma" - - ExpectingOpeningQuote -> - "a quote (`\"`) to start a constant term" - - ExpectingClosingQuote -> - "a closing quote (`\"`) to end this constant term" - - ExpectingNumber -> - "a number for an integer term" - - InvalidNumber -> - "an integer like 1234 (no floats, hex, octal, etc.)" - - ExpectingVariable -> - "a variable (a name starting with a letter and continuing on with alphanumeric characters)" - - ExpectingImplies -> - "a `:-` followed by a rule body" - - ExpectingNewline -> - "a newline" - - ExpectingEnd -> - "the end of the program" - - ExpectingPeriod -> - "a period to end a rule" - - ExpectingUnderscore -> - "an underscore for an anonymous variable" - - ExpectingNot -> - "a 'not' to negate an atom in the rule body" - - ExpectingComment -> - "a line comment starting with '--'" - - InvalidRule Rule.NotRangeRestricted -> - "a rule, which must use all the variables from the head in the body" - - InvalidRule Rule.UnnamedHeadVariable -> - "a rule, which may not use anonymous variables in the head" - - InvalidRule Rule.VariableAppearsNegatedButNotPositive -> - "a rule, which may not contain atoms that appear in a negated expression without appearing in a positive one" - - InvalidProgram Datalog.CycleWithNegation -> - -- TODO: wow, awkward way to phase this. Make it better! - "a program, which may not contain cycles including negation" - - -parser : Parser Context Problem Program -parser = - Parser.succeed identity - |. spacesOrComment - |= Parser.loop [] rules - |> Parser.andThen - (\parsedRules -> - case program parsedRules of - Ok program_ -> - Parser.succeed program_ - - Err problem -> - Parser.problem (InvalidProgram problem) - ) - - -rules : List Rule -> Parser Context Problem (Parser.Step (List Rule) (List Rule)) -rules soFar = - Parser.oneOf - [ Parser.succeed (\rule_ -> Parser.Loop (rule_ :: soFar)) - |= rule - |. spacesOrComment - , Parser.end ExpectingEnd - |> Parser.map (\() -> Parser.Done (List.reverse soFar)) - ] - - -rule : Parser Context Problem Rule -rule = - (Parser.succeed Rule.rule - |= atom - |= Parser.oneOf - [ Parser.succeed (::) - |. spacesOrComment - |. Parser.token (Parser.Token ":-" ExpectingImplies) - |. spacesOrComment - |= bodyAtom - |= Parser.loop [] ruleTail - , Parser.succeed [] - |. Parser.token (Parser.Token "." ExpectingPeriod) - ] - ) - |> Parser.andThen - (\ruleResult -> - case ruleResult of - Ok validRule -> - Parser.succeed validRule - - Err err -> - Parser.problem (InvalidRule err) - ) - |> Parser.inContext Rule - - -ruleTail : List (Negatable Atom) -> Parser Context Problem (Parser.Step (List (Negatable Atom)) (List (Negatable Atom))) -ruleTail soFar = - Parser.oneOf - [ Parser.succeed (\atom_ -> Parser.Loop (atom_ :: soFar)) - |. spacesOrComment - |. Parser.token (Parser.Token "," ExpectingComma) - |. spacesOrComment - |= bodyAtom - , Parser.map (\() -> Parser.Done (List.reverse soFar)) - (Parser.token (Parser.Token "." ExpectingPeriod)) - ] - - -bodyAtom : Parser Context Problem (Negatable Atom) -bodyAtom = - Parser.succeed (\negator atom_ -> negator atom_) - |= Parser.oneOf - [ Parser.succeed Negatable.negative - |. Parser.token (Parser.Token "not " ExpectingNot) - , Parser.succeed Negatable.positive - ] - |= atom - - -atom : Parser Context Problem Atom -atom = - let - atomName = - Parser.variable - { start = Char.isLower - , inner = Char.isAlphaNum - , reserved = Set.empty - , expecting = ExpectingAtomName - } - - atomTerms = - Parser.sequence - { start = Parser.Token "(" ExpectingStartOfTerms - , separator = Parser.Token "," ExpectingComma - , end = Parser.Token ")" ExpectingEndOfTerms - , spaces = spacesOrComment - , item = term - , trailing = Parser.Forbidden - } - in - Parser.inContext (Atom Nothing) atomName - |> Parser.andThen - (\name -> - Parser.inContext (Atom (Just name)) <| - Parser.succeed Atom.atom - |= Parser.succeed name - |. spacesOrComment - |= atomTerms - ) - - -term : Parser Context Problem Term -term = - Parser.oneOf [ variable, string, int, anonymous ] - - -string : Parser Context Problem Term -string = - Parser.succeed (Term.Constant << Term.String) - |. Parser.token (Parser.Token "\"" ExpectingOpeningQuote) - |= Parser.getChompedString (Parser.chompWhile (\c -> c /= '"')) - |. Parser.token (Parser.Token "\"" ExpectingClosingQuote) - - -int : Parser Context Problem Term -int = - Parser.int ExpectingNumber InvalidNumber - |> Parser.map (Term.Constant << Term.Int) - - -variable : Parser Context Problem Term -variable = - Parser.variable - { start = Char.isAlpha - , inner = Char.isAlphaNum - , reserved = Set.empty - , expecting = ExpectingVariable - } - |> Parser.map (Term.Variable << Term.Named) - - -anonymous : Parser Context Problem Term -anonymous = - Parser.succeed Term.anonymous - |. Parser.token (Parser.Token "_" ExpectingUnderscore) - - -spacesOrComment : Parser Context Problem () -spacesOrComment = - spaces - |. Parser.oneOf [ lineComment, Parser.succeed () ] - |. spaces - - -spaces : Parser Context Problem () -spaces = - Parser.chompWhile (\c -> c == ' ' || c == '\n') - - -lineComment : Parser Context Problem () -lineComment = - Parser.lineComment (Parser.Token "--" ExpectingComment) diff --git a/src/Datalog/Rule.elm b/src/Datalog/Rule.elm deleted file mode 100644 index 731b17a..0000000 --- a/src/Datalog/Rule.elm +++ /dev/null @@ -1,128 +0,0 @@ -module Datalog.Rule exposing (Problem(..), Rule, body, fact, head, isFact, rule, toString) - -import Datalog.Atom as Atom exposing (Atom) -import Datalog.Negatable as Negatable exposing (Direction(..), Negatable(..)) -import Datalog.Term as Term -import Sort.Set as Set - - -type Rule - = Rule Atom (List (Negatable Atom)) - - -type Problem - = NotRangeRestricted - | UnnamedHeadVariable - | VariableAppearsNegatedButNotPositive - - -rule : Atom -> List (Negatable Atom) -> Result Problem Rule -rule head_ body_ = - let - candidate = - Rule head_ body_ - in - if hasUnnamedHeadVariable candidate then - Err UnnamedHeadVariable - - else if not (isRangeRestricted candidate) then - Err NotRangeRestricted - - else if not (isNegationSafe candidate) then - Err VariableAppearsNegatedButNotPositive - - else - Ok candidate - - -fact : Atom -> Result Problem Rule -fact fact_ = - rule fact_ [] - - -isFact : Rule -> Bool -isFact (Rule head_ body_) = - Atom.isGround head_ && List.isEmpty body_ - - -hasUnnamedHeadVariable : Rule -> Bool -hasUnnamedHeadVariable (Rule head_ _) = - List.any Term.isAnonymous (Atom.variables head_) - - -{-| Do all the variables in the head occur in the body? --} -isRangeRestricted : Rule -> Bool -isRangeRestricted (Rule head_ body_) = - let - bodyVars = - List.concatMap (Negatable.value >> Atom.variables) body_ - in - List.all - (\headVar -> List.member headVar bodyVars) - (Atom.variables head_) - - -{-| Do all the variables in negated expressions also appear in positive -expressions? --} -isNegationSafe : Rule -> Bool -isNegationSafe (Rule _ body_) = - body_ - |> List.foldl - (\(Negatable direction atom) occurrences_ -> - List.foldl - (\variable occurrences -> - case direction of - Positive -> - { occurrences | positive = Set.insert variable occurrences.positive } - - Negative -> - { occurrences | negative = Set.insert variable occurrences.negative } - ) - occurrences_ - (Atom.variables atom) - ) - { positive = Set.empty Term.variableSorter - , negative = Set.empty Term.variableSorter - } - |> (\{ positive, negative } -> - negative - |> Set.dropIf (Set.memberOf positive) - |> Set.dropIf ((==) Term.Anonymous) - |> Set.isEmpty - ) - - -head : Rule -> Atom -head (Rule head_ _) = - head_ - - -body : Rule -> List (Negatable Atom) -body (Rule _ body_) = - body_ - - -toString : Rule -> String -toString (Rule head_ body_) = - case body_ of - [] -> - Atom.toString head_ ++ "." - - _ -> - Atom.toString head_ - ++ " :- " - ++ String.join ", " - (List.map - (\negatableAtom -> - case negatableAtom of - Negatable Positive atom -> - Atom.toString atom - - Negatable Negative atom -> - "not " ++ Atom.toString atom - ) - body_ - ) - ++ "." diff --git a/src/Datalog/Term.elm b/src/Datalog/Term.elm deleted file mode 100644 index 6b11c3e..0000000 --- a/src/Datalog/Term.elm +++ /dev/null @@ -1,83 +0,0 @@ -module Datalog.Term exposing (Constant(..), Term(..), Variable(..), anonymous, int, isAnonymous, isGround, string, toString, variable, variableSorter) - -import Sort exposing (Sorter) - - -type Term - = Constant Constant - | Variable Variable - - -type Constant - = String String - | Int Int - - -type Variable - = Named String - | Anonymous - - -variableSorter : Sorter Variable -variableSorter = - Sort.by - (\var -> - case var of - Named name -> - name - - Anonymous -> - "_" - ) - Sort.alphabetical - - -string : String -> Term -string = - Constant << String - - -int : Int -> Term -int = - Constant << Int - - -variable : String -> Term -variable = - Variable << Named - - -anonymous : Term -anonymous = - Variable Anonymous - - -isAnonymous : Variable -> Bool -isAnonymous var = - var == Anonymous - - -isGround : Term -> Bool -isGround term = - case term of - Constant _ -> - True - - Variable _ -> - False - - -toString : Term -> String -toString term = - case term of - Constant (String string_) -> - "\"" ++ string_ ++ "\"" - - Constant (Int int_) -> - String.fromInt int_ - - Variable (Named variable_) -> - variable_ - - Variable Anonymous -> - "_" diff --git a/tests/Datalog/AtomTests.elm b/tests/Datalog/AtomTests.elm deleted file mode 100644 index 52c4eac..0000000 --- a/tests/Datalog/AtomTests.elm +++ /dev/null @@ -1,219 +0,0 @@ -module Datalog.AtomTests exposing (..) - -import Datalog.Atom exposing (..) -import Datalog.Term as Term exposing (anonymous, int, string, variable) -import Expect -import Sort.Dict as Dict -import Test exposing (..) - - -isGroundTest : Test -isGroundTest = - describe "atom isGround" - [ test "if there are terms, the atom is ground" <| - \_ -> atom "x" [] |> isGround |> Expect.equal False - , test "if all terms are constant, the atom is ground" <| - \_ -> atom "x" [ string "a" ] |> isGround |> Expect.equal True - , test "if all terms are variable, the atom is not ground" <| - \_ -> atom "x" [ variable "X" ] |> isGround |> Expect.equal False - , test "with a mix of constant and variable terms, the atom is not ground" <| - \_ -> - atom "x" [ variable "X", string "a" ] - |> isGround - |> Expect.equal False - ] - - -variablesTest : Test -variablesTest = - describe "variables" - [ test "a variables shows up" <| - \_ -> - atom "x" [ variable "x" ] - |> variables - |> Expect.equal [ Term.Named "x" ] - , test "concrete terms does not show up" <| - \_ -> - atom "x" [ string "a", int 1 ] - |> variables - |> Expect.equal [] - ] - - -unifyTest : Test -unifyTest = - describe "unify" - [ test "atoms with different names do not unify" <| - \_ -> - unify (atom "a" []) (atom "b" []) - |> Expect.equal Nothing - , test "atoms with different arities do not unify" <| - \_ -> - unify - (atom "a" [ variable "A" ]) - (atom "a" [ variable "A", variable "B" ]) - |> Expect.equal Nothing - , test "conflicting constants do not unify" <| - \_ -> - unify - (atom "a" [ string "x" ]) - (atom "a" [ string "y" ]) - |> Expect.equal Nothing - , test "compatible constants unify" <| - \_ -> - unify - (atom "a" [ string "x" ]) - (atom "a" [ string "x" ]) - |> Expect.equal (Just emptySubstitutions) - , test "a unbound var/constant pair unifies" <| - \_ -> - unify - (atom "a" [ variable "X" ]) - (atom "a" [ string "a" ]) - |> Expect.equal (Just (singleton (Term.Named "X") (Term.String "a"))) - , test "a bound var/constant pair unifies if it does not conflict" <| - \_ -> - unify - (atom "a" [ variable "X", variable "X" ]) - (atom "a" [ string "a", string "a" ]) - |> Expect.equal (Just (singleton (Term.Named "X") (Term.String "a"))) - , test "a constant/bound var pair does not unify if it conflicts" <| - \_ -> - unify - (atom "a" [ variable "X", variable "X" ]) - (atom "a" [ string "a", string "b" ]) - |> Expect.equal Nothing - , test "a constant/unbound var pair unifies" <| - \_ -> - unify - (atom "a" [ string "a" ]) - (atom "a" [ variable "X" ]) - |> Expect.equal (Just (singleton (Term.Named "X") (Term.String "a"))) - , test "a constant/bound var pair unifies if it does not conflict" <| - \_ -> - unify - (atom "a" [ string "a", string "a" ]) - (atom "a" [ variable "X", variable "X" ]) - |> Expect.equal (Just (singleton (Term.Named "X") (Term.String "a"))) - , test "a bound var/constant pair does not unify if it conflicts" <| - \_ -> - unify - (atom "a" [ string "a", string "b" ]) - (atom "a" [ variable "X", variable "X" ]) - |> Expect.equal Nothing - , test "variables unify with each other but don't generate any bindings" <| - \_ -> - unify - (atom "a" [ variable "X" ]) - (atom "a" [ variable "Y" ]) - |> Expect.equal (Just emptySubstitutions) - , test "more than one variable can be bound in an atom" <| - \_ -> - unify - (atom "a" [ variable "X", variable "Y" ]) - (atom "a" [ string "a", string "b" ]) - |> Expect.equal - (Just - (Dict.fromList Term.variableSorter - [ ( Term.Named "X", Term.String "a" ) - , ( Term.Named "Y", Term.String "b" ) - ] - ) - ) - , test "multiple variables can cause conflicts which fail to unify" <| - \_ -> - unify - (atom "a" [ variable "X", variable "X" ]) - (atom "a" [ string "a", string "b" ]) - |> Expect.equal Nothing - , test "anonymous variables don't bind and cause conflicts" <| - \_ -> - unify - (atom "a" [ anonymous, anonymous ]) - (atom "a" [ string "a", string "b" ]) - |> Expect.equal (Just (Dict.empty Term.variableSorter)) - ] - - -substituteTest : Test -substituteTest = - describe "substitute" - [ test "an empty substitutions has no effect" <| - \_ -> - let - subject = - atom "a" [ variable "X" ] - in - substitute subject emptySubstitutions - |> Expect.equal subject - , test "an atom with no terms is unmodified" <| - \_ -> - let - subject = - atom "a" [] - in - substitute subject (singleton (Term.Named "X") (Term.String "a")) - |> Expect.equal subject - , test "a constant term is not replaed" <| - \_ -> - let - subject = - atom "a" [ string "a" ] - in - substitute subject (singleton (Term.Named "X") (Term.String "a")) - |> Expect.equal subject - , test "a variable term is replaced if there is a replacement" <| - \_ -> - substitute - (atom "a" [ variable "X" ]) - (singleton (Term.Named "X") (Term.String "a")) - |> Expect.equal (atom "a" [ string "a" ]) - , test "a variable term is not replace if there is no replacement" <| - \_ -> - let - subject = - atom "a" [ variable "X" ] - in - substitute subject (singleton (Term.Named "Y") (Term.String "a")) - |> Expect.equal subject - ] - - -mergeSubstitutionsTest : Test -mergeSubstitutionsTest = - describe "mergeSubstitutions" - [ test "keys in left should be preserved" <| - \_ -> - mergeSubstitutions - (singleton (Term.Named "X") (Term.String "a")) - emptySubstitutions - |> Dict.get (Term.Named "X") - |> Expect.equal (Just (Term.String "a")) - , test "keys in right should be preserved" <| - \_ -> - mergeSubstitutions - emptySubstitutions - (singleton (Term.Named "X") (Term.String "a")) - |> Dict.get (Term.Named "X") - |> Expect.equal (Just (Term.String "a")) - , test "keys in both should be preserved" <| - \_ -> - mergeSubstitutions - (singleton (Term.Named "X") (Term.String "a")) - (singleton (Term.Named "Y") (Term.String "b")) - |> Expect.all - [ Dict.get (Term.Named "X") >> Expect.equal (Just (Term.String "a")) - , Dict.get (Term.Named "Y") >> Expect.equal (Just (Term.String "b")) - ] - , test "keys in left take precedence" <| - \_ -> - mergeSubstitutions - (singleton (Term.Named "X") (Term.String "a")) - (singleton (Term.Named "X") (Term.String "b")) - |> Dict.get (Term.Named "X") - |> Expect.equal (Just (Term.String "a")) - ] - - -singleton = - Dict.singleton Term.variableSorter diff --git a/tests/Datalog/ParserTests.elm b/tests/Datalog/ParserTests.elm deleted file mode 100644 index a856f19..0000000 --- a/tests/Datalog/ParserTests.elm +++ /dev/null @@ -1,176 +0,0 @@ -module Datalog.ParserTests exposing (..) - -import Datalog exposing (Program, program) -import Datalog.Atom as Atom -import Datalog.Negatable exposing (negative, positive) -import Datalog.Parser exposing (..) -import Datalog.Rule as Rule exposing (Rule) -import Datalog.Term as Term -import Expect -import Test exposing (..) - - -parseTests : Test -parseTests = - describe "parse" - [ describe "success" - [ test "a fact" <| - \_ -> - Expect.equal - (Ok (unsafeProgram [ Rule.fact (Atom.atom "greek" [ Term.string "Socrates" ]) ])) - (parse "greek(\"Socrates\").") - , test "a fact with leading space" <| - \_ -> - Expect.equal - (Ok (unsafeProgram [ Rule.fact (Atom.atom "greek" [ Term.string "Socrates" ]) ])) - (parse " greek(\"Socrates\").") - , test "a fact with a number" <| - \_ -> - Expect.equal - (Ok (unsafeProgram [ Rule.fact (Atom.atom "theAnswer" [ Term.int 42 ]) ])) - (parse "theAnswer(42).") - , test "a rule with a variable" <| - \_ -> - Expect.equal - (Ok - (unsafeProgram - [ Rule.rule - (Atom.atom "mortal" [ Term.variable "whom" ]) - [ positive (Atom.atom "greek" [ Term.variable "whom" ]) ] - ] - ) - ) - (parse "mortal(whom) :- greek(whom).") - , test "a rule with multiple clauses" <| - \_ -> - Expect.equal - (Ok - (unsafeProgram - [ Rule.rule - (Atom.atom "ancestor" [ Term.variable "Child", Term.variable "Ancestor" ]) - [ positive (Atom.atom "parent" [ Term.variable "Child", Term.variable "Parent" ]) - , positive (Atom.atom "ancestor" [ Term.variable "Parent", Term.variable "Ancestor" ]) - ] - ] - ) - ) - (parse "ancestor(Child, Ancestor) :- parent(Child, Parent), ancestor(Parent, Ancestor).") - , test "a program with multiple rules" <| - \_ -> - Expect.equal - (Ok - (unsafeProgram - [ Rule.fact (Atom.atom "greek" [ Term.string "Socrates" ]) - , Rule.rule - (Atom.atom "mortal" [ Term.variable "Whom" ]) - [ positive (Atom.atom "greek" [ Term.variable "Whom" ]) ] - ] - ) - ) - (parse "greek(\"Socrates\").\nmortal(Whom) :- greek(Whom).") - , test "a program with whitespace between rules" <| - \_ -> - Expect.equal - (Ok - (unsafeProgram - [ Rule.fact (Atom.atom "greek" [ Term.string "Socrates" ]) - , Rule.rule - (Atom.atom "mortal" [ Term.variable "Whom" ]) - [ positive (Atom.atom "greek" [ Term.variable "Whom" ]) ] - ] - ) - ) - (parse "greek(\"Socrates\").\n\nmortal(Whom) :- greek(Whom).") - , test "a rule with newlines in between body atoms" <| - \_ -> - Expect.equal - (Ok - (unsafeProgram - [ Rule.rule - (Atom.atom "ancestor" [ Term.variable "Child", Term.variable "Ancestor" ]) - [ positive (Atom.atom "parent" [ Term.variable "Child", Term.variable "Parent" ]) - , positive (Atom.atom "ancestor" [ Term.variable "Parent", Term.variable "Ancestor" ]) - ] - ] - ) - ) - (parse "ancestor(Child, Ancestor) :-\n parent(Child, Parent),\n ancestor(Parent, Ancestor).") - , test "a rule using anonymous variables" <| - \_ -> - Expect.equal - (Ok - (unsafeProgram - [ Rule.rule - (Atom.atom "iceCream" [ Term.variable "favoriteFlavor" ]) - [ positive (Atom.atom "person" [ Term.anonymous, Term.variable "favoriteFlavor" ]) ] - ] - ) - ) - (parse "iceCream(favoriteFlavor) :- person(_, favoriteFlavor).") - , test "a rule using negation in a body atom" <| - \_ -> - Expect.equal - (Ok - (unsafeProgram - [ Rule.rule - (Atom.atom "ancestor" [ Term.variable "Child", Term.variable "Parent" ]) - [ positive (Atom.atom "parent" [ Term.variable "Child", Term.variable "Parent" ]) - , negative (Atom.atom "samePerson" [ Term.variable "Child", Term.variable "Parent" ]) - ] - ] - ) - ) - (parse "ancestor(Child, Parent) :- parent(Child, Parent), not samePerson(Child, Parent).") - , test "a line comment on a separate line" <| - \_ -> Expect.ok (parse "-- this is a comment") - , test "a line comment after a fact" <| - \_ -> Expect.ok (parse "greek(\"Socrates\"). -- lol classic Socrates") - , test "a line comment ater a rule" <| - \_ -> Expect.ok (parse "mortal(thing) :- greek(thing). -- wow, profound") - , test "a line comment between the body atoms in a rule" <| - \_ -> Expect.ok (parse "mortal(thing) :- -- just hanging out you know?\n greek(thing).") - ] - , describe "failure" - [ test "leaving the terms off an atom is not allowed" <| - \_ -> Expect.err (parse "greek") - , test "leaving the closing quote off a constant is not allowed" <| - \_ -> Expect.err (parse "greek(\"Socrates") - , test "leaving the closing parenthesis off a term list is not allowed" <| - \_ -> Expect.err (parse "greek(\"Socrates\"") - , test "leaving a period off a fact is not allowed" <| - \_ -> Expect.err (parse "greek(\"Socrates\")") - , test "adding a trailing comma in a term list is not allowed" <| - \_ -> Expect.err (parse "greek(\"Socrates\",)") - , test "having an implies horn but no body is not allowed" <| - \_ -> Expect.err (parse "mortal(whom) :-") - , test "having a trailing comma in a rule body is not allowed" <| - \_ -> Expect.err (parse "ancestor(Child, Ancestor) :- parent(Child, Parent),") - , test "leaving a period off a rule is not allowed" <| - \_ -> Expect.err (parse "mortal(Whom) :- greek(Whom)") - , test "entering a non-range-restricted rules is not allowed" <| - \_ -> Expect.err (parse "mortal(unused) :- greek(\"Socrates\").") - ] - ] - - -unsafeProgram : List (Result x Rule) -> Datalog.Program -unsafeProgram questionableRules = - let - rulesOrCrash = - List.filterMap - (\res -> - case res of - Ok cool -> - Just cool - - Err err -> - Debug.todo (Debug.toString err) - ) - questionableRules - in - case program rulesOrCrash of - Ok program_ -> - program_ - - Err err -> - Debug.todo (Debug.toString err) diff --git a/tests/Datalog/RuleTests.elm b/tests/Datalog/RuleTests.elm deleted file mode 100644 index 94ab1dd..0000000 --- a/tests/Datalog/RuleTests.elm +++ /dev/null @@ -1,73 +0,0 @@ -module Datalog.RuleTests exposing (..) - -import Datalog.Atom exposing (atom) -import Datalog.Negatable as Negatable exposing (negative, positive) -import Datalog.Rule exposing (..) -import Datalog.Term exposing (anonymous, int, string, variable) -import Expect -import Test exposing (..) - - -ruleTest : Test -ruleTest = - describe "rule" - [ test "a range-restricted rule is allowed" <| - \_ -> - rule - (atom "mortal" [ variable "whom" ]) - [ positive (atom "greek" [ variable "whom" ]) ] - |> Expect.ok - , test "a non-range-restricted rule is not allowed" <| - \_ -> - rule - (atom "mortal" [ variable "whom" ]) - [] - |> Expect.equal (Err NotRangeRestricted) - , test "anonymous terms are not allowed in the head" <| - \_ -> - rule - (atom "mortal" [ anonymous ]) - [ positive (atom "greek" [ anonymous ]) ] - |> Expect.equal (Err UnnamedHeadVariable) - , test "negative terms are allowed if they also appear in a positive form" <| - \_ -> - rule - (atom "unreachable" [ variable "a", variable "b" ]) - [ positive (atom "node" [ variable "a" ]) - , positive (atom "node" [ variable "b" ]) - , negative (atom "reachable" [ variable "a", variable "b" ]) - ] - |> Expect.ok - , test "negative terms which are introduced outside the head are still allowed if they appear in positive form" <| - \_ -> - rule - (atom "peopleWithoutEmails" [ variable "Name" ]) - [ positive (atom "people" [ variable "Id", variable "Name" ]) - , negative (atom "peopleToEmails" [ variable "Id", anonymous ]) - ] - |> Expect.ok - , test "negative terms are not allowed if they don't also appear in a positive form" <| - \_ -> - rule - (atom "immortal" [ variable "whom" ]) - [ negative (atom "mortal" [ variable "whom" ]) ] - |> Expect.equal (Err VariableAppearsNegatedButNotPositive) - ] - - -factTest : Test -factTest = - describe "fact" - [ test "a fact with all concrete terms is allowed" <| - \_ -> - fact (atom "age" [ string "Socrates", int 2490 ]) - |> Expect.ok - , test "a fact with a named variable is not allowed" <| - \_ -> - fact (atom "notGreat" [ variable "unbound" ]) - |> Expect.equal (Err NotRangeRestricted) - , test "a fact with an anonymous variable is not allowed" <| - \_ -> - fact (atom "notGreat" [ anonymous ]) - |> Expect.equal (Err UnnamedHeadVariable) - ] diff --git a/tests/Datalog/TermTests.elm b/tests/Datalog/TermTests.elm deleted file mode 100644 index 13cfe82..0000000 --- a/tests/Datalog/TermTests.elm +++ /dev/null @@ -1,17 +0,0 @@ -module Datalog.TermTests exposing (..) - -import Datalog.Term exposing (..) -import Expect -import Test exposing (..) - - -isGroundTest : Test -isGroundTest = - describe "term isGround" - [ test "a string is ground" <| - \_ -> string "a" |> isGround |> Expect.equal True - , test "an integer is ground" <| - \_ -> int 1 |> isGround |> Expect.equal True - , test "a variable is not ground" <| - \_ -> variable "X" |> isGround |> Expect.equal False - ] diff --git a/tests/DatalogTests.elm b/tests/DatalogTests.elm deleted file mode 100644 index 62e5712..0000000 --- a/tests/DatalogTests.elm +++ /dev/null @@ -1,249 +0,0 @@ -module DatalogTests exposing (..) - -import Datalog exposing (..) -import Datalog.Atom as Atom exposing (Atom, atom) -import Datalog.Negatable as Negatable exposing (Negatable, negative, positive) -import Datalog.Rule as Rule exposing (Rule) -import Datalog.Term as Term exposing (anonymous, string, variable) -import Dict exposing (Dict) -import Expect -import Test exposing (..) - - -solveTest : Test -solveTest = - describe "solve" - [ test "ground rules are solved" <| - \_ -> - unsafeProgram - [ Rule.fact (atom "greek" [ string "Socrates" ]) ] - |> solve - |> get ( "greek", 1 ) - |> Expect.equal [ atom "greek" [ string "Socrates" ] ] - , test "non-ground rules are solved" <| - \_ -> - unsafeProgram - [ Rule.fact (atom "greek" [ string "Socrates" ]) - , Rule.rule - (atom "mortal" [ variable "Whom" ]) - [ positive (atom "greek" [ variable "Whom" ]) ] - ] - |> solve - |> get ( "mortal", 1 ) - |> Expect.equal [ atom "mortal" [ string "Socrates" ] ] - , test "recursive rules are solved" <| - \_ -> - unsafeProgram - [ Rule.fact (atom "link" [ string "a", string "b" ]) - , Rule.fact (atom "link" [ string "b", string "c" ]) - - -- the rule - , Rule.rule - (atom "reachable" [ variable "X", variable "Y" ]) - [ positive (atom "link" [ variable "X", variable "Y" ]) ] - , Rule.rule - (atom "reachable" [ variable "X", variable "Z" ]) - [ positive (atom "link" [ variable "X", variable "Y" ]) - , positive (atom "reachable" [ variable "Y", variable "Z" ]) - ] - ] - |> solve - |> get ( "reachable", 2 ) - |> Expect.equal - [ atom "reachable" [ string "a", string "c" ] - , atom "reachable" [ string "b", string "c" ] - , atom "reachable" [ string "a", string "b" ] - ] - , test "can solve all-pairs reachability" <| - \_ -> - solve allPairsReachability - |> get ( "query", 1 ) - |> Expect.equal - [ atom "query" [ string "d" ] - , atom "query" [ string "c" ] - , atom "query" [ string "b" ] - ] - , describe "negation" - [ test "simple (semipositive) negation" <| - \_ -> - unsafeProgram - [ Rule.fact (atom "link" [ string "a", string "b" ]) - , Rule.fact (atom "link" [ string "b", string "c" ]) - , Rule.fact (atom "link" [ string "c", string "c" ]) - - -- node - , Rule.rule (atom "node" [ variable "name" ]) - [ positive (atom "link" [ variable "name", anonymous ]) ] - , Rule.rule (atom "node" [ variable "name" ]) - [ positive (atom "link" [ anonymous, variable "name" ]) ] - - -- the thing with negation - , Rule.rule (atom "disconnected" [ variable "x", variable "y" ]) - [ positive (atom "node" [ variable "x" ]) - , positive (atom "node" [ variable "y" ]) - , negative (atom "link" [ variable "x", variable "y" ]) - ] - ] - |> solve - |> get ( "disconnected", 2 ) - |> Expect.equal - [ atom "disconnected" [ string "c", string "b" ] - , atom "disconnected" [ string "c", string "a" ] - , atom "disconnected" [ string "b", string "b" ] - , atom "disconnected" [ string "b", string "a" ] - , atom "disconnected" [ string "a", string "c" ] - , atom "disconnected" [ string "a", string "a" ] - ] - , test "siblings example" <| - \_ -> - unsafeProgram - [ Rule.fact (atom "parent" [ string "Child A", string "Parent" ]) - , Rule.fact (atom "parent" [ string "Child B", string "Parent" ]) - - -- helper for negation - , Rule.rule - (atom "samePerson" [ variable "name", variable "name" ]) - [ positive (atom "parent" [ variable "name", anonymous ]) ] - , Rule.rule - (atom "samePerson" [ variable "name", variable "name" ]) - [ positive (atom "parent" [ anonymous, variable "name" ]) ] - - -- now the actual rule - , Rule.rule - (atom "siblings" [ variable "person", variable "sibling" ]) - [ positive (atom "parent" [ variable "person", variable "parent" ]) - , positive (atom "parent" [ variable "sibling", variable "parent" ]) - , negative (atom "samePerson" [ variable "person", variable "sibling" ]) - ] - ] - |> solve - |> get ( "siblings", 2 ) - |> Expect.equal - [ atom "siblings" [ string "Child B", string "Child A" ] - , atom "siblings" [ string "Child A", string "Child B" ] - ] - , test "stratifies a stratifiable program" <| - \_ -> - unsafeProgram - [ Rule.fact (atom "link" [ string "a", string "b" ]) - , Rule.fact (atom "link" [ string "b", string "c" ]) - , Rule.fact (atom "link" [ string "c", string "c" ]) - , Rule.fact (atom "link" [ string "c", string "d" ]) - - -- reachable - , Rule.rule (atom "reachable" [ variable "a", variable "b" ]) - [ positive (atom "link" [ variable "a", variable "b" ]) ] - , Rule.rule (atom "reachable" [ variable "a", variable "c" ]) - [ positive (atom "link" [ variable "a", variable "b" ]) - , positive (atom "reachable" [ variable "b", variable "c" ]) - ] - - -- node - , Rule.rule (atom "node" [ variable "a" ]) - [ positive (atom "link" [ variable "a", anonymous ]) ] - , Rule.rule (atom "node" [ variable "a" ]) - [ positive (atom "link" [ anonymous, variable "a" ]) ] - - -- unreachable - , Rule.rule (atom "unreachable" [ variable "a", variable "b" ]) - [ positive (atom "node" [ variable "a" ]) - , positive (atom "node" [ variable "b" ]) - , negative (atom "reachable" [ variable "a", variable "b" ]) - ] - ] - |> solve - |> get ( "unreachable", 2 ) - |> Expect.equal - [ atom "unreachable" [ string "d", string "d" ] - , atom "unreachable" [ string "d", string "c" ] - , atom "unreachable" [ string "d", string "b" ] - , atom "unreachable" [ string "d", string "a" ] - , atom "unreachable" [ string "c", string "b" ] - , atom "unreachable" [ string "c", string "a" ] - , atom "unreachable" [ string "b", string "b" ] - , atom "unreachable" [ string "b", string "a" ] - , atom "unreachable" [ string "a", string "a" ] - ] - , test "does not stratify an unstratifiable program" <| - \_ -> - [ Rule.rule (atom "p" [ variable "x" ]) - [ positive (atom "exists" [ variable "x" ]) - , negative (atom "q" [ variable "x" ]) - ] - , Rule.rule (atom "q" [ variable "x" ]) - [ positive (atom "exists" [ variable "x" ]) - , negative (atom "p" [ variable "x" ]) - ] - ] - |> List.map - (\ruleOrErr -> - case ruleOrErr of - Ok rule -> - rule - - Err err -> - Debug.todo (Debug.toString err) - ) - |> program - |> Expect.equal (Err Datalog.CycleWithNegation) - ] - ] - - -{-| All-pairs reachability example from Datalog and Recursive Query -Programming. --} -allPairsReachability : Datalog.Program -allPairsReachability = - unsafeProgram - [ -- base data - Rule.fact (atom "link" [ string "a", string "b" ]) - , Rule.fact (atom "link" [ string "b", string "c" ]) - , Rule.fact (atom "link" [ string "c", string "c" ]) - , Rule.fact (atom "link" [ string "c", string "d" ]) - - -- recursive rule - , Rule.rule - (atom "reachable" [ variable "X", variable "Y" ]) - [ positive (atom "link" [ variable "X", variable "Y" ]) ] - , Rule.rule - (atom "reachable" [ variable "X", variable "Y" ]) - [ positive (atom "link" [ variable "X", variable "Z" ]) - , positive (atom "reachable" [ variable "Z", variable "Y" ]) - ] - - -- query - , Rule.rule - (atom "query" [ variable "X" ]) - [ positive (atom "reachable" [ variable "a", variable "X" ]) ] - ] - - -get : ( String, Int ) -> Dict ( String, Int ) ( a, List a ) -> List a -get key dict = - Dict.get key dict - |> Maybe.map (\( first, rest ) -> first :: rest) - |> Maybe.withDefault [] - - -unsafeProgram : List (Result x Rule) -> Datalog.Program -unsafeProgram questionableRules = - let - rulesOrCrash = - List.filterMap - (\res -> - case res of - Ok cool -> - Just cool - - Err err -> - Debug.todo (Debug.toString err) - ) - questionableRules - in - case program rulesOrCrash of - Ok program_ -> - program_ - - Err err -> - Debug.todo (Debug.toString err)