[
  {
    "path": ".gitignore",
    "content": "/target\n/lib\n/classes\n/checkouts\n/resources/public/third-party\n/resources/public/app.js\npom.xml\n*.jar\n*.class\n.lein-deps-sum\n.lein-failures\n.lein-plugins\n.lein-env\n*~\nlibpeerconnection.log\n.clj-kondo\n.lein-repl-history"
  },
  {
    "path": "LICENSE.html",
    "content": "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\" />\n<title>Eclipse Public License - Version 1.0</title>\n<style type=\"text/css\">\n  body {\n    size: 8.5in 11.0in;\n    margin: 0.25in 0.5in 0.25in 0.5in;\n    tab-interval: 0.5in;\n    }\n  p {  \t\n    margin-left: auto;\n    margin-top:  0.5em;\n    margin-bottom: 0.5em;\n    }\n  p.list {\n  \tmargin-left: 0.5in;\n    margin-top:  0.05em;\n    margin-bottom: 0.05em;\n    }\n  </style>\n\n</head>\n\n<body lang=\"EN-US\">\n\n<h2>Eclipse Public License - v 1.0</h2>\n\n<p>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE\nPUBLIC LICENSE (&quot;AGREEMENT&quot;). ANY USE, REPRODUCTION OR\nDISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS\nAGREEMENT.</p>\n\n<p><b>1. DEFINITIONS</b></p>\n\n<p>&quot;Contribution&quot; means:</p>\n\n<p class=\"list\">a) in the case of the initial Contributor, the initial\ncode and documentation distributed under this Agreement, and</p>\n<p class=\"list\">b) in the case of each subsequent Contributor:</p>\n<p class=\"list\">i) changes to the Program, and</p>\n<p class=\"list\">ii) additions to the Program;</p>\n<p class=\"list\">where such changes and/or additions to the Program\noriginate from and are distributed by that particular Contributor. A\nContribution 'originates' from a Contributor if it was added to the\nProgram by such Contributor itself or anyone acting on such\nContributor's behalf. Contributions do not include additions to the\nProgram which: (i) are separate modules of software distributed in\nconjunction with the Program under their own license agreement, and (ii)\nare not derivative works of the Program.</p>\n\n<p>&quot;Contributor&quot; means any person or entity that distributes\nthe Program.</p>\n\n<p>&quot;Licensed Patents&quot; mean patent claims licensable by a\nContributor which are necessarily infringed by the use or sale of its\nContribution alone or when combined with the Program.</p>\n\n<p>&quot;Program&quot; means the Contributions distributed in accordance\nwith this Agreement.</p>\n\n<p>&quot;Recipient&quot; means anyone who receives the Program under\nthis Agreement, including all Contributors.</p>\n\n<p><b>2. GRANT OF RIGHTS</b></p>\n\n<p class=\"list\">a) Subject to the terms of this Agreement, each\nContributor hereby grants Recipient a non-exclusive, worldwide,\nroyalty-free copyright license to reproduce, prepare derivative works\nof, publicly display, publicly perform, distribute and sublicense the\nContribution of such Contributor, if any, and such derivative works, in\nsource code and object code form.</p>\n\n<p class=\"list\">b) Subject to the terms of this Agreement, each\nContributor hereby grants Recipient a non-exclusive, worldwide,\nroyalty-free patent license under Licensed Patents to make, use, sell,\noffer to sell, import and otherwise transfer the Contribution of such\nContributor, if any, in source code and object code form. This patent\nlicense shall apply to the combination of the Contribution and the\nProgram if, at the time the Contribution is added by the Contributor,\nsuch addition of the Contribution causes such combination to be covered\nby the Licensed Patents. The patent license shall not apply to any other\ncombinations which include the Contribution. No hardware per se is\nlicensed hereunder.</p>\n\n<p class=\"list\">c) Recipient understands that although each Contributor\ngrants the licenses to its Contributions set forth herein, no assurances\nare provided by any Contributor that the Program does not infringe the\npatent or other intellectual property rights of any other entity. Each\nContributor disclaims any liability to Recipient for claims brought by\nany other entity based on infringement of intellectual property rights\nor otherwise. As a condition to exercising the rights and licenses\ngranted hereunder, each Recipient hereby assumes sole responsibility to\nsecure any other intellectual property rights needed, if any. For\nexample, if a third party patent license is required to allow Recipient\nto distribute the Program, it is Recipient's responsibility to acquire\nthat license before distributing the Program.</p>\n\n<p class=\"list\">d) Each Contributor represents that to its knowledge it\nhas sufficient copyright rights in its Contribution, if any, to grant\nthe copyright license set forth in this Agreement.</p>\n\n<p><b>3. REQUIREMENTS</b></p>\n\n<p>A Contributor may choose to distribute the Program in object code\nform under its own license agreement, provided that:</p>\n\n<p class=\"list\">a) it complies with the terms and conditions of this\nAgreement; and</p>\n\n<p class=\"list\">b) its license agreement:</p>\n\n<p class=\"list\">i) effectively disclaims on behalf of all Contributors\nall warranties and conditions, express and implied, including warranties\nor conditions of title and non-infringement, and implied warranties or\nconditions of merchantability and fitness for a particular purpose;</p>\n\n<p class=\"list\">ii) effectively excludes on behalf of all Contributors\nall liability for damages, including direct, indirect, special,\nincidental and consequential damages, such as lost profits;</p>\n\n<p class=\"list\">iii) states that any provisions which differ from this\nAgreement are offered by that Contributor alone and not by any other\nparty; and</p>\n\n<p class=\"list\">iv) states that source code for the Program is available\nfrom such Contributor, and informs licensees how to obtain it in a\nreasonable manner on or through a medium customarily used for software\nexchange.</p>\n\n<p>When the Program is made available in source code form:</p>\n\n<p class=\"list\">a) it must be made available under this Agreement; and</p>\n\n<p class=\"list\">b) a copy of this Agreement must be included with each\ncopy of the Program.</p>\n\n<p>Contributors may not remove or alter any copyright notices contained\nwithin the Program.</p>\n\n<p>Each Contributor must identify itself as the originator of its\nContribution, if any, in a manner that reasonably allows subsequent\nRecipients to identify the originator of the Contribution.</p>\n\n<p><b>4. COMMERCIAL DISTRIBUTION</b></p>\n\n<p>Commercial distributors of software may accept certain\nresponsibilities with respect to end users, business partners and the\nlike. While this license is intended to facilitate the commercial use of\nthe Program, the Contributor who includes the Program in a commercial\nproduct offering should do so in a manner which does not create\npotential liability for other Contributors. Therefore, if a Contributor\nincludes the Program in a commercial product offering, such Contributor\n(&quot;Commercial Contributor&quot;) hereby agrees to defend and\nindemnify every other Contributor (&quot;Indemnified Contributor&quot;)\nagainst any losses, damages and costs (collectively &quot;Losses&quot;)\narising from claims, lawsuits and other legal actions brought by a third\nparty against the Indemnified Contributor to the extent caused by the\nacts or omissions of such Commercial Contributor in connection with its\ndistribution of the Program in a commercial product offering. The\nobligations in this section do not apply to any claims or Losses\nrelating to any actual or alleged intellectual property infringement. In\norder to qualify, an Indemnified Contributor must: a) promptly notify\nthe Commercial Contributor in writing of such claim, and b) allow the\nCommercial Contributor to control, and cooperate with the Commercial\nContributor in, the defense and any related settlement negotiations. The\nIndemnified Contributor may participate in any such claim at its own\nexpense.</p>\n\n<p>For example, a Contributor might include the Program in a commercial\nproduct offering, Product X. That Contributor is then a Commercial\nContributor. If that Commercial Contributor then makes performance\nclaims, or offers warranties related to Product X, those performance\nclaims and warranties are such Commercial Contributor's responsibility\nalone. Under this section, the Commercial Contributor would have to\ndefend claims against the other Contributors related to those\nperformance claims and warranties, and if a court requires any other\nContributor to pay any damages as a result, the Commercial Contributor\nmust pay those damages.</p>\n\n<p><b>5. NO WARRANTY</b></p>\n\n<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS\nPROVIDED ON AN &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS\nOF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,\nANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY\nOR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely\nresponsible for determining the appropriateness of using and\ndistributing the Program and assumes all risks associated with its\nexercise of rights under this Agreement , including but not limited to\nthe risks and costs of program errors, compliance with applicable laws,\ndamage to or loss of data, programs or equipment, and unavailability or\ninterruption of operations.</p>\n\n<p><b>6. DISCLAIMER OF LIABILITY</b></p>\n\n<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT\nNOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING\nWITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR\nDISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED\nHEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</p>\n\n<p><b>7. GENERAL</b></p>\n\n<p>If any provision of this Agreement is invalid or unenforceable under\napplicable law, it shall not affect the validity or enforceability of\nthe remainder of the terms of this Agreement, and without further action\nby the parties hereto, such provision shall be reformed to the minimum\nextent necessary to make such provision valid and enforceable.</p>\n\n<p>If Recipient institutes patent litigation against any entity\n(including a cross-claim or counterclaim in a lawsuit) alleging that the\nProgram itself (excluding combinations of the Program with other\nsoftware or hardware) infringes such Recipient's patent(s), then such\nRecipient's rights granted under Section 2(b) shall terminate as of the\ndate such litigation is filed.</p>\n\n<p>All Recipient's rights under this Agreement shall terminate if it\nfails to comply with any of the material terms or conditions of this\nAgreement and does not cure such failure in a reasonable period of time\nafter becoming aware of such noncompliance. If all Recipient's rights\nunder this Agreement terminate, Recipient agrees to cease use and\ndistribution of the Program as soon as reasonably practicable. However,\nRecipient's obligations under this Agreement and any licenses granted by\nRecipient relating to the Program shall continue and survive.</p>\n\n<p>Everyone is permitted to copy and distribute copies of this\nAgreement, but in order to avoid inconsistency the Agreement is\ncopyrighted and may only be modified in the following manner. The\nAgreement Steward reserves the right to publish new versions (including\nrevisions) of this Agreement from time to time. No one other than the\nAgreement Steward has the right to modify this Agreement. The Eclipse\nFoundation is the initial Agreement Steward. The Eclipse Foundation may\nassign the responsibility to serve as the Agreement Steward to a\nsuitable separate entity. Each new version of the Agreement will be\ngiven a distinguishing version number. The Program (including\nContributions) may always be distributed subject to the version of the\nAgreement under which it was received. In addition, after a new version\nof the Agreement is published, Contributor may elect to distribute the\nProgram (including its Contributions) under the new version. Except as\nexpressly stated in Sections 2(a) and 2(b) above, Recipient receives no\nrights or licenses to the intellectual property of any Contributor under\nthis Agreement, whether expressly, by implication, estoppel or\notherwise. All rights in the Program not expressly granted under this\nAgreement are reserved.</p>\n\n<p>This Agreement is governed by the laws of the State of New York and\nthe intellectual property laws of the United States of America. No party\nto this Agreement will bring a legal action under this Agreement more\nthan one year after the cause of action arose. Each party waives its\nrights to a jury trial in any resulting litigation.</p>\n\n</body>\n\n</html>"
  },
  {
    "path": "README.md",
    "content": "# [learndatalogtoday](https://www.learndatalogtoday.org)\n\nAn interactive [Datalog tutorial](https://www.learndatalogtoday.org).\n\n## Prerequisites\n\nYou will need [Leiningen](https://github.com/technomancy/leiningen) and java installed.\n\n## Run locally\n\n    $ lein uberjar\n    $ java -cp target/learndatalogtoday-standalone.jar clojure.main -m learndatalogtoday.handler\n\nServer is now running on `$PORT` (`http://localhost:8080` by default).\n\n## License\n\nCopyright © 2013-2023 Jonas Enlund\n\nDistributed under the Eclipse Public License, the same as Clojure.\n"
  },
  {
    "path": "externs.js",
    "content": "var CodeMirror;\nfunction CodeMirror(){};\nCodeMirror.fromTextArea = {};\nCodeMirror.refresh = {};\nCodeMirror.getValue = {};\nCodeMirror.setValue = {};\nvar $;\nfunction $(){};\n$.on = {};\n\n"
  },
  {
    "path": "project.clj",
    "content": "(defproject learndatalogtoday \"0.1.0\"\n  :description \"Interactive Datalog Tutorial\"\n  :url \"http://learndatalogtoday.org\"\n  :dependencies [[org.clojure/clojure \"1.8.0\"]\n                 [org.clojure/clojurescript \"1.9.227\"]\n                 [compojure \"1.5.1\"]\n                 [ring/ring-jetty-adapter \"1.5.0\"]\n                 [com.datomic/datomic-free \"0.9.5394\"]\n                 [datomic-query-helpers \"0.1.1\"]\n                 [hiccup \"1.0.5\"]\n                 [markdown-clj \"0.9.89\"]\n                 [fipp \"0.6.6\"]\n                 [com.taoensso/timbre \"4.7.4\"]\n                 [org.clojure/core.rrb-vector \"0.1.2\"]\n                 ;; cljs\n                 [hylla \"0.2.0\"]\n                 [hiccups \"0.3.0\"]\n                 [domina \"1.0.3\"]]\n  :plugins [[lein-ring \"0.9.7\"]\n            [lein-cljsbuild \"1.1.4\"]]\n  :source-paths [\"src/clj\"]\n  :ring {:handler learndatalogtoday.handler/app}\n  :main learndatalogtoday.handler\n  :aot :all\n  :uberjar-name \"learndatalogtoday-standalone.jar\"\n  :min-lein-version \"2.0.0\"\n  :profiles {:dev {:dependencies [[ring-mock \"0.1.5\"]]}}\n  :cljsbuild {:builds [{:source-paths [\"src/cljs\"]\n                        :compiler {:output-to \"resources/public/app.js\"\n                                   :optimizations :advanced\n                                   :externs [\"externs.js\"]\n                                   :static-fns true}}]})\n"
  },
  {
    "path": "resources/chapters/TOC.md",
    "content": "* Chapter 0: Introduction\n  - Welcome\n  - Datalog\n  - Datomic\n  - EDN\n  - Copy/Paste a query as a simple exercise\n\n* Chapter 1: Basic Queries\n  - About the example database\n  - The Datalog data model\n  - Queries as edn data\n  - Example query\n  - Wildcard pattern / elide values on right\n  - Explenation of pattern variables and data patterns \n  - 2-3 exercises\n    * Find entity ids of movies made in 1987\n    * Find all movie names\n    * Find the entity id's of all the directors\n    \n* Chapter 2: Data patterns\n  - Joins are implicit\n  - Simple example\n  - A second example\n  - At least 4 exercises\n\n* Chapter 3: Input, part 1\n  - Leave $ and % for later\n  - do all of \n    * scalars: ?title \n    * tuples: [?title ?name] \n    * collections: [?name ...] \n    * relations: [ [?title ?score] ]\n  - Exercises for all of the above\n    * Given a movie director, list all his movies.\n    * Given two actors, list all movies they've performed in together.\n    * Given a list of movie title, list all the title and the year the movie was released.\n    \n* Chapter 4: More queries\n  - query for attributes\n  - query for transaction\n  - ~3 examples\n    * When was the sample data imported?\n    * What attributes are associated with the movie \"Die Hard\"\n    * Find all available attributes and there type.\n\n* Chapter 5: Predicates\n  - ~3 exercises\n    * Find movies older than ?year\n    * Find movies older than ?title\n\n* Chapter 6: Transformation functions\n  - Exercises\n    * Given a namespace (as a string) Find all attributes, types and cardinality\n\n* Chapter 7: Aggregates\n\n* Chapter 8: Rules\n\n* Chapter 9: Querying multiple databases \n\n* Chapter 10: The End.\n"
  },
  {
    "path": "resources/chapters/chapter-0.edn",
    "content": "{:title \"Welcome\"\n :text-file \"chapters/chapter-0.md\"\n :exercises [{:question \"Find all movies titles in the database.\" \n              :hints []\n              :inputs [{:type :query\n                        :value [:find ...]\n                        :correct-value [:find ?title \n                                        :where \n                                        [_ :movie/title ?title]]}]}]}\n"
  },
  {
    "path": "resources/chapters/chapter-0.md",
    "content": "# Extensible Data Notation\n\nIn Datomic, a Datalog query is written in\n[extensible data notation (edn)](http://edn-format.org). Edn is a data format similar to JSON, but it:\n\n* is extensible with user defined value types,\n* has more base types,\n* is a subset of [Clojure](http://clojure.org) data.\n\nEdn consists of:\n\n* Numbers: `42`, `3.14159`\n* Strings: `\"This is a string\"`\n* Keywords: `:kw`, `:namespaced/keyword`, `:foo.bar/baz`\n* Symbols: `max`, `+`, `?title`\n* Vectors: `[1 2 3]` `[:find ?foo ...]`\n* Lists: `(3.14 :foo [:bar :baz])`, `(+ 1 2 3 4)`\n* Instants: `#inst \"2013-02-26\"`\n* .. and a few other things which we will not need in this tutorial.\n\nHere is an example query that finds all movie titles in our example database:\n\n    [:find ?title\n     :where \n     [_ :movie/title ?title]]\n\nNote that the query is a vector with four elements:\n\n* the keyword `:find`\n* the symbol `?title`\n* the keyword `:where`\n* the vector `[_ :movie/title ?title]`\n\nWe'll go over the specific parts of the query later, but for now you \nshould simply type the above query verbatim into the textbox below,\npress **Run Query**, and then continue to the next part of the tutorial.\n"
  },
  {
    "path": "resources/chapters/chapter-1.edn",
    "content": "{:title \"Basic Queries\"\n :text-file \"chapters/chapter-1.md\"\n :exercises [{:question \"Find the entity ids of movies made in 1987\"\n              :hints [\"You will need to use the `:movie/year` attribute\" \n                      \"The data pattern should look something like `[?e :movie/title 1987]`\"]\n              :inputs [{:type :query\n                        :value [:find ?e :where ...]\n                        :correct-value [:find ?e :where [?e :movie/year 1987]]}]}\n             \n             {:question \"Find the entity-id **and** titles of movies in the database\"\n              :inputs [{:type :query\n                        :value [:find ?e ?title :where ...]\n                        :correct-value [:find ?e ?title :where [?e :movie/title ?title]]}]}\n\n             {:question \"Find the name of all people in the database\"\n              :inputs [{:type :query\n                        :value [:find]\n                        :correct-value [:find ?name \n                                        :where \n                                        [?p :person/name ?name]]}]}]}\n"
  },
  {
    "path": "resources/chapters/chapter-1.md",
    "content": "# Basic Queries\n\nThe example database we'll use contains *movies* mostly, but not\nexclusively, from the 80s. You'll find information about movie titles,\nrelease year, directors, cast members etc. As the tutorial advances\nwe'll learn more about the contents of the database and how it's organized.\n\nThe data model in Datomic is based around atomic facts called\n**datoms**. A datom is a 4-tuple consisting of\n\n* Entity ID\n* Attribute\n* Value\n* Transaction ID\n\nYou can think of the database as a flat **set of datoms** of the form:\n\n    [<e-id>  <attribute>      <value>          <tx-id>]\n    ...\n    [ 167    :person/name     \"James Cameron\"    102  ]\n    [ 234    :movie/title     \"Die Hard\"         102  ]\n    [ 234    :movie/year      1987               102  ]\n    [ 235    :movie/title     \"Terminator\"       102  ]\n    [ 235    :movie/director  167                102  ]\n    ...\n\nNote that the last two datoms share the same entity ID, which means\nthey are facts about the same movie. Note also that the last datom's\nvalue is the same as the first datom's entity ID, i.e. the value of\nthe `:movie/director` attribute is itself an entity. All the datoms in\nthe above set were added to the database in the same transaction, so \nthey share the same transaction ID.\n\nA query is represented as a vector starting with the keyword `:find` \nfollowed by one or more **pattern variables** (symbols starting with `?`,\ne.g. `?title`). After the find clause comes the `:where` clause which\nrestricts the query to datoms that match the given **data patterns**.\n\nFor example, this query finds all entity-ids that have the attribute \n`:person/name` with a value of `\"Ridley Scott\"`:\n\n    [:find ?e\n     :where\n     [?e :person/name \"Ridley Scott\"]]\n\nA data pattern is a datom with some parts replaced with pattern\nvariables. It is the job of the query engine to figure out every\npossible value of each of the pattern variables and return the ones that are\nspecified in the `:find` clause.\n\nThe symbol `_` can be used as a\nwildcard for the parts of the data pattern that you wish to ignore. You can\nalso elide trailing values in a data pattern. Therefore, the previous query \nis equivalent to this next query, because we ignore the transaction part of the datoms.\n\n    [:find ?e\n     :where\n     [?e :person/name \"Ridley Scott\" _]]\n"
  },
  {
    "path": "resources/chapters/chapter-2.edn",
    "content": "{:title \"Data Patterns\"\n :text-file \"chapters/chapter-2.md\"\n :exercises\n [{:question \"Find movie titles made in 1985\"\n   :inputs [{:type :query\n             :value [:find ?title :where ...]\n             :correct-value [:find ?title \n                             :where \n                             [?m :movie/title ?title] \n                             [?m :movie/year 1985]]}]}\n\n  {:question \"What year was \\\"Alien\\\" released?\"\n   :inputs [{:type :query\n             :value [:find ?year :where ...]\n             :correct-value [:find ?year \n                             :where \n                             [?m :movie/title \"Alien\"] \n                             [?m :movie/year ?year]]}]}\n  \n  {:question \"Who directed RoboCop? You will need to use `[<movie-eid> :movie/director <person-eid>]` to find the director for a movie.\"\n   :inputs [{:type :query\n             :value [:find ?name :where ...]\n             :correct-value [:find ?name :where [?m :movie/title \"RoboCop\"] [?m :movie/director ?d] [?d :person/name ?name]]}]}\n\n  {:question \"Find directors who have directed Arnold Schwarzenegger in a movie.\"\n   :inputs [{:type :query\n             :value [:find ?name :where ...]\n             :correct-value [:find ?name \n                             :where \n                             [?p :person/name \"Arnold Schwarzenegger\"] \n                             [?m :movie/cast ?p] \n                             [?m :movie/director ?d] \n                             [?d :person/name ?name]]}]}]}\n"
  },
  {
    "path": "resources/chapters/chapter-2.md",
    "content": "# Data patterns\n\nIn the previous chapter, we looked at **data patterns**, i.e., vectors\nafter the `:where` clause, such as `[?e :movie/title \"Commando\"]`. \nThere can be many data patterns in a `:where` clause:\n\n    [:find ?title\n     :where\n     [?e :movie/year 1987]\n     [?e :movie/title ?title]]\n\nThe important thing to note here is that the pattern variable `?e` is\nused in both data patterns. When a pattern variable is used in\nmultiple places, the query engine requires it to be bound to the same\nvalue in each place. Therefore, this query will only find movie titles\nfor movies made in 1987.\n\nThe order of the data patterns does not matter (aside from performance \nconsiderations), so the previous query could just as well have been written this way:\n\n    [:find ?title\n     :where\n     [?e :movie/title ?title]\n     [?e :movie/year 1987]]\n\nIn both cases, the result set will be exactly the same.\n\nLet's say we want to find out who starred in \"Lethal Weapon\". We\nwill need three data patterns for this. The first one finds the\nentity ID of the movie with \"Lethal Weapon\" as the title:\n\n    [?m :movie/title \"Lethal Weapon\"]\n\nUsing the same entity ID at `?m`, we can find the cast members with the data\npattern:\n\n    [?m :movie/cast ?p] \n\nIn this pattern, `?p` will now be (the entity ID of) a person entity, so we can grab the\nactual name with:\n\n    [?p :person/name ?name] \n\nThe query will therefore be:\n\n    [:find ?name\n     :where\n     [?m :movie/title \"Lethal Weapon\"]\n     [?m :movie/cast ?p]\n     [?p :person/name ?name]]\n"
  },
  {
    "path": "resources/chapters/chapter-3.edn",
    "content": "{:title \"Parameterized Queries\"\n :text-file \"chapters/chapter-3.md\"\n\n:exercises \n [{:question \"Find movie title by year\"\n   :inputs [{:type :query\n             :value [:find ?title :in $ ?year :where ...]\n             :correct-value [:find ?title \n                             :in $ ?year \n                             :where \n                             [?m :movie/year ?year] \n                             [?m :movie/title ?title]]}\n            {:type :value\n             :value 1988}]}\n  {:question \"Given a list of movie titles, find the title and the year that movie was released.\"\n   :inputs [{:type :query\n             :value [:find ?title ?year :in ... :where ...]\n             :correct-value [:find ?title ?year \n                             :in $ [?title ...] \n                             :where \n                             [?m :movie/title ?title] \n                             [?m :movie/year ?year]]}\n            {:type :value\n             :value [\"Lethal Weapon\" \"Lethal Weapon 2\" \"Lethal Weapon 3\"]}]}\n\n  {:question \"Find all movie `?title`s where the `?actor` and the `?director` has worked together\"\n   :inputs [{:type :query\n             :value [:find ?title \n                     :in $ ?actor ?director\n                     :where \n                     ...]\n             :correct-value [:find ?title \n                             :in $ ?actor ?director\n                             :where \n                             [?a :person/name ?actor]\n                             [?d :person/name ?director]\n                             [?m :movie/cast ?a]\n                             [?m :movie/director ?d]\n                             [?m :movie/title ?title]]}\n            {:type :value\n             :value \"Michael Biehn\"}\n            {:type :value\n             :value \"James Cameron\"}]}\n\n  {:question \"Write a query that, given an actor name and a relation with movie-title/rating, finds the movie titles and corresponding rating for which that actor was a cast member.\"\n   :inputs [{:type :query\n             :value [:find ?title ?rating :in ... :where ...]\n             :correct-value [:find ?title ?rating \n                             :in $ ?name [[?title ?rating]] \n                             :where \n                             [?p :person/name ?name] \n                             [?m :movie/cast ?p] \n                             [?m :movie/title ?title]]}\n            {:type :value\n             :value \"Mel Gibson\"}\n            {:type :value\n             :value [[\"Die Hard\" 8.3] \n                     [\"Alien\" 8.5]\n                     [\"Lethal Weapon\" 7.6]\n                     [\"Commando\" 6.5]\n                     [\"Mad Max Beyond Thunderdome\" 6.1] \n                     [\"Mad Max 2\" 7.6]\n                     [\"Rambo: First Blood Part II\" 6.2]\n                     [\"Braveheart\" 8.4]\n                     [\"Terminator 2: Judgment Day\" 8.6] \n                     [\"Predator 2\" 6.1]\n                     [\"First Blood\" 7.6]\n                     [\"Aliens\" 8.5]\n                     [\"Terminator 3: Rise of the Machines\" 6.4]\n                     [\"Rambo III\" 5.4]\n                     [\"Mad Max\" 7.0]\n                     [\"The Terminator\" 8.1] \n                     [\"Lethal Weapon 2\" 7.1]\n                     [\"Predator\" 7.8]\n                     [\"Lethal Weapon 3\" 6.6]\n                     [\"RoboCop\" 7.5]]}]}]}\n"
  },
  {
    "path": "resources/chapters/chapter-3.md",
    "content": "# Parameterized queries\n\nLooking at this query:\n\n    [:find ?title\n     :where\n     [?p :person/name \"Sylvester Stallone\"]\n     [?m :movie/cast ?p]\n     [?m :movie/title ?title]]\n\nIt would be great if we could reuse this query to find movie\ntitles for any actor and not just for \"Sylvester Stallone\". This is\npossible with an `:in` clause, which provides the query with input\nparameters, much in the same way that function or method arguments does\nin your programming language.\n\nHere's that query with an input parameter for the actor:\n\n    [:find ?title\n     :in $ ?name\n     :where\n     [?p :person/name ?name]\n     [?m :movie/cast ?p]\n     [?m :movie/title ?title]]\n\nThis query takes two arguments: `$` is the database itself (implicit,\nif no `:in` clause is specified) and `?name` which presumably will be\nthe name of some actor.\n\nThe above query is executed like `(q query db \"Sylvester Stallone\")`,\nwhere `query` is the query we just saw, and `db` is a database value.\nYou can have any number of inputs to a query.\n\nIn the above query, the input pattern variable `?name` is bound to a\nscalar - a string in this case. There are four different kinds of\ninput: scalars, tuples, collections and relations.\n\n## A quick aside\n\nHold on. Where does that `$` get used? In query, each of these data\npatterns is actually a **5 tuple**, of the form:\n\n    [<database> <entity-id> <attribute> <value> <transaction-id>]\n\nIt's just that the `database` part is implicit, much like the first\nparameter in the `:in` clause. This query is functionally identical\nto the previous one:\n\n    [:find ?title\n     :in $ ?name\n     :where\n     [$ ?p :person/name ?name]\n     [$ ?m :movie/cast ?p]\n     [$ ?m :movie/title ?title]]\n\n## Tuples\n\nA tuple input is written as e.g. `[?name ?age]` and can be used when\nyou want to destructure an input. Let's say you have the vector\n`[\"James Cameron\" \"Arnold Schwarzenegger\"]` and you want to use this\nas input to find all movies where these two people collaborated:\n\n    [:find ?title\n     :in $ [?director ?actor]\n     :where\n     [?d :person/name ?director]\n     [?a :person/name ?actor]\n     [?m :movie/director ?d]\n     [?m :movie/cast ?a]\n     [?m :movie/title ?title]]\n\nOf course, in this case, you could just as well use two distinct inputs instead:\n\n    :in $ ?director ?actor\n\n## Collections\n\nYou can use collection destructuring to implement a kind of logical **or** in your query. Say you want to find all movies directed by either James Cameron **or** Ridley Scott:\n\n    [:find ?title\n     :in $ [?director ...]\n     :where\n     [?p :person/name ?director]\n     [?m :movie/director ?p]\n     [?m :movie/title ?title]]\n\nHere, the `?director` pattern variable is initially bound to both \"James Cameron\" and \"Ridley Scott\". Note that the ellipsis following `?director` is a literal, not elided code.\n\n## Relations\n\nRelations - a set of tuples - are the most interesting and powerful of\ninput types, since you can join external relations with the datoms in\nyour database.\n\nAs a simple example, let's consider a relation with tuples `[movie-title box-office-earnings]`:\n\n    [\n     ...\n     [\"Die Hard\" 140700000]\n     [\"Alien\" 104931801]\n     [\"Lethal Weapon\" 120207127]\n     [\"Commando\" 57491000]\n     ...\n    ]\n\nLet's use this data and the data in our database to find\nbox office earnings for a particular director:\n\n    [:find ?title ?box-office\n     :in $ ?director [[?title ?box-office]]\n     :where\n     [?p :person/name ?director]\n     [?m :movie/director ?p]\n     [?m :movie/title ?title]]\n\nNote that the `?box-office` pattern variable does not\nappear in any of the data patterns in the `:where` clause.\n"
  },
  {
    "path": "resources/chapters/chapter-4.edn",
    "content": "{:text-file \"chapters/chapter-4.md\"\n :exercises \n [{:question \"What attributes are associated with a given movie.\"\n   :inputs \n   [{:type :query\n     :value [:find ?attr :in $ ?title :where ...]\n     :correct-value [:find ?attr \n                     :in $ ?title \n                     :where \n                     [?m :movie/title ?title] \n                     [?m ?a]\n                     [?a :db/ident ?attr]]}\n    {:type :value\n     :value \"Commando\"}]}\n  \n  {:question \"Find the names of all people associated with a particular movie (i.e. both the actors and the directors)\"\n   :inputs \n   [{:type :query\n     :value [:find ?name :in $ ?title [?attr ...] :where ...]\n     :correct-value [:find ?name \n                     :in $ ?title [?attr ...] \n                     :where \n                     [?m :movie/title ?title] \n                     [?m ?attr ?p] \n                     [?p :person/name ?name]]}\n    {:type :value\n     :value \"Die Hard\"}\n    {:type :value\n     :value [:movie/cast :movie/director]}]}\n\n  {:question \"Find all available attributes, their type and their cardinality. This is essentially a query to find **the schema of the database**. To find all installed attributes you must use the `:db.install/attribute` attribute. You will also need to use the `:db/valueType` and `:db/cardinality` attributes as well as `:db/ident`.\"\n   :inputs \n   [{:type :query\n     :value [:find ?attr ?type ?card :where ...]\n     :correct-value [:find ?attr ?type ?card \n                     :where \n                     [_ :db.install/attribute ?a] \n                     [?a :db/valueType ?t] \n                     [?a :db/cardinality ?c] \n                     [?a :db/ident ?attr] \n                     [?t :db/ident ?type] \n                     [?c :db/ident ?card]]}]}\n\n  {:question \"When was the seed data imported into the database? Grab the transaction of any datom in the database, e.g., `[_ :movie/title _ ?tx]` and work from there.\"\n   :inputs [{:type :query\n             :value [:find ?inst :where ...]\n             :correct-value [:find ?inst \n                             :where \n                             [_ :movie/title _ ?tx] \n                             [?tx :db/txInstant ?inst]]}]}]}"
  },
  {
    "path": "resources/chapters/chapter-4.md",
    "content": "# More queries\n\nA datom, as described earlier, is the 4-tuple `[eid attr val tx]`. So far, we have only asked questions about values and/or entity-ids. It's important to remember that it's also possible to ask questions about attributes and transactions.\n\n## Attributes \n\nFor example, say we want to find all attributes that are associated with person entities in our database. We know for certain that `:person/name` is one such attribute, but are there others we have not yet seen?\n\n    [:find ?attr\n     :where \n     [?p :person/name]\n     [?p ?attr]]\n\nThe above query returns a set of entity ids referring to the attributes we are interested in. To get the actual keywords we need to look them up using the `:db/ident` attribute:\n\n    [:find ?attr\n     :where\n     [?p :person/name]\n     [?p ?a]\n     [?a :db/ident ?attr]]\n\nThis is because attributes are also entities in our database!\n\n## Transactions\n\nIt's also possible to run queries to find information about transactions, such as:\n\n* When was a fact asserted?\n* When was a fact retracted?\n* Which facts were part of a transaction?\n* Etc.\n\nThe transaction entity is the fourth element in the datom vector. The only attribute associated with a transaction (by default) is `:db/txInstant` which is the instant in time when the transaction was committed to the database.\n\nHere's how we use the fourth element to find the time that \"James Cameron\" was set as the name for that person entity:\n\n    [:find ?timestamp\n     :where\n     [?p :person/name \"James Cameron\" ?tx]\n     [?tx :db/txInstant ?timestamp]]\n     \n"
  },
  {
    "path": "resources/chapters/chapter-5.edn",
    "content": "{:text-file \"chapters/chapter-5.md\"\n :exercises \n [{:question \"Find movies older than a certain year (inclusive)\"\n   :inputs \n   [{:type :query\n     :value [:find ?title ...]\n     :correct-value [:find ?title \n                     :in $ ?year \n                     :where \n                     [?m :movie/title ?title] \n                     [?m :movie/year ?y] \n                     [(<= ?y ?year)]]}\n    {:type :value\n     :value 1979}]}\n\n  {:question \"Find **actors** older than Danny Glover\"\n   :inputs \n   [{:type :query\n     :value [:find ?actor ...]\n     :correct-value [:find ?actor\n                     :where \n                     [?d :person/name \"Danny Glover\"] \n                     [?d :person/born ?b1]\n                     [?e :person/born ?b2]\n                     [_ :movie/cast ?e]\n                     [(< ?b2 ?b1)]\n                     [?e :person/name ?actor]]}]}\n  \n  \n\n  {:question \"Find movies newer than `?year` (inclusive) and has a `?rating` higher than the one supplied\"\n   :inputs \n   [{:type :query\n     :value [:find ?title :in ... :where ...]\n     :correct-value [:find ?title \n                     :in $ ?year ?rating [[?title ?r]] \n                     :where \n                     [(< ?rating ?r)] \n                     [?m :movie/title ?title] \n                     [?m :movie/year ?y] \n                     [(<= ?year ?y)]]}\n    {:type :value\n     :value 1990}\n    {:type :value\n     :value 8.0}\n    {:type :value\n     :value [[\"Die Hard\" 8.3] \n             [\"Alien\" 8.5]\n             [\"Lethal Weapon\" 7.6]\n             [\"Commando\" 6.5]\n             [\"Mad Max Beyond Thunderdome\" 6.1] \n             [\"Mad Max 2\" 7.6]\n             [\"Rambo: First Blood Part II\" 6.2]\n             [\"Braveheart\" 8.4]\n             [\"Terminator 2: Judgment Day\" 8.6] \n             [\"Predator 2\" 6.1]\n             [\"First Blood\" 7.6]\n             [\"Aliens\" 8.5]\n             [\"Terminator 3: Rise of the Machines\" 6.4]\n             [\"Rambo III\" 5.4]\n             [\"Mad Max\" 7.0]\n             [\"The Terminator\" 8.1] \n             [\"Lethal Weapon 2\" 7.1]\n             [\"Predator\" 7.8]\n             [\"Lethal Weapon 3\" 6.6]\n             [\"RoboCop\" 7.5]]}]}]}\n"
  },
  {
    "path": "resources/chapters/chapter-5.md",
    "content": "# Predicates\n\nSo far, we have only been dealing with **data patterns**: \n`[?m :movie/year ?year]`. We have not yet seen a proper way of handling\nquestions like \"*Find all movies released before 1984*\". This is where\n**predicate clauses** come into play.\n\nLet's start with the query for the question above:\n\n    [:find ?title\n     :where\n     [?m :movie/title ?title]\n     [?m :movie/year ?year]\n     [(< ?year 1984)]]\n\nThe last clause, `[(< ?year 1984)]`, is a predicate clause. The\npredicate clause filters the result set to only include results for\nwhich the predicate returns a \"truthy\" (non-nil, non-false) value. You\ncan use any Clojure function or Java method as a predicate function:\n\n    [:find ?name\n     :where \n     [?p :person/name ?name]\n     [(.startsWith ?name \"M\")]]\n\nClojure functions must be fully namespace-qualified, so if you have\ndefined your own predicate `awesome?` you must write it as\n`(my.namespace/awesome? ?movie)`. Some ubiquitous predicates can be\nused without namespace qualification: `<, >, <=, >=, =, not=` and so on.\n"
  },
  {
    "path": "resources/chapters/chapter-6.edn",
    "content": "{:text-file \"chapters/chapter-6.md\"\n :exercises \n [{:question \"Find people by age. Use the function `tutorial.fns/age` to find the age given a birthday and a date representing \\\"today\\\".\"\n   :inputs [{:type :query\n             :value [:find ?name\n                     :in $ ?age ?today\n                     :where\n                     ...]\n             :correct-value [:find ?name \n                             :in $ ?age ?today\n                             :where \n                             [?p :person/name ?name] \n                             [?p :person/born ?born] \n                             [(tutorial.fns/age ?born ?today) ?age]]}\n            {:type :value\n             :value 63}\n            {:type :value\n             :value #inst \"2013-08-02\"}]}\n  \n  {:question \"Find people younger than Bruce Willis and their ages.\"\n   :inputs [{:type :query\n             :value [:find ?name ?age\n                     :in $ ?today\n                     :where\n                     ...]\n             :correct-value [:find ?name ?age \n                             :in $ ?today\n                             :where \n                             [?p :person/name \"Bruce Willis\"] \n                             [?p :person/born ?sborn] \n                             [?p2 :person/name ?name] \n                             [?p2 :person/born ?born] \n                             [(< ?sborn ?born)] \n                             [(tutorial.fns/age ?born ?today) ?age]]}\n            {:type :value\n             :value #inst \"2013-08-02\"}]}\n\n  {:question \"The birthday paradox states that in a room of 23 people there is a 50% chance that someone has the same birthday. Write a query to find who has the same birthday. Use the `<` predicate on the names to avoid duplicate answers. You can use (the deprecated) `.getDate` and `.getMonth` java `Date` methods.\"\n   :inputs [{:type :query\n             :value [:find ?name-1 ?name-2 \n                     :where \n                     ...\n                     [(< ?name-1 ?name-2)]]\n             :correct-value [:find ?name-1 ?name-2\n                             :where\n                             [?p1 :person/name ?name-1]\n                             [?p2 :person/name ?name-2]\n                             [?p1 :person/born ?born-1]\n                             [?p2 :person/born ?born-2]\n                             [(.getMonth ?born-1) ?m]\n                             [(.getMonth ?born-2) ?m]\n                             [(.getDate ?born-1) ?d]\n                             [(.getDate ?born-2) ?d]\n                             [(< ?name-1 ?name-2)]]}]}]}\n"
  },
  {
    "path": "resources/chapters/chapter-6.md",
    "content": "# Transformation functions\n\n**Transformation functions** are pure (= side-effect free) functions\nor methods which can be used in queries to transform values and bind\ntheir results to pattern variables. Say, for example, there exists an\nattribute `:person/born` with type `:db.type/instant`. Given the\nbirthday, it's easy to calculate the (very approximate) age of a\nperson:\n\n    (defn age [birthday today]\n      (quot (- (.getTime today)\n               (.getTime birthday))\n            (* 1000 60 60 24 365)))\n\nwith this function, we can now calculate the age of a person **inside the query itself**:\n\n    [:find ?age\n     :in $ ?name ?today\n     :where\n     [?p :person/name ?name]\n     [?p :person/born ?born]\n     [(tutorial.fns/age ?born ?today) ?age]]\n\nA transformation function clause has the shape `[(<fn> <arg1> <arg2> ...) <result-binding>]` where `<result-binding>` can be the same binding forms as we saw in [chapter 3](/chapter/3):\n\n* Scalar: `?age`\n* Tuple: `[?foo ?bar ?baz]`\n* Collection: `[?name ...]`\n* Relation: `[[?title ?rating]]`\n\nOne thing to be aware of is that transformation functions can't be nested. You can't write\n\n    [(f (g ?x)) ?a]\n\ninstead, you must bind intermediate results in temporary pattern variables\n\n    [(g ?x) ?t]\n    [(f ?t) ?a]\n"
  },
  {
    "path": "resources/chapters/chapter-7.edn",
    "content": "{:text-file \"chapters/chapter-7.md\"\n :exercises\n [{:question \"`count` the number of movies in the database\"\n   :inputs\n   [{:type :query\n     :value [:find ... :where ...]\n     :correct-value [:find (count ?m) :where [?m :movie/title]]}]}\n\n  {:question \"Find the birth date of the oldest person in the database.\"\n   :inputs\n   [{:type :query\n     :value [:find ... :where ...]\n     :correct-value [:find (min ?date) :where [_ :person/born ?date]]}]}\n\n  {:question \"Given a collection of actors and (the now familiar) ratings data. Find the average rating for each actor. The query should return the actor name and the `avg` rating.\"\n   :inputs\n   [{:type :query\n     :value [:find ... :in ... :where ...]\n     :correct-value [:find ?name (avg ?rating)\n                     :in $ [?name ...] [[?title ?rating]]\n                     :where\n                     [?p :person/name ?name]\n                     [?m :movie/cast ?p]\n                     [?m :movie/title ?title]]}\n    {:type :value\n     :value [\"Sylvester Stallone\" \"Arnold Schwarzenegger\" \"Mel Gibson\"]}\n    {:type :value\n     :value [[\"Die Hard\" 8.3] \n             [\"Alien\" 8.5]\n             [\"Lethal Weapon\" 7.6]\n             [\"Commando\" 6.5]\n             [\"Mad Max Beyond Thunderdome\" 6.1] \n             [\"Mad Max 2\" 7.6]\n             [\"Rambo: First Blood Part II\" 6.2]\n             [\"Braveheart\" 8.4]\n             [\"Terminator 2: Judgment Day\" 8.6] \n             [\"Predator 2\" 6.1]\n             [\"First Blood\" 7.6]\n             [\"Aliens\" 8.5]\n             [\"Terminator 3: Rise of the Machines\" 6.4]\n             [\"Rambo III\" 5.4]\n             [\"Mad Max\" 7.0]\n             [\"The Terminator\" 8.1] \n             [\"Lethal Weapon 2\" 7.1]\n             [\"Predator\" 7.8]\n             [\"Lethal Weapon 3\" 6.6]\n             [\"RoboCop\" 7.5]]}]}]}\n"
  },
  {
    "path": "resources/chapters/chapter-7.md",
    "content": "# Aggregates\n\nAggregate functions such as `sum`, `max` etc. are readily available in Datomic's Datalog implementation. They are written in the `:find` clause in your query:\n\n    [:find (max ?date)\n     :where\n     ...]\n\nAn aggregate function collects values from multiple datoms and returns\n\n* A single value: `min`, `max`, `sum`, `avg`, etc.\n* A collection of values: `(min n ?d)` `(max n ?d)` `(sample n ?e)` etc. where `n` is an integer specifying the size of the collection. \n"
  },
  {
    "path": "resources/chapters/chapter-8.edn",
    "content": "{:text-file \"chapters/chapter-8.md\"\n :exercises \n [{:question \"Write a rule `[movie-year ?title ?year]` where `?title` is the title of some movie and `?year` is that movies release year.\"\n   :inputs\n   [{:type :query\n     :value [:find ?title :in $ % :where [movie-year ?title 1991]]}\n    {:type :rule\n     :value [[(movie-year ?title ?year) ...]]\n     :correct-value [[(movie-year ?title ?year)\n                      [?m :movie/title ?title]\n                      [?m :movie/year ?year]]]}]}\n  \n  {:question \"Two people are friends if they have worked together in a movie. Write a rule `[friends ?p1 ?p2]` where `p1` and `p2` are person entities. Try with a few different `?name` inputs to make sure you got it right. There might be some edge cases here.\"\n   :inputs\n   [{:type :query\n     :value [:find ?friend\n             :in $ % ?name\n             :where \n             [?p1 :person/name ?name]\n             (friends ?p1 ?p2)\n             [?p2 :person/name ?friend]]}\n    {:type :rule\n     :value [[(friends ?p1 ?p2)\n              ...]]\n     :correct-value [[(friends ?p1 ?p2)\n                      [?m :movie/cast ?p1]\n                      [?m :movie/cast ?p2]\n                      [(not= ?p1 ?p2)]]\n                     [(friends ?p1 ?p2)\n                      [?m :movie/cast ?p1]\n                      [?m :movie/director ?p2]]\n                     [(friends ?p1 ?p2)\n                      (friends ?p2 ?p1)]]}\n    {:type :value\n     :value \"Sigourney Weaver\"}]}\n\n  {:question \"Write a rule `[sequels ?m1 ?m2]` where `?m1` and `?m2` are movie entities. You'll need to use the attribute `:movie/sequel`. To implement this rule correctly you can think of the problem like this: A movie `?m2` is a sequel of `?m1` if either \n* `?m2` is the \\\"direct\\\" sequel of `m1` **or** \n* `?m2` is the sequel of some movie `?m` **and** that movie `?m` is the sequel to `?m1`.\n\nThere are (at least) three different ways to write the above query. Try to find all three solutions.\"\n   :inputs\n   [{:type :query\n     :value [:find ?sequel\n             :in $ % ?title\n             :where \n             [?m :movie/title ?title]\n             (sequels ?m ?s)\n             [?s :movie/title ?sequel]]}\n    {:type :rule\n     :value [[(sequels ?m1 ?m2) ...]]\n     :correct-value [[(sequels ?m1 ?m2)\n                      [?m1 :movie/sequel ?m2]]\n                     [(sequels ?m1 ?m2)\n                      [?m :movie/sequel ?m2]\n                      (sequels ?m1 ?m)]]}\n    {:type :value\n     :value \"Mad Max\"}]}]}\n"
  },
  {
    "path": "resources/chapters/chapter-8.md",
    "content": "# Rules\n\nMany times over the course of this tutorial, we have had to write the\nfollowing three lines of repetitive query code:\n\n    [?p :person/name ?name]\n    [?m :movie/cast ?p]\n    [?m :movie/title ?title]\n\n**Rules** are the means of abstraction in Datalog. You can abstract\naway reusable parts of your queries into rules, give them meaningful\nnames and forget about the implementation details, just like you can with functions\nin your favorite programming language. Let's create a rule for the three lines above:\n\n    [(actor-movie ?name ?title)\n     [?p :person/name ?name]\n     [?m :movie/cast ?p]\n     [?m :movie/title ?title]]\n\nThe first vector is called the *head* of the rule where the first\nsymbol is the name of the rule. The rest of the rule is called the body. \n\nIt is possible to use `(...)` or `[...]` to enclose it, but it is conventional to use `(...)` to aid the eye when distinguishing between the rule's head and its body, and also between rule invocations and normal data patterns, as we'll see below.\n\nYou can think of a rule as a kind of function, but remember that this\nis logic programming, so we can use the same rule to:\n\n* find movie titles given an actor name, and\n* find actor names given a movie title.\n\nPut another way, we can use both `?name` and `?title` in `(actor-movie ?name ?title)` for input as well as for output. If we provide values for neither, we'll get all the possible combinations in the database. If we provide values for one or both, it'll constrain the result returned by the query as you'd expect.\n\nTo use the above rule, you simply write the head of the rule instead of the data patterns. Any variable with values already bound will be input, the rest will be output.\n\nThe query to find cast members of some movie,\nfor which we previously had to write:\n\n    [:find ?name\n     :where\n     [?p :person/name ?name]\n     [?m :movie/cast ?p]\n     [?m :movie/title \"The Terminator\"]]\n\nNow becomes:\n\n    [:find ?name\n     :in $ %\n     (actor-movie ?name \"The Terminator\")]\n\nThe `%` symbol in the `:in` clause represent the rules. You can write\nany number of rules, collect them in a vector, and pass them\nto the query engine like any other input:\n\n    [[(rule-a ?a ?b)\n      ...]\n     [(rule-b ?a ?b)\n      ...]\n     ...]\n\nYou can use [data patterns](/chapter/2), [predicates](/chapter/5),\n[transformation functions](/chapter/6) and calls to other rules in the body of\na rule.\n\nRules can also be used as another tool to write logical OR queries, as the\nsame rule name can be used several times:\n\n    [[(associated-with ?person ?movie)\n      [?movie :movie/cast ?person]]\n     [(associated-with ?person ?movie)\n      [?movie :movie/director ?person]]]\n\nSubsequent rule definitions will only be used if the ones preceding it aren't satisfied.\n\nUsing this rule, we can find both directors and cast members very easily:\n\n    [:find ?name\n     :in $ %\n     :where\n     [?m :movie/title \"Predator\"]\n     (associated-with ?p ?m)\n     [?p :person/name ?name]]\n\nGiven the fact that rules can contain calls to other rules, what would\nhappen if a rule called itself? Interesting things, it turns out, but\nlet's find out in the exercises.\n\n"
  },
  {
    "path": "resources/chapters/chapter-9.edn",
    "content": "{:text-file \"chapters/chapter-9.md\"}\n"
  },
  {
    "path": "resources/chapters/chapter-9.md",
    "content": "# The End.\n"
  },
  {
    "path": "resources/db/data.edn",
    "content": "[\n {:db/id #db/id [:db.part/user -100]\n  :person/name \"James Cameron\"\n  :person/born #inst \"1954-08-16\"}\n\n {:db/id #db/id [:db.part/user -101]\n  :person/name \"Arnold Schwarzenegger\"\n  :person/born #inst \"1947-07-30\"}\n\n {:db/id #db/id [:db.part/user -102]\n  :person/name \"Linda Hamilton\"\n  :person/born #inst \"1956-09-26\"}\n\n {:db/id #db/id [:db.part/user -103]\n  :person/name \"Michael Biehn\"\n  :person/born #inst \"1956-07-31\"}\n\n {:db/id #db/id [:db.part/user -104]\n  :person/name \"Ted Kotcheff\"\n  :person/born #inst \"1931-04-07\"}\n \n {:db/id #db/id [:db.part/user -105]\n  :person/name \"Sylvester Stallone\"\n  :person/born #inst \"1946-07-06\"}\n \n {:db/id #db/id [:db.part/user -106]\n  :person/name \"Richard Crenna\"\n  :person/born #inst \"1926-11-30\"\n  :person/death #inst \"2003-01-17\"}\n \n {:db/id #db/id [:db.part/user -107]\n  :person/name \"Brian Dennehy\"\n  :person/born #inst \"1938-07-09\"}\n\n {:db/id #db/id [:db.part/user -108]\n  :person/name \"John McTiernan\"\n  :person/born #inst \"1951-01-08\"}\n \n {:db/id #db/id [:db.part/user -109]\n  :person/name \"Elpidia Carrillo\"\n  :person/born #inst \"1961-08-16\"}\n\n {:db/id #db/id [:db.part/user -110]\n  :person/name \"Carl Weathers\"\n  :person/born #inst \"1948-01-14\"}\n\n {:db/id #db/id [:db.part/user -111]\n  :person/name \"Richard Donner\"\n  :person/born #inst \"1930-04-24\"}\n\n {:db/id #db/id [:db.part/user -112]\n  :person/name \"Mel Gibson\"\n  :person/born #inst \"1956-01-03\"}\n\n {:db/id #db/id [:db.part/user -113]\n  :person/name \"Danny Glover\"\n  :person/born #inst \"1946-07-22\"}\n\n {:db/id #db/id [:db.part/user -114]\n  :person/name \"Gary Busey\"\n  :person/born #inst \"1944-07-29\"}\n\n {:db/id #db/id [:db.part/user -115]\n  :person/name \"Paul Verhoeven\"\n  :person/born #inst \"1938-07-18\"}\n\n {:db/id #db/id [:db.part/user -116]\n  :person/name \"Peter Weller\"\n  :person/born #inst \"1947-06-24\"}\n\n {:db/id #db/id [:db.part/user -117]\n  :person/name \"Nancy Allen\"\n  :person/born #inst \"1950-06-24\"}\n\n {:db/id #db/id [:db.part/user -118]\n  :person/name \"Ronny Cox\"\n  :person/born #inst \"1938-07-23\"}\n\n {:db/id #db/id [:db.part/user -119]\n  :person/name \"Mark L. Lester\"\n  :person/born #inst \"1946-11-26\"}\n\n {:db/id #db/id [:db.part/user -120]\n  :person/name \"Rae Dawn Chong\"\n  :person/born #inst \"1961-02-28\"}\n\n {:db/id #db/id [:db.part/user -121]\n  :person/name \"Alyssa Milano\"\n  :person/born #inst \"1972-12-19\"}\n\n {:db/id #db/id [:db.part/user -122]\n  :person/name \"Bruce Willis\"\n  :person/born #inst \"1955-03-19\"}\n\n {:db/id #db/id [:db.part/user -123]\n  :person/name \"Alan Rickman\"\n  :person/born #inst \"1946-02-21\"}\n\n {:db/id #db/id [:db.part/user -124]\n  :person/name \"Alexander Godunov\"\n  :person/born #inst \"1949-11-28\"\n  :person/death #inst \"1995-05-18\"}\n\n {:db/id #db/id [:db.part/user -125]\n  :person/name \"Robert Patrick\"\n  :person/born #inst \"1958-11-05\"}\n\n {:db/id #db/id [:db.part/user -126]\n  :person/name \"Edward Furlong\"\n  :person/born #inst \"1977-08-02\"}\n\n {:db/id #db/id [:db.part/user -127]\n  :person/name \"Jonathan Mostow\"\n  :person/born #inst \"1961-11-28\"}\n\n {:db/id #db/id [:db.part/user -128]\n  :person/name \"Nick Stahl\"\n  :person/born #inst \"1979-12-05\"}\n\n {:db/id #db/id [:db.part/user -129]\n  :person/name \"Claire Danes\"\n  :person/born #inst \"1979-04-12\"}\n\n {:db/id #db/id [:db.part/user -130]\n  :person/name \"George P. Cosmatos\"\n  :person/born #inst \"1941-01-04\"\n  :person/death #inst \"2005-04-19\"}\n\n {:db/id #db/id [:db.part/user -131]\n  :person/name \"Charles Napier\"\n  :person/born #inst \"1936-04-12\"\n  :person/death #inst \"2011-10-05\"}\n\n {:db/id #db/id [:db.part/user -132]\n  :person/name \"Peter MacDonald\"}\n\n {:db/id #db/id [:db.part/user -133]\n  :person/name \"Marc de Jonge\"\n  :person/born #inst \"1949-02-16\"\n  :person/death #inst \"1996-06-06\"}\n\n {:db/id #db/id [:db.part/user -134]\n  :person/name \"Stephen Hopkins\"}\n\n {:db/id #db/id [:db.part/user -135]\n  :person/name \"Ruben Blades\"\n  :person/born #inst \"1948-07-16\"}\n\n {:db/id #db/id [:db.part/user -136]\n  :person/name \"Joe Pesci\"\n  :person/born #inst \"1943-02-09\"}\n\n {:db/id #db/id [:db.part/user -137]\n  :person/name \"Ridley Scott\"\n  :person/born #inst \"1937-11-30\"}\n\n {:db/id #db/id [:db.part/user -138]\n  :person/name \"Tom Skerritt\"\n  :person/born #inst \"1933-08-25\"}\n\n {:db/id #db/id [:db.part/user -139]\n  :person/name \"Sigourney Weaver\"\n  :person/born #inst \"1949-10-08\"}\n\n {:db/id #db/id [:db.part/user -140]\n  :person/name \"Veronica Cartwright\"\n  :person/born #inst \"1949-04-20\"}\n\n {:db/id #db/id [:db.part/user -141]\n  :person/name \"Carrie Henn\"}\n\n {:db/id #db/id [:db.part/user -142]\n  :person/name \"George Miller\"\n  :person/born #inst \"1945-03-03\"}\n\n {:db/id #db/id [:db.part/user -143]\n  :person/name \"Steve Bisley\"\n  :person/born #inst \"1951-12-26\"}\n\n {:db/id #db/id [:db.part/user -144]\n  :person/name \"Joanne Samuel\"}\n\n {:db/id #db/id [:db.part/user -145]\n  :person/name \"Michael Preston\"\n  :person/born #inst \"1938-05-14\"}\n\n {:db/id #db/id [:db.part/user -146]\n  :person/name \"Bruce Spence\"\n  :person/born #inst \"1945-09-17\"}\n\n {:db/id #db/id [:db.part/user -147]\n  :person/name \"George Ogilvie\"\n  :person/born #inst \"1931-03-05\"}\n\n {:db/id #db/id [:db.part/user -148]\n  :person/name \"Tina Turner\"\n  :person/born #inst \"1939-11-26\"}\n\n {:db/id #db/id [:db.part/user -149]\n  :person/name \"Sophie Marceau\"\n  :person/born #inst \"1966-11-17\"}\n\n {:db/id #db/id [:db.part/user -200]\n  :movie/title \"The Terminator\"\n  :movie/year 1984\n  :movie/director #db/id [:db.part/user -100]\n  :movie/cast [#db/id [:db.part/user -101]\n               #db/id [:db.part/user -102]\n               #db/id [:db.part/user -103]]\n  :movie/sequel #db/id [:db.part/user -207]}\n\n {:db/id #db/id [:db.part/user -201]\n  :movie/title \"First Blood\"\n  :movie/year 1982\n  :movie/director #db/id [:db.part/user -104]\n  :movie/cast [#db/id [:db.part/user -105]\n               #db/id [:db.part/user -106]\n               #db/id [:db.part/user -107]]\n  :movie/sequel #db/id [:db.part/user -209]}\n\n {:db/id #db/id [:db.part/user -202]\n  :movie/title \"Predator\"\n  :movie/year 1987\n  :movie/director #db/id [:db.part/user -108]\n  :movie/cast [#db/id [:db.part/user -101]\n               #db/id [:db.part/user -109]\n               #db/id [:db.part/user -110]]\n  :movie/sequel #db/id [:db.part/user -211]}\n\n {:db/id #db/id [:db.part/user -203]\n  :movie/title \"Lethal Weapon\"\n  :movie/year 1987\n  :movie/director #db/id [:db.part/user -111]\n  :movie/cast [#db/id [:db.part/user -112]\n               #db/id [:db.part/user -113]\n               #db/id [:db.part/user -114]]\n  :movie/sequel #db/id [:db.part/user -212]}\n\n {:db/id #db/id [:db.part/user -204]\n  :movie/title \"RoboCop\"\n  :movie/year 1987\n  :movie/director #db/id [:db.part/user -115]\n  :movie/cast [#db/id [:db.part/user -116]\n               #db/id [:db.part/user -117]\n               #db/id [:db.part/user -118]]}\n\n {:db/id #db/id [:db.part/user -205]\n  :movie/title \"Commando\"\n  :movie/year 1985\n  :movie/director #db/id [:db.part/user -119]\n  :movie/cast [#db/id [:db.part/user -101]\n               #db/id [:db.part/user -120]\n               #db/id [:db.part/user -121]]\n  :trivia \"In 1986, a sequel was written with an eye to having\n  John McTiernan direct. Schwarzenegger wasn't interested in reprising\n  the role. The script was then reworked with a new central character,\n  eventually played by Bruce Willis, and became Die Hard\"}\n\n {:db/id #db/id [:db.part/user -206]\n  :movie/title \"Die Hard\"\n  :movie/year 1988\n  :movie/director #db/id [:db.part/user -108]\n  :movie/cast [#db/id [:db.part/user -122]\n               #db/id [:db.part/user -123]\n               #db/id [:db.part/user -124]]}\n\n {:db/id #db/id [:db.part/user -207]\n  :movie/title \"Terminator 2: Judgment Day\"\n  :movie/year 1991\n  :movie/director #db/id [:db.part/user -100]\n  :movie/cast [#db/id [:db.part/user -101]\n               #db/id [:db.part/user -102]\n               #db/id [:db.part/user -125]\n               #db/id [:db.part/user -126]]\n  :movie/sequel #db/id [:db.part/user -208]}\n\n {:db/id #db/id [:db.part/user -208]\n  :movie/title \"Terminator 3: Rise of the Machines\"\n  :movie/year 2003\n  :movie/director #db/id [:db.part/user -127]\n  :movie/cast [#db/id [:db.part/user -101]\n               #db/id [:db.part/user -128]\n               #db/id [:db.part/user -129]]}\n\n {:db/id #db/id [:db.part/user -209]\n  :movie/title \"Rambo: First Blood Part II\"\n  :movie/year 1985\n  :movie/director #db/id [:db.part/user -130]\n  :movie/cast [#db/id [:db.part/user -105]\n               #db/id [:db.part/user -106]\n               #db/id [:db.part/user -131]]\n  :movie/sequel #db/id [:db.part/user -210]}\n\n {:db/id #db/id [:db.part/user -210]\n  :movie/title \"Rambo III\"\n  :movie/year 1988\n  :movie/director #db/id [:db.part/user -132]\n  :movie/cast [#db/id [:db.part/user -105]\n               #db/id [:db.part/user -106]\n               #db/id [:db.part/user -133]]}\n\n {:db/id #db/id [:db.part/user -211]\n  :movie/title \"Predator 2\"\n  :movie/year 1990\n  :movie/director #db/id [:db.part/user -134]\n  :movie/cast [#db/id [:db.part/user -113]\n               #db/id [:db.part/user -114]\n               #db/id [:db.part/user -135]]}\n\n {:db/id #db/id [:db.part/user -212]\n  :movie/title \"Lethal Weapon 2\"\n  :movie/year 1989\n  :movie/director #db/id [:db.part/user -111]\n  :movie/cast [#db/id [:db.part/user -112]\n               #db/id [:db.part/user -113]\n               #db/id [:db.part/user -136]]\n  :movie/sequel #db/id [:db.part/user -213]}\n\n {:db/id #db/id [:db.part/user -213]\n  :movie/title \"Lethal Weapon 3\"\n  :movie/year 1992\n  :movie/director #db/id [:db.part/user -111]\n  :movie/cast [#db/id [:db.part/user -112]\n               #db/id [:db.part/user -113]\n               #db/id [:db.part/user -136]]}\n\n {:db/id #db/id [:db.part/user -214]\n  :movie/title \"Alien\"\n  :movie/year 1979\n  :movie/director #db/id [:db.part/user -137]\n  :movie/cast [#db/id [:db.part/user -138]\n               #db/id [:db.part/user -139]\n               #db/id [:db.part/user -140]]\n  :movie/sequel #db/id [:db.part/user -215]}\n\n {:db/id #db/id [:db.part/user -215]\n  :movie/title \"Aliens\"\n  :movie/year 1986\n  :movie/director #db/id [:db.part/user -100]\n  :movie/cast [#db/id [:db.part/user -139]\n               #db/id [:db.part/user -141]\n               #db/id [:db.part/user -103]]}\n\n {:db/id #db/id [:db.part/user -216]\n  :movie/title \"Mad Max\"\n  :movie/year 1979\n  :movie/director #db/id [:db.part/user -142]\n  :movie/cast [#db/id [:db.part/user -112]\n               #db/id [:db.part/user -143]\n               #db/id [:db.part/user -144]]\n  :movie/sequel #db/id [:db.part/user -217]}\n\n {:db/id #db/id [:db.part/user -217]\n  :movie/title \"Mad Max 2\"\n  :movie/year 1981\n  :movie/director #db/id [:db.part/user -142]\n  :movie/cast [#db/id [:db.part/user -112]\n               #db/id [:db.part/user -145]\n               #db/id [:db.part/user -146]]\n  :movie/sequel #db/id [:db.part/user -218]}\n\n {:db/id #db/id [:db.part/user -218]\n  :movie/title \"Mad Max Beyond Thunderdome\"\n  :movie/year 1985\n  :movie/director [#db/id [:db.part/user -142]\n                   #db/id [:db.part/user -147]]\n  :movie/cast [#db/id [:db.part/user -112]\n               #db/id [:db.part/user -148]]}\n\n {:db/id #db/id [:db.part/user -219]\n  :movie/title \"Braveheart\"\n  :movie/year 1995\n  :movie/director [#db/id [:db.part/user -112]]\n  :movie/cast [#db/id [:db.part/user -112]\n               #db/id [:db.part/user -149]]}\n]\n"
  },
  {
    "path": "resources/db/schema.edn",
    "content": "[\n {:db/ident :movie/title\n  :db/valueType :db.type/string\n  :db/cardinality :db.cardinality/one\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n\n {:db/ident :movie/year\n  :db/valueType :db.type/long\n  :db/cardinality :db.cardinality/one\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n\n {:db/ident :movie/director\n  :db/valueType :db.type/ref\n  :db/cardinality :db.cardinality/many\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n\n {:db/ident :movie/sequel\n  :db/valueType :db.type/ref\n  :db/cardinality :db.cardinality/one\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n \n {:db/ident :movie/cast\n  :db/valueType :db.type/ref\n  :db/cardinality :db.cardinality/many\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n \n {:db/ident :person/name\n  :db/valueType :db.type/string\n  :db/cardinality :db.cardinality/one\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n\n {:db/ident :person/born\n  :db/valueType :db.type/instant\n  :db/cardinality :db.cardinality/one\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n\n{:db/ident :person/death\n  :db/valueType :db.type/instant\n  :db/cardinality :db.cardinality/one\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n\n {:db/ident :trivia\n  :db/valueType :db.type/string\n  :db/cardinality :db.cardinality/many\n  :db/id #db/id [:db.part/db]\n  :db.install/_attribute :db.part/db}\n]"
  },
  {
    "path": "resources/public/style.css",
    "content": ".textcontent h1 {\n    color: #08c;\n    font-size: 18pt;\n}\n\n.textcontent h2 {\n    color: #08c;\n    font-size: 16pt;\n}\n\n.textcontent p {\n    text-align: justify;\n    line-height: 1.6em;\n}\n\n.CodeMirror {\n    height: auto;\n    border: 1px solid lightgrey;\n    margin-bottom:10px;\n}\n.CodeMirror-scroll {\n    overflow-y: hidden;\n    overflow-x: auto;\n}\n\ntable.resultset {\n    font-family: monospace;\n}\n\ntable.resultset thead {\n    font-weight: bold;\n}"
  },
  {
    "path": "resources/toc.md",
    "content": "# Learn Datalog Today\n\n**Learn Datalog Today** is an interactive tutorial designed to teach you the [Datomic](https://datomic.com) dialect of [Datalog](https://en.wikipedia.org/wiki/Datalog). Datalog is a declarative **database query language** with roots in logic programming. Datalog has similar expressive power as [SQL](https://en.wikipedia.org/wiki/Sql).\n\nDatomic is a database with an interesting and novel architecture, giving its users a unique set of features. You can read more about Datomic at [https://datomic.com](http://datomic.com) and the architecture is described in some detail [in this InfoQ article](https://www.infoq.com/articles/Architecture-Datomic).\n\n## Table of Contents\n\n- [Extensible Data Notation](/chapter/0)\n- [Basic Queries](/chapter/1)\n- [Data Patterns](/chapter/2)\n- [Parameterized Queries](/chapter/3)\n- [More Queries](/chapter/4)\n- [Predicates](/chapter/5)\n- [Transformation Functions](/chapter/6)\n- [Aggregates](/chapter/7)\n- [Rules](/chapter/8)\n\nThis tutorial was written on rainy days for the [Lisp In Summer Projects](http://lispinsummerprojects.org) 2013. If you find bugs, or have suggestions on how to improve the tutorial, please visit the project on [github](https://github.com/jonase/learndatalogtoday).\n\nMany thanks to [Robert Stuttaford](https://twitter.com/RobStuttaford) for his careful proof reading/editing. I'd also like to thank everyone who has [contributed](https://github.com/jonase/learndatalogtoday/graphs/contributors) by fixing bugs and spelling mistakes.\n\nIf you learn datalog today, you can consider [sponsoring](https://github.com/sponsors/jonase) the maintenance and running costs of this website.\n"
  },
  {
    "path": "src/clj/learndatalogtoday/handler.clj",
    "content": "(ns learndatalogtoday.handler\n  (:gen-class)\n  (:require [clojure.java.io :as io]\n            [clojure.edn :as edn]\n            [compojure.core :refer [routes GET POST]]\n            [compojure.handler :as handler]\n            [compojure.route :as route]\n            [datomic-query-helpers.core :refer [check-query\n                                                normalize\n                                                pretty-query-string]]\n            [datomic.api :as d]\n            [fipp.edn :as fipp]\n            [hiccup.page :refer [html5]]\n            [learndatalogtoday.views :as views]\n            [ring.adapter.jetty :as jetty]\n            [taoensso.timbre :as log]\n            [tutorial.fns])\n  (:import [java.util Date]))\n\n(def dev? (boolean (System/getenv \"DEVMODE\")))\n\n\n(defn edn-response [edn-data]\n  {:status 200\n   :headers {\"Content-Type\" \"application/edn\"}\n   :body (pr-str edn-data)})\n\n(defn read-file [s]\n  (with-open [r (io/reader (io/resource s))]\n    (read-string (slurp r))))\n\n(defn read-chapter-data [chapter]\n  (->> chapter\n       (format \"chapters/chapter-%s.edn\")\n       read-file))\n\n(defn read-chapter\n  \"Returns a html string\"\n  [chapter]\n  (let [chapter-data (read-chapter-data chapter)]\n    (assoc chapter-data\n           :html (views/chapter-response (assoc chapter-data\n                                                :chapter chapter)))))\n\n\n(def whitelist '#{< > <= >= not= = tutorial.fns/age .getDate .getMonth\n                  movie-year sequels friends avg min max sum count})\n\n(defn validate [[query & args]]\n  (let [syms (check-query query args whitelist)]\n    (if (empty? syms)\n      (cons (normalize query) args)\n      (throw (ex-info (str \"Non-whitelist symbol used in query/args: \" syms\n                           \". The symbol whitelist is \" whitelist)\n                      {:syms syms})))))\n\n(def toc (if dev? views/toc (memoize views/toc)))\n(def read-chapter-data (if dev? read-chapter-data (memoize read-chapter-data)))\n(def read-chapter (if dev? read-chapter (memoize read-chapter)))\n\n(defn app-routes [db chapters]\n  (routes\n   (GET \"/\"\n     []\n     (toc))\n\n   (GET [\"/chapter/:n\" :n #\"[0-9]+\"]\n     [n]\n     (:html (read-chapter (Integer/parseInt n))))\n\n   (GET [\"/query/:chapter/:exercise\" :chapter #\"[0-9]+\" :exercise #\"[0-9]+\"]\n     {{:keys [chapter exercise data] :as params} :params}\n     (try\n       (let [chapter (Integer/parseInt chapter)\n             exercise (Integer/parseInt exercise)\n             usr-input (edn/read-string data)\n             ans-input (get-in (read-chapter-data chapter) [:exercises exercise :inputs])\n             [ans-query & ans-args] (validate (map #(or (:correct-value %1) %2) ans-input usr-input))\n             [usr-query & usr-args] (validate (edn/read-string data))\n             usr-result (apply d/q usr-query db usr-args)\n             ans-result (apply d/q ans-query db ans-args)]\n         (if (= usr-result ans-result)\n           (do (log/info (format \"Success query [%s,%s]: %s\" chapter exercise (pr-str usr-input)))\n\n               (edn-response {:status :success\n                              :result usr-result}))\n           (do (log/info (format \"Fail query [%s,%s]: %s\" chapter exercise (pr-str usr-input)))\n               (edn-response {:status :fail\n                              :result usr-result\n                              :correct-result ans-result}))))\n       (catch Exception e\n         (let [msg (.getMessage e)]\n           (log/info (format \"Error query [%s,%s]. Data: %s Message: %s\" chapter exercise data msg))\n           (edn-response {:status :error\n                          :message (.getMessage e)})))))\n\n   (GET [\"/answer/:chapter/:exercise\" :chapter #\"[0-9]+\" :exercise #\"[0-9]+\"]\n     [chapter exercise]\n     (try\n       (let [chapter (Integer/parseInt chapter)\n             exercise (Integer/parseInt exercise)\n             ans-input (get-in (read-chapter-data chapter) [:exercises exercise :inputs])\n             value #(or (:correct-value %) (:value %))\n             answer (map (fn [input]\n                           (condp = (:type input)\n                             :query (pretty-query-string (value input))\n                             :rule (with-out-str\n                                     (fipp/pprint (value input)))\n                             :value (with-out-str\n                                      (fipp/pprint (value input)))))\n                         ans-input)]\n         (do (log/info (format \"Answer request [%s,%s]: %s\" chapter exercise (seq answer)))\n             (edn-response answer)))))\n\n   (route/resources \"/\")\n   (route/not-found \"Not Found\")))\n\n(defn init-db [name schema seed-data]\n  (let [uri (str \"datomic:mem://\" name)\n        conn (do (d/delete-database uri)\n                 (d/create-database uri)\n                 (d/connect uri))]\n    @(d/transact conn schema)\n    @(d/transact conn seed-data)\n    (d/db conn)))\n\n(def app\n  (let [schema (read-file \"db/schema.edn\")\n        seed-data (read-file \"db/data.edn\")\n        db (init-db \"movies\" schema seed-data)\n        chapters (mapv read-chapter (range 8))] ;; TODO 9\n    (handler/site (app-routes db chapters))))\n\n(defn -main []\n  (let [port (Integer/parseInt (or (System/getenv \"PORT\") \"8080\"))]\n    (jetty/run-jetty app {:port port :join? false})))\n"
  },
  {
    "path": "src/clj/learndatalogtoday/views.clj",
    "content": "(ns learndatalogtoday.views\n  (:require [clojure.java.io :as io]\n            [datomic-query-helpers.core :refer [pretty-query-string]]\n            [fipp.edn :as fipp]\n            [hiccup.element :refer [javascript-tag]]\n            [hiccup.page :refer [html5 include-js include-css]]\n            [markdown.core :as md]))\n\n(defn footer []\n  [:footer.text-center {:style \"border-top: 1px solid lightgrey; margin-top: 40px;padding:10px;\"}\n   [:small\n    [:p [:a {:href \"https://www.learndatalogtoday.org\"} \"www.learndatalogtoday.org\"]\n     \" &copy; 2013 - 2023 Jonas Enlund\"]\n    [:p\n     [:a {:href \"https://github.com/jonase/learndatalogtoday\"} \"github\"] \" | \"\n     [:a {:href \"http://lispinsummerprojects.org/\"} \"lispinsummerprojects.org\"]]]])\n\n(defn row [& content]\n  [:div.row\n   [:div.offset2.span8\n    content]])\n\n(defn base [chapter text exercises ecount]\n  (list\n   [:head\n    (include-css \"/third-party/bootstrap/css/bootstrap.css\")\n    (include-css \"/third-party/codemirror-3.15/lib/codemirror.css\")\n    (include-css \"/style.css\")\n    [:title \"Learn Datalog Today!\"]]\n   [:body\n    [:div.container\n     (row [:div.textcontent text])\n     (row (when (> chapter 0)\n            [:a {:href (str \"/chapter/\" (dec chapter))}\n             \"<< Previous chapter\"])\n          (when (< chapter 8)\n            [:a.pull-right {:href (str \"/chapter/\" (inc chapter))}\n             \"Next chapter >>\"]))\n     (row [:div.exercises {:style \"margin-top: 14px\"} exercises])\n     (row (footer))]\n    (include-js \"/third-party/jquery/jquery-1.10.1.min.js\")\n    (include-js \"/third-party/codemirror-3.15/lib/codemirror.js\")\n    (include-js \"/third-party/codemirror-3.15/mode/clojure/clojure.js\")\n    (include-js \"/third-party/bootstrap/js/bootstrap.js\")\n    (include-js \"/app.js\")\n    (javascript-tag (format \"learndatalogtoday.core.init(%s, %s);\" chapter ecount))]))\n\n(defn build-input [tab-n input-n input]\n  (let [label (condp = (:type input)\n                :query \"Query:\"\n                :rule \"Rules:\"\n                :value (str \"Input #\" input-n \":\"))\n        input-str (condp = (:type input)\n                    :query (pretty-query-string (:value input))\n                    :rule (with-out-str (fipp/pprint (:value input)))\n                    :value (with-out-str (fipp/pprint (:value input))))]\n    [:div.span8\n     [:div.row\n      [:div.span8 [:p [:small [:strong label]\n                       (when (= :query (:type input))\n                         [:span.pull-right \"[ \" [:a {:href \"#\" :class (str \"show-ans-\" tab-n)} \"I give up!\"] \" ]\"])]]]]\n     [:div.row\n      [:div.span8 [:textarea {:class (str \"input-\" tab-n)} input-str]]]]))\n\n(defn build-inputs [tab-n inputs]\n  (map-indexed (partial build-input tab-n) inputs))\n\n(defn build-exercise [tab-n exercise]\n  (list [:div {:class (if (zero? tab-n) \"tab-pane active\" \"tab-pane\")\n               :id (str \"tab\" tab-n)}\n\n         (md/md-to-html-string (:question exercise))\n         [:div.row.inputs\n          (build-inputs tab-n (:inputs exercise))]\n         [:div.row\n          [:div.span8\n           [:button.btn.btn-block {:id (str \"run-query-\" tab-n)\n                                   :data-tab tab-n}\n            \"Run Query\"]]]\n         [:div.row\n          [:div.span8\n           [:div.alerts]\n           [:table.table.table-striped.resultset\n            [:thead]\n            [:tbody]]]]]))\n\n(defn build-exercises [exercises]\n  (list [:div.tabbable\n         [:ul.nav.nav-tabs\n          (for [n (range (count exercises))]\n            [:li (when (zero? n) {:class \"active\"})\n             [:a {:href (str \"#tab\" n)\n                  :data-toggle \"tab\"}\n              [:span.label n]]])]\n         [:div.tab-content\n          (map-indexed build-exercise exercises)]]))\n\n(defn read-chapter [file]\n  (with-open [r (io/reader (io/resource file))]\n    (md/md-to-html-string (slurp r))))\n\n(defn chapter-response [chapter-data]\n  (let [text (-> chapter-data :text-file read-chapter)\n        exercises (build-exercises (:exercises chapter-data))\n        ecount (count (:exercises chapter-data))\n        chapter (:chapter chapter-data)]\n    (html5 (base chapter text exercises ecount))))\n\n(defn toc []\n  (html5\n   [:head\n    (include-css \"/third-party/bootstrap/css/bootstrap.css\")\n    (include-css \"/style.css\")\n    [:title \"Learn Datalog Today!\"]]\n   [:body\n    [:div.container\n     (row [:div.textcontent (md/md-to-html-string\n                             (with-open [r (io/reader (io/resource \"toc.md\"))]\n                               (slurp r)))])\n     (row (footer))]\n    (include-js \"/third-party/jquery/jquery-1.10.1.min.js\")\n    (include-js \"/third-party/bootstrap/js/bootstrap.js\")]))\n"
  },
  {
    "path": "src/clj/tutorial/fns.clj",
    "content": "(ns tutorial.fns\n  (:import [java.util Calendar Date]))\n\n(defn date-vector \n  \"Returns the vector [year month day]\"\n  [date]\n  (let [c (Calendar/getInstance)]\n    (.setTime c date)\n    [(.get c Calendar/YEAR)\n     (inc (.get c Calendar/MONTH))\n     (.get c Calendar/DAY_OF_MONTH)]))\n\n(defn age \n  \"Very crude.\"\n  [^Date birthday ^Date today]\n  (quot (- (.getTime today)\n           (.getTime birthday))\n        (* 1000 60 60 24 365)))\n"
  },
  {
    "path": "src/cljs/learndatalogtoday/core.cljs",
    "content": "(ns learndatalogtoday.core\n  (:require [learndatalogtoday.reader :refer [read-string]]\n            [datomic-query-helpers.core :refer [normalize]]\n            [hylla.remote :as remote]\n            [domina :refer [by-id nodes]]\n            [domina.css :refer [sel]]\n            [domina.events :refer [listen! prevent-default]]\n            [hiccups.runtime :refer [render-html]]))\n\n(defn find-clause [q]\n  (:find (normalize q)))\n\n(defn render-row [row]\n  (render-html\n   [:tr (for [x row] [:td (pr-str x)])]))\n\n(defn render-error [msg]\n  (render-html\n   [:div.alert.alert-error\n    [:strong \"Oh snap!\"]\n    [:p msg]]))\n\n(defn render-fail [msg]\n  (render-html\n   [:div.alert\n    [:p msg]]))\n\n(defn render-result [exercise query result-data]\n  (let [alert (sel (str \"#tab\" exercise \" .alerts\"))\n        thead (sel (str \"#tab\" exercise \" thead\"))\n        tbody (sel (str \"#tab\" exercise \" tbody\"))]\n    (domina/destroy-children! alert)\n    (domina/destroy-children! thead)\n    (domina/destroy-children! tbody)\n    (if (= :error (:status result-data))\n      (domina/append! alert (render-error (:message result-data)))\n      (do (if (= :fail (:status result-data))\n            (domina/append! alert (render-fail \"Sorry, these results are not correct\"))\n            (domina/add-class! (sel \".active .label\") \"label-success\"))\n          (domina/append! thead (render-row (find-clause query)))\n          (doseq [row (:result result-data)]\n            (domina/append! tbody (render-row row)))))))\n\n(defn run-query-fn [chapter exercise editors]\n  (fn [event]\n    (try\n      (let [input-strings (map #(.getValue %) editors)\n            input-data (map read-string input-strings)]\n        (remote/get (str \"/query/\" chapter \"/\" exercise)\n                     input-data\n                     #(render-result exercise (first input-data) %)))\n      (catch js/Error e\n        (render-result exercise nil\n                       {:status :error\n                        :message (.-message e)})))))\n\n(defn show-ans-fn [chapter exercise editors]\n  (fn [e]\n    (prevent-default e)\n    (remote/get (str \"/answer/\" chapter \"/\" exercise)\n                nil\n                (fn [ans]\n                  (mapv #(.setValue %1 %2) editors ans)))))\n\n(defn ^:export init [chapter ecount]\n  (doseq [n (range ecount)]\n    (let [button-id (str \"#run-query-\" n)\n          input-class (str \".input-\" n)\n          editors (mapv #(.fromTextArea js/CodeMirror %)\n                        (nodes (sel input-class)))\n          show-answer-class (str \".show-ans-\" n)]\n\n      ;; Need to refresh the codemirror editors when tab is shown. I'd\n      ;; rather do this with domina but \"shown\" is a bootstrap\n      ;; specific event.\n      (.on (js/$ \"a[data-toggle=\\\"tab\\\"]\")\n           \"shown\"\n           (fn [e] (mapv #(.refresh %) editors)))\n\n      (listen! (sel button-id) :click\n               (run-query-fn chapter n editors))\n      (listen! (sel show-answer-class)  :click\n               (show-ans-fn chapter n editors)))))\n"
  },
  {
    "path": "src/cljs/learndatalogtoday/reader.cljs",
    "content": ";   Copyright (c) Rich Hickey. All rights reserved.\n;   The use and distribution terms for this software are covered by the\n;   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)\n;   which can be found in the file epl-v10.html at the root of this distribution.\n;   By using this software in any fashion, you are agreeing to be bound by\n;   the terms of this license.\n;   You must not remove this notice, or any other, from this software.\n\n;; This is a patched version of the ClojureScript reader. It contains\n;; bugfixes for CLJS-454, CLJS-564 and CLJS-565 as well as an\n;; optimized StringPushbackReader.\n\n(ns learndatalogtoday.reader\n  (:require [goog.string :as gstring]))\n\n(defprotocol PushbackReader\n  (read-char [reader] \"Returns the next char from the Reader,\nnil if the end of stream has been reached\")\n  (unread [reader ch] \"Push back a single character on to the stream\"))\n\n(deftype StringPushbackReader [s buffer ^:mutable idx]\n  PushbackReader\n  (read-char [reader]\n    (if (zero? (.-length buffer))\n      (do\n        (set! idx (inc idx))\n        (aget s idx))\n      (.pop buffer)))\n  (unread [reader ch]\n    (.push buffer ch)))\n\n(defn push-back-reader [s]\n  \"Creates a StringPushbackReader from a given string\"\n  (StringPushbackReader. s (array) -1))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; predicates\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n(defn- ^boolean whitespace?\n  \"Checks whether a given character is whitespace\"\n  [ch]\n  (or (gstring/isBreakingWhitespace ch) (identical? \\, ch)))\n\n(defn- ^boolean numeric?\n  \"Checks whether a given character is numeric\"\n  [ch]\n  (gstring/isNumeric ch))\n\n(defn- ^boolean comment-prefix?\n  \"Checks whether the character begins a comment.\"\n  [ch]\n  (identical? \\; ch))\n\n(defn- ^boolean number-literal?\n  \"Checks whether the reader is at the start of a number literal\"\n  [reader initch]\n  (or (numeric? initch)\n      (and (or (identical? \\+ initch) (identical? \\- initch))\n           (numeric? (let [next-ch (read-char reader)]\n                       (unread reader next-ch)\n                       next-ch)))))\n\n(declare read macros dispatch-macros)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; read helpers\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n\n; later will do e.g. line numbers...\n(defn reader-error\n  [rdr & msg]\n  (throw (js/Error. (apply str msg))))\n\n(defn ^boolean macro-terminating? [ch]\n  (and (not (identical? ch \"#\"))\n       (not (identical? ch \\'))\n       (not (identical? ch \":\"))\n       (macros ch)))\n\n(defn read-token\n  [rdr initch]\n  (loop [sb (gstring/StringBuffer. initch)\n         ch (read-char rdr)]\n    (if (or (nil? ch)\n            (whitespace? ch)\n            (macro-terminating? ch))\n      (do (unread rdr ch) (. sb (toString)))\n      (recur (do (.append sb ch) sb) (read-char rdr)))))\n\n(defn skip-line\n  \"Advances the reader to the end of a line. Returns the reader\"\n  [reader _]\n  (loop []\n    (let [ch (read-char reader)]\n      (if (or (identical? ch \\n) (identical? ch \\r) (nil? ch))\n        reader\n        (recur)))))\n\n(def int-pattern (re-pattern \"([-+]?)(?:(0)|([1-9][0-9]*)|0[xX]([0-9A-Fa-f]+)|0([0-7]+)|([1-9][0-9]?)[rR]([0-9A-Za-z]+)|0[0-9]+)(N)?\"))\n(def ratio-pattern (re-pattern \"([-+]?[0-9]+)/([0-9]+)\"))\n(def float-pattern (re-pattern \"([-+]?[0-9]+(\\\\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?\"))\n(def symbol-pattern (re-pattern \"[:]?([^0-9/].*/)?([^0-9/][^/]*)\"))\n\n(defn- re-find*\n  [re s]\n  (let [matches (.exec re s)]\n    (when-not (nil? matches)\n      (if (== (alength matches) 1)\n        (aget matches 0)\n        matches))))\n\n(defn- match-int\n  [s]\n  (let [groups (re-find* int-pattern s)\n        group3 (aget groups 2)]\n    (if-not (or (nil? group3)\n                (< (alength group3) 1))\n      0\n      (let [negate (if (identical? \"-\" (aget groups 1)) -1 1)\n            a (cond\n               (aget groups 3) (array (aget groups 3) 10)\n               (aget groups 4) (array (aget groups 4) 16)\n               (aget groups 5) (array (aget groups 5) 8)\n               (aget groups 7) (array (aget groups 7) (js/parseInt (aget groups 7)))\n               :default (array nil nil))\n            n (aget a 0)\n            radix (aget a 1)]\n        (if (nil? n)\n          nil\n          (* negate (js/parseInt n radix)))))))\n\n\n(defn- match-ratio\n  [s]\n  (let [groups (re-find* ratio-pattern s)\n        numinator (aget groups 1)\n        denominator (aget groups 2)]\n    (/ (js/parseInt numinator) (js/parseInt denominator))))\n\n(defn- match-float\n  [s]\n  (js/parseFloat s))\n\n(defn- re-matches*\n  [re s]\n  (let [matches (.exec re s)]\n    (when (and (not (nil? matches))\n               (identical? (aget matches 0) s))\n      (if (== (alength matches) 1)\n        (aget matches 0)\n        matches))))\n\n(defn- match-number\n  [s]\n  (cond\n   (re-matches* int-pattern s) (match-int s)\n   (re-matches* ratio-pattern s) (match-ratio s)\n   (re-matches* float-pattern s) (match-float s)))\n\n(defn escape-char-map [c]\n  (cond\n   (identical? c \\t) \"\\t\"\n   (identical? c \\r) \"\\r\"\n   (identical? c \\n) \"\\n\"\n   (identical? c \\\\) \\\\\n   (identical? c \\\") \\\"\n   (identical? c \\b) \"\\b\"\n   (identical? c \\f) \"\\f\"\n   :else nil))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; unicode\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n(defn read-2-chars [reader]\n  (.toString\n    (gstring/StringBuffer.\n      (read-char reader)\n      (read-char reader))))\n\n(defn read-4-chars [reader]\n  (.toString\n    (gstring/StringBuffer.\n      (read-char reader)\n      (read-char reader)\n      (read-char reader)\n      (read-char reader))))\n\n(def unicode-2-pattern (re-pattern \"[0-9A-Fa-f]{2}\"))\n(def unicode-4-pattern (re-pattern \"[0-9A-Fa-f]{4}\"))\n\n(defn validate-unicode-escape [unicode-pattern reader escape-char unicode-str]\n  (if (re-matches unicode-pattern unicode-str)\n    unicode-str\n    (reader-error reader \"Unexpected unicode escape \\\\\" escape-char unicode-str)))\n\n(defn make-unicode-char [code-str]\n    (let [code (js/parseInt code-str 16)]\n      (.fromCharCode js/String code)))\n\n(defn escape-char\n  [buffer reader]\n  (let [ch (read-char reader)\n        mapresult (escape-char-map ch)]\n    (if mapresult\n      mapresult\n      (cond\n        (identical? ch \\x)\n        (->> (read-2-chars reader)\n          (validate-unicode-escape unicode-2-pattern reader ch)\n          (make-unicode-char))\n\n        (identical? ch \\u)\n        (->> (read-4-chars reader)\n          (validate-unicode-escape unicode-4-pattern reader ch)\n          (make-unicode-char))\n\n        (numeric? ch)\n        (.fromCharCode js/String ch)\n\n        :else\n        (reader-error reader \"Unexpected unicode escape \\\\\" ch )))))\n\n(defn read-past\n  \"Read until first character that doesn't match pred, returning\n   char.\"\n  [pred rdr]\n  (loop [ch (read-char rdr)]\n    (if (pred ch)\n      (recur (read-char rdr))\n      ch)))\n\n(defn read-delimited-list\n  [delim rdr recursive?]\n  (loop [a (transient [])]\n    (let [ch (read-past whitespace? rdr)]\n      (when-not ch (reader-error rdr \"EOF while reading\"))\n      (if (identical? delim ch)\n        (persistent! a)\n        (if-let [macrofn (macros ch)]\n          (let [mret (macrofn rdr ch)]\n            (recur (if (identical? mret rdr) a (conj! a mret))))\n          (do\n            (unread rdr ch)\n            (let [o (read rdr true nil recursive?)]\n              (recur (if (identical? o rdr) a (conj! a o))))))))))\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; data structure readers\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n(defn not-implemented\n  [rdr ch]\n  (reader-error rdr \"Reader for \" ch \" not implemented yet\"))\n\n(declare maybe-read-tagged-type)\n\n(defn read-dispatch\n  [rdr _]\n  (let [ch (read-char rdr)\n        dm (dispatch-macros ch)]\n    (if dm\n      (dm rdr _)\n      (if-let [obj (maybe-read-tagged-type rdr ch)]\n        obj\n        (reader-error rdr \"No dispatch macro for \" ch)))))\n\n(defn read-unmatched-delimiter\n  [rdr ch]\n  (reader-error rdr \"Unmached delimiter \" ch))\n\n(defn read-list\n  [rdr _]\n  (apply list (read-delimited-list \")\" rdr true)))\n\n(def read-comment skip-line)\n\n(defn read-vector\n  [rdr _]\n  (read-delimited-list \"]\" rdr true))\n\n(defn read-map\n  [rdr _]\n  (let [l (read-delimited-list \"}\" rdr true)]\n    (when (odd? (count l))\n      (reader-error rdr \"Map literal must contain an even number of forms\"))\n    (apply hash-map l)))\n\n(defn read-number\n  [reader initch]\n  (loop [buffer (gstring/StringBuffer. initch)\n         ch (read-char reader)]\n    (if (or (nil? ch) (whitespace? ch) (macros ch))\n      (do\n        (unread reader ch)\n        (let [s (. buffer (toString))]\n          (or (match-number s)\n              (reader-error reader \"Invalid number format [\" s \"]\"))))\n      (recur (do (.append buffer ch) buffer) (read-char reader)))))\n\n(defn read-string*\n  [reader _]\n  (loop [buffer (gstring/StringBuffer.)\n         ch (read-char reader)]\n    (cond\n     (nil? ch) (reader-error reader \"EOF while reading\")\n     (identical? \"\\\\\" ch) (recur (do (.append buffer (escape-char buffer reader)) buffer)\n                        (read-char reader))\n     (identical? \\\" ch) (. buffer (toString))\n     :default (recur (do (.append buffer ch) buffer) (read-char reader)))))\n\n(defn special-symbols [t not-found]\n  (cond\n   (identical? t \"nil\") nil\n   (identical? t \"true\") true\n   (identical? t \"false\") false\n   :else not-found))\n\n(defn read-symbol\n  [reader initch]\n  (let [token (read-token reader initch)]\n    (if (gstring/contains token \"/\")\n      (symbol (subs token 0 (.indexOf token \"/\"))\n              (subs token (inc (.indexOf token \"/\")) (.-length token)))\n      (special-symbols token (symbol token)))))\n\n(defn read-keyword\n  [reader initch]\n  (let [token (read-token reader (read-char reader))\n        a (re-matches* symbol-pattern token)\n        token (aget a 0)\n        ns (aget a 1)\n        name (aget a 2)]\n    (if (or (and (not (undefined? ns))\n                 (identical? (. ns (substring (- (.-length ns) 2) (.-length ns))) \":/\"))\n            (identical? (aget name (dec (.-length name))) \":\")\n            (not (== (.indexOf token \"::\" 1) -1)))\n      (reader-error reader \"Invalid token: \" token)\n      (if (and (not (nil? ns)) (> (.-length ns) 0))\n        (keyword (.substring ns 0 (.indexOf ns \"/\")) name)\n        (keyword token)))))\n\n(defn desugar-meta\n  [f]\n  (cond\n   (symbol? f) {:tag f}\n   (string? f) {:tag f}\n   (keyword? f) {f true}\n   :else f))\n\n(defn wrapping-reader\n  [sym]\n  (fn [rdr _]\n    (list sym (read rdr true nil true))))\n\n(defn throwing-reader\n  [msg]\n  (fn [rdr _]\n    (reader-error rdr msg)))\n\n(defn read-meta\n  [rdr _]\n  (let [m (desugar-meta (read rdr true nil true))]\n    (when-not (map? m)\n      (reader-error rdr \"Metadata must be Symbol,Keyword,String or Map\"))\n    (let [o (read rdr true nil true)]\n      (if (satisfies? IWithMeta o)\n        (with-meta o (merge (meta o) m))\n        (reader-error rdr \"Metadata can only be applied to IWithMetas\")))))\n\n(defn read-set\n  [rdr _]\n  (set (read-delimited-list \"}\" rdr true)))\n\n(defn read-regex\n  [rdr ch]\n  (-> (read-string* rdr ch) re-pattern))\n\n(defn read-discard\n  [rdr _]\n  (read rdr true nil true)\n  rdr)\n\n(defn macros [c]\n  (cond\n   (identical? c \\\") read-string*\n   (identical? c \\:) read-keyword\n   (identical? c \\;) not-implemented ;; never hit this\n   (identical? c \\') (wrapping-reader 'quote)\n   (identical? c \\@) (wrapping-reader 'deref)\n   (identical? c \\^) read-meta\n   (identical? c \\`) not-implemented\n   (identical? c \\~) not-implemented\n   (identical? c \\() read-list\n   (identical? c \\)) read-unmatched-delimiter\n   (identical? c \\[) read-vector\n   (identical? c \\]) read-unmatched-delimiter\n   (identical? c \\{) read-map\n   (identical? c \\}) read-unmatched-delimiter\n   (identical? c \\\\) read-char\n   (identical? c \\#) read-dispatch\n   :else nil))\n\n;; omitted by design: var reader, eval reader\n(defn dispatch-macros [s]\n  (cond\n   (identical? s \"{\") read-set\n   (identical? s \"<\") (throwing-reader \"Unreadable form\")\n   (identical? s \"\\\"\") read-regex\n   (identical? s\"!\") read-comment\n   (identical? s \"_\") read-discard\n   :else nil))\n\n(defn read\n  \"Reads the first object from a PushbackReader. Returns the object read.\n   If EOF, throws if eof-is-error is true. Otherwise returns sentinel.\"\n  [reader eof-is-error sentinel is-recursive]\n  (let [ch (read-char reader)]\n    (cond\n     (nil? ch) (if eof-is-error (reader-error reader \"EOF while reading\") sentinel)\n     (whitespace? ch) (recur reader eof-is-error sentinel is-recursive)\n     (comment-prefix? ch) (recur (read-comment reader ch) eof-is-error sentinel is-recursive)\n     :else (let [f (macros ch)\n                 res\n                 (cond\n                  f (f reader ch)\n                  (number-literal? reader ch) (read-number reader ch)\n                  :else (read-symbol reader ch))]\n     (if (identical? res reader)\n       (recur reader eof-is-error sentinel is-recursive)\n       res)))))\n\n(defn read-string\n  \"Reads one object from the string s\"\n  [s]\n  (let [r (push-back-reader s)]\n    (read r true nil false)))\n\n\n;; read instances\n\n(defn ^:private zero-fill-right-and-truncate [s width]\n  (cond (= width (count s)) s\n        (< width (count s)) (subs s 0 width)\n        :else (loop [b (gstring/StringBuffer. s)]\n                (if (< (.getLength b) width)\n                  (recur (.append b \"0\"))\n                  (.toString b)))))\n\n(defn ^:private divisible?\n  [num div]\n  (zero? (mod num div)))\n\n(defn ^:private indivisible?\n  [num div]\n    (not (divisible? num div)))\n\n(defn ^:private leap-year?\n  [year]\n  (and (divisible? year 4)\n       (or (indivisible? year 100)\n           (divisible? year 400))))\n\n(def ^:private days-in-month\n  (let [dim-norm [nil 31 28 31 30 31 30 31 31 30 31 30 31]\n        dim-leap [nil 31 29 31 30 31 30 31 31 30 31 30 31]]\n    (fn [month leap-year?]\n      (get (if leap-year? dim-leap dim-norm) month))))\n\n(def ^:private timestamp-regex #\"(\\d\\d\\d\\d)(?:-(\\d\\d)(?:-(\\d\\d)(?:[T](\\d\\d)(?::(\\d\\d)(?::(\\d\\d)(?:[.](\\d+))?)?)?)?)?)?(?:[Z]|([-+])(\\d\\d):(\\d\\d))?\")\n\n(defn ^:private parse-int [s]\n  (let [n (js/parseInt s)]\n    (if-not (js/isNaN n)\n      n)))\n\n(defn ^:private check [low n high msg]\n  (when-not (<= low n high)\n    (reader-error nil (str msg \" Failed:  \" low \"<=\" n \"<=\" high))) \n  n)\n\n(defn parse-and-validate-timestamp [s]\n  (let [[_ years months days hours minutes seconds fraction offset-sign offset-hours offset-minutes :as v] \n        (re-matches timestamp-regex s)]\n    (if-not v\n      (reader-error nil (str \"Unrecognized date/time syntax: \" s))\n      (let [years (parse-int years)\n            months (or (parse-int months) 1)\n            days (or (parse-int days) 1)\n            hours (or (parse-int hours) 0)\n            minutes (or (parse-int minutes) 0)\n            seconds (or (parse-int seconds) 0)\n            fraction (or (parse-int (zero-fill-right-and-truncate fraction 3)) 0)\n            offset-sign (if (= offset-sign \"-\") -1 1)\n            offset-hours (or (parse-int offset-hours) 0)\n            offset-minutes (or (parse-int offset-minutes) 0)\n            offset (* offset-sign (+ (* offset-hours 60) offset-minutes))]\n        [years\n         (check 1 months 12 \"timestamp month field must be in range 1..12\")\n         (check 1 days (days-in-month months (leap-year? years)) \"timestamp day field must be in range 1..last day in month\")\n         (check 0 hours 23 \"timestamp hour field must be in range 0..23\")\n         (check 0 minutes 59 \"timestamp minute field must be in range 0..59\")\n         (check 0 seconds (if (= minutes 59) 60 59) \"timestamp second field must be in range 0..60\")\n         (check 0 fraction 999 \"timestamp millisecond field must be in range 0..999\")\n         offset]))))\n\n(defn parse-timestamp\n  [ts]\n  (if-let [[years months days hours minutes seconds ms offset]\n           (parse-and-validate-timestamp ts)]\n    (js/Date.\n     (- (.UTC js/Date years (dec months) days hours minutes seconds ms)\n        (* offset 60 1000)))\n    (reader-error nil (str \"Unrecognized date/time syntax: \" ts))))\n\n(defn ^:private read-date\n  [s]\n  (if (string? s)\n    (parse-timestamp s)\n    (reader-error nil \"Instance literal expects a string for its timestamp.\")))\n\n\n(defn ^:private read-queue\n  [elems]\n  (if (vector? elems)\n    (into cljs.core.PersistentQueue/EMPTY elems)\n    (reader-error nil \"Queue literal expects a vector for its elements.\")))\n\n\n(defn ^:private read-uuid\n  [uuid]\n  (if (string? uuid)\n    (UUID. uuid)\n    (reader-error nil \"UUID literal expects a string as its representation.\")))\n\n(def *tag-table* (atom {\"inst\"  read-date\n                        \"uuid\"  read-uuid\n                        \"queue\" read-queue}))\n\n(def *default-data-reader-fn*\n  (atom nil))\n\n(defn maybe-read-tagged-type\n  [rdr initch]\n  (let [tag (read-symbol rdr initch)\n        pfn (get @*tag-table* (str tag))\n        dfn @*default-data-reader-fn*]\n    (cond\n     pfn (pfn (read rdr true nil false))\n     dfn (dfn tag (read rdr true nil false))\n     :else (reader-error rdr\n                         \"Could not find tag parser for \" (str tag)\n                         \" in \" (pr-str (keys @*tag-table*))))))\n\n(defn register-tag-parser!\n  [tag f]\n  (let [tag (str tag)\n        old-parser (get @*tag-table* tag)]\n    (swap! *tag-table* assoc tag f)\n    old-parser))\n\n(defn deregister-tag-parser!\n  [tag]\n  (let [tag (str tag)\n        old-parser (get @*tag-table* tag)]\n    (swap! *tag-table* dissoc tag)\n    old-parser))\n\n(defn register-default-tag-parser!\n  [f]\n  (let [old-parser @*default-data-reader-fn*]\n    (swap! *default-data-reader-fn* (fn [_] f))\n    old-parser))\n\n(defn deregister-default-tag-parser!\n  []\n  (let [old-parser @*default-data-reader-fn*]\n    (swap! *default-data-reader-fn* (fn [_] nil))\n    old-parser))\n"
  }
]