Did you know ... Search Documentation:
Title for pldoc(default)
Branch: development (switch to stable),
version to version

SWI-Prolog Changelog from version 10.1.15 to 10.1.16

[Sep 24 2026]

  • FIXED: listing/2 ignored the options it forwards to portray_clause/3 The predicate_options/3 declaration says pass_to(portray_clause/3, 3), but list_clause/6 called portray_clause/1 and the options were dropped. So listing(M:p/1, [module(M2)]) -- the operator table to write the clauses with -- had no effect.
  • MODIFIED: write_term/2: space between a prefix operator and a symbol not -a was written not-a. That reads back correctly -- the reader always prefers the prefix operator, and even zzz - x parses as zzz(-(x)) -- but it suggests an infix - to a human reader.
  • FIXED: #1531 tmp_dir flag kept 8+3 short names on Windows %TEMP% is often a short name such as C:\Users\RUNNER~1\AppData\Local\Temp, while a file loaded from it is known by its long, on-disk name. A file built from the flag therefore did not compare equal to the same file as the system reports it, e.g., in the load context or the sources of a .qlf file. The flag is now normalised the same way as other file names.
  • MODIFIED: Prolog flag var_prefix now takes a character The flag var_prefix is either false (default) or the character that starts a variable. '_' is the old true; true is still accepted as an alias, but current_prolog_flag/2 now reports '_'. Setting it to a symbol character such as ? allows for variables such as ?x.

[Sep 23 2026]

  • ENHANCED: Keep addr2line(1) running to look up C source locations. Printing C backtraces and '$foreign_predicate_source'/2 used to start a new addr2line (atos on MacOS) process per address. Looking up all foreign predicates in module system now takes 0.07 sec instead of 23 sec on Linux and 0.09 sec instead of 35 sec on MacOS.
  • FIXED: #1530 use-after-free when abolishing all tables clear_variant_table() releases the tables one by one. Once a table is released, atom-GC may reclaim its answer trie while a table released later still holds a conditional answer that delays on it, after which destroy_delay_info() accesses the freed trie. We now destroy the delay info of all tables before emptying the variant table.
  • FIXED: Internal PL_compare() calls ignored CMP_ERROR. PL_compare() returns CMP_ERROR with a pending exception if the terms cannot be compared. Callers now propagate this error. Sset_exception() uses an equality test instead, as raising an exception there would replace the pending one.
  • ADDED: Prolog flag incomparable to raise an error on incomparable terms. Cyclic terms such as A = s(B,0), B = s(A,1) have no standard order. compare/3 returns an arbitrary order for them, which silently breaks sort/2, library(assoc), library(ordsets), etc. With the flag incomparable set to error, compare/3, sort/2, etc. raise representation_error(standard_order(Sub1,Sub2)). The flag is thread specific.
  • ENHANCED: partial_compare/3: report incomparable cyclic terms. If two cyclic terms have no order, i.e., their lexicographic comparison runs into a cycle, partial_compare/3 now unifies Order with incomparable(Sub1,Sub2), where Sub1 and Sub2 are the pair of subterms at which the comparison cycles.
  • FIXED: #1529 compare/3 on cyclic terms depended on sharing. When comparing cyclic terms, compare/3 assumes two compounds whose comparison is in progress are equal. That is sound for ==/2, but when the terms turn out to differ, the order derived from this assumption may be wrong, so == terms could compare differently depending on how they are shared. If such an assumption was used and one of the terms is cyclic, the order is now recomputed by skipping == arguments and descending into the first pair that differs. If this descent cycles, no consistent order exists and the old result is kept. Also applies to partial_compare/3.
  • DOC: CLP(Q,R): answers no longer show slack variables The "Status and known problems" section showed {X+Y>=1} printing as {Y=1-X+_A, _A>=0}, which no longer happens. Describe what projection does guarantee instead, that copy_term/3 is projected onto the constraint store reachable from the copied term rather than onto the term itself, and that dump/3 is the way to project onto chosen variables.

[Sep 22 2026]

  • ADDED: partial_compare/3: distinguish stable from unstable comparison. partial_compare/3 is a variant of compare/3 that only decides the order of two terms if that decision cannot be invalidated by binding a variable. If the order depends on a variable it unifies Order with undecided(Sub1,Sub2), the first pair of subterms that could not be compared.

