WebShumway brings the Shumway Prolog engine to your browser. The
engine and your files live in this tab and never leave it. There is
no server behind this page.
Making a query
Type a goal in the ?- box and press Enter. When an answer
appears the engine is holding the next one back: press
; (or space) for it, . (or Enter, or Escape) to
stop. Stop abandons a search that is taking too long — the
page stays usable while one runs.
Writing a program
Write it on the left and press Consult (Ctrl+Enter)
to load it. Loading REPLACES what the text defines, so pressing it
twice does not define anything twice. Tab completes a
predicate name; Ctrl+S saves the file.
Files and workspaces
A workspace is a folder of files and the engine's working directory,
so :- use_module('other.pl'). and open/4
resolve inside it. A file you edit is reloaded by the file that
imports it when you consult again. Switching workspace starts a
fresh engine, because a workspace is a separate program.
Files live in this browser's private storage and survive a reload.
Download saves one to your computer, Export saves the
whole workspace as a zip.
Libraries that come with the engine
Constraints, coroutining and reified conditions (library(reif)) are opt-in, from the program itself:
:- use_module(library(clpfd)).
puzzle([S,E,N,D,M,O,R,Y]) :-
Vars = [S,E,N,D,M,O,R,Y], Vars ins 0..9,
all_different(Vars), S #\= 0, M #\= 0,
1000*S + 100*E + 10*N + D + 1000*M + 100*O + 10*R + E
#= 10000*M + 1000*O + 100*N + 10*E + Y,
label(Vars).
The Predicate reference (the book icon) lists everything the
engine provides, with the mode of each argument.
Libraries from elsewhere
Libraries… brings in someone else's. Import a folder from
your computer, or fetch one straight from a GitHub directory —
Scryer's, SWI's and Trealla's are offered, so you do not have to
install a Prolog system to borrow its library. What you import is a
collection: Scryer's lib/ is one folder and
sixty libraries, and every x.pl in it becomes
library(x).
Each collection carries a dialect. A library written for
Scryer loads under Scryer's reading of Prolog and one written for
SWI under SWI's, which is what lets both systems' versions of the
same library sit here at once.
A library loads faster once compiled — several times faster,
because the compiling stops happening on every consult. That runs by
itself in the background after an import, doing the ones your
programs import first, and each becomes fast the moment its own
build lands. It pauses while you consult and you can stop it at any
time; what is already built stays built, across reloads too.
You can read and edit a library's sources from the same dialog. They
are sources: what a program resolves is the compiled version
beside them, so an edit takes effect when you press rebuild.
Libraries are shared by every workspace and do not travel in a
workspace's zip or share link.
Sharing
Share puts a file or a whole workspace INSIDE the link, in the
part of a URL browsers never send to a server. Opening one never
overwrites your files without asking.
Debugging
Debug switches the page into debug mode: the engine restarts
compiled for debugging and your program is loaded again — by its
file name, so breakpoints work in every file of the workspace, not
just the one on screen. Debug again returns to the normal top
level.
In debug mode the program runs on the interpreter, so breakpoints
can stop it, and the first line of the top level shows Tier-0.
Libraries stay compiled; stepping does not go into them.
Breakpoints
Click the strip left of the code to set one; click it again to
remove it. A hollow dot is a breakpoint on a line the loaded
program does not reach. Right-click the gutter — or the source
itself — for the line's menu:
- Set / remove breakpoint.
- Enable / disable — kept but greyed out, so a breakpoint
you want back later need not be redrawn.
- Condition — a goal; the breakpoint stops only when it
succeeds.
- Log message — makes it a logpoint: it prints the
message and keeps going instead of stopping.
{X} holes fill from the frame.
- Run to cursor — run to this line and stop, once.
- Set next statement — move where execution will resume to
this line, running nothing in between.
Breakpoints are remembered per workspace and survive a reload; a
deleted workspace forgets its own.
Stopping and stepping
When a query stops, the tabs under the transcript show where it is
and what it holds:
- Call stack — click a frame to go to it; library and prelude
frames collapse into one [library code] row.
- Locals — the frame's variables, residual constraints
included.
- Watches — goals you add, re-evaluated at every stop.
- Immediate — see below.
The ⇄ button at the end of a tab bar sends its current view
to a second dock under the program panel, so two views — say the
call stack and the locals — stay visible at once. An emptied dock
gives its space back. Drag a dock's top edge to make it taller or
shorter — as the divider between the two panels resizes those — and
a double-click gives a seam its default back. All of it is
remembered.
Step with the buttons above the answers, or the usual keys:
- F5 continue
- F10 step over · F11 step into ·
Shift+F11 step out
- F9 toggle a breakpoint on the caret's line ·
Ctrl+F9 enable/disable it
- Ctrl+F10 run to the caret's line ·
Ctrl+Shift+F10 set the next
statement there
Break pauses a running query at its next goal — it lands in
your own program's code, so a goal made only of builtins offers it
nowhere to stop (Stop still abandons the query there).
Calling debugger_break from your program stops in debug
mode exactly as a breakpoint on that line would — a breakpoint you
write into the code, useful for a spot a click is awkward to place.
Outside debug mode it does nothing and succeeds, so it can be left
in.
The Immediate window
While stopped, the ?- box (and the Immediate tab)
evaluate a goal against the selected frame — the variables you name
are that frame's:
- A goal runs on a copy;
; asks for its next
solution.
- A goal prefixed with
! runs on the real
frame, so a binding or a posted constraint sticks (append
, fail for a dry run).
What is different here
Programs run on two tiers: the bytecode interpreter (Tier-0) and a
just-in-time compiler (Tier-1) that translates them to WebAssembly.
By default the whole program is compiled after each consult.
jit_compile(off) keeps everything on the interpreter,
jit_compile(16) compiles a predicate after it has been
called 16 times, and jit_compile(all) restores the
default. The first line of the top level shows which tier is in
use. Both tiers give the same results; only the speed changes.
Shumway — a Prolog compiler and interpreter for .NET.
MIT licensed. Copyright © 2026 Gustavo Brown.
github.com/gusbro/Shumway