luv

Workshop wiki

lisp.lisp

wiki/lisp.lisp

system luv-wiki · 49 definitions · on GitHub

Reading Lisp source without loading it.

The site is built by luv-wiki, which does not load luv. To show definitions in place we read source files with Eclector into a tree of lisp-node objects: symbols become tokens (package prefix and name as written), every node keeps its source range, comments and skipped input are kept as nodes, and nothing is interned or evaluated. Over the top-level forms of the files ASDF lists for luv we build a definition index: kind, name, file, line range, and the #ID figure mentions inside.

in-package#:luv.wiki

Nodes

defclasslisp-node
start:initarg:start:initformnil:accessornode-start
end:initarg:end:initformnil:accessornode-end
text:initarg:text:initformnil:accessornode-text:documentation"The source text of the node."
:documentation

A piece of Lisp source with its character range.

defclasslisp-list
children:initarg:children:initform'
:accessorelement-children
:documentation

A parenthesized list; CHILDREN are nodes in order.

defclasslisp-vector
:documentation

A #( ) vector literal.

defclasslisp-symbol
name:initarg:name:accessorlisp-symbol-name
package:initarg:package:initformnil:accessorlisp-symbol-package:documentation

The package prefix as written, or NIL for the current package; "KEYWORD" for keywords; :uninterned for #:.

external-p:initarg:external-p:initformnil:accessorlisp-symbol-external-p
:documentation

A symbol token; nothing is interned.

defclasslisp-atom
:documentation

Any other atom, kept as its source text: numbers, strings, characters, pathnames, and reader-macro results we do not model.

defclasslisp-prefix
prefix:initarg:prefix:accessorlisp-prefix-string:documentation"One of ' #' ` , ,@ #."
child:initarg:child:accessorlisp-prefix-child
:documentation

A quote-like prefix applied to one form.

defclasslisp-conditional
sign:initarg:sign:accessorlisp-conditional-sign:documentation"The string \"#+\" or \"#-\"."
feature:initarg:feature:accessorlisp-conditional-feature
form:initarg:form:initformnil:accessorlisp-conditional-form:documentation

The guarded form node, or NIL when it was skipped; then TEXT of the whole node still shows it.

:documentation

A #+ or #- guarded form.

defclasslisp-comment
:documentation

A ; or #| |# comment, TEXT included.

defclasslisp-skipped
:documentation

Input the reader skipped for another reason.

defmethodprint-object
print-unreadable-object
nodestream:typet
let
text
node-textnode
whentext
formatstream"~S"
if
>
lengthtext
40
concatenate'string
subseqtext037
"..."
text
defunnode-line

The 1-based line on which node starts, given the vector line-starts.

defunline-of-position
let

Binary search for the last line start <= position.

loopwhile
<lowhigh
do
let
mid
ceiling
+lowhigh
2
if
setflowmid
setfhigh
1-mid
1+low
defunline-starts
text
let
starts
list0
loopforifrom0below
lengthtext
when
char=
chartexti
#\Newline
do
push
1+i
starts
coerce
nreversestarts
'vector

The Eclector client

defclasssource-reader
eclector.parse-result:parse-result-client
text:initarg:text:accessorreader-text:documentation"The whole source text, for slicing node text."
:documentation

An Eclector client that builds lisp-node trees.

defstruct
symbol-token
:constructormake-symbol-token
packagenameexternal-p
packagenameexternal-p
defmethodeclector.reader:interpret-symbol
input-streampackage-indicatorsymbol-nameinternp
declare
ignoreinput-stream
make-symbol-token
cond
eqpackage-indicator:current
nil
eqpackage-indicator:keyword
"KEYWORD"
nullpackage-indicator
:uninterned
tpackage-indicator
symbol-name
and
stringppackage-indicator
notinternp

Quote-like reader macros return marker lists so that make-expression-result can recognize them; the material is a node.

defmethodeclector.reader:wrap-in-quote
material
list:prefix"'"material
defmethodeclector.reader:wrap-in-function
name
list:prefix"#'"name
defmethodeclector.reader:wrap-in-quasiquote
form
list:prefix"`"form
defmethodeclector.reader:wrap-in-unquote
form
list:prefix","form
defmethodeclector.reader:wrap-in-unquote-splicing
form
list:prefix",@"form
defmethodeclector.reader:evaluate-expression
expression

#. is never evaluated; it becomes a prefix node.

list:prefix"#."expression
defmethodeclector.reader:evaluate-feature-expression
expression

Feature expressions are tokens, not symbols; evaluate them against this image's *FEATURES* by name so the reader can proceed. Both outcomes are kept visible: a true branch becomes a lisp-conditional with its form and a false branch a lisp-conditional whose text still shows it.