[Sep 21 2026]

[Sep 20 2026]

  • FIXED: representation errors on UTF-16 and wchar_t streams reperror() wrote the &#NN;, \xNN\ or \uNNNN replacement text using put_byte(), which produces garbage on streams that do not use a byte encoding, e.g. the UTF-16 encoded console on Windows. It now writes the (ASCII) replacement using put_code().

[Sep 19 2026]

  • FIXED: report of a condition that was simplified away A completed table answers from its compiled clause (trie_gen_compiled/2), and compile_trie_node() writes a T_DELAY instruction for every answer that is conditional at that moment. Simplification can make such an answer unconditional without changing the trie -- destroy_delay_info() clears the delay info of the answer -- so nothing discards the compiled clause and T_DELAY keeps pushing a condition that no longer exists. call_delays/2 then reports undefined for an answer the table holds as unconditionally true:
  • FIXED: re-evaluation to leave a table claiming undefined answers Preparing a table for re-evaluation removes the delay lists of its conditional answers (reeval_prep_node()) but left wl->undefined counting them, while the flags beside it (has_answers, negative, neg_delayed) are reset. The stale count makes propagate_to_answer() skip the negative case: it cannot decide tnot(Table) while the table may still have undefined answers. So answers of other tables delaying on tnot(Table) stayed conditional for ever, although Table had been re-evaluated to definitely false:
  • FIXED: simplification to write to a freed SCC A worklist that is kept when its table completes (because the table has conditional answers or other answers delay on it) outlives its component: '$tbl_free_component'/1 frees that while the worklist stays attached to the table. propagate_to_answer() incremented wl->component->simplifications for every worklist it propagates into, and the delay lists lead to tables of older SCCs -- so it wrote to freed memory. The reproducer is plain Prolog: a chain a/1 -> o/1 -> x/1 where x/1 is incremental and its conditional answer becomes unconditional on re-evaluation, which propagates through o/1 into a/1 (AddressSanitizer: heap-use-after-free in propagate_to_answer(), freed by free_component()).
  • FIXED: keep the delay back-references a later abolish needs A conditional answer is recorded twice: in its own delay list and, per delay element, in the delays buffer of the worklist of the table it delays on. The latter is what destroy_depending_worklists() uses to find the tables that depend on a table being abolished, and what propagate_result() walks to simplify them.
  • FIXED: crash abolishing a table another table under evaluation delays on Abolishing a table destroys the tables holding conditional answers that delay on it (destroy_depending_worklists()). These were destroyed immediately, also when such a table is still under evaluation, and free_worklist() then released the worklist the running evaluation uses. The next answer added to it read freed memory:
  • FIXED: retractall/1 to report an exception raised while retracting If retractClauseDefinition() raises an exception, the loop of the allvars branch breaks out with rc = false, but the next statement unconditionally set rc back to true. retractall/1 thus returned true with the exception still pending, so the caller was told the predicate was emptied while it still held all its clauses and never saw the error.
  • ADDED: edit/0: use last load command as default file. This is used as fallback if there is no commandline file.

