Line Editor in uLisp


This is my very fist Lisp program (yay!), so I’m interested in any kind of feedback on conventions, best practices, how I’ve mangled a beautiful programming language, etc…

I’ve made a little standalone uLisp computer using a Teensy 4.1 and a Propeller to generate an NTSC signal, so I thought a text editor would be a fine choice for a first program. ‘Back in the day’ DOS included a ‘line editor’ called edlin and this approach would mean I could use the existing uLisp/Terminal functionality for entering text and not have to worry about cursor placement, and such.

It’s not too lengthy, so I’m posting it here. It can be invoked with an optional parameter that’s expected to be a list of strings, and it will return the final list of strings after editing is completed. There’s nothing particular to my little uLisp machine about it; it works in the terminal…

(defun edlin (&optional textlines)
  (cond ((not (listp textlines))
        (setq textlines (list ""))))
  (let ((currline "")
        (currlineno (length textlines))
        (lexresults (list #\a #\b 0))
        (action #\a))
          (format t "~a>" currlineno)
          (setq currline (read-line))
          (setq lexresults (read-from-string currline))
           ((listp lexresults)
            (setq action (car lexresults))
             ((numberp (cadr lexresults))
              (setq currlineno (cadr lexresults)))
              (setq currlineno (length textlines))))
             ((eq action 'e)
              (format t "~a|~a~%" currlineno (nth currlineno textlines)))
             ((eq action 'd)
              (setq textlines (removenth currlineno textlines))
              (setq currlineno (length textlines)))
             ((eq action 'i)
              (setq textlines (insertnth '"" currlineno textlines)))
             ((eq action 'k)
               ((numberp (cadr lexresults))
                (setq currlineno (cadr lexresults)))
                (setq currlineno 0)))
              (dotimes (i 5)
                       (format t "~a|~a~%" currlineno (nth currlineno textlines))
                       (incf currlineno)))
             ((eq action 'l)
              (setq currlineno 0)
              (dolist (aline textlines)
                      (format t "~a|~a~%" currlineno aline)
                      (incf currlineno)))
             ((eq action 'x)
              (return textlines))))
             ((< currlineno (length textlines))
              (setf (nth currlineno textlines) currline)
              (setq currlineno (length textlines)))
             ((>= currlineno (length textlines))
              (setq textlines (reverse (cons currline (reverse textlines))))
              (setq currlineno (length textlines)))))))))

I cheated a bit and found a couple of functions to insert and remove nth items from a list (Lee Mac Programs), so I’m including them here for reference.

(defun removenth (n l)
  (if (and l (< 0 n))
      (cons (car l) (removenth (1- n) (cdr l)))
      (cdr l)))

(defun insertnth (x n l)
   ((null l) nil)
   ((< 0 n) (cons (car l) (insertnth x (1- n) (cdr l))))
   ((cons x l))))


I should have mentioned… You can enter commands to edlin instead of new text…

(d linenumber) - delete a line
(e linenumber) - edit a line
(i beforelinenumber) - insert a new line
(k firstline) - list 5 lines starting with firstline
(l) - list all lines
(x) - quit


The following function, edlinsd, wraps edlin with basic file i/o. A couple of functions for reading and writing files to the SD card are separate from edlinsd, not sure if that’s the LISP way but I bet it is :).

edlinsd takes a filename as argument and will open that file in edlin, then save it again when you exit edlin…

(defun edlinsd (filename)
  (let ((document (read-file-sd filename)))
    (setq document (edlin document))
    (write-file-sd filename document)))

read-file-sd simply reads a file, which is assumes to be text, and returns a list of strings…

(defun read-file-sd (filename)
  (with-sd-card (str filename)
    (let ((l "") (d  '()))
        (setq l (read-line str))
          (unless l (return d))
          (setq d (reverse (cons l (reverse d))))))))

Finally, write-file-sd takes a filename as first argument and overwrites the existing file with the list of strings (second argument)…

(defun write-file-sd (filename listofstrings)
  (with-sd-card (str filename 2)
    (dolist (aline listofstrings)
      (write-line aline str))))