3 Code Services
dxtr edited this page 2026-10-19 08:32:36 +02:00

Code Services

pyssel/editor/services is what the code editor asks of the Lisp it runs in, as LispWorks' IDE asks LispWorks: where a name is defined, who calls a function, completions, argument lists, help, a macro's expansion, compiling and evaluating with the compiler's notes at their places, a thread's frames in the debugger, and objects' parts, as an inspector shows them. It knows nothing of the user interface, so the Script screen, the REPL, the debugger and your programs ask it alike. Texts are Lisp texts, and a place's forms are read in the package text-package finds there, as SLY finds a buffer's.

(pyssel/editor/services:argument-hint "(subseq string " 0 15)
;; => "(subseq sequence start &optional end)", 17, 22

(mapcar #'pyssel/editor/services:definition-title
        (pyssel/editor/services:find-definitions 'print-object))
;; => ("(defgeneric print-object)" "(defmethod print-object (editor t))" ...)

Symbols and definitions

token-symbol gives the symbol a token names in a package, as the reader would read it, but interning nothing. find-definitions gives a name's definitions, a definition each, as SLY's M-. finds them: of a variable, a constant, a type, a symbol macro, a macro, a compiler macro, a function, a generic function and each of its methods, a SETF expander, a structure, a condition, a class, a method combination and a package of that name, each with its kind, its title, as (defmethod draw :before (sprite)), its file and the line and column its form starts at, a structure's accessor at its slot. The place is where the form is in the file as it is now: where it was compiled while the file is as it was, or while its top-level form still defines the name, else at the first form that does. who-calls and who-macroexpands give the functions that call a function, or where a macro expands, as SBCL's cross references know them.

Completion and argument hints

completions gives the completions of a symbol's text as typed, as SLY's flex completion finds them: the characters typed in order, each after the one before, in upper case, keywords for a text that starts with a colon, the symbols the package reaches, the external symbols of the others with their packages' shortest names, uite for ui:text-edit, and with two colons, every package's; the best matches first, each a completion with its text, its symbol, its kind and where the text typed matched. A completion over the 55,000 external symbols of an editor's Lisp takes about 4 ms.

argument-hint gives the lambda list of the operator of the form a place is in, as SLY's autodoc shows it, and where in it the parameter the place's argument binds starts and ends: out through four lists at most, into a macro's destructuring lists, a keyword parameter by its keyword, and make-instance of a quoted class with the class's initargs.

Help and expansions

symbol-help gives what a Lisp knows of a symbol as text: what it names, an operator with its lambda list, a variable with its value, a class with its superclasses and slots, each with its documentation. expand-form expands a macro form once, or with :all, every macro in it.

Compiling and evaluating

compile-form compiles the text of a form at a place of a file, as SLY's C-c C-c, and compile-and-load-file a file, as C-c C-k, each to a temporary file, and loads what compiled unless the compilation failed, as on an error or a warning that is not a style warning. Each gives a compilation: whether it failed and whether it loaded, and its compiler-notes, each with its severity, :error, :read-error, :warning, :style-warning, :note or :redefinition, its message and where the form it concerns starts and ends in the file, read from SBCL's compiler error context. The definitions a form compiled makes are found at its place in its file. evaluate-form evaluates a form's text in a package and gives its values.

The debugger

A debugger hook runs on the thread whose error or break entered the debugger, its stack as it was, so these are called there, as the editor's debugger calls them. backtrace gives the thread's frames, a frame each, with its index and its description, the call it stands for, from the frame that called error or break, :start and :count saying which; frame-locals a frame's variables that have a value where it is, as (symbol . value); frame-source where its form is in its file as the file is now, as find-definitions finds a definition; frame-package the package of its function's name; and evaluate-in-frame evaluates a form's text in its lexical environment, its variables visible, and gives its values. A frame is valid only on its thread, while the thread stays in the debugger.

(defun my-failing-function (n)
  (error "~D is too few." n))

(let ((sb-ext:*invoke-debugger-hook*      ; a hook, as the editor's
        (lambda (condition hook)
          (declare (ignore hook))
          (let ((frame (first (pyssel/editor/services:backtrace :count 1))))
            (print (pyssel/editor/services:frame-description frame))
            (print (pyssel/editor/services:frame-locals frame))
            (invoke-restart (find-restart 'abort condition))))))
  (with-simple-restart (abort "Leave.")
    (my-failing-function 4)))
;; prints "(MY-FAILING-FUNCTION 4)" and ((N . 4))

Inspecting

object-parts gives the parts of an object an inspector shows, as SLY's inspector shows them, a part each, with its label, its value and whether it has one, as an unbound slot has not: an instance's, a structure's or a condition's slots; a list's or a vector's elements, and a dotted list's tail; an array's dimensions and elements; a hash table's test, count and entries; a symbol's name, package, value, function and properties; a package's names, uses and symbols; a function's name, lambda list and documentation; a pathname's components; a character's code and name; a ratio's and a complex's parts. It gives the first +inspected-parts+, 500, and how many more there are, or nil for a circular list. It is a generic function, which a program extends for objects of its own. object-title heads an object, its type and how it prints, and object-text prints one as an inspector shows it, on one line, cut short.

(mapcar #'pyssel/editor/services:part-label
        (pyssel/editor/services:object-parts (make-hash-table)))
;; => ("test" "count")

Thread rules

Each function answers on the thread that calls it and keeps no state between calls; the debugger's, on the thread in the debugger. Compiling and evaluating run there, which their output and errors reach, so the editor calls them on a thread of its own; compiling holds SBCL's lock of the compiler while it works.