Paolo Amoroso's Journal

CommonLisp

I wrote ILsee, an Interlisp source file viewer. It is the first of the ILtools collection of tools for viewing and accessing Interlisp data.

I developed ILsee in Common Lisp on Linux with SBCL and the McCLIM implementation of the CLIM GUI toolkit. SLY for Emacs completed my Lisp tooling and, as for infrastructure, ILtools is the first new project I host at Codeberg.

This is ILsee showing the code of an Interlisp file:

Screenshot of the ILsee GUI program displaying the code of an Interlisp source file.

Motivation

The concepts and features of CLIM, such as stream-oriented I/O and presentation types, blend well with Lisp and feel natural to me. McCLIM has come a long way since I last used it a couple of decades ago and I have been meaning to play with it again for some time.

I wanted to do a McCLIM project related to Medley Interlisp, as well as try out SLY and Codeberg. A suite of tools for visualising and processing Interlisp data seemed the perfect fit.

The Interlisp file viewer ILsee is the first such tool.

Interlisp source files

Why an Interlisp file viewer instead of less or an editor?

In the managed residential environment of Medley Interlisp you don't edit text files of Lisp code. You edit the code in the running image and the system keeps track of and saves the code to “symbolic files”, i.e. databases that contain code and metadata.

Medley maintains symbolic files automatically and you aren't supposed to edit them. These databases have a textual format with control codes that change the text style.

When displaying the code of a symbolic file with, say, the SEdit structure editor, Medley interprets the control codes to perform syntax highlighting of the Lisp code. For example, the names of functions in definitions are in large bold text, some function names and symbols are in bold, and the system also performs a few character substitutions like rendering the underscore _ as the left arrow and the caret ^ as the up arrow .

This is what the same Interlisp code of the above screenshot looks like in the TEdit WYSIWYG editor on Medley:

Screenshot of the code of an Interlisp source file displayed by the TEdit editor on Medley Interlisp.

Medley comes with the shell script lsee, an Interlisp file viewer for Unix systems. The script interprets the control codes to appropriately render text styles as colors in a terminal. lsee shows the above code like this:

Screenshot of the lsee shell script displaying the code of an Interlisp source file in a Linux terminal.

The file viewer

ILsee is like lsee but displays files in a GUI instead of a terminal.

The GUI comprises a main pane that displays the current Interlisp file, a label with the file name, a command line processor that executes commands (also available as items of the menu bar), and the standard CLIM pointer documentation pane.

There are two commands, See File to display an Interlisp file and Quit to terminate the program.

Since ILsee is a CLIM application it supports the usual facilities of the toolkit such as input completion and presentation types. This means that, in the command processor pane, the presentations of commands and file names become mouse sensitive in input contexts in which a command can be executed or a file name is requested as an argument.

The ILtools repository provides basic instructions for installing and using the application.

Application design and GUI

I initially used McCLIM a couple of decades ago but mostly left it after that and, when I picked it back up for ILtools, I was a bit rusty.

The McCLIM documentation, the CLIM specification, and the research literature are more than enough to get started and put together simple applications. The code of the many example programs of McCLIM help me fill in the details and understand features I'm not familiar with. Still, I would have appreciated the CLIM specification to provide more examples, the near lack of which makes the many concepts and features harder to grasp.

The design of ILsee mirrors the typical structure of CLIM programs such as the definitions of application frames and commands. The slots of the application frame hold application specific data: the name of the currently displayed file and a list of text lines read from the file.

The function display-file does most of the work and displays the code of a file in the application pane.

It processes the text lines one by one character by character, dispatching on the control codes to activate the relevant text attributes or perform character substitution. display-file does incremental redisplay to reduce flicker when repainting the pane, for example after it is scrolled or obscured.

The code has some minor and easy to isolate SBCL dependencies.

Next steps

I'm pleased at how ILsee turned out. The program serves as a useful tool and writing it was a good learning experience. I'm also pleased at CLIM and its nearly complete implementation McCLIM. It takes little CLIM code to provide a lot of advanced functionality.