labels
feature-p
x
typecasex
symbol-token
and
member
symbol-token-namex
*features*:test#'string-equal
t
cons
let
operator
and
symbol-token-p
firstx
string-upcase
symbol-token-name
firstx
cond
equaloperator"AND"
every#'feature-p
restx
equaloperator"OR"
some#'feature-p
restx
equaloperator"NOT"
not
feature-p
tnil
tnil
feature-pexpression
defmethodeclector.reader:check-feature-expression
expression
declare
ignoreexpression
t
defmethodeclector.reader:make-structure-instance
nameinitargs
list:structurenameinitargs
defunsource-slice
clientsource
subseq
reader-textclient
carsource
cdrsource
defunconditional-source-p
clientsource

Return "#+" or "#-" when the source range begins with one.

let
text
reader-textclient
start
carsource
and
<
1+start
lengthtext
char=
chartextstart
#\#
member
chartext
1+start
'
#\+#\-
subseqtextstart
+start2
defmethodeclector.parse-result:make-expression-result
resultchildrensource
let
start
carsource
end
cdrsource
text
source-sliceclientsource
flet
node
class&restinitargs
apply#'make-instanceclass:startstart:endend:texttextinitargs
cond

#+feature form / #-feature form: two children, the feature and the form.

and
=
lengthchildren
2
node'lisp-conditional:sign:feature
firstchildren
:form
secondchildren
and
conspresult
eq
firstresult
:prefix
node'lisp-prefix:prefix
secondresult
:child
or
first
lastchildren
thirdresult
symbol-token-presult
node'lisp-symbol:name
symbol-token-nameresult
:package
symbol-token-packageresult
:external-p
symbol-token-external-presult
stringpresult
numberpresult
characterpresult
vectorpresult
node'lisp-vector:childrenchildren
conspresult
node'lisp-list:childrenchildren
nullresult
if
and
plusp
lengthtext
char=
chartext0
#\(
node'lisp-list:childrenchildren
node'lisp-symbol:name"NIL":packagenil
defmethodeclector.parse-result:make-skipped-input-result
streamreasonchildrensource
declare
ignorestream
let*
text
string-right-trim'
#\Newline#\Return
source-sliceclientsource
class
cond
and
conspreason
eq
carreason
:line-comment
'lisp-comment
eqreason:block-comment
'lisp-comment
and
conspreason
member
carreason
'
:sharpsign-plus:sharpsign-minus
'lisp-conditional
if
make-instanceclass:start
carsource
:end
cdrsource
:texttext:sign
if
eq
carreason
:sharpsign-plus
"#+""#-"
:feature
firstchildren
:formnil
make-instanceclass:start
carsource
:end
cdrsource
:texttext

Reading files

defunread-lisp-string
text&key
name"string"

Read text into a list of top-level LISP-NODEs, comments included, in order. Reader errors are recovered from where Eclector offers a restart and otherwise end the read; name labels warnings.

let*
client
make-instance'source-reader:texttext
stream
make-string-input-streamtext
nodes'
eof
list:eof
loop
multiple-value-bind
resultorphans
handler-bind
error
lambda
condition
let
restart
find-restart'eclector.reader:recovercondition
ifrestart
invoke-restartrestart
progn
warn"Stopped reading ~A: ~A"namecondition
eclector.parse-result:readclientstreamnileof

READ returns the parse result and the orphan results (comments and other skipped input before it), or EOF and the orphans.

setfnodes
append
reverseorphans
nodes
when
eqresulteof
pushresultnodes
merge-adjacent-comments
nreversenodes
text
defunmerge-adjacent-comments
nodestext

Join runs of line comments separated only by a newline into one lisp-comment, recursively inside lists, so a comment block is one node.

let
result'
dolist
nodenodes
let
previous
firstresult
cond
setf
node-endprevious
node-endnode
node-textprevious
string-right-trim'
#\Newline#\Return
subseqtext
node-startprevious
node-endnode
t
when
setf
element-childrennode
merge-adjacent-comments
element-childrennode
text
pushnoderesult
nreverseresult
defunadjacent-lines-p
abtext

True when node B starts on the line after the last line of node A, with only indentation between: consecutive comment lines. A's range may or may not include its trailing newline, so count from the end of its text.

let*
a-end
+
node-starta
length
node-texta
between
subseqtexta-end
node-startb
and
=1
count#\Newlinebetween
every
lambda
c
memberc'
#\Newline#\Return#\Space#\Tab
between
defunread-lisp-file
pathname
read-lisp-string
uiop:read-file-stringpathname
:name
namestringpathname

Definitions

defclassdefinition
kind:initarg:kind:accessordefinition-kind:documentation"The operator name, downcased, e.g. \"defun\"."
name:initarg:name:accessordefinition-name:documentation"The name as written, e.g. \"foo\" or \"(setf foo)\"."
qualifiers:initarg:qualifiers:initform'
:accessordefinition-qualifiers
specializers:initarg:specializers:initform'
:accessordefinition-specializers:documentation

For a method, the specializer texts of its required parameters, e.g. ("block-world" "t").

