Releases: kidoz/dotprolog
Release list
v0.11.0
Added
- SWI-Prolog's
initialization(Goal, When)directive inModern:nowruns Goal where the
directive stands,after_loadisinitialization/1, andmainruns Goal after every other
initialization goal and ends the program with SWI's status — 0 when Goal succeeds, 1 when it fails,
and 2 when it raises an error, with SWI's message — so SWI scripts that start with
:- initialization(main, main).run unchanged. Another When is a
domain_error(initialization_type, When).StrictIsorejects the directive as an extension.
Fixed
dotnet prolog runreports an error a directive raises while its file loads, and exits with status
70, rather than crashing with an unhandled .NET exception.- Consulting a file that has directives or initialization goals from a running goal no longer
crashesRunPendingGoals, and sodotnet prolog run: the file's goals run as soon as the
consulting goal ends, ahead of the goals still waiting.
v0.10.0
Added
\uXXXXand\UXXXXXXXXescapes in quoted text inModern, with exactly four and eight
hexadecimal digits, as SWI-Prolog reads them. A surrogate escape is a syntax error rather than half
of a pair, andStrictIsorejects both forms, which ISO does not have.- In
Modern, a symbol or punctuation character outside ASCII reads as a one-character atom, as
SWI-Prolog reads it:f(😀)isf('😀'),[€, ∀]is a list of two atoms, andwriteq/1writes
such an atom bare. Like SWI-Prolog 10.1, it never joins its neighbours, so😀😀is a syntax error
and- 😀is the prefix operator applied to😀. Brackets and quotation marks outside ASCII, such
as«, stay syntax errors. Unquoted names follow Unicode's identifier properties: any letter or
letter number that is not uppercase starts an atom, as in中文andⅠ, and combining marks and
superscript digits continue one, as inx́andx².
Changed
- In
Modern, a character is a Unicode code point rather than a UTF-16 code unit, so a character
outside the Basic Multilingual Plane is one character with one code:atom_length('😀', N)gives
1,atom_codes('😀', L)gives[128512],char_code(C, 128512)succeeds, and"😀é"reads as a
two-element list. The atom and string predicates count, slice, and enumerate by code point —
atom_length/2,sub_atom/5,atom_concat/3,atom_chars/2,atom_codes/2,char_code/2,
number_codes/2,atom_number/2's0'c,string_length/2,sub_string/5,string_code/3,
split_string/4— andchar_type/2andcode_type/2classify supplementary characters. A
surrogate code is not a character code; a host string's unpaired surrogate reads as U+FFFD. The
ISO predicates keep ISO'srepresentation_error(character_code).StrictIsois unchanged: its
characters remain UTF-16 code units. - In
Modern, the reader and writer work in code points too.0'😀is 128512,\x…\escapes reach
U+10FFFF, and a supplementary letter reads and writes like any other letter —𝑎bcis an atom,
𐐀xa variable, andwriteq/1leaves𝑎bcunquoted. The layout linter's line-length limit
counts code points. - In
Modern, streams andformatwork in code points as well.get_char/1,2,peek_char/1,2,
get_code/1,2, andpeek_code/1,2read a supplementary character as one character, a peek
included, andput_char/1,2andput_code/1,2write one.format/2,3counts columns in code
points, so~tpadding lines up around a supplementary character, and a supplementary fill
character works.char_conversion/2maps any character. - In
Modern, the standard order compares atoms, strings, and functor names by code point, as
SWI-Prolog does. UTF-16 order had put a supplementary character before U+E000–U+FFFF, so
msort(['😀', '\xFFFD\'], L)now gives['\xFFFD\', '😀']; text within the Basic Multilingual
Plane sorts as before.StrictIsokeeps code-unit order. CharacterConversionTabletakes and returns character codes asintrather thanchar, so it
can hold a supplementary character:Convert(int),Set(int, int), andAll()returning
(int Input, int Output)pairs. Hosts that call it directly need recompiling.char_type/2andcode_type/2classify characters outside ASCII from the Unicode data, the way
SWI-Prolog does since 10.1, rather than by rules that put unassigned code points ingraph,
print, andpunct. Now an unassigned code point has no type, a format character such as U+200B
iscntrl, andalpha,upper, andlowerfollow Unicode's Alphabetic, Uppercase, and Lowercase
properties, soªislower,Ⅰisupper, and the Devanagari vowel signs arealpha. A number
such as²isalnumrather thanpunct. ASCII characters keep their types.writeq/1leaves aModernatom bare when it starts with any letter that is not uppercase, so
中文andx́are no longer quoted.
Fixed
format's~cno longer truncates a code silently: a negative code raises
format_argument_type(c, Code), and a code past Unicode or in the surrogate range raises
representation_error(code_point), as SWI-Prolog does.code_type/2'send_of_lineholds for vertical tab and form feed, as in SWI-Prolog, and for
U+0085, U+2028, and U+2029.- A quoted atom or string holding a layout character other than the space, such as U+00A0 or U+2028,
is written with a\xHEX\escape, because the reader rejects the raw character between quotes;
such an atom now reads back.
v0.9.0
Added
- Lint diagnostics for double-quoted text read one way and used another, judged under the
double_quotesvalue in force where the text appears — the mode's or override's initial value,
moved byset_prolog_flag(double_quotes, _)directives in the file.DPL3011reports a literal
passed to a conversion that needs another list kind, such asnumber_codes(N, "42")while text
reads as characters.DPL3012reports text read as characters that reaches, throughphrase/2,3
or a grammar-rule terminal, a grammar in the same file that compares character codes — the case
that fails silently rather than raising an error. Both help move code-list programs to the
charsdefault. - The SWI-aligned string predicates read text the way SWI-Prolog does, predicate by predicate,
through one shared reader. Character and code lists are text forstring_length/2,
split_string/4,string_code/3,atom_string/2,string_to_atom/2,string_chars/2and
string_codes/2,number_string/2, the term converters, theformattemplate, and~s— so
split_string("a,b", ",", "", P)andterm_string(T, "f(a)")work on double-quoted text under
thecharsdefault — whilestring_concat/3,sub_string/5, the case mappings, and
atomic_list_concat/2,3reject lists as SWI's do. Rejected text raises SWI's errors:
type_error(text, X),type_error(list, X),type_error(character, E),
type_error(character_code, E),format_argument_type(Directive, X), andinstantiation_error
for a partial list. A bound output is compared by text, soatom_string(abc, [a,b,c])succeeds.
The ISO atom predicates are unchanged, and none of this reachesStrictIso, which has no string
library. text_to_string/2andterm_string/3, and thecodes/2andchars/2output sinks for
with_output_to/2andformat/3, which also accept a stream or alias.
Changed
term_to_atom/2andterm_string/2write the term withquoted(true), as SWI does, rather than
in canonical form:term_to_atom(1+2, A)gives'1+2'.with_output_to/2andformat/3check their sink before running the goal or the format, with
SWI's errors —instantiation_error,type_error(output, Sink),existence_error(stream, Sink)—
instead ofdomain_error(output_sink, Sink), and an unbound sink is no longer bound after the goal.number_string/2reads only strings and character or code lists as its text, and fails rather
than trimming when the number is surrounded by layout, as SWI does;string_code/3raises
type_error(integer, I)anddomain_error(not_less_than_zero, I)for a bad index.- Numbers are no longer text where SWI does not take them as text:
split_string/4,
string_code/3, and theformattemplate reject them withtype_error(text, N), and~a
withformat_argument_type(a, N). must_be(text, X)raisesinstantiation_errorfor a partial or non-ground list.- Under the
charsdefault,""is[], and where lists are text[]is empty text:
string_length("", N)gives 0 andformat("", [])prints nothing. The term converters read[]
as the atom, soread_term_from_atom('[]', T, [])still gives[].
Fixed
atomic_list_concat/2,3,upcase_atom/2,downcase_atom/2, andformat's~aaccept strings;
they rejected them with a self-contradictorytype_error(atomic, "…").with_output_to(string(S), write([]))gives"[]"; the captured text no longer passes through an
atom that could be taken for the empty list.string_concat/3raisestype_error(atomic, Culprit)for a bound argument that is not atomic —
a character list, say — checking left to right as SWI-Prolog does. It raised
instantiation_error, as if the argument were unbound.
v0.8.0
Changed
Modernis the default language mode everywhere:new PrologEngine(),new BytecodeProgram(),
dotnet prolog runandlint, theDotPrologLanguageModeproject default, and generated entry
points, test hosts, and facades. Double-quoted text therefore reads as a list of one-character
atoms unless a project says otherwise —"abc" = [L|Ls]givesL = a, Ls = [b,c]. ISO/IEC
13211-1 leaves the initial value implementation defined, socharsis a conforming default,
and it is the one Scryer, Trealla, ichiban, Flowlog, and Trilog use.StrictIsostill starts
atcodes.- The ISO conformance case for the initial
double_quotesvalue now checks that it lies in the
flag's ISO domain rather than pinningcodes, since the standard leaves the value to the
processor.
Removed
- The
Extendedlanguage mode. It differed fromModernonly in startingdouble_quotesat
codes, which thedouble_quotes=codesflag override already expresses.--mode extendedand
<DotPrologLanguageMode>extendedare now rejected as unknown mode names, and
PrologLanguageMode.Extendedno longer exists;PrologLanguageMode.Modernis now the enum's
first value.dotnet prologanddotnet prolog lintlistmodern|strict-iso.
Compatibility
- A program that relied on the unstated
codesdefault reads double-quoted text differently.
Mixing the two conventions can fail silently in grammars — a terminal written as"abc"no
longer matches code input — or raise type errors, as innumber_codes(N, "42"). Restore the
old reading for a whole project with<DotPrologFlags>double_quotes=codes</DotPrologFlags>or
dotnet prolog run --flag double_quotes=codes, for one file with
:- set_prolog_flag(double_quotes, codes)., or for an embedding host with
new PrologFlagOverrides { DoubleQuotes = DoubleQuotesMode.Codes }. A project or host that
namedextendedreplaces it with that override.
v0.7.0
Added
-
Rational numbers:
1r3literals in theExtendedandModernmodes (strict ISO lexing is
unchanged),rdiv/2as a 400yfxoperator, exact arithmetic mixing integers and rationals
with float contagion, rounding functions over rationals, thenumerator/1,denominator/1,
rational/1, andrationalize/1evaluables, therational/1type test,must_be(rational, X),
number conversion of theNrMspelling, standard-order ranking beside integers, and canonical
demotion so a denominator of 1 is always an integer. Rationals reach both execution paths and
the embedding API (PrologRational,PrologInput.Rational), and are verified against
SWI-Prolog by the differential corpus. -
Unbounded integers: arithmetic promotes past the 60-bit tagged range to interned big
integers and normalizes back when results fit, literals of any length read to their exact value
in source text and runtime term input, and number conversion, formatting (~d,~D,~r,
~R), ordering, first-argument indexing, the dynamic database, generated C#, and the embedding
API (PrologBigInteger,PrologInput.Big,PrologMarshal.ToBigInteger) all carry the new
representation. Theboundedflag is nowfalse;max_integerandmin_integerkeep
reporting the fixnum promotion threshold.evaluation_error(int_overflow)and
representation_error(max_integer|min_integer)no longer occur; a shift count or exponent
whose result would exceed the representation raisesresource_error(memory). Verified against
SWI-Prolog by the differential corpus; the pinned independent ISO corpus now counts 763
applicable declarations, with five bounded-arithmetic cases correctly gated out by their
upstreamcondition(current_prolog_flag(bounded, true))option. -
print_message/2with SWI's message system sized to this machine: the message term is
translated toFormat-Argslines, a user-definedmessage_hook/3may intercept them, and what
remains is written touser_errorbehind SWI's kind prefixes (ERROR:,Warning:,%).
Theerror(Formal, Context)translations follow SWI 10's wording — including the culprit
classification of type errors and the special-cased permission and existence messages — and are
verified against SWI-Prolog by the differential corpus.silentanddebug(_)messages are
not printed; SWI's source-location and thread decorations are not reproduced. -
A string term type:
"..."reads as a distinct interned string under
set_prolog_flag(double_quotes, string)— an extension value available in theExtendedand
Modernmodes and gated out ofStrictIso; no mode defaults to it.string/1is true of a
string,atom/1false,atomic/1true; unification is identity;write/1emits the bare text
andwriteq/1the"..."spelling with ISO escapes; the standard order places strings between
numbers and atoms, as SWI-Prolog 10 sorts them. Strings surviveassertz/1,findall/3,
copy_term/2,nb_setval/2, build-time compiled code, and the embedding API (PrologString),
and theDotPrologFlags/--flagoverride surface acceptsdouble_quotes=string. -
The string library:
string_length/2,string_concat/3with split enumeration,
atom_string/2,string_to_atom/2,string_chars/2,string_codes/2,number_string/2,
term_string/2,sub_string/5,split_string/4,string_code/3,string_lower/2,
string_upper/2;string(S)sinks forformat/3andwith_output_to/2;~sand format
text accepting strings; andmust_be(string, X)withtextadmitting strings. Verified
against SWI-Prolog by the differential corpus. The atom predicates deliberately keep ISO's
type_error(atom, …)for strings. -
The SWI-aligned library surface:
library(error)(must_be/2,is_of_type/2, and the
error-raising helpers),library(assoc)as AVL trees,library(ordsets),foldl/6,
findall/4,numbervars/3,atom_to_term/3, andtab/2— available in theExtendedand
Modernmodes, rejected byStrictIso. -
A SWI compatibility ledger recording, feature by feature,
which parts of the SWI-Prolog surface the extended modes implement, which are later roadmap
phases, and which are out of charter. -
More SWI-aligned predicates:
del_assoc/4,transpose_pairs/2,ord_union/2,
ord_intersection/2,aggregate/3,4andaggregate_all/4for the simple specs,variant/2,
?=/2,char_type/2andcode_type/2for bound characters,between/3with aninfupper
bound, and the~r/~Rradix format directives. -
An opt-in differential suite that runs a shared goal corpus against a locally installed
SWI-Prolog and asserts the outputs agree (DOTPROLOG_RUN_SWI_DIFFERENTIAL_TESTS=1); the corpus
also runs unconditionally against DotProlog alone. -
Engine-scoped global variables:
nb_setval/2andnb_getval/2store and read a detached copy
that survives backtracking, whileb_setval/2andb_getval/2hold the live term and the
assignment is undone when execution backtracks past it, including throughcatch/3and at the
end of the top-level goal. -
setup_call_cleanup/3andcall_cleanup/2: the cleanup runs exactly once on deterministic
exit (including the redo that exhausts the alternatives), failure, or a thrown ball, with SWI's
ball precedence. A surrounding cut that discards pending alternatives does not fire the
deferred cleanup;once(Goal)gives commit semantics. -
setarg/3andnb_setarg/3: destructive argument assignment, undone on backtracking for the
first form through a value-undo stack interleaved with trail unwinding.nb_setarg/3accepts
atomic replacement values only, which the counter idiom needs and heap truncation allows. -
The
occurs_checkflag (false,true,error) in theExtendedandModernmodes,
guarding general unification with SWI'soccurs_check(Var, Term)error term. Write-mode head
unification is a documented unchecked window;StrictIsokeeps the ISO flag set unchanged. -
A natural-language processing sample in
Modernmode (samples/NaturalLanguage), and the
DotProlog logo on the project landing pages. -
nb_current/2, which fails for an unset name and enumerates the set variables when the name
is unbound;ord_seteq/2andord_symdiff/3, completinglibrary(ordsets); and the witnessed
max/2andmin/2aggregation specs acrossaggregate/3,4andaggregate_all/3,4, which
compare arithmetically, keep the first solution on a tie, and answermax(Value, Witness)/
min(Value, Witness). All verified against SWI-Prolog by the differential corpus. -
The rest of SWI's
library(assoc)surface:is_assoc/1,gen_assoc/3,get_assoc/5,
map_assoc/2,3,del_min_assoc/4, anddel_max_assoc/4. -
Compound aggregation templates:
aggregate_all(r(sum(X), count), Goal, r(Sum, Count))and the
same shape inaggregate/3,4andaggregate_all/4, each template argument a spec, with SWI's
instantiation, type, and domain errors for invalid templates. -
The
~@and~Wformat directives:~@runs a goal once and inserts its output in place —
a failing or throwing goal still emits the text before it, as SWI's streaming does — and~W
writes a term under awrite_term/2option list.write_termitself gains SWI's
spacing(standard|next_argument)option. -
portray_clause/1,2with SWI's listing layout — one goal per line, bracketed disjunction,
if-then-else, and soft-cut blocks,A,B, ... variable names,_singletons — byte-identical
to SWI-Prolog 10 for the covered constructs, pinned by the differential corpus. -
char_type/2andcode_type/2enumerate an unbound character over a bound type in code
order; characters are UTF-16 code units, so Unicode-wide classes cover the BMP. -
term_size/2: the cells of a detached copy of a term, counting shared subterms once per
occurrence and raisingrepresentation_error(cyclic_term)for a cyclic one. -
Project-level initial flag overrides: the
DotPrologFlagsproperty in a.dplproj(and the
repeatable--flagoption ondotnet prolog runandlint) layers an initial flag value over
the language mode, e.g.double_quotes=charswhile staying inextendedmode. The mode remains
the curated profile;double_quotes(codes,chars,atom) is the first overridable flag.
Generated code records the initialdouble_quotesvalue and refuses to install into an engine
that starts elsewhere, the same way it already guards the language mode.
Fixed
- Meta-called control goals now operate on the caller's live terms: the runtime lowering compiles
only the control skeleton and passes every leaf-goal argument through a register instead of
rebuilding bound terms, which destructive assignment made observable. - A compiled predicate that lowered a meta-called control goal could crash the dispatch loop with
an index error when appending the lowered bytecode grew the program's code array; the loop now
refreshes its cached arrays after compiled execution returns. - A build-time-compiled source containing
:- set_prolog_flag(double_quotes, ...)no longer leaks
that value into the host engine when its generatedInstallreplays the directive: the flag is
restored to its entering value afterwards, matching what consulting the same file leaves behind.
Changed
- An aggregation template that is unknown (
aggregate_all(foo, ...)), a variable, or a compound
mixing specs with other terms now raises the error SWI raises instead of failing silently. char_type/2andcode_type/2with an unbound character now enumerate instead of raising an
instantiation error; the error remains when the type is unbound.Modernmode is chartered as the SWI-aligned dialect: an ISO-conforming core,double_quotes
seeded atchars, and SWI-Prolog as the documented reference for extension behavior.
-...
v0.5.0
A conformance release. StrictIso now implements the ISO Prolog family rather than its core alone:
ISO/IEC 13211-1:1995 with Technical Corrigenda 1:2007, 2:2012, and 3:2017, the ISO/IEC 13211-2:2000
module system, and the ISO/IEC TS 13211-3:2025 definite clause grammars. Two traceability ledgers
publish the licensed-text audit, one row per normative area with the executable evidence that
covers it, and the audit is what found the defects fixed below.
Modules moved to the standard interface-and-body representation. Extended and Modern keep the
Quintus-family declarations they already accepted, so existing programs are unaffected; strict mode
takes the standard spelling only.
Added
- ISO/IEC 13211-2 module interfaces and bodies, including export, import, re-export,
metapredicates, module-local reader state, visible-database reflection, context-sensitive I/O and
database operations, static clause inspection, and the fixedcolon_sets_calling_contextflag. - Generated-C# and NativeAOT preservation of Part 2 module metadata and retained static clauses.
- The Corrigendum 3
variable_names/1write option. It writes the leftmost applicable name for a
variable and does not bind the option's terms. - Traceability ledgers for Part 1 and
Parts 2 and 3. Each maps a normative area to the
focused, corpus, generated-C#, cross-path, and NativeAOT evidence that covers it, and paraphrases
rather than reproduces the licensed publications. The processor-characteristics reference records
the choices they point at. - A pinned Part 1 inventory of the predefined predicates and evaluable functors, so adding or
removing one has to be a deliberate change rather than a silent drift. - Corrigenda cases in the repository conformance corpus, which now stands at 608 and continues to
run on every execution path.
Changed
- StrictIso accepts the Part 2
module/1interface andbody/1representation and rejects the
Quintus-familymodule/2,use_module/1,2, andmeta_predicate/1compatibility declarations.
Fixed
- Runtime-built DCG bodies are expanded into one goal before execution, so
|alternatives and
grammar or embedded cuts have the same behavior as statically translated grammar rules. phrase/3is steadfast in its third argument, and strict mode treats a runtime-built soft cut as
an ordinary nonterminal just as it does in static grammar rules.phrase/2reports the Part 3listtype for an invalid input sequence.- Strict mode no longer inherits the extended predefined operators
:=, infix., and$. The
strict initial table omits them;ExtendedandModernkeep them, and a strict program can
still declare its own permitted operators withop/3. op/3rejects every prefix or postfix declaration of|, not only those below priority 1001.sort/2,sort/4,msort/2, andkeysort/2validate the result argument before unifying with
it, andkeysort/2checks the pairs on both sides, so an improper result raises its ISO error
instead of failing.open/4,close/2, andwrite_term/2,3reject an unknown option name with the domain error for
that option type, before the option's value is inspected.
Compatibility
- Part 2 module text is interfaces and bodies. Source that uses
module/2,use_module/1,2, or
meta_predicate/1still loads inExtendedandModern, and must move to the standard
declarations to load underStrictIso. - Removing
:=, infix., and$from the strict initial operator table changes how strict
source that used them reads. Such source was outside Part 1 to begin with;Extendedis
unchanged. current_prolog_flag/2enumerates ten flags: the nine from Part 1 and the Part 2
colon_sets_calling_context. A program that counted the flag set has to count ten.- The complete applicable independent Part 1 corpus passes on every execution path. Parts 2 and 3
rest on licensed traceability plus focused managed, generated-C#, and NativeAOT evidence, not on
an independent suite. None of this is a claim of SWI-Prolog compatibility.
v0.4.0
A linting release. dotnet prolog lint reads source and reports on it without consulting it or
running a directive, so a file can be checked without being trusted. It ships with the semantic
rules on by default and an opt-in covington profile carrying the layout guidelines that are
automatically checkable, with every threshold configurable per project.
Added
- A non-executing
dotnet prolog lintcommand with stableDPL3xxxdiagnostics for singleton
variables and repeated underscore-prefixed singleton markers. It accepts multiple files and the
shared language modes; warnings remain advisory unless--warnings-as-errorsis selected. - A reusable
PrologLinterAPI inDotProlog.Compiler, package-consumer coverage for the installed
tool, and source-linting documentation. - An opt-in
covingtonlint profile for spaces, indentation, line and clause length, comma spacing,
clause and subgoal layout, and trailing whitespace. Numeric layout limits are configurable while
the default profile remains semantic-only.
Changed
- The codebase uses implicit types and the current language forms — pattern matching, switch
expressions, collection expressions, and range and index operators..editorconfignow states
those preferences, so the style is expressed where tooling can apply it rather than by convention
alone. No observable behaviour changes, and the engine benchmarks are unmoved.
v0.3.0
A language-mode release. A mode now carries the initial ISO flag values that go with its predefined
surface, not just the surface itself, and the new opt-in Modern mode starts double_quotes at
chars — so a double-quoted token reads as a list of one-character atoms, which is the convention
the newer Prolog systems settled on and what makes DCGs over text readable. Extended remains the
default and keeps the ISO initial value codes, so existing programs are unaffected.
Selecting a mode is now done by name everywhere, which replaces the 0.2.0 strict-ISO booleans.
Added
- A
Modernlanguage mode: the extended predefined surface withdouble_quotesseeded atchars.
It is available from the embedding constructor,dotnet prolog, generated code, and.dplproj
builds, and generated source still refuses to install into an engine created in a different mode. dotnet prolog run --mode extended|strict-iso|modern, and theDotPrologLanguageModeproperty
for.dplprojprojects. Mode names parse case-insensitively from one shared table, so the command
line and MSBuild cannot drift apart.BytecodeProgram.InitialDoubleQuotes, recording the value the flag was seeded with before any
source was read.- A
TextGrammarsample: a.dplprojapplication inModernmode that decomposes a string, sums a
run of numbers, and splits a sentence into words with DCGs written against characters. - NativeAOT acceptance coverage for
Modernmode, exercising build-time compiled clauses, the
reader running inside the published binary, a consulted grammar, load-unit flag scope, and the
mode-mismatch guard. - Benchmarks comparing engine construction and consulting across language modes.
Changed
double_quotesis scoped to the load unit. Aset_prolog_flag(double_quotes, _)directive still
governs the rest of the file that issued it, and the value in force when that file began is
restored when it finishes, including when a directive throws. A library that declares its own
convention can no longer change how whatever is consulted next is read. Deferred
initialization/1goals observe the restored value.- The bundled bootstrap and standard libraries are read under
codesin every mode; they are
processor implementation, so a host's choice of dialect does not reinterpret them. - A program's language mode is validated against an explicit allowlist, so a mode cannot be accepted
before it has been given its initial flag values.
Removed
dotnet prolog run --strict-iso, replaced by--mode strict-iso.- The
DotPrologStrictIsoMSBuild property, replaced byDotPrologLanguageMode. A project setting
the old property now fails the build rather than silently ignoring it.
Compatibility
- The initial
double_quotesvalue stayscodesinExtendedandStrictIso, as ISO/IEC 13211-1
requires.Modernis an extension and sits outside the conformance claim. - Scoping
double_quotesto the load unit changes an observable behavior that no previous release
pinned. Source that relied on a directive outliving its file must set the flag in each file that
needs it.
v0.2.0
The second release of DotProlog, and the first with independent conformance evidence: all 768
applicable declarations of the pinned Logtalk 3.101.0 ISO Prolog corpus pass — as consulted
bytecode, as generated C#, across both compiled↔bytecode boundaries, and from a published
NativeAOT executable. The engine gained first-argument clause indexing, the language gained an
opt-in strict ISO mode, and dotnet test now drives Prolog test projects directly.
A changelog section dated 0.1.1 (2026-07-30) was never shipped — no tag, no GitHub release, and
nothing on NuGet.org — so its content is folded in here and this release is 0.2.0.
Added
- First-argument clause indexing in the bytecode VM. Static multi-clause predicates dispatch
through a clause table keyed on the first argument, and dynamic predicates skip clauses whose
first argument could never unify — under the unchanged logical update view. A call with a bound
first argument that can only reach one clause creates no choice point. Solutions and their order
are unchanged; build-time generated C# keeps its existing in-order clause chains. - The pinned 768-case independent ISO corpus now runs exhaustively through generated C#,
compiled-to-bytecode, bytecode-to-compiled, and NativeAOT paths in CI. - Part 3 DCG semicontexts and
Name//Aritydeclarations fordynamic/1,multifile/1, and
discontiguous/1. - An opt-in strict ISO language mode for embedding,
dotnet prolog, generated code, and
.dplprojbuilds. It rejects known predefined extensions during source preparation and at
runtime meta-call and host-binding boundaries. - A
prolog-testtemplate whosetest_*predicates are discovered and run bydotnet test. - A clean local-feed consumer gate that installs the packed templates and .NET tool, then builds,
runs, NativeAOT-publishes, and tests generated projects from a path containing spaces. - A uv-managed MkDocs documentation site on Python 3.14 with getting-started, language, .NET
integration, architecture, and contributing guides, plus strict link validation in CI. - The ISO evaluable functors for trigonometry, logarithms, exponentials, rounding, float
decomposition, and bitwise complement. - ISO
unify_with_occurs_check/2with transactional failure and cycle-safe rational-tree
traversal. - ISO
current_predicate/1enumeration for static, dynamic, declared-empty, and runtime-created
user procedures.
Changed
- Module preparation now requires one leading
module/2declaration and rejects malformed,
unexported, or conflicting selected imports. - DCG processing uses the Part 3
|operator priority, rejects reserved and predefined grammar
heads, and applies the specifiedphrase/2and terminal-sequence validation. - Extended DCGs lower soft cut consistently in static and runtime-loaded source; strict mode treats
the additional control as an ordinary nonterminal. - The repository test suite now uses Microsoft.Testing.Platform v2 through the .NET 10
global.jsontest-runner contract, so xUnit and Prolog test projects run together. - Release checksums now cover packages, symbols, the SBOM, and all native binaries; the CycloneDX
tool is pinned, and a missing NuGet API key fails rather than silently skipping publication. - Child build processes close standard input, are killed with their process tree if they exceed the
integration-test limit, and every CI and release job has an explicit timeout. DotProlog.Sdknow carries a real package title and description.- Arithmetic now enforces ISO operand signatures, float division result types, bounded-overflow
errors, and the distinct exceptional cases for zero division, undefined results, and float
overflow. - The repository's ISO-derived conformance corpus now contains 563 passing cases.
- Integration tests no longer tolerate the zero-tests-ran exit code, child-process output reads
share the process deadline instead of waiting on a held pipe forever, and a failing release
verify platform no longer cancels its siblings.
Fixed
- The repository gates pass again on Windows and Linux: conformance and strict-ISO runner
rebuilds now use isolated build outputs instead of overwriting the shared test assembly that a
later no-build step executes, MSBuild worker nodes no longer outlive their step holding task
assemblies locked, host paths interpolated into quoted atoms escape their backslashes, the
Logtalk adapter emits the same line endings on every platform, and the packed-package consumer
gate findsnuget.configon case-sensitive filesystems. - The machine protected new environment frames only against the newest choice point, so a frame
deallocated by last-call optimisation could be overwritten while an older choice point still
referenced it. Solutions were silently dropped —forall/2with a compound action and
findall/3over goals with in-clause disjunction or negation were the visible cases — and an
uncaught exception could vanish while backtracking throughcatch/3. The protection watermark
is now monotone up the choice-point stack. retract/1andclause/2re-read the database generation on every redo, letting clauses
asserted after the goal started appear mid-enumeration. Both now keep the generation captured at
the first solution and resume from a stable clause cursor, soasserta/1between solutions can
no longer shift or repeat answers.- Copying a cyclic term — through
copy_term/2,findall/3,assertz/1, orthrow/1— looped
forever. It now raises catchablerepresentation_error(cyclic_term), and the term writer prints
cycles as...instead of hanging. - Every meta-call of a control term compiled a fresh clause into the append-only program. Compiled
control goals are now cached by shape, so long-running loops no longer grow memory without bound. - Integer division by zero always raises
evaluation_error(zero_divisor); only float0.0/0.0
remainsundefined. atom_chars/2,atom_codes/2,number_chars/2, andnumber_codes/2with a bound first
argument now convert it and unify with the list instead of raisinginstantiation_error.- Number conversion no longer lets oversized float literals become IEEE infinities or wraps
oversized radix literals; out-of-range input raises the reader'ssyntax_error(float_overflow)
andrepresentation_error(max_integer|min_integer). format/3accepts real stream handles and aliases, andformat(user_error, ...)writes to the
error stream instead of the current output.phrase/2,3treated a run-time if-then-else as a plain disjunction and offered the else branch
as an extra solution.writeq/1emits the named ISO escapes and delimited hex escapes for control characters, so its
output always reads back.- The build task deletes generated facades whose contract was removed or renamed,
dotnet clean
removes the generated directory, and facade generation reruns when the project file or the set
of source files changes — not only when a surviving file's timestamp moves. - Contract mistakes that previously escaped into raw C# compiler errors — or crashed the build
task — are reported asDPL2011–DPL2014diagnostics, and anondetexport with no outputs
streams one unit value per solution as ADR 0006 promises. - Prolog test projects honour run filters, capture
user_errorinto failure reports, and fail a
looping test after a configurable per-test timeout instead of hangingdotnet test.
Compatibility
- Requires .NET 10. Packages are platform-neutral; NativeAOT publishing is exercised on Linux,
Windows, and macOS in CI. - Independent evidence, but still no full ISO claim. The 768 applicable declarations of the
pinned Logtalk 3.101.0 corpus pass on every execution path, and the repository's own 563 cases
encoded from ISO/IEC 13211-1 pass as well. Licensed-text traceability and the Part 2 module and
Part 3 grammar completion gates remain open. See COMPATIBILITY.md. - The strict ISO mode is opt-in; the default profile is unchanged, so existing programs that
use predefined extensions keep working. - There is still no string type: an atom is the only text term, and the SWI-Prolog string
predicates are absent rather than aliased to atoms.
Known limitations
plcand the foreign-predicate source generator are designed but not implemented.- First-argument indexing applies to the bytecode VM; build-time generated C# still tries a
predicate's clauses in order. - A character is a UTF-16 code unit, so
atom_length/2counts a character outside the Basic
Multilingual Plane as two.
Full Changelog: v0.1.0...v0.2.0
v0.1.0
The first release of DotProlog — a Prolog implementation for .NET 10, built as a first-class
SDK language rather than an interpreter you embed. It compiles Prolog to bytecode and runs it on a
virtual machine written in C#, emits no CLR IL, and therefore works unchanged inside a NativeAOT
binary: a published executable can consult a .pl file it has never seen and run it.
Added
- The language. Facts, rules, unification, backtracking, and the control constructs
,/2,
;/2,->/2,*->/2,\+/1,!/0, andcall/1..8, with ISO cut scoping in clause bodies and
in meta-called goals alike. Exceptions throughthrow/1andcatch/3, with every engine error
raised as a catchableerror(Formal, Context)term. - All solutions.
findall/3,bagof/3,setof/3,forall/2, andaggregate_all/3. - The database.
assertz/1,asserta/1,retract/1,retractall/1,clause/2,abolish/1,
and:- dynamic, with logical-update-view semantics. - A standard library. Text conversion, the list library, sorting, higher-order predicates,
copy_term/2,term_variables/2,succ/2,plus/3, andformat/1,2,3with column stops. - Operators.
op/3andcurrent_op/3, and a term writer that honours them — whatever
writeq/1produces reads back as the same term. - Grammars.
-->/2translated at load time, withphrase/2,3,{}/1, pushback lists, and
terminals written as lists or double-quoted strings. - Streams.
open/3,4,close/1, the current-stream predicates,read/1,2,read_term/2,3,
character I/O,with_output_to/2,term_to_atom/2, andread_term_from_atom/3. - Modules.
:- module/2hides what its export list omits, withuse_module/1,2,Module:Goal,
and:- meta_predicate/1. .dplprojprojects throughDotProlog.Sdk, an additive MSBuild SDK. A.dplicontract turns
a Prolog predicate into an idiomatic .NET method, consumable from C#, F#, and Visual Basic.dotnet newtemplates:prolog-consoleandprolog-lib.dotnet prolog run, a .NET tool.- Testing.
DotProlog.Testingruns a project'stest_*predicates under
Microsoft.Testing.Platform, each in a fresh engine. - Embedding.
PrologEngine.Query/1enumerates solutions lazily, marshalling each binding into a
PrologValue;PrologHostbinds a predicate once and calls it asProve,CallOnce, or
CallAll.
Compatibility
- Requires .NET 10. Packages are platform-neutral; NativeAOT publishing is exercised on
Linux, Windows, and macOS. - No independent conformance claim. 244 cases encoded from ISO/IEC 13211-1 pass, but they are
DotProlog's own reading of the standard rather than a third-party suite. Known differences are
listed in COMPATIBILITY.md. - There is no string type: an atom is the only text term, and the SWI-Prolog string predicates are
absent rather than aliased to atoms.
Known limitations
dotnet testcannot yet drive a.dplprojtest project; run the test host directly.- No binary streams and no stream repositioning.
- No first-argument clause indexing, so a predicate with many facts is scanned linearly.
plcand the foreign-predicate source generator are designed but not implemented.
Full Changelog: https://github.com/kidoz/dotprolog/commits/v0.1.0