[Sep 18 2026]

  • ADDED: source_file_property/2: property loaded(Stamp) Get access to the last time this file was loaded.
  • ENHANCED: Command line completion on [Alias(Prefix for any alias. Used to work only for library(Prefix.
  • ADDED: demo/trace.pl: demo program for the command line tracer A small route planner over a cyclic map, meant to be traced rather than run. The module documentation holds three exercises: creeping through the call, exit, fail and redo ports, using the search commands and spy points on a query that fails after a long search, and watching the exception port unwind the recursion.
  • FIXED: crash in the command line tracer searching for a goal The tracer commands /c, /e, /r, /f, /u and /a take an optional goal to search for. Comparing that goal against the goal of the current frame addressed the arguments of the search goal using the local frame layout and an unshifted word, which made the search crash.

[Sep 17 2026]

  • FIXED: macOS app: declare NSLocalNetworkUsageDescription Without this key macOS may silently block access to hosts on the local network from swipl-win and processes started from it, which shows up as "No route to host".

Package RDF

[Sep 20 2026]

  • ENHANCED: MacOS: Control-click asks for a popup menu Use `event ->is_popup' rather than testing for the right button going down, so that the popups of the RDF diagram and the W3C test browser also open on Control-click, which is the secondary click on MacOS.

Package clpqr

[Sep 23 2026]

  • FIXED: CLP(Q,R): copy_term/3 and the toplevel reported the internal store Answers mentioned the slack variables of the simplex tableau: {X+Y>=1} printed as {Y=1-X+_A, _A>=0} and copy_term/3 returned the same. The cause was that attribute_goals//1 projected onto every attributed variable it could reach, so projection had nothing left to eliminate and dump/3 ran as an expensive identity. Variables the solver introduces for itself -- slack variables, the objective of minimize/1, the witness of a disequation -- are now marked in the clpqr_itf record and left out of the projection target.
  • FIXED: CLP(Q,R): delayed goals that had already run stayed attached When two variables that both carry delayed non-linear goals are unified, the goals of both run and the survivor's attribute is supposed to be cleared; the goals re-attach themselves if they are still non-linear. geler.pl wrote
  • FIXED: CLP(Q,R): every solver call left a choice point Not one of {}/1, inf/2, minimize/1, bb_inf/3, dump/3, entailed/1 or a plain unification with a constrained variable was deterministic, so a program using CLP(Q,R) accumulated choice points it could never use and could not be last-call optimised. Nearly a tenth of the test suite carried [nondet] because of it.

[Sep 22 2026]

  • TEST: CLP(Q,R): finish the test suite Three rounds of work on the suite, squashed:
  • DOC: CLP(Q,R): add a section on working on this code Section 16 collects what the repairs of section 14 taught, so the next person does not rediscover it:
  • FIXED: CLP(Q,R): inf/2,4 clobbered the global variable `inf' The same defect as bb_inf/3's `prov_opt', in the other optimiser. inf_lin/4 reaches the extremal vertex by pivoting, stores the result, then fails so that backtracking undoes the pivots. It kept the result in the global variable `inf', and global variables share one namespace with the calling program:
  • CLEANUP: CLP(Q,R): remove predicates that have no caller A call graph over the package turns up eleven predicates that nothing reaches. Removing them takes about 300 lines out and lifts clause coverage of the test suite -- bv_*.pl from 80% to 83%, clpqr/dump.pl from 81% to 89%, clpqr/geler.pl from 67% to 80%.
  • ENHANCED: CLP(Q): implement the isolating axiom for X = Y^Z The manual documents "X = pow(Y,Z) ... X and Y are ground => 8 = 2^Z" and "X = exp(Y,Z) ... X and Z are ground => 8 = Y^3" as isolating axioms, but only CLP(R) ever solved the second one. In CLP(Q), {8 =:= Y^3} succeeded with Y unbound and a residual goal, which is both undocumented and unsound looking: the query reports success for a store it has not decided.
  • FIXED: CLP(Q,R): several exceptions were not error/2 terms They printed as "Unknown message", and Q and R disagreed on which of them were malformed:
  • FIXED: CLP(Q,R): bb_inf/3,4,5 clobbered the global variable `prov_opt' The branch and bound incumbent was kept in the global variable `prov_opt'. Global variables share one namespace with the calling program, so bb_inf/3 destroyed a user variable of that name:
  • FIXED: CLP(Q,R): a delayed optimisation made copy_term/3 answers unrunnable minimize/1, inf/2 and bb_inf/3 delay through wait_linear/3 until their expression becomes linear. transg//1 reported such a pending optimisation by emitting the solver's own continuation into the {}/1 residual:
  • FIXED: CLP(Q,R): pivot/2 read the class from the order field pivot/2 in bv_q.pl and bv_r.pl did
  • FIXED: CLP(Q,R): a contradictory ordering/1 threw a bare term arrangement/2 threw the atom `unsatisfiable_ordering', which is not an error(Formal, Context) term, so it printed as
  • FIXED: CLP(Q,R): ordering/1 had no effect at all Four defects conspired to make ordering/1 a no-op, and none of the four worked examples of the "Variable Ordering" section of the OFAI manual (Holzbaur, TR-95-09) could be reproduced.
  • FIXED: CLP(Q,R): non-linear residual goals were reported once per variable attribute_goals//1 reports the constraints on all attributed variables of the term at once and then removes the solver attribute from each of them, so that the next variable does not report the same conjunction again. It only removed clpqr_itf, but a variable that carries nothing but a delayed non-linear goal has no clpqr_itf attribute -- only clpqr_geler. Such a store was therefore printed once per variable:
  • FIXED: CLP(Q): inverting A = B^X returned a huge inexact rational nl_invertible/4 computed the exponent as
  • FIXED: CLP(Q,R): arrange_pivot/1 could never fire arrange_pivot/1 in clpqr/project.pl guarded on
  • FIXED: CLP(R): #(pi) was 3.14259265 instead of 3.14159265 The two digits were transposed, making #(pi) and #(p) wrong by 1.0e-3, four orders of magnitude above the solver's own epsilon of 1.0e-10.
  • TEST: Add a test suite and design document for CLP(Q,R) The clpq and clpr libraries had no tests at all. This adds test_clpq.pl (242 tests) and test_clpr.pl (229 tests), registered with CMake as `ctest -R clpqr:`, and doc/design.md describing the implementation and the literature it derives from.

Package json

[Sep 20 2026]

  • FIXED: #3 emitting non-BMP characters on Windows json_write/2 and friends turned characters outside the BMP into garbage on Windows. json.c has no config.h, so SIZEOF_WCHAR_T was never defined and the code that combines UTF-16 surrogate pairs into a code point was compiled out. As a result the surrogates were passed to Sputcode(), which cannot represent them.

Package sgml

[Sep 20 2026]

  • ENHANCED: MacOS: Control-click opens the insert menu Use `event ->is_popup' rather than testing for the right button going down, so that the SGML mode's insert menu also opens on Control-click, which is the secondary click on MacOS.

Package xpce

[Sep 25 2026]

  • ENHANCED: demo(ispell). Deal with Unicode, do not block the event loop.
  • ENHANCED: class stream (process, socket) encodes and decodes text Added stream<->encoding, default utf8 (class variable). Output of ->append, ->format, etc. is encoded using the Prolog stream layer and input is decoded into characters before it is handed to the record_separator, input_message and <-read_line. Previously wide text was written as raw wchar_t data and input was read as ISO Latin-1.
  • FIXED: SDL: process->wait hung when the child's output pipe reached EOF The fd watcher only reacted to POLLIN, but Linux reports EOF on a pipe as POLLHUP without POLLIN. The EOF was never delivered, so the input side was never closed and process->wait looped forever (while the watcher thread busy-looped on poll()).
  • FIXED: xpce: a fixed graphical moved to another window was not painted there Moving the grip of a pane between the windows of its tabs left it displayed, so ->display_fixed did not ask for it to be painted. Also apply the scroll offset of the fixed layer in changedImageGraphical().

[Sep 24 2026]

  • FIXED: xpce: a hidden tab forgot the pane clicked last Clicking a pane moved the keyboard focus without telling its tab_frame, so a tab switched away from fell back on the pane last made current by ->current, e.g., the thread monitor split beside a console. The tab was relabelled after it and gave it the focus when raised again.
  • ENHANCED: Epilog: Emacs window keys ^X 2, ^X 3 and ^X 0; ^X 0 closes a view beside other panes Epilog now splits with ^X 2 and ^X 3 and closes a terminal with ^X 0. In both Epilog and PceEmacs, ^X 0 closes the only view of a tab as long as the window shows something else, such as a console beside the editor. New method pane->last_in_frame tells whether closing a pane would leave its window empty.
  • FIXED: xpce: crash printing to a closed Prolog output stream during halt Console output from xpce, e.g., debug messages while windows are destroyed, now goes to C stderr if Prolog's output stream is gone.
  • ENHANCED: PceEmacs: ESC aborts the argument prompter, as ^G does Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
  • ENHANCED: PceEmacs: support the Prolog flag var_prefix set to a character Singleton replacement renames ?x to ?_x or ?_ if the buffer sets var_prefix to ?, and reading a selection and finding dependencies use the buffer's var_prefix.

[Sep 23 2026]

  • FIXED: terminal_image: size in cells ignored the initial size in pixels A terminal_image started with 25 rows and the width of its pixel size without the margins, and only a later change of size or font computed these from the pixel size. A terminal that was never resized had more rows than it showed, so a line feed on its last visible row did not scroll.
  • ENHANCED: Epilog: lines scrolled into the terminal take the background colour As xterm's background colour erase (bce), the empty lines of IL, DL, SU, SD and a scroll of the region, as well as a line a line feed scrolls into the window, are painted in the current background colour.
  • FIXED: Epilog: background colour of an erased line leaked into later lines An erase in line with a background colour records that colour on the line. Freeing a line without text (as opened by IL, DL or a scroll region) did not reset it, so a line later written in the same slot was painted on the old background. ED 1 also left the recorded erase of the lines it cleared in place.
  • ENHANCED: Epilog: the link popup item opens non-Prolog files and copies URLs A file:// link to a Prolog file is still consulted. Other file links are opened with the desktop application and other links are copied.
  • FIXED: Epilog: hovering a link that wraps over lines arms all of it Also rejoin the pieces of a link when rewrapping moves part of it to a line holding the rest, so a resize no longer leaves it split.
  • FIXED: lazy method binding refused all nested binds Binding a method runs Prolog, which may need to bind other methods, e.g. when a signal handler halts inside the callback. Only refuse re-binding the same method, which would loop.

[Sep 22 2026]

  • FIXED: complete font.system_fonts and font.pango_families with the built-in values A Defaults file that overrules one of these class variables replaces it entirely, so every alias or family it left out was simply undefined. makeBuiltinFonts() now walks over the value class font declares itself after the user defined values have been loaded. As neither fontAlias() nor the family loader overwrites an entry that is already there, this adds exactly the omitted ones, so a Defaults file only needs to mention what it wants to change.

[Sep 21 2026]

  • ENHANCED: xpce: complete the SDL3 function key coverage keycode_to_name() stopped at F12 and the four arrows, so the second row of function keys, Insert, the VT220 editing keys and the Sun and HP keys beside the alphabet produced no event at all. They are now matched to the event ids xpce has had for them all along -- most of initial_tree[] under `named_function' was unreachable -- with f13..f24 and the clipboard keys added to the tree, and the terminal sends the ones that have a meaning to a client: Insert as `CSI 2~', Find and Select as `CSI 1~' and `CSI 4~', the middle of the keypad as `CSI E' and F13..F24 as the Shift+F1..F12 that xterm's terminfo entry spells them as.
  • FIXED: do not re-enter the redraw from the live resize watch SDL runs an event watch at SDL_PushEvent() time, before the event is queued, so an SDL call made while we are painting lands in live_resize_watch() with a drawing context open: `frame ->size' asked for by a ->compute or a ->_redraw_area is enough. It then laid the frame out and painted it from under that context, which paints a window a second time inside its own redraw -- clipped to the area the outer redraw was given -- and lets ws_geometry_window() destroy the backing store the open context draws into. This is what printed "d_window(...): Context is already open". Leave the event in the queue for the main loop instead.
  • FIXED: crash narrowing a terminal window whose scrollback is full Rewrapping a narrower terminal needs more lines than the ring holds, so the oldest text scrolls off to pay for them. If the line to be wrapped was itself the oldest one, rlc_shift_lines_down() dropped exactly that line to make the room, leaving the wrap to copy from a line that no longer held its text: a segmentation fault on shrinking a console with a full buffer. Drop the head of the line instead, which is the text that scrolls off anyway.

[Sep 20 2026]

  • FIXED: MacOS: closing a window did not reactivate another one of the app AppKit activates the next window of the application when the key window closes, and does not do so for the windows SDL creates: closing the window opened with Command-N left the one it was opened from visible but inactive, with no caret and no keys. Hand the keyboard on ourselves.
  • FIXED: tab_frame: closing a pane moved the focus to the far side of the tab `tab_frame ->update_current' fell back on the first window of <-windows, which is the order the windows were displayed in rather than the order they are laid out in. Closing a pane from a split therefore pulled the focus to whichever pane happened to be displayed first. Hand it to the pane that takes over the room instead: the one after it, and the one before it when it was the last.
  • ENHANCED: MacOS: Control-click asks for a popup menu Control-click is the documented alternative for the secondary click on MacOS. The new `event ->is_popup' tests whether an event asks for a popup (context) menu: the right button going down or, on MacOS, Control-left. Class popup_gesture activates on it, as does the code that opens a popup without using a gesture: list_browser, toc windows, the inspector, the thread monitor, PceEmacs fragments and the Epilog terminal.
  • FIXED: terminal: pass Page Up and Page Down to the client The page keys were bound to ->cursor_page_up and ->cursor_page_down, which scroll the scroll back whatever is running on the terminal. A client never saw them, which left `less', `man', `vim' and every other program that is paged with those keys a key short. They now follow the rule the control characters and the function keys already follow: while a client owns the pty, or while the alternate screen is up, the keys are its own and are sent as xterm's `CSI 5~' and `CSI 6~'. With nothing running they scroll the scroll back as before, and Shift is the way out of the client, as it is for the wheel.
  • ENHANCED: drop a pane on the tab bar to give it a tab of its own Dragging a pane by its grip onto another window splits that window; dropping it on the tab bar -- the label row, or the menu bar above it, which is the whole of the bar while a single tab hides its label -- now gives the pane a tab of its own instead. Where in the row the pointer is says where the new tab goes, and a ghost of the label the tab will carry shows that while the pointer is there.
  • FIXED: xpce: typing did nothing in a window that claims the keyboard focus After a confirmer, popup or completer that holds a pointer grab took the focus away and closed again, SDL text input stayed disabled for the frame that regains the focus. A terminal or editor then still drew an active caret, but typing printable characters had no effect until the focus was moved to another window and back.

[Sep 19 2026]

  • FIXED: terminal: let the caret follow the mouse through a selection The input caret of the terminal now moves as soon as the button goes down and travels with a drag, so that it ends up at the end of the selection, as it does in an editor. It used to wait for the button to come up and then only move if no selection had been made. A click or drag outside the line being edited still leaves the caret alone.
  • FIXED: colour and hyperlinks for output from the xpce thread Epilog enabled the color_term and hyperlink_term flags using set_prolog_flag/2, which creates a thread-local copy of the flag once a second thread is running. The xpce event thread therefore kept the global false and messages it printed, e.g. from in_pce_thread(xref_source(File)), reached the terminal as plain text without a clickable source location.

[Sep 18 2026]

  • FIXED: "No implementation for: ->current_terminal" on a detached terminal A terminal that is in no window of the IDE told a plain frame it had the keyboard, and a plain frame knows nothing about terminals.
  • STYLE: draw the pullright marker as a Windows style ">" Popup menu items with a pullright submenu are now marked with a plain chevron drawn by draw_popup_indicator() rather than an image or a 3d triangle. This removes menu <->popup_image and the global @ms_left_arrow_image.
  • FIXED: Epilog: open the client's stdout and stderr read-write The three descriptors a terminal hands its client are the terminal as far as it, and anything it starts, is concerned, and a terminal is one device that a program both reads and writes. Ours opened stdout and stderr write-only, so a client that asked the terminal a question on stdout could not read the answer back from it, which is how termenv, and with it every Go program with a terminal user interface, learns the colours it draws on. Its read failed with EBADF, the answer stayed in the input queue, and the next program to read stdin found an escape sequence it never asked for: `gh run download' in an Epilog OS shell died with "could not prompt: unexpected escape sequence from terminal".

[Sep 17 2026]

  • CLEANUP: Remove all usage of old font names. E.g. family screen, style normal, etc.
  • FIXED: MacOS: keep popup menus inside the display SDL's popup constraining is disabled on MacOS because it misplaces popups on secondary displays. Clamp the popup ourselves against the usable bounds of the display holding it.
  • TEST: Only run the too-wide glyph test if SWIPL_TEST_FAIL_ON_UNLIKELY=y The outcome depends on the installed fonts. With only DejaVu, U+23BF has no glyph and the missing-glyph box crosses the cell boundary.
  • FIXED: PceEmacs failed to start if there is no config directory If HOME does not exist, user_app_config has no expansion and locating the bookmark and pane layout stores raised an existence error, so @emacs could not be created. The stores now live in memory only in that case.