v1.0.0
First stable release: the exported surface is now considered stable.
This release fixes the ISO 13211-1 conformance defects a first systematic audit
of the standard's syntax and builtin error contracts turned up — reachable from
ordinary Prolog source text, and each now covered by
tests/iso-conformance.lisp, which states its cases as the standard does.
Fixed
- atoms are now identified by their text, as ISO 13211-1 6.4.2 requires.
Quoting an atom used to produce a different atom —hello == 'hello'was
false — because an unquoted name was interned upcased while a quoted one kept
its spelling. The two encodings now live in different packages
(cl-prolog.verbatim-atomsholds any text containing an upper-case letter),
which makes the text/symbol mapping a bijection, and=/2,==/2,
compare/3andsort/2all decide identity on text. Consequences, each a
behavior change:writeq/1andprint/1no longer lose case:'FooBar'printed as
foobar, soterm_to_atom/2could not round-trip a mixed-case atomatom_codes/2,atom_chars/2,char_code/2,atom_string/2,
upcase_atom/2,downcase_atom/2,char_type/2andformat/2's~a
and~sreport the atom's text rather than its upcased symbol name —
atom_codes(abc, X)gave[65,66,67]andformat("~a", [hello])
printedHELLO- the standard order of terms ranks atoms by their characters (ISO 7.2.3),
so'B' @< aholds; it previously compared upcased symbol names, then
home packages, then creation order, which also letX == Ybe false for
a pairX = Yunified []is the atom of text"[]":[] == '[]'andatom_length([], 2)char_conversion/2andopen/3receive the character and pathname their
argument spells, not an upcased one
New exportedprolog-atomandprolog-atom-textname an atom by its text
from Lisp, which is now the only way to write one whose text is not lower
case; see Semantics.
- an atom that is an operator can now be written as a term, per ISO 6.3.3.1
(as an argument) and 6.3.4.3 (bracketed).functor(T, +, 2),
T =.. [+, 1, 2],X = (-),atom_length(-, 1),current_op(P, T, +),
compare(<, A, B)andsort(0, @<, L, S)all raised a syntax error, which
madeop/3,sort/4,predsort/3andcompare/3unusable from Prolog
source. An operator with no operand after it now reads as its atom; an
operator used unbracketed as another operator's left operand still does not,
as ISO requires the brackets there - a run of graphic characters is now one token, per ISO 6.4.2, and an
undeclared one is an atom. The tokenizer matched the longest declared
operator instead, so:- op(700, xfx, ===).— declaring an operator that by
definition does not exist yet — split===into==and=and failed to
parse assertz/1,asserta/1,retract/1andretractall/1now convert a
:-/2 term to a clause, per ISO 7.6.1. Only the Lisp-level
(:- HEAD . BODY-GOALS)shape was recognized, soassertz((h :- Body))from
Prolog source stored the whole:-/2 term as a fact head and lefth
undefined; runtime rule assertion was broken outright.clause/2returns the
body in the shape source text spells it, and a fact matches
retract((h :- true))- an atom is now quoted only when it cannot read back bare, per ISO 6.4.2.
writeq/1,print/1,~qandwrite_canonical/1quoted every atom that was
not a plain atom name, so a graphic token printed as'+','=..','@<',
'\+'and the solo chars as'!',';'— all of which are name tokens
needing no quotes. Still quoted, each because the reader forces it:,and
|(they would read back as separators),{}(not yet read as an atom), a
lone.(the end token per ISO 6.4.8), and a compound's functor unless it is
a plain name (this reader does not yet require(to follow a functor with no
layout, so a bare+(1,2)would read as the prefix operator applied to
(1,2), andwrite_canonical/1output has to stay re-readable) - the lexer now reads the numeric notations of ISO 6.4.4: the character-code
constant0'c(including0'',0'\nand0'\x41\) and the radix constants
0x,0o,0b. None of them parsed, soX is 0'a— the way a character code
is written — was a syntax error, and so was reading a term containing one from
a stream, since the source splitter also mistook0''s quote for the start of
a quoted atom - escape sequences in a quoted token are now decoded, per ISO 6.4.2.1. A
\passed the following character through verbatim instead, so'a\nb'held
the letternrather than a newline.\a \b \e \f \n \r \t \v \0, the radix
forms\xHH\and\OOO\,\\ \' \" \``, and the`-newline continuation are
all honored, in'...'and"..."alike - the bitwise operators of ISO 6.3.4.4 table 7 are now declared:
/\,\/,
xorat 500 yfx,<<and>>at 400 yfx, and prefix\at 200 fy. Their
evaluable functors already existed, soX is 1 << 3andX is 12 /\ 10were
computable but unwritable asserta/1andassertz/1no longer accept an unbound argument: they
stored a clause whose head was a fresh variable, which no call could ever
match, instead of raising the instantiation_error ISO 8.9.1.3 requires. A
rule with a variable head is rejected the same waycompare/3validates a bound Order argument (ISO 8.4.2.3): a non-atom is a
type_error(atom, _)and an atom other than<,=,>a
domain_error(order, _), where both silently failedclause/2rejects a non-callable Body withtype_error(callable, _)(ISO
8.8.1.3) instead of failing, andcurrent_input/1andcurrent_output/1
reject a non-stream argument withdomain_error(stream, _)(ISO 8.11.1.3),
which used to fail silently and hide a misspelled aliasnumber_chars/2andnumber_codes/2raisesyntax_error/1for text that
spells no number, as ISO 8.16.7.3 and 8.16.8.3 require, rather than
domain_error(number_text, _); theprolog-syntax-errorcondition is now
exported.read_term_from_atom/3validates its options through the same
read-option checkerread_term/2,3uses, so an unsupported option is a
domain_error(read_option, _)instead of being accepted and ignoredatom_number/2still fails rather than raising on unparseable text, and now
decides that before running its continuation, so an error raised by a later
goal is no longer mistaken for its own and swallowed- a bare
{}reads as the atom of that name (ISO 6.3.6) (If -> Then)without an else branch now works. ISO 7.8.7 makes if-then a
control construct in its own right, which fails when the condition fails; only
the if-then-else form had a solver, so the common( Cond -> Then )raised
existence_error(procedure, (->)/2).(If *-> Then)likewise- arithmetic now follows ISO 9.x where it had drifted:
**is 9.3.1's float
power (2 ** 3is8.0) while^is 9.3.10's integer-preserving one (2 ^ 3
is8), raising an integer to a negative integer power is
type_error(float, Base), and dividing two integers exactly stays integral
(4 / 2was2.0). Float overflow and underflow reach Prolog as
evaluation_error(float_overflow)/(underflow)instead of escaping as a
host condition - stream and I/O error contracts corrected across ISO 8.11-8.14: a
designator that could never name a stream isdomain_error(stream_or_alias)
rather thanexistence_error;open/3,4reports a non-atom mode as
type_error(atom);get_char/2andpeek_char/2reject a bound non-character
argument withtype_error(in_character)where they silently failed;
get_byte/put_byteusetype_error(in_byte)/type_error(byte)and check
the argument before the stream, and the stream's permission error names the
stream's actual type as the culprit (text_stream) rather than the required
one;write_term/2,3reports an unsupported option in thewrite_option
domain; andcurrent_char_conversion/2rejects a non-character argument char_code/2raisesrepresentation_error(character_code)for an integer that
names no character, per ISO 8.16.6.3, rather than a domain error; the
prolog-representation-errorcondition is new and exportedcurrent_prolog_flag/2raisesdomain_error(prolog_flag, F)for an atom that
names no flag (ISO 8.17.2.3) instead of failing- one operator name now means one operator in the operator table, whichever
symbol spells it, so redefining a standard operator throughop/3replaces
its entry instead of adding a second invisible one at a different priority numbervars/3uses the ISO 8.14.2 functor'$VAR', whose text is upper
case; the distinct lower-case atom'$var'is no longer written as a
variable name- CI: the SBCL check timeout now sends a follow-up
SIGKILL, since SBCL can
defer a bareSIGTERMpast a tight compiled loop;nix flake check's step
now carries its own budget narrower than the job's, so a hang reports an
attributable timeout instead of an ambiguous job cancellation - CI:
packages.defaultandapps.test(the two entry points README.md
documents) are now actually built bynix flake check, not merely
evaluated —apps.testin particular exercises a genuinely different code
path (cl-weave's own dynamic-space sizing) that was previously untested - CI: tag releases now require a green
nix flake checkon the tagged commit
before publishing, closing a gap where a redmaincould still be tagged
and released; the release workflow's:versionextraction is now anchored
the same wayflake.nix's already was, so a comment merely mentioning
:versioncan no longer shadow the real field - CI: a missing
CACHIX_CACHErepository variable now emits a build warning
instead of silently falling back to an uncached build with no other signal docs.yml's path filter now includescl-prolog.asd, so a version-only
bump reliably retriggers a documentation rebuild
Added
tests/atom-canonicalization.lisp: the atom text/symbol bijection, quoting
invisibility, case significance,writeqround-tripping (including a
property test over generated mixed-case texts), text-based ordering, and the
agreement of=/2,==/2andcompare/3- regression coverage for operator atoms as arguments, graphic-token lexing,
and:-/2 clause conversion throughassertz/retract/clause .github/dependabot.ymlfor thegithub-actionsecosystem (root workflows
and thesetup-nixcomposite action).github/workflows/benchmarks.yml: runs the self-contained micro-benchmarks
onworkflow_dispatchand a weekly schedule only — never on push or pull
request, consistent with benchmarks being diagnostic rather than a gate
Changed
- split three of the largest source files along existing internal seams,
with no behavior change:data.lispintologic-variable.lisp/
clause.lisp/predicate-index.lisp/data.lisp;unification.lisp
intoenvironment-index.lisp/unification.lisp;prover.lispinto
proof-state.lisp/prover.lisp - consolidated repeated macro-shaped code across the engine and builtins:
duplicated constraint-hook and first-solution proof-search prologues in
prover.lisp/builtins/control.lisp; the dual:current/:explicit
stream builtin bodies inbuiltins/io-streams.lisp/builtins/io-code.lisp;
theaggregate_all/3reducer dispatch, several scalar-argument guard
checks, and three-way duplicated builtin triples acrossbuiltins/*.lisp;
the runtime-error condition classes and their%raise-*wrappers now
generate from one shared table instead of two hand-maintained lists - removed an unused clause-indexing structure (four
rulebaseslots plus
their maintenance code) that was superseded by the predicate-descriptor
index and had no production reader; removed a vestigial cycle-detection
parameter from the unification indexer left over from before the
tortoise-and-hare occurs-check - upgraded
cl-weavev0.4.0→v1.0.0(additive; fixes an integer-shrinker
bug and a swallowed-hook-timeout bug relevant to this suite) and pinned
paredit-clitov0.7.0(previously an unpinned input silently frozen
three commits behind what it appeared to track) src/weave.lisp's:setquery-assertion kind now reports structured
missing/unexpected solutions on failure via acl-weave:defmatcher
(:to-solve) instead of collapsing to a bare pass/fail boolean- rewrote several
tests/*.lispcases to use the existingdeftest-queries/
assert-queryhelpers instead of hand-rolleddolist/signals-condition
boilerplate, and added coverage for five previously-untested branches
(string-scanner escape handling,%text-ofcoercion arms,sub_string/5's
fully-enumerated mode, a decimal-digit-limit boundary, and aformat/2
column directive) flake.nixnow also builds onaarch64-darwin, verified locally end to
end (nix build,nix develop,nix flake check) — the prior
Linux-only restriction was self-imposed, not inherited from either flake
input- all in-repo
github.com/takeokunn/cl-prologreferences now point at
github.com/nerima-lisp/cl-prolog, the project's current org