{"id":20425364,"url":"https://github.com/catseye/cardboard-prolog","last_synced_at":"2026-02-27T10:02:00.127Z","repository":{"id":142239884,"uuid":"207822030","full_name":"catseye/Cardboard-Prolog","owner":"catseye","description":"MIRROR of https://codeberg.org/catseye/Cardboard-Prolog : A bare-bones inference engine in 120 lines of purely functional Scheme","archived":false,"fork":false,"pushed_at":"2022-11-30T19:15:01.000Z","size":4,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-04T06:06:39.403Z","etag":null,"topics":["deductive-inference","didactic","inference-engine","logic-programming","prolog-interpreter","pure-functional"],"latest_commit_sha":null,"homepage":"https://catseye.tc/node/Cardboard%20Prolog","language":"Scheme","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/catseye.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-11T13:40:39.000Z","updated_at":"2024-05-13T01:56:30.000Z","dependencies_parsed_at":"2023-03-29T18:51:22.705Z","dependency_job_id":null,"html_url":"https://github.com/catseye/Cardboard-Prolog","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/catseye/Cardboard-Prolog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FCardboard-Prolog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FCardboard-Prolog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FCardboard-Prolog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FCardboard-Prolog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/catseye","download_url":"https://codeload.github.com/catseye/Cardboard-Prolog/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FCardboard-Prolog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278283455,"owners_count":25961311,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deductive-inference","didactic","inference-engine","logic-programming","prolog-interpreter","pure-functional"],"created_at":"2024-11-15T07:13:03.184Z","updated_at":"2025-10-04T07:53:16.578Z","avatar_url":"https://github.com/catseye.png","language":"Scheme","funding_links":[],"categories":[],"sub_categories":[],"readme":"Cardboard Prolog\n================\n\nThis is a tiny inference engine (~120 lines of purely functional R5RS\nScheme) I wrote a while back, when I was refreshing myself on how a\nProlog interpreter works.  I found several descriptions and examples\nof Prolog interpreters online, but none were quite what I wanted.\n\nCardboard Prolog lacks all the amenties the Prolog language proper, and\nit uses Scheme literals instead of Prolog syntax, but it does do the thing\nthat's at the core of Prolog execution: deduction based on Horn clauses.\n\nThere are no comments, but there is a suite of tests.  You can run the\ntests with (for example) Chicken Scheme, by running\n\n    csi -b test-cardboard-prolog.scm\n\nI'll also try to briefly describe what's going on here.\n\n### Overview of the Design and Implementation\n\nA term in Cardboard Prolog is represented by a Scheme list, where atoms\nare symbols and variables are vectors of length 1 or 2.  The first\nentry of a variable vector is a symbol giving the variable name, and the\nsecond entry is an index which is used to disambiguate different instances\nof a variable.\n\n`ground?` and `variable?` are predicates on terms.\n\n`rename-term` takes a term and returns a new term which is the same as\nthe input term except that all variables are given new indexes.  The\npurpose is to obtain a \"fresh\" version of the term with no bound variables.\n\n`collect-vars` takes a term and returns a list of all variables found\nin it.\n\n`match-var` and `unify` are mutually-recursive functions which implement\nunification.  `unify` takes two patterns (terms which may contain\nvariables) and returns a list of bindings if the each pattern matches\nthe other, or `#f` if they cannot be matched.  Such a list of bindings\nis called an \"environment\" (abbreviated `env`) in this code.  Each binding\nis a two-element list of a variable, and the subterm that it matched with,\nwhich may itself be a variable, or contain variables.\n\nNote that, for simplicitly, the unification algorithm here does not perform\nan occurs check.  For the sake of correctness, it should perform one, but\nsince it's very easy to implement and doesn't really add explanatory value\nto the exposition,  I left it out.  You can undertake adding one as an\nexercise, if you like.\n\n`expand` takes a term and an environment and returns a new term which\nis the same as the input term except that all variables are replaced\nwith the terms that they are bound to in the environment.  `subst` is a\nhelper function used by `expand`.\n\nDuring the search process, a variable like `#(X)` will be instantiated\nto a variable like `#(X 2)` (where 2 indicates the depth of the search),\nand it is `#(X 2)` that will match a term, but this information is\nusually irrelevant to the user, for whom the report that `#(X)` matched\nwould be more meaningful.  `collapse-env` (with its helper functions\n`expand-env` and `expand-binding`) and `restrict-to-vars` are used clean\nup the output of the engine, and make its results more presentable to\nthe user in this way.\n\n`search` implements the core inference process.  It is given a database\n(a list of facts and rules, where a fact is simply a rule with no\npremises), and a list of goals.  It keeps track of the current\nenvironment (list of bindings) and the current search depth.\n\n`search` tries to `unify` each rule in the database with the first goal\nof the current list of goals, under the current environment.  If this\nsucceeds, it takes the unifying environment (which we now call a\n\"unifier\"), `expand`s the consequent of the rule and the remaining goals\nusing the unifier, joins these together to obtain a new list of goals,\nand recursively calls itself with the new list of goals and the new\nenvironment, to continue to the search.  If there are no more goals in\nthe list to satisfy, the search was a success and the final unifying\nenvironment is returned.\n\nBut note that `search` might actually return to itself, because it\ncalls itself recursively.  So it returns a list of unifying environments,\nand collects these lists to ultimately return all of the successful searches\nin the database.\n\nA real Prolog interpreter would do this piecemeal, asking the user if they\nwant it to search for the next answer after each answer is found.  For\nsimplicitly, Cardboard Prolog always returns all the answers, and\nif there are infinitely many answers, this will simply not terminate.\n\n(This design choice was for simplicitly, but it would certainly be\nan interesting exercise to rewrite it to work in the fashion of Prolog.\nMany of the descriptions I found online did describe how Prolog\ninterpreters accomplish this, but none of them phrased it in terms of\ncontinuations, which is probably how you'd want to do it in Scheme.)\n\nFinally, `match-all` is a driver function for `search`, and the main\ninterface to the inference engine.  It takes a database and a list of\ngoals, and returns a list of comprehensible answers.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fcardboard-prolog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcatseye%2Fcardboard-prolog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fcardboard-prolog/lists"}