Announcing uLisp Release 4.8


#1

The latest release of uLisp, 4.8 features an improved implementation of streams, Lisp’s way of providing a unified interface to input and output protocols such as Serial, I2C, SPI, SD Cards, Wi-Fi, strings, or graphics.

Following a suggestion made by user @dragoncoder047 streams now use a lookup table. This is not only more efficient, but provides the additional benefit that you can extend uLisp by defining custom streams.

For example, you could write an Extensions File that implements the 1-Wire protocol and a 1-Wire stream, allowing you to use functions such as print, format, and read to communicate over 1-Wire. I have provided an example Extensions File that implements the Lisp form with-input-from-string, not currently a standard part of uLisp, by implementing a string input stream.

For more information about the implementation see Streams.


#2

That’s a very good explanation and now I understand how to work with ulisp streams better than I did before. with-input-from-string looks really useful. That said I have several requests and questions. The first thing I thought of was having a function that let you read up to your chosen delimeter instead of just a newline. It turns out ulisp has this internally as readstring but its not exposed. So here’s my attempt at providing it:

object *fn_readuntil (object *args, object *env) {
  (void) env;
  object *obj = first(args);
  gfun_t gfun = gstreamfun(rest(args)); 
  char delim = checkchar(obj);
  return readstring(delim, false, gfun);
}

const char string_readuntil[] PROGMEM = "read-until";

const char docreaduntil[] PROGMEM = "(read-until delim [stream])\n"
"Reads characters from the serial input up to the specifed delimiter character,\n"
"and returns them as a string, excluding the delimiter.\n"
"If stream is specified the line is read from the specified stream.";

//const tbl_entry_t lookup_table2[] PROGMEM = {
  { string_readuntil, fn_readuntil, 0212, docreaduntil },
//};

My second issue is with the way read-line (and read-until) handles string streams that don’t terminate in a newline (or delimiter), where they will show an error and return nothing. I’m not sure if that’s the most useful behavior because I would think that not finding a line or delimiter would be something that the function would expect. I suppose one can always append a newline or delimiter to the end if they don’t want the function to fail, but that seems awkward. I don’t know how to handle that scenario any better though, and that seems to be how Common Lisp handles it as well? For Arduino you get functions like find, findUntil, peek, and available. Would it make sense to implement that functionality here to help deal with that? Or what’s a good way to deal with this?


#3

Yes, I wasn’t sure how best to handle reading past the end of a string (or any other stream), so I copied what Common Lisp does and gave an error, which as you say may not be the most useful option in an Arduino world.

The Wi-Fi functions already provide available. Perhaps that should be provided for all streams? However that wouldn’t help with using read-line on a string-input-stream with no final newline.

Alternatively you could perhaps use ignore-errors to catch the end-of-stream error.


#4

So I came ended up coming up with this function for StringRead, which just returns nil when there’s nothing left in the buffer. Apparently you have to return -1 to the readstring function for it to return nil. The other streams like the SD card, I2C, and SPI do that as well from what I understand.

int StringRead () {
  char c = 0;
  if(InputString != NULL){
    c =  nthchar(InputString, InputStringIndex);
  }
  if (c == 0) { 
    InputString = NULL;
    InputStringIndex = 0;
    return -1;
  }
  else { InputStringIndex++; }
  return c;
}

#5

Are there any resources for what api’s a new stream needs to implement, and how we should be thinking about streams. Like would a touch screen input be a good source of streamable data? Is a stream a pull stream (you ask for the next item in the buffer) or a push stream (it fires off your function whenever a new item is present)? Back pressure? Buffer limits? Are there stream “packets”, or are these always individual chars?


#6

From what I can see, streams

  • are always pull streams (you have to call read-char on them to get something)
  • can have as big of a buffer buffer as you want if you are implementing it yourself
  • are not packetized, but if you program the stream read function to return data as Lisp s-expressions you can read one “packet” using plain ol’ read.

In terms of implementing the streams all you need is a gstream_ptr_t and/or a pstream_ptr_t which are functions that take an address/port and return a function pointer to the actual put or get function. A readonly stream will have a NULL for the pstream_ptr_t and a write-only stream will have NULL for the gstream_ptr_t. Those two things go in a user-streams table in the same sort of way as user functions in a normal extension.


#7

Only one thing to add to @dragoncoder047’s explanation:

There isn’t currently a read-char function in uLisp; you have to use read-byte.

You can define read-char as:

(defun read-char (&optional stream) (code-char (read-byte stream)))