package:initarg:package:initformnil:accessordefinition-package:documentation"The IN-PACKAGE in force, as written, or NIL."
pathname:initarg:pathname:accessordefinition-pathname
line:initarg:line:accessordefinition-line
end-line:initarg:end-line:accessordefinition-end-line
node:initarg:node:accessordefinition-node
comments:initarg:comments:initform'
:accessordefinition-comments:documentation"Comment nodes immediately preceding the form."
mentions:initarg:mentions:initform'
:accessordefinition-mentions:documentation"Figure IDs mentioned in the form or its comments."
:documentation

One top-level defining form of a source file.

defmethodprint-object
print-unreadable-object
definitionstream:typet
formatstream"~A ~A"
definition-kinddefinition
definition-namedefinition
defundefinition-file-name
file-namestring
definition-pathnamedefinition
defuntext-mentions
text

Figure IDs mentioned as #ID in text, in order, without duplicates.

let
loopforifrom0below
lengthtext
do
when
char=
chartexti
#\#
let
end
whenend
pushnew
subseqtext
1+i
end
ids:test#'string=
nreverseids
defparameter*defining-operators*'
"defun""defmacro""defgeneric""defmethod""zdefun""zdefmethod""defclass""defstruct""deftype""defvar""defparameter""defconstant""define-condition""defpackage""define-symbol-macro""defsetf""define-modify-macro""deftest"

Operators whose top-level forms become definitions. Any other operator whose name starts with DEF or DEFINE- is accepted too.

defundefining-operator-p
name
let
name
string-downcasename
or
membername*defining-operators*:test#'string=
starts-with"def"name
defunzoned-definition-name-node

Return the actual definition name from a ZDEF name-and-options node.

if
let*
children
element-childrennode
first
firstchildren

(SETF ACCESSOR) is itself a function name. Every other list begins with the ordinary function name followed by :ZONE/:VALUE options.

if
and
typepfirst'lisp-symbol
string-equal
lisp-symbol-namefirst
"setf"
nodefirst
node
defunform-definition
nodecommentspathnameline-startspackage

Make a definition for the top-level list node if it defines something.

let*
children
element-childrennode
operator
symbol-node-name
firstchildren
when
andoperator
secondchildren
let*
zoned-p
memberoperator'
"zdefun""zdefmethod"
:test#'string-equal
name-node
qualifiers
loopforchildin
cddrchildren
while
typepchild'lisp-symbol
collect
node-textchild
method-p
memberoperator'
"defmethod""zdefmethod"
:test#'string-equal
lambda-list
andmethod-p
find-if
lambda
c
typepc'lisp-list
cddrchildren
specializers
andlambda-list
loopforparameterin
element-childrenlambda-list
until
and
typepparameter'lisp-symbol
char=
char
lisp-symbol-nameparameter
0
#\&
collect
if
and
typepparameter'lisp-list
second
element-childrenparameter
node-text
second
element-childrenparameter
"t"
texts
cons
node-textnode
mapcar#'node-textcomments
make-instance'definition:kind
string-downcaseoperator
:name
node-textname-node
:qualifiers
ifmethod-pqualifiers'
:specializersspecializers:packagepackage:pathnamepathname:line:end-line:nodenode:commentscomments:mentions
remove-duplicates
mapcan#'text-mentionstexts
:test#'string=:from-endt
defunnodes-definitions
nodespathnameline-starts

The DEFINITIONs among the top-level nodes of a file, in order. Comments that precede a form belong to it, and IN-PACKAGE forms set the package.

let
packagenil
pending'
definitions'
dolist
nodenodes
typecasenode
lisp-comment
pushnodepending
lisp-list
let
operator
symbol-node-name
first
element-childrennode
when
andoperator
string-equaloperator"in-package"
setfpackage
node-text
second
element-childrennode
let
definition
form-definitionnode
reversepending
pathnameline-startspackage
whendefinition
pushdefinitiondefinitions
setfpending'
t
setfpending'
nreversedefinitions
defunfile-definitions
pathname&optional
text
uiop:read-file-stringpathname

The DEFINITIONs of the source file at pathname, in order.

nodes-definitions
read-lisp-stringtext:name
namestringpathname
pathname
defundefinition-references
definitions

A hash table from figure ID to the DEFINITIONs mentioning it.

let
table
make-hash-table:test'equal
dolist
definitiondefinitions
dolist
id
definition-mentionsdefinition
pushdefinition
gethashidtable
maphash
lambda
idlist
setf
gethashidtable
nreverselist
table
table
defunfind-definition
namedefinitions&keykind

The first definition whose name matches name, case-insensitively, with or without a package prefix on either side.

flet
bare
string
let
colon
position#\:string:from-endt
ifcolon
subseqstring
1+colon
string
find-if
lambda
and
or
nullkind
string-equalkind
definition-kinddefinition
or
string-equalname
definition-namedefinition
string-equal
barename
bare
definition-namedefinition
definitions