But I have some more work to do and ideas for ILsee and ILtools. Aside from small fixes, a few additional features can make the program more practical and flexible.

The pane layout may need tweaking to better adapt to different window sizes and shapes. Typing file names becomes tedious quickly, so I may add a simple browser pane with a list of clickable files and directories to display the code or navigate the file system.

And, of course, I will write more tools for the ILtools collection.

#ILtools #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

DandeGUI now does graphics and this is what it looks like.

Some text and graphics output windows created with DandeGUI on Medley Interlisp.

In addition to the square root table text output demo, I created the other graphics windows with the newly implemented functionality. For example, this code draws the random circles of the top window:

(DEFUN RANDOM-CIRCLES (&KEY (N 200)
                            (MAX-R 50)
                            (WIDTH 640)
                            (HEIGHT 480))
       (LET ((RANGE-X (- WIDTH (* 2 MAX-R)))
             (RANGE-Y (- HEIGHT (* 2 MAX-R)))
             (SHADES (LIST IL:BLACKSHADE IL:GRAYSHADE (RANDOM 65536))))
            (DANDEGUI:WITH-GRAPHICS-WINDOW (STREAM :TITLE "Random Circles")
					   (DOTIMES (I N)
						    (DECLARE (IGNORE I))
						    (IL:FILLCIRCLE (+ MAX-R (RANDOM RANGE-X))
								   (+ MAX-R (RANDOM RANGE-Y))
								   (RANDOM MAX-R)
								   (ELT SHADES (RANDOM 3))
								   STREAM)))))

GUI:WITH-GRAPHICS-WINDOW, GUI:OPEN-GRAPHICS-STREAM, and GUI:WITH-GRAPHICS-STREAM are the main additions. These functions and macros are the equivalent for graphics of what GUI:WITH-OUTPUT-TO-WINDOW, GUI:OPEN-WINDOW-STREAM, and GUI:WITH-WINDOW-STREAM, respectively, do for text. The difference is the text facilities send output to TEXTSTREAM streams whereas the graphics facilities to IMAGESTREAM, a type of device-independent graphics streams.

Under the hood DandeGUI text windows are customized TEdit windows with an associated TEXTSTREAM. TEdit is the rich text editor of Medley Interlisp.

Similarly, the graphics windows of DandeGUI run the Sketch line drawing editor under the hood. Sketch windows have an IMAGESTREAM which Interlisp graphics primitives like IL:DRAWLINE and IL:DRAWPOINT accept as an output destination. DandeGUI creates and manages Sketch windows with the type of stream the graphics primitives require. In other words, IMAGESTREAM is to Sketch what TEXTSTREAM is to TEdit.

The benefits of programmatically using Sketch for graphics are the same as TEdit windows for text: automatic window repainting, scrolling, and resizing. The downside is overhead. Scrolling more than a few thousand graphics elements is slow and adding even more may crash the system. However, this is an acceptable tradeoff.

The new graphics functions and macros work similarly to the text ones, with a few differences. First, DandeGUI now depends on the SKETCH and SKETCH-STREAM library modules which it automatically loads.

Since Sketch has no notion of a read-only drawing area GUI:OPEN-GRAPHICS-STREAM achieves the same effect by other means:

(DEFUN OPEN-GRAPHICS-STREAM (&KEY (TITLE "Untitled"))
   "Open a new window and return the associated IMAGESTREAM to send graphics output to.
Sets the window title to TITLE if supplied."
   (LET* ((STREAM (IL:OPENIMAGESTREAM '|Untitled| 'IL:SKETCH '(IL:FONTS ,*DEFAULT-FONT*)))
          (WINDOW (IL:\\SKSTRM.WINDOW.FROM.STREAM STREAM)))
         (IL:WINDOWPROP WINDOW 'IL:TITLE TITLE)
         ;; Disable left and middle-click title bar menu
         (IL:WINDOWPROP WINDOW 'IL:BUTTONEVENTFN NIL)
         ;; Disable sketch editing via right-click actions
         (IL:WINDOWPROP WINDOW 'IL:RIGHTBUTTONFN NIL)
         ;; Disable querying the user whether to save changes
         (IL:WINDOWPROP WINDOW 'IL:DONTQUERYCHANGES T)
         STREAM))

Only the mouse gestures and commands of the middle-click title bar menu and the right-click menu change the drawing area interactively. To disable these actions GUI:OPEN-GRAPHICS-STREAM removes their menu handlers by setting to NIL the window properties IL:BUTTONEVENTFN and IL:RIGHTBUTTONFN. This way only programmatic output can change the drawing area.

The function also sets IL:DONTQUERYCHANGES to T to prevent querying whether to save the changes at window close. By design output to DandeGUI windows is not permanent, so saving isn't necessary.

GUI:WITH-GRAPHICS-STREAM and GUI:WITH-GRAPHICS-WINDOW are straightforward:

(DEFMACRO WITH-GRAPHICS-STREAM ((VAR STREAM)
                                &BODY BODY)
   "Perform the operations in BODY with VAR bound to the graphics window STREAM.
Evaluates the forms in BODY in a context in which VAR is bound to STREAM which must already exist, then returns the value of the last form of BODY."
   `(LET ((,VAR ,STREAM))
         ,@BODY))

(DEFMACRO WITH-GRAPHICS-WINDOW ((VAR &KEY TITLE)
                                &BODY BODY)
   "Perform the operations in BODY with VAR bound to a new graphics window stream.
Creates a new window titled TITLE if supplied, binds VAR to the IMAGESTREAM associated with the window, and executes BODY in this context. Returns the value of the last form of BODY."
   `(WITH-GRAPHICS-STREAM (,VAR (OPEN-GRAPHICS-STREAM :TITLE (OR ,TITLE "Untitled")))
           ,@BODY))

Unlike GUI:WITH-TEXT-STREAM and GUI:WITH-TEXT-WINDOW, which need to call GUI::WITH-WRITE-ENABLED to establish a read-only environment after every output operation, GUI:OPEN-GRAPHICS-STREAM can do this only once at window creation.

GUI:CLEAR-WINDOW, GUI:WINDOW-TITLE, and GUI:PRINT-MESSAGE now work with graphics streams in addition to text streams. For IMAGESTREAM arguments GUI:PRINT-MESSAGE prints to the system prompt window as Sketch stream windows have no prompt area.

The random circles and fractal triangles graphics demos round up the latest additions.

#DandeGUI #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

Printing rich text to windows is one of the planned features of DandeGUI, the GUI library for Medley Interlisp I'm developing in Common Lisp. I finally got around to this and implemented the GUI:WITH-TEXT-STYLE macro which controls the attributes of text printed to a window, such as the font family and face.

GUI:WITH-TEXT-STYLE establishes a context in which text printed to the stream associated with a TEdit window is rendered in the style specified by the arguments. The call to GUI:WITH-TEXT-STYLE here extends the square root table example by printing the heading in a 12-point bold sans serif font:

(GUI:WITH-OUTPUT-TO-WINDOW (STREAM :TITLE "Table of square roots")
  (GUI:WITH-TEXT-STYLE STREAM :FAMILY :SANS :SIZE 12 :FACE :BOLD)
    (FORMAT STREAM "~&Number~40TSquare Root~2%"))
  (LOOP
    FOR N FROM 1 TO 30
    DO (FORMAT STREAM "~&~4D~40T~8,4F~%" N (SQRT N))))

The code produces this window in which the styled column headings stand out:

Medley Interlisp window of a square root table generated by the DandeGUI GUI library.

The :FAMILY, :SIZE, and :FACE arguments determine the corresponding text attributes. :FAMILY may be a generic family such as :SERIF for an unspecified serif font; :SANS for a sans serif font; :FIX for a fixed width font; or a keyword denoting a specific family like :TIMESROMAN.

At the heart of GUI:WITH-TEXT-STYLE is a pair of calls to the Interlisp function PRINTOUT that wrap the macro body, the first for setting the font of the stream to the specified style and the other for restoring the default:

(DEFMACRO WITH-TEXT-STYLE ((STREAM &KEY FAMILY SIZE FACE)
                           &BODY BODY)
   (ONCE-ONLY (STREAM)
          `(UNWIND-PROTECT
               (PROGN (IL:PRINTOUT ,STREAM IL:.FONT (TEXT-STYLE-TO-FD ,FAMILY ,SIZE ,FACE))
                      ,@BODY)
               (IL:PRINTOUT ,STREAM IL:.FONT *DEFAULT-FONT*))))

PRINTOUT is an Interlisp function for formatted output similar to Common Lisp's FORMAT but with additional font control via the .FONT directive. The symbols of PRINTOUT, i.e. its directives and arguments, are in the Interlisp package.

In turn GUI:WITH-TEXT-STYLE calls GUI::TEXT-STYLE-TO-FD, an internal DandeGUI function which passes to .FONT a font descriptor matching the required text attributes. GUI::TEXT-STYLE-TO-FD calls IL:FONTCOPY to build a descriptor that merges the specified attributes with any unspecified ones copied from the default font.

The font descriptor is an Interlisp data structure that represents a font on the Medley environment.

#DandeGUI #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

I continued working on DandeGUI, a GUI library for Medley Interlisp I'm writing in Common Lisp. I added two new short public functions, GUI:CLEAR-WINDOW and GUI:PRINT-MESSAGE, and fixed a bug in some internal code.

GUI:CLEAR-WINDOW deletes the text of the window associated with the Interlisp TEXTSTREAM passed as the argument:

(DEFUN CLEAR-WINDOW (STREAM)
   "Delete all the text of the window associated with STREAM. Returns STREAM"
   (WITH-WRITE-ENABLED (STR STREAM)
          (IL:TEDIT.DELETE STR 1 (IL:TEDIT.NCHARS STR)))
   STREAM)

It's little more than a call to the TEdit API function IL:TEDIT.DELETE for deleting text in the editor buffer, wrapped in the internal macro GUI::WITH-WRITE-ENABLED that establishes a context for write access to a window.

I also wrote GUI:PRINT-MESSAGE. This function prints a message to the prompt area of the window associated with the TEXTSTREAM passed as an argument, optionally clearing the area prior to printing. The prompt area is a one-line Interlisp prompt window attached above the title bar of the TEdit window where the editor displays errors and status messages.

(DEFUN PRINT-MESSAGE (STREAM MESSAGE &OPTIONAL DONT-CLEAR-P)
   "Print MESSAGE to the prompt area of the window associated with STREAM. If DONT-CLEAR-P is non NIL the area will be cleared first. Returns STREAM."
   (IL:TEDIT.PROMPTPRINT STREAM MESSAGE (NOT DONT-CLEAR-P))
   STREAM)

GUI:PRINT-MESSAGE just passes the appropriate arguments to the TEdit API function IL:TEDIT.PROMPTPRINT which does the actual printing.

The documentation of both functions is in the API reference on the project repo.

Testing DandeGUI revealed that sometimes text wasn't appended to the end but inserted at the beginning of windows. To address the issue I changed GUI::WITH-WRITE-ENABLED to ensure the file pointer of the stream is set to the end of the file (i.e -1) prior to passing control to output functions. The fix was to add a call to the Interlisp function IL:SETFILEPTR:

(IL:SETFILEPTR ,STREAM -1)

#DandeGUI #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

I'm working on DandeGUI, a Common Lisp GUI library for simple text and graphics output on Medley Interlisp. The name, pronounced “dandy guy”, is a nod to the Dandelion workstation, one of the Xerox D-machines Interlisp-D ran on in the 1980s.

DandeGUI allows the creation and management of windows for stream-based text and graphics output. It captures typical GUI patterns of the Medley environment such as printing text to a window instead of the standard output. The main window of this screenshot was created by the code shown above it.

A text output window created with DandeGUI on Medley Interlisp and the Lisp code that generated it.

The library is written in Common Lisp and exposes its functionality as an API callable from Common Lisp and Interlisp code.

Motivations

In most of my prior Lisp projects I wrote programs that print text to windows.

In general these windows are actually not bare Medley windows but running instances of the TEdit rich-text editor. Driving a full editor instead of directly creating windows may be overkill, but I get for free content scrolling as well as window resizing and repainting which TEdit handles automatically.

Moreover, TEdit windows have an associated TEXTSTREAM, an Interlisp data structure for text stream I/O. A TEXTSTREAM can be passed to any Common Lisp or Interlisp output function that takes a stream as an argument such as PRINC, FORMAT, and PRIN1. For example, if S is the TEXTSTREAM associated with a TEdit window, (FORMAT S "~&Hello, Medley!~%") inserts the text “Hello, Medley!” in the window at the position of the cursor. Simple and versatile.

As I wrote more GUI code, recurring patterns and boilerplate emerged. These programs usually create a new TEdit window; set up the title and other options; fetch the associated text stream; and return it for further use. The rest of the program prints application specific text to the stream and hence to the window.

These patterns were ripe for abstracting and packaging in a library that other programs can call. This work is also good experience with API design.

Usage

An example best illustrates what DandeGUI can do and how to use it. Suppose you want to display in a window some text such as a table of square roots. This code creates the table in the screenshot above:

(GUI:WITH-OUTPUT-TO-WINDOW (STREAM :TITLE "Table of square roots")
  (FORMAT STREAM "~&Number~40TSquare Root~2%")
  (LOOP
    FOR N FROM 1 TO 30
    DO (FORMAT STREAM "~&~4D~40T~8,4F~%" N (SQRT N))))

DandeGUI exports all the public symbols from the DANDEGUI package with nickname GUI. The macro GUI:WITH-OUTPUT-TO-WINDOW creates a new TEdit window with title specified by :TITLE, and establishes a context in which the variable STREAM is bound to the stream associated with the window. The rest of the code prints the table by repeatedly calling the Common Lisp function FORMAT with the stream.

GUI:WITH-OUTPUT-TO-WINDOW is best suited for one-off output as the stream is no longer accessible outside of its scope.

To retain the stream and send output in a series of steps, or from different parts of the program, you need a combination of GUI:OPEN-WINDOW-STREAM and GUI:WITH-WINDOW-STREAM. The former opens and returns a new window stream which may later be used by FORMAT and other stream output functions. These functions must be wrapped in calls to the macro GUI:WITH-WINDOW-STREAM to establish a context in which a variable is bound to the appropriate stream.

The DandeGUI documentation on the project repository provides more details, sample code, and the API reference.

Design

DandeGUI is a thin wrapper around the Interlisp system facilities that provide the underlying functionality.

The main reason for a thin wrapper is to have a simple API that covers the most common user interface patterns. Despite the simplicity, the library takes care of a lot of the complexity of managing Medley GUIs such as content scrolling and window repainting and resizing.

A thin wrapper doesn't hide much the data structures ubiquitous in Medley GUIs such as menus and font descriptors. This is a plus as the programmer leverages prior knowledge of these facilities.

So far I have no clear idea how DandeGUI may evolve. One more reason not to deepen the wrapper too much without a clear direction.

The user needs not know whether DandeGUI packs TEdit or ordinary windows under the hood. Therefore, another design goal is to hide this implementation detail. DandeGUI, for example, disables the main command menu of TEdit and sets the editor buffer to read-only so that typing in the window doesn't change the text accidentally.

Using Medley Common Lisp

DandeGUI relies on basic Common Lisp features. Although the Medley Common Lisp implementation is not ANSI compliant it provides all I need, with one exception.

The function DANDEGUI:WINDOW-TITLE returns the title of a window and allows to set it with a SETF function. However, the SEdit structure editor and the File Manager of Medley don't support or track function names that are lists such as (SETF WINDOW-TITLE). A good workaround is to define SETF functions with DEFSETF which Medley does support along with the CLtL macro DEFINE-SETF-METHOD.

Next steps

At present DandeGUI doesn't do much more than what described here.

To enhance this foundation I'll likely allow to clear existing text and give control over where to insert text in windows, such as at the beginning or end. DandeGUI will also have rich text facilities like printing in bold or changing fonts.

The windows of some of my programs have an attached menu of commands and a status area for displaying errors and other messages. I will eventually implement such menu-ed windows.

To support programs that do graphics output I plan to leverage the functionality of Sketch for graphics in a way similar to how I build upon TEdit for text.

Sketch is the line drawing editor of Medley. The Interlisp graphics primitives require as an argument a IMAGESTREAM, a data stracture that represents an output sink for graphics. It is possible to use the Sketch drawing area as an output destination by associating a IMAGESTREAM with the editor's window. Like TEdit, Sketch takes care of repainting content as well as window scrolling and resizing. In other words, IMAGESTREAM is to Sketch what TEXTSTREAM is to TEdit.

DandeGUI will create and manage Sketch windows with associated streams suitable for use as the IMAGESTREAM the graphics primitives require.

#DandeGUI #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

I implemented the last features originally planned for Insphex, my hex dump tool in Common Lisp for Medley Interlisp.

The first new feature is an Exec command for invoking the program. The command HD works the same way as the function INSPHEX:HEXDUMP and accepts the same arguments, a file name and an optional boolean flag to indicate whether the output should go to a separate window:

← HD FILENAME [NEWIN-P]

The other feature is the addition to the File Browser menu of the Hexdump command, which shows the hex dump of the selected files in as many separate windows:

Hexdump File Browser command of the Insphex hex dump tool on Medley Interlisp.

For other commands that produce output in windows the File Browser lets the user view one window at a time, with menu options for skipping through the windows. Insphex doesn't do anything so elaborate though.

Implementing the features was easy as the relevant Interlisp APIs are well documented and I have experience with adding an Exec command to Stringscope.

The Medley Lisp library modules manual covers the File Browser API from page 115 of the PDF, with the explanation of how to add commands on page 118. It's as simple as registering a callback function the command invokes, INSPHEX::FB-HEXDUMP for Insphex.

An issue I bumped into is that instead of 4 arguments as the manual says, the callback actually requires 5. The last, undocumented argument was likely introduced since the publication of the manual.

#insphex #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

I added a GUI to Insphex, the hex dump tool I'm writing in Common Lisp on the Medley Interlisp environment.

The initial version printed the hex dump only to the standard output, now optionally to a separate TEdit window with a command menu. The menu has items for displaying the next page of output, redisplaying from the beginning of the file, and exiting the program.

Window and command menu of the Insphex hex dump tool for Medley Interlisp.

Most window, menu, and other Medley GUI facilities, like the TEdit rich text editor, provide Interlisp APIs in the IL package that Common Lisp programs such as Insphex can access. However, since the APIs usually rely on Interlisp records, from Common Lisp it's often necessary to write quite a few package qualifiers like this example to create a menu record:

(IL:CREATE IL:MENU
           IL:ITEMS IL:← '(ITEM1 ITEM2 ITEM3)
           IL:MENUFONT IL:← '(IL:MODERN 12)
           IL:TITLE IL:← "Menu"
           IL:CENTERFLG IL:← T)

The XCL:DEFINE-RECORD macro helps reduce package qualifiers by wrapping Interlisp records in equivalent Common Lisp structures with ordinary structure accessors, setters, predicates, and constructors. The structures can be in any package, not just IL like Interlisp symbols. XCL:DEFINE-RECORD is described on page 7-3 (page 143 of the PDF) of the Medley 1.0 release notes.

This way Common Lisp blends well with Interlisp and reduces verbosity. For example, this is the Insphex Common Lisp function that creates the output window:

(DEFUN CREATE-HEX-WINDOW (FILE)
   "Create and return a window to display the hex dump of FILE."
   (LET* ((IN (OPEN FILE :DIRECTION :INPUT :ELEMENT-TYPE '(UNSIGNED-BYTE 8)))
          (COMMANDS (IL:MENUWINDOW (MAKE-MENU :ITEMS '(("Next" :NEXT "Show the next page.")
                                                       ("Reread" :REREAD "Reread the input file.")
                                                       ("Exit" :EXIT "Quit the program."))
                                          :MENUFONT
                                          '(IL:MODERN 12)
                                          :TITLE "Commands" :CENTERFLG T :WHENSELECTEDFN 
                                          #'HANDLE-MENU)))
          (OUT (IL:OPENTEXTSTREAM))
          (TEDIT-PROC (IL:TEDIT OUT))
          (WINDOW (IL:WFROMDS OUT)))
     (IL:ATTACHWINDOW COMMANDS WINDOW 'IL:TOP 'IL:LEFT)
     (IL:WINDOWPROP WINDOW 'INSTREAM IN)
     (IL:WINDOWPROP WINDOW 'OUTSTREAM OUT)
     (IL:WINDOWPROP WINDOW 'BLOCK-OFFSET 0)
     (IL:WINDOWPROP WINDOW 'IL:TITLE (FORMAT NIL "Insphex ~A" FILE))
     (NEXT-HEX-PAGE WINDOW)
     WINDOW))

The INSPHEX::MAKE-MENU constructor creates a Common Lisp INSPHEX::MENU structure that wraps the Interlisp IL:MENU record.

Most of the Insphex GUI functionality is in place but I need to work on a couple of tweaks.

First, the Insphex window should be read-only whereas now the user can type into the editor buffer. Next, I need to clean up all the allocated resources when the user quits the program via various interaction flows, such as closing the window instead of clicking the Exit menu item.

#insphex #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

I'm developing the new program Insphex (inspect hex), a hex dump tool that is created with and runs on the Medley Interlisp environment.

Similarly to the Linux command hexdump, it shows the contents of files as hexadecimal values and the corresponding ASCII characters. An early version of the program prints the hex dump to the standard output like this.

Output of the Insphex hex dump tool for Medley Interlisp.

I plan to enhance Insphex to optionally display the dump in a separate window one page at a time. An attached menu will have options for showing the next page and exiting. I'll also provide an Exec command for running the program.

The code is in Common Lisp but will include some Interlisp to access the required system functionality.

Although Insphex is useful in itself, I have three main goals for it. First, I want a real project to practice the process for writing Common Lisp with the residential environment of Medley. This is the native way of coding on Medley and takes full advantage of its development environment and features such as the File Manager and the SEdit editor.

Most Medley tools and facilities are written in Interlisp or expose Interlisp APIs through which the functionality can be invoked. So another goal is to interface with Interlisp from Common Lisp to access the functionality I need like windows and menus.

My third goal is to experiment with displaying textual output in TEdit, the Medley word processor where the hex dump will optionally go.

Although the Interlisp API of TEdit supports advanced editing and formatting, Insphex does only basic text output. The primary feature I want is TEdit's ability to automatically handle repainting the window after it's resized or a hidden portion is exposed. This is handy as by default Interlisp windows mostly don't handle the repaint.

Now that the basic functionality of Insphex is in place I will implement displaying the hex dump in a TEdit window.

#insphex #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

When the Medley Interlisp Project began reviving the system around 2020, its Common Lisp implementation was in the state it had when commercial development petered off in the 1990s, mostly prior to the ANSI standard.

Back then Medley Common Lisp mostly supported CLtL1 plus CLOS and the condition system. Some patches submitted several years later to bring the language closer to CLtL2 needed review and integration.

Aside from these general areas there was no detailed information on what Medley missed or differed from ANSI Common Lisp.

In late 2021 Larry Masinter proposed to evaluate the ANSI compatibility of Medley Common Lisp by running the code of popular Common Lisp books and documenting any divergences. In March of 2024 I set to work to test the code of the book Practical Common Lisp by Peter Seibel.

I went over the book chapter by chapter and completed a first pass, documenting the effort in a GitHub issue and a series of discussion posts. In addition I updated a running list of divergences from ANSI Common Lisp.

Methodology

Part of the code of the book is contained in the examples in the text and the rest in the downloadable source files, which constitute some more substantial projects.

To test the code on Medley I evaluated the definitions and expressions at a Xerox Common Lisp Exec, noting any errors or differences from the expected outcomes. When relevant source files were available I loaded them prior to evaluating the test expressions so that any required definitions and dependencies were present. ASDF hasn't been ported to Medley, so I loaded the files manually.

Adapting the code

Before running the code I had to apply a number of changes. I filled in any missing function and class definitions the book leaves out as incidental to the exposition. This also involved adding appropriate function calls and object instantiations to exercise the definitions or produce the expected output.

The source files of the book needed adaptation too due to the way Medley handles pure Common Lisp files.

Skipped code

The text and source files contain also code I couldn't run because some features are known to be missing from Medley, or key dependencies can't be fulfilled. For example, a few chapters rely on the AllegroServe HTTP server which doesn't run on Medley. Although Medley does have a XNS network stack, providing the TCP/IP network functions AllegroServe assumes would be a major project.

Some chapters depend on code in earlier chapters that uses features not available in Medley Common Lisp, so I had to skip those too.

Findings

Having completed the first pass over Practical Common Lisp, my initial impression is Medley's implementation of Common Lisp is capable and extensive. It can run with minor or no changes code that uses most basic and intermediate Common Lisp features.

The majority of the code I tried ran as expected. However, this work did reveal significant gaps and divergences from ANSI.

To account for the residential environment and other peculiarities of Medley, packages need to be defined in a specific way. For example, some common defpackage keyword arguments differ from ANSI. Also, uppercase strings seem to work better than keywords as package designators.

As for the gaps the loop iteration macro, symbol-macrolet, the #p reader macro, and other features turned out to be missing or not work.

While the incompatibilities with ANSI Common Lisp are relativaly easy to address or work around, what new users may find more difficult is understanding and using the residential environment of Medley.

Bringing Medley closer to ANSI Common Lisp

To plug the gaps this project uncovered Larry ported or implemented some of the missing features and fixed a few issues.

He ported a loop implementation which he's enhancing to add missing functionality like iterating over hash tables. Iterating over packages, which loop lacks at this time, is trickier. More work went into adding #p and an experimental symbol-macrolet.

Reviewing and merging the CLtL2 patches is still an open issue, a major project that involves substantial effort.

Future work and conclusion

When the new features are ready I'll do a second pass to check if more of the skipped code runs. Another outcome of the work may be the beginning of a test suite for Medley Common Lisp.

Regardless of the limitations, what the project highlighted is Medley is ready as a development environment for writing new Common Lisp code, or porting libraries and applications of small to medium complexity.

#CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space

I got a cheap used copy of the book A Programmer's Guide to COMMON LISP by Deborah G. Tatar, Digital Press, 1987.

The book A Programmer's Guide to COMMON LISP by Deborah G.

Why did I read such an old book, published a few years after CLtL1 and well before ANSI finalized the Common Lisp standard?

I'm always looking for good Lisp books. Since Medley is my primary Lisp environment, I'm particularly interested in books published when the system was originally developed and used. These works are relevant because they cover a set of features close to the state of the Common Lisp implementation of Medley, and present a programming style typical of Lisp development in those years.

Two old reviews got me curious about A Programmer's Guide to COMMON LISP, one by Daniel Weinreb and the other by Richard Caruana.

Both reviews point out the book is different from most contemporary introductory Lisp books which focus on AI. Although Tatar's does contain some AI code, such as an interesting and complete toy expert system, the sample code spans a wider range of domains like a text formatter similar to nroff.

What sets A Programmer's Guide to COMMON LISP apart from other Lisp books is its environment independent discussion of the interactive Lisp programming process. Writing code in the editor, evaluating expressions from the editor, interacting with the REPL for testing expressions and exploring, and so on.

I've never seen the process expressed so clearly in any book, past of present. I'm familiar with it but the material is particulary helpful for complete beginners.

Although the short chapter on macros presents some interesting examples like a simplified version of defstruct, it doesn't discuss gensym and variable capture. This is unusual. But it's only one of a few issues and the book is a valuable addition to my Lisp library.

#CommonLisp #books #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space