New features in uLisp 3.2

This is a summary of the new features in uLisp 3.2.

Formatted output

All versions of uLisp now support a subset of Common Lisp's format function, which provides a convenient way of formatting output neatly using a control string containing format directives such as ~a.

format function

Syntax: (format output controlstring arguments*)

Outputs its arguments formatted according to the format directives in the control string.

The output argument can be one of:

  • nil: the formatted output is returned as a string. For example:
> (format nil "The answer is ~a" 42)
"The answer is 42"
  • t: the formatted output is printed to the serial output. For example:
> (format t "The answer is ~a" 42)
The answer is 42
nil
  • a stream: the formatted output is printed to the specified stream.

Format directives

The control string can contain the following format directives:

Directive Name Description
~a ASCII Output as princ would print it in human-readable format.
~s Sexp Output as prin1 would print it, with escape characters and quotation marks.
~d Decimal Decimal, as princ would print it.
~x Hexadecimal Hexadecimal.
~g General General floating-point format, as princ would print it.
~~ Tilde Just prints a ~.
~% Newline Outputs a newline.
~& Newline Outputs a newline unless already at the start of a line.
~{ List start Formats elements from a list.
~} List end Ends formatting list elements.

Any other characters in the control string are just output unchanged.

For example:

> (format t "The battery is ~aV or ~a%." batt (* batt 20))
The battery is 4.75V or 95%.

If the argument is the wrong type for the directives ~d, ~x, or ~g the argument is output as if with ~a.

Width parameter

Each of the directives ~a, ~s, ~d, ~g, or ~x can include a width parameter after the tilde, and the output is padded with spaces to the specified width. If the output wouldn't fit in the specified width it is output in full, and overflows the width. The ~a and ~s directives pad with spaces to the right; all the other directives pad with spaces to the left. In uLisp this is the only difference between ~a and ~d or ~g.

The width parameter is useful for formatting data in a neat table. For example:

(defun facts ()
  (let ((n 1)) 
    (dotimes (x 10) 
      (format t "n = ~2d,  n! = ~7d~%" (1+ x) (setq n (* n (1+ x)))))))

This prints:

> (facts)
n =  1,  n! =       1
n =  2,  n! =       2
n =  3,  n! =       6
n =  4,  n! =      24
n =  5,  n! =     120
n =  6,  n! =     720
n =  7,  n! =    5040
n =  8,  n! =   40320
n =  9,  n! =  362880
n = 10,  n! = 3628800
nil

Pad character

The directives ~d, ~g, or ~x can also include a pad character, which is output in place of spaces to fill the argument to the specified width. The pad character is specified after the width, separated by a comma and a single quote. The pad character is useful combined with the width character for formatting output to be displayed on output devices such as seven-segment displays; for example:

> (format nil "~2,'0d-~2,'0d-~2,'0d" 1 45 7)
"01-45-07"

Lists

The ~{ and ~} directives allow you to format elements in a list. The argument must be a list, and the format directives between ~{ and ~} are repeated until all the items in the list have been used. For example:

> (format t "~{~a/~a ~}" '(1 2 3 4 5 6))
1/2 3/4 5/6

Note that uLisp only supports one level of nesting of ~{ and ~}.

Arrays

The 32-bit versions of uLisp now include arrays, as a more efficient and convenient alternative to using lists with nth etc. With a 5000-element array it is on average approximately 20 times faster to access elements than with a 5000-element list.

Arrays in uLisp can have one or more dimensions. The size of arrays is limited only by the amount of available workspace.

Arrays will be useful for collecting data from sensors, or processing mathematical functions. When you save your workspace with save-image, arrays and their current contents are saved too. 

make-array function

Syntax: (make-array size [:initial-element element])

If size is an integer make-array creates a one-dimensional array with elements from 0 to size-1. By default they are initialised to nil, but you can provide a default value with the :initial-element keyword parameter. For example:

(defvar a (make-array 100 :initial-element 0))

One-dimensional arrays are printed in the standard #( ... ) notation; for example:

> (make-array 3 :initial-element "Hello")
#("Hello" "Hello" "Hello")

If size is a list of n integers make-array creates an n-dimensional array. For example:

(defvar a (make-array '(2 3 4) :initial-element 0))

N-dimensional arrays are printed using the notation #nA( ... ), where n is the number of dimensions in the array. For example, the above array is printed as:

> a
#3A(((0 0 0 0) (0 0 0 0) (0 0 0 0)) ((0 0 0 0) (0 0 0 0) (0 0 0 0)))

To access or change array elements in arrays use the aref function.

aref function

Syntax: (aref array [index*])

Returns an element from the specified array. The aref function can be used to return a value as in:

(print (aref a 99))

To access elements in a multi-dimensional array you need to givean index for each dimension; for example:

(print (aref b 2 3))

The aref function can also be used with the in-place operations setf, incf, and decf to change an element of an array; for example, to change the value of an array element:

(setf (aref b 42) 3.14159)

or to increment an element:

(incf (aref c 99))

arrayp function

Syntax: (arrayp item)

Returns t if its argument is an array.

array-dimensions function

Syntax: (array-dimensions item)

Returns the dimensions of an array. For example:

> (defvar a (make-array '(2 3 4) :initial-element 0))
a

> (array-dimensions a)
(2 3 4)

You can also use length to get the length of a one-dimensional array.

Outputting to a string

You can now create a stream that outputs characters to a string and returns it.

with-output-to-string special form

Syntax: (with-output-to-string (stream) form*)

Returns a string containing the output to the specified stream variable.

For example:

> (with-output-to-string (str) (princ "The result is: " str) (princ 24 str))
"The result is: 24"

uLisp only supports outputting to one string at a time from with-output-to-string or princ-to-string.

Terminal support

Version 3.2 of uLisp now includes features to make it more convenient to interact with uLisp from a terminal. There's an integrated line editor, allowing you to delete mistakes during entry, and parenthesis matching to show the structure of what you're entering.

Line editor

To get the line editor compile uLisp with the option:

#define lineeditor

uLisp provides an input buffer, allowing you to correct mistakes by pressing the delete key, or typing backspace. The line is not submitted to the REPL until you press enter.

This is designed for using uLisp from a serial terminal rather than the Arduino IDE's Serial Monitor.

Parenthesis matching

If, in addition, you include the option:

#define vt100

uLisp uses ANSI standard terminal codes to implement parenthesis matching, highlighting matching parentheses in inverse video as you type.

Other additions

The following other features have been added:

pprintall function

Syntax: (pprintall item [stream])

The pprintall pretty-printer now includes an optional stream parameter. This lets you write a formatted version of your workspace to an SD card with:

(defun save (filename)
  (with-sd-card (str filename 2)
    (pprintall str)))

You can then load it on a computer, or load it back in to uLisp with:

(defun load (filename)
  (with-sd-card (str filename)
    (loop
     (let ((form (read str)))
       (unless form (return))
       (print (second form))
       (eval form)))))

This also prints the name of each function as it is loaded.