Unsupported Common Lisp Features
rontolisp is a deliberately small subset of Common Lisp that runs identically on three backends (interpreter, JVM, WASM). To keep the language compilable to plain bytecode without a runtime metaobject protocol, many features of full Common Lisp are intentionally left out.
This page lists only what is missing or partial. For what is available, see the Language Reference.
| Feature | Status |
|---|---|
| restarts | available; no debugger integration (break, *debugger-hook*) and no condition-restart association |
&environment | accepted in a defmacro lambda list but always bound to nil (there is no macro-expansion environment object). &whole works, in defmacro and destructuring-bind alike |
loop (extended) | partial (see below) |
| CLOS | partial (static subset + a definition-time MOP subset) |
defstruct :include | single inheritance only; slot-overrides (:include parent (slot default) ...) work |
declare / declaim / proclaim / the | never change a result; on WASM an array type declaration directs the element-accessor emission (smaller, faster modules), everywhere else parsed no-ops |
typep / subtypep / coerce / concatenate | literal (quoted) type specifiers only; coerce targets 'list / 'vector / 'string (or a float type), concatenate builds those same three sequence families |
make-package / rename-package / delete-package / unintern / shadow (runtime) | not available; export / unexport / import / use-package ARE, as read/compile-time directives like in-package; defpackage :shadow / :shadowing-import-from are errors |
eval-when | treated as progn (no phase distinction) |
#:name | reads as a plain symbol, without gensym-style freshness |
*modules* | not available (require/provide are) |
| complex numbers | not available |
catch / throw / unwind-protect / conditions under --no-gc | compile error (available on every other backend) |
Multiple values
values and its consumers are available,
including the values of user functions. The remaining deviations from Common
Lisp:
- a producer that calls
valuesin a non-tail position and then returns normally may leave stale extra values behind, so keepvaluesin result position (a consumer clears the values it received, so only avaluescall that nothing consumes can leave leftovers); funcall #'values(the first-class value) yields the primary value only in compiled programs;multiple-value-callwith a built-in#'namekeeps the wrapper's fixed arity — pass a user function orlambdafor other argument counts;- other built-ins with secondary values in CL (
read-from-string,subtypep, ...) remain single-value —find-symbolandinterndo answer the accessibility status, andmacroexpand-1/macroexpanddo answerexpanded-p.
Non-local exit
catch /
throw,
block /
return-from and
tagbody /
go are available, with two gaps on the
compiled backends (the interpreter is unaffected):
- a
return-fromthat would cross anflet/labelslocal function is not yet supported (one crossing alambdais, as a non-local exit); gomust target a tag of atagbodythat lexically encloses it; the interpreter additionally supports dynamicgoacross function-call boundaries, i.e. a tag established by the caller. A tag reached from inside a nestedlambda-- the shape ahandler-bindhandler that resumes the protected loop with agoproduces, and what quri's:lenientpercent-decoding does -- is lowered like a cross-lambdareturn-from: a non-local exit that re-enters thetagbodyat the tag and carries on.
A cross-lambda return-from or go, catch/throw, unwind-protect, and
condition catching all compile in exception-handling mode; under --no-gc
catch/throw, unwind-protect and the condition forms are a compile error.
Restarts
The condition system is complete through the restart layer:
handler-bind handlers run at the signal
point before unwinding, restart-case /
restart-bind /
with-simple-restart establish
restarts, and find-restart /
invoke-restart /
compute-restarts /
muffle-warning /
abort /
continue drive them;
cerror is continuable. What is missing is the
interactive debugger: break and *debugger-hook* do not exist, a restart's
:report is stored but never rendered and its :interactive function never
runs, and restarts are not associated with conditions (the optional condition
argument of find-restart/compute-restarts is ignored).
check-type /
assert /
ccase still signal without offering a
store-value restart. Under --no-gc the restart forms degrade to the primary
form (that backend has no condition objects at all); on the wasm-GC backends
only signaled conditions are catchable — a runtime trap still aborts.
The loop macro
A bounded subset of the extended loop is
available -- that page lists the supported clauses, which include destructuring
patterns, parallel and, the anaphoric it, loop-finish and
thereis/always/never. What is out of scope: named (and the
return-from it would name); a destructuring pattern does not recognize
lambda-list keywords (&optional and friends bind as ordinary variables instead
of signalling); being drives hash tables, but its package form
(being the external-symbols of ...) parses and iterates the EMPTY sequence,
because there is no runtime intern table.
Structures and objects
defstruct supports :include
inheritance in its single-inheritance form only. Slot-overrides work:
(:include parent (slot new-default) ...) re-defaults an inherited slot in the
child's layout while it keeps its inherited index, so the parent's accessors
still read it. An instance prints in the standard #S(...) syntax, and
a #S(...) literal reads back into an instance -- in source and through the
runtime read / read-from-string on every backend (a compiled program's
reader has frontend parity; only #., #+/#- and #n=/#n# signal there).
A structure that carries a (:print-object fn) / (:print-function fn) option
prints through that function instead; both options are supported.
CLOS is a static subset
(defclass,
defgeneric /
defmethod dispatching on the first
argument, make-instance and
slot-value with literal quoted names).
A slot written with no :initform starts UNBOUND, as in CL:
slot-boundp reports it,
slot-makunbound restores it, and a
read signals unbound-slot.
change-class changes an instance's class
in place (the target may be a runtime symbol or a class metaobject), and
reinitialize-instance / shared-initialize are callable with no user method —
the system defaults fill the supplied initargs, as in CL. A definition-time MOP
subset is in:
find-class and
class-of answer real standard-class
metaobjects, allocate-instance
works, and a (:metaclass M) class option runs the class-definition protocol at
definition time (see defclass) — this
is what loads postmodern's DAO layer verbatim. Multiple inheritance works
(class precedence list, slot merge across superclasses). Out of scope: runtime
class construction
(ensure-class from computed data, a non-top-level defclass, add-method,
compute-applicable-methods, class redefinition,
update-instance-for-different-class) — the class and method sets of a compiled
program are fixed at compile time.
User-defined packages
defpackage is a literal,
top-level, read/compile-time directive supporting :use, :export,
:nicknames and :import-from (:documentation/:size are accepted and
ignored). :shadow and :shadowing-import-from are errors (there is no symbol
shadowing). use-package, export,
unexport and import exist as the same
kind of read/compile-time directive in-package is: a literal top-level call
takes effect for the forms that follow it, on every backend, and a
runtime-computed call works on the interpreter only. Creating or renaming a
package at run time does not: make-package, rename-package and
delete-package are not available.
unintern (and the runtime shadow / shadowing-import) cannot exist here at
all — a symbol IS its name, so there is no intern table to remove it from.
The queries are real: find-package,
package-name,
list-all-packages,
package-use-list,
package-used-by-list and
package-shadowing-symbols
(always nil), with the compiled backends answering from a table baked in at
compile time — so a package a compiled program creates later is invisible there.
When several used packages export the same name, the first package in :use
order wins instead of signaling a conflict.
Dynamic (special) variables
Dynamic binding through let/let* and
progv is supported, with one
limitation on the compiled backends (the interpreter is unaffected): while
normal exit and a return/return-from that unwinds across a special let
boundary both restore the binding, an error caught by a handler outside the
let (a go across it, and on the WASM backends a return that also crosses
an unwind-protect/handler-case) does not. progv restores on every exit an
unwind-protect covers, including those cases.
Numeric tower
rontolisp supports integers (including arbitrary-precision bignums), ratios
(1/3), and double floats, but not complex numbers. A negative square root
yields a float NaN rather than a complex result:
CL-USER> (sqrt -1)
NaN ; full Common Lisp would return #C(0.0 1.0)
Other omissions
- lambda lists: an extended
defmacrolambda list (&whole,&optional,&key,&aux, nested destructuring patterns) routes throughdestructuring-bind, which is deliberately lenient -- a missing argument isniland a surplus one is ignored rather than signalling; and a function is limited to 10 physical parameters on the funcall/apply path. - user macros are unknown to the runtime
evalof compiled programs, and alambdabuilt at runtime by thatevaldoes not parse lambda-list keywords (see Compiled eval Limitations). - the PRETTY PRINTER produces the text a wide enough line holds, but never
changes the LAYOUT: no rontolisp stream carries a column, so a logical block
never wraps, every conditional line break (
pprint-newlinewith:linear/:fill/:miser, the format directives~_/~:_/~@_/~i) is a no-op, and*print-right-margin*/*print-miser-width*/*print-lines*are accepted and ignored. Only(pprint-newline :mandatory)and~:@_break a line. Every other*print-*variable exists and holds the value the printer really behaves as -- binding one to a non-default value is what has no effect, except for*print-escape*/*print-readably*/*print-pretty*and*print-case*, which are honored.*print-case*converts the case of the symbols the printer spells but leaves a symbol nested in a structure, a CLOS instance, a hash table or an array of rank other than one at its stored spelling (Reader Case). The ordinary printing operators do not consult*print-pprint-dispatch*: an entry fires where the program calls the entry function itself. #.read-time eval is skipped with a warning inside.asdfiles.- built-in macro names (
cond,case,when,setf,push, ...) cannot be redefined.
This list is not exhaustive; rontolisp implements a focused core rather than the full standard.