All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.1.1] - 2026-08-05
Fixed
- The package
descriptioninmix.exsexceeded Hex's 300-character limit, somix hex.build(and thusmix hex.publish, and CI's package-assembly check) failed outright. Trimmed to fit while keeping the same content.
[0.1.0] - 2026-08-05
Added
- Documentation split into embedding vs. language layers, matching
logos's own
guides/language/structure:guides/TUTORIAL.md,guides/EXAMPLES.md, andguides/CHEATSHEET.mdare now entirely about using Aletheia as a library from Elixir (all rewritten: consult/query/query_once/query_lazy+next_solution, errors at the host boundary, the REPL, multi-database isolation, pagination). The previous content of those three files (the Prolog dialect itself) moved toguides/language/TUTORIAL.md,guides/language/ALETHEIA.md(wasREFERENCE.md),guides/language/ALETHEIA_EXAMPLES.md(wasEXAMPLES.md), andguides/language/ALETHEIA_CHEATSHEET.md(wasCHEATSHEET.md), with every embedding-specific bit (install, the REPL, the old "Top-level Elixir API" table) trimmed out since that's the top-level guides' own job now.CASE_STUDY.md,README.md, andmix.exs'sextras/groups_for_extrasupdated throughout. - Documentation overhaul, matching the depth of sibling projects
(logos, episteme): README.md rewritten with a "Why embed a Prolog
dialect in Elixir?" section, a
## Componentsbreakdown of the reader/term-IR/database/engine/builtins/REPL pipeline, CI/Docs/ License badges, and a full## Documentationindex. New CASE_STUDY.md: a single larger, real-world worked example (a structured-log auditor combining a DCG grammar, real strings, the dynamic database, and grouped aggregation), written as one complete.alpprogram rather than Elixir term-building — the same problem Episteme's ownCASE_STUDY.mdsolves the other way, worth comparing side by side. Wired intomix.exs'sextras/package.files. - The reader now reaches everything episteme's v0.2-v0.5 work
shipped, closing out the entire numbered roadmap end to end through
.alpsource:- Double-quoted string literals (
"hello") — a real Elixir binary, a genuinely distinct term class from atoms, no escape processing (same simplification quoted atoms already have). {Goal}(ISO's own{}/1 term shape) — ordinary syntax anywhere a term can appear.- DCG rules (
Head --> Body.) — a dedicated clause form (not parsed through the operator table), producing a{:dcg, head, body}formEpisteme.Database.consult_forms/2translates and stores;phrase/2,3anddcg_translate/2were already plain-atom-functor goals needing no reader changes. - Standard order of terms operators (
@<,@>,@=<,@>=) — added to the default operator table (priority 700, same class as=/==) and toAletheia.Reader.ControlSyntax's translation table (order_less/order_greater/order_less_or_equal/order_greater_or_equal). - Every other v0.2-v0.5 addition (
ignore/1,unify_with_occurs_check/2,bagof/3/setof/3, term inspection, the dynamic-database round-out, the list-predicate family, the atom/string conversion family,format/1,2) was already reachable with zero reader changes — plain atom-functor goals flow through untouched, the same waybetween/3and friends did inepisteme_ahead_of_schedule. - Known, deliberate gap:
=../2(univ/2works, but its ISO punctuation can't be tokenized by this reader's simplified lexer — a lone.inside a longer operator token is indistinguishable from end-of-clause). mix.exs'sepistemedependency switched to a path dependency ({:episteme, path: "../episteme"}), interim until episteme's own v0.2-v0.5 work is released to Hex.- Fixed a pre-existing gap found along the way:
mix precommit'ssobelowstep ran without--skip, silently ignoring every#sobelow_skipcomment in the codebase (episteme's own equivalent alias already had this right). Now"sobelow --skip", matching episteme; used it to justify the reader's own atom-creation risk (routed through oneto_prolog_atom/1instead of five separate call sites) the same way episteme'sEpisteme.Builtins.Stringsalready does. - Guides/
README.mdupdated throughout; seetest/aletheia/reader_test.exs,test/aletheia/reader/control_syntax_test.exs, andtest/aletheia/end_to_end_test.exsfor the new coverage.
- Double-quoted string literals (
- Docs are now cross-referenced from the project config:
mix.exssetshomepage_url/canonical(docs) and a"Docs"package link pointing at the GitHub Pages site (https://joetjen.github.io/aletheia/), and the README references it too. The site itself is built by.github/workflows/docs.ymlon every push tomain; it won't actually be live until aletheia's first release reachesmain. - A handful of v0.2/v0.3 predicates already work, ahead of schedule:
assert/1,asserta/1,assertz/1,retract/1,retractall/1(dynamic database),findall/3(aggregation),forall/2, andbetween/3(not even ISO, a de facto library predicate) — all shipped as part ofepisteme0.1.0's own initial extraction and confirmed working end-to-end through.alpsource andAletheia's top-level API with zero Aletheia-side code changes (none involve ISO punctuation, soAletheia.Reader.ControlSyntaxnever needed to touch them). Previously undocumented and untested at this level; now covered inguides/language/ALETHEIA.mdandtest/aletheia/end_to_end_test.exs. - v0.1 milestone, complete: reader, term representation,
unification, SLD-resolution, a genuinely clause-scoped cut, core
control constructs,
is/2arithmetic, exceptions, a runtime-mutableop/3operator table, the v0.1 list predicate family, minimal I/O, and consult/REPL. Seeguides/language/ALETHEIA.mdfor the full predicate list. - Reader: a real Aether grammar
(
lib/aletheia/reader/grammar.aether) withIchor.Toolkit.Prattwired in via@native(...)for operator-precedence parsing — comma is a genuinexfy(1000)operator (unlike Ichor's owntest/prolog/prolog.aetherfixture, which leaves it grammar-structural), needing a second@nativeentry point (arg_term) parsed at a raised minimum precedence for ISO's own "argument priority =< 999" rule.:- op(Prec, Type, Name).directives genuinely extend the operator table mid-file. - Cut: implemented as a distinct clause-scoped primitive via
throw/catchbarriers, not a thin wrapper overIchor.Backtrack.Tree'sonce/1(which only limits solution count, not clause commitment) — seeguides/language/ALETHEIA.md#cutfor the distinguishing test cases. Every predicate call (andcall/N,once/1,\+/1, each cut-opaque per ISO) mints its own barrier. - Ahead-of-time grammar generation: the grammar is compiled via
mix ichor.gen(aliasedmix gen.grammar) into a checked-inlib/aletheia/reader/grammar_generated.ex, rather than parsed at runtime —ichor(the Aether front-end,Grammar.Analysis, both codegen backends) is a dev-only dependency (only: :dev, runtime: false) and never ships in a release;ichor_runtimeis the one real runtime dependency. - Top-level API:
Aletheia.consult/1,consult_string/2,query/2,query_once/2, and a genuinely lazyquery_lazy/2+next_solution/2pair (pulls one solution at a time instead of forcing an entire, possibly-infinite search) — the REPL is built on the lazy pair specifically so it never over-computes past what a user actually asks to see via;.
Changed
- Split into two packages:
Aletheia.Term,Aletheia.Database,Aletheia.Engine, andAletheia.Builtins.*moved out of this repository into a new sibling package,episteme(Episteme.Term/Database/Engine/Builtins.*, plus a top-levelEpistememodule for querying an already-built term against an already-built database, with no parser involved). This package (aletheia) is now the syntax front-end only: the reader and REPL, depending onepistemefor everything past parsing. The top-levelAletheia.consult/1,consult_string/2,query/2,query_once/2,query_lazy/2, andnext_solution/2API is unchanged for existing callers; code that referencedAletheia.Term/Database/Engine/Builtins.*directly needs to referenceEpisteme.*instead. epistemepublished to Hex: the dependency onepistemeis now a plain Hex version requirement ({:episteme, "~> 0.1"}) instead of a path dependency onto a sibling checkout — no localepistemecheckout is needed to buildaletheiaanymore. Pulled inichor_runtime ~> 0.2andichor ~> 0.3transitively, both bumped here too.epistemebumped to~> 0.2: covers everythingepisteme0.2.0 shipped past its initial 0.1.0 release — standard order of terms, term inspection (functor/3/arg/3/univ/2), the dynamic-database round-out (dynamic/1/abolish/1/clause/2),bagof/3/setof/3, a real string type plus the full atom/string conversion family,format/1,2, and DCG support (-->/phrase) — all reachable from.alpsource now (see CASE_STUDY.md for a worked example touching most of it). Briefly a path dependency onto a sibling checkout while this work was developed and verified againstepisteme's owndevelopbranch; back to a plain Hex version requirement now that it's released.- New
Aletheia.Reader.ControlSyntaxmodule:episteme0.1.0 renamed the control constructs and comparison operators it dispatches on from ISO Prolog punctuation to plain-English names (,→and/2,;→or/2,!→cut,\+→not/1,=→unify/2, and so on — seeepisteme's ownCHANGELOG.md).Aletheia.Readeritself still parses and produces the classic punctuation-named terms (unchanged, and still the correct reading of.alpsource);Aletheia's top-level API now translates rule bodies and query goals throughAletheia.Reader.ControlSyntax.to_goal/1before handing them toEpisteme, including rewriting the ISO(Cond -> Then ; Else)nested pattern to the explicitif_then_else/3epistemenow requires. No change to.alpsource syntax or to any publicAletheiafunction signature.
Fixed
- Found (and fixed upstream, in
ichor/ichor_runtime) a real gap in theichor/ichor_runtimepackage split:ichor_runtime, as originally split, did not includeIchor.Toolkit.Pratt,Ichor.Toolkit.TermWalk, orIchor.Backtrack(+.Bindings/.Term/.Tree) — all three are called directly by Aletheia's own runtime code (the@nativePratt callback, the term/engine/builtin layers), not just by generated parser code. Released as part ofichor_runtime0.1.0 /ichor0.2.0.