Proposal to add keyword arguments in the next release


#1

I’m thinking about adding support for keyword arguments in the next release of uLisp. So, for example, instead of having to remember that the command to define an I/O pin as INPUT_PULLUP is:

(pinmode pin 2)

you will be able to write:

(pinmode pin :input-pullup)

This will be especially useful with the Arduino function analogreference which will be supported in the next version of uLisp, as some platforms support multiple options. For, example, on the Arduino Mega 2560 you will be able to specify any of the options:

(analogreference :default)
(analogreference :internal1v1)
(analogreference :internal2v56)
(analogreference :external)

The keyword parameters will automatically pick up the values they represent from the appropriate Arduino core, so for example it’s not a problem if the number representing :input-pullup is different on two different platforms.

The simplest way to implement the keyword parameters will be to include them in the table of built-in symbols, called lookup_table[] in the uLisp source. However, there’s a problem: because the keywords are platform-specific, and there’s a different number of keywords on each platform, they will have to go at the end of the table with #defines to select the keywords for each platform. This will slightly complicate the procedure for Adding your own functions, which currently doesn’t require you to renumber the existing function entries in the table when you add new ones.

Does this sound like a useful feature? Can you think of any other uses for these keyword parameters? Any suggestions about the implementation?


#2

I think you might wish to consider adding them only to the ARM variant, if they take up RAM, but not to AVR.

(Moreover, I think a builtin “equal” might be useful — eq is a bit annoying when working with list comparisons, and pretty much one of the very first things I always do is to define “equal”, define a “member”-function matching on “equal”, and so forth — and exactly “equal” is a well-known keyword argument.)


#3

I’ve thought of a way of doing the keyword arguments that won’t take up RAM, and I’ve even got it working nicely on the Arduino Uno, so I hope that puts your mind at rest!

I’ll think about equal


#4

I’d prefer to have named constants (not only keywords) separate from functions, in separate C table without need to keep enums in sync.


#5

I don’t quite understand what you mean. Can you give an example?


#6

To have separate table with constants like
{“INPUT-PULLUP-PIN”, “2”}, {“OUTPUT-PIN”, “4”}, etc.
that will be compiled into ROM/PROGMEM and the value in second column will be read every time the symbol in first column is referenced. So

(pinmode pin INPUT-PULLUP-PIN)

is evaluated as

(pinmode pin (read-from-string "2"))


#7

The reason I chose to use keywords for these Arduino options is that the values of the options are arbitrary, processor-specific numbers, and there isn’t any benefit in the user being able to access them. Keywords evaluate to themselves; for example:

> :input-pullup
:input-pullup

Keywords are used for this sort of function in Common Lisp; for example, the function open lets you specify the direction as :input or :output.

If I represented these options as constants in a separate table, as you’re suggesting, there would be two possible approaches. Either they could be defined in RAM, like other user-defined symbols, in which case they would use up RAM (and space in the symbol table). Alternatively they could be in a separate constants table in PROGMEM, in which case I’d have to scan that table separately when a form is entered in the REPL, which would be an overhead. My proposal of including them in the main lookup_table[] avoids this overhead.

What application have you got in mind that would benefit from your approach?


#8

I have no problem with keywords but one needs to define the corresponding values somewhere anyway. Precompiled constants are conceptually simpler to me. But both can be implemented no problem, say, when in the proposed table below the value is NULL, name is treated as a keyword.

Example what I have in mind with separate table: I have following color definitions in LispLibrary, and it now takes nontrivial amount of RAM even for unused colors:

(defvar TFT-BLACK #x0000)
(defvar TFT-NAVY #x000F)
(defvar TFT-DARKGREEN #x03E0)
(defvar TFT-DARKCYAN #x03EF)
(defvar TFT-MAROON #x7800)
(defvar TFT-PURPLE #x780F)
(defvar TFT-OLIVE #x7BE0)
(defvar TFT-LIGHTGREY #xD69A)
(defvar TFT-DARKGREY #x7BEF)
(defvar TFT-BLUE #x001F)
(defvar TFT-GREEN #x07E0)
(defvar TFT-CYAN #x07FF)
(defvar TFT-RED #xF800)
(defvar TFT-MAGENTA #xF81F)
(defvar TFT-YELLOW #xFFE0)
(defvar TFT-WHITE #xFFFF)
(defvar TFT-ORANGE #xFDA0)
(defvar TFT-GREENYELLOW #xB7E0)
(defvar TFT-PINK #xFE19)
(defvar TFT-BROWN #x9A60)
(defvar TFT-GOLD #xFEA0)
(defvar TFT-SILVER #xC618)
(defvar TFT-SKYBLUE #x867D)
(defvar TFT-VIOLET #x915C)

With separate PROGMEM-only constant table, value will be read into RAM only if constant is referenced

typedef struct {
  PGM_P name;
  PGM_P value;
} constant_entry_t;

const constant_entry_t lookup_table[]{
//color definitions from TFT_eSPI.h, #stringify the values
{PGM("TFT-BLACK"), PGM(#TFT_BLACK)},...

#9

The Adafruit libraries I’m referencing for graphics include these predefined colours:

// Some ready-made 16-bit ('565') color settings:
#define ST77XX_BLACK 0x0000
#define ST77XX_WHITE 0xFFFF
#define ST77XX_RED 0xF800
#define ST77XX_GREEN 0x07E0
#define ST77XX_BLUE 0x001F
#define ST77XX_CYAN 0x07FF
#define ST77XX_MAGENTA 0xF81F
#define ST77XX_YELLOW 0xFFE0
#define ST77XX_ORANGE 0xFC00

I can include keywords for these, called something a bit friendlier, like:

:color-orange

Would that be OK?


#10

It’s fine. As long as adding new colors and other constants won’t require editing in 3 places like functions do.


#11

The only really important part here is that the order of the three listings must be the same, right? So wouldn’t it be possible to have users add their functions between the built-in ones and the keywords, and label the correct position in the source?


#12

I’ve added keywords in uLisp 3.4, and you can still add user functions at the end, after the keywords, just like before. The only difference is that the number of keywords may vary on different platforms, so you have to number your user functions sequentially after the keywords for the platform you are using.


#13

I’d noticed. I’m just wondering if it might be easier to explain by not doing the sequential numbering.


#14

That would be good, but I don’t quite understand how it would work. Can you give an example?


#15

Well, as far as I can tell the only thing that matters is the order things are defined in. As long as the enum value, string, and lookup table entry all maintain the same relative order, it just works. So for the ARM version, if I put the enum entries PEEK and POKE in front of KEYWORDS (and right after INVERTDISPLAY), and then alter the string list and lookup table so the relevant parts look like this (I’m omitting the functions themselves):

const char string211[] PROGMEM = "invert-display";
const char user0[] PROGMEM = "peek";
const char user1[] PROGMEM = "poke";
const char string212[] PROGMEM = "";
const char string213[] PROGMEM = ":high";
const char string214[] PROGMEM = ":low";
  { string211, fn_invertdisplay, 0x11 },
  { user0, peekaboo, 0x11},
  { user1, with_a_stick, 0x22},
  { string212, NULL, 0x00 },
  { string213, (fn_ptr_type)HIGH, DIGITALWRITE },
  { string214, (fn_ptr_type)LOW, DIGITALWRITE },

Then everything should just continue working. In fact, in looking at the code to reassure myself, I found out that there shouldn’t even be a strict ordering dependency for the strings. The reader indexes through the pointers in the lookup table, so it’ll find the right symbol no matter where the string goes. (This could be done differently: Start at the first string and count the NULs you’ve passed by while looking for a byte-for-byte match. That would be order-sensitive.)

So, basically, an alternative approach would be to put comments there, something like

// Insert your own symbol names here

and

// Insert your own table entries here

to match up with the existing comment for where the function definitions go.

I at least don’t see a reason why it shouldn’t work. It should certainly compile. :p


#17

You’re correct - there’s no need to renumber anything as long as the order is consistent.

Also it should work whether the user-defined entries are put before or after the keyword entries. Putting them after the keyword entries is probably the tidiest approach.

I’ll test it, and then update the document Adding your own functions.


#18

I’ve added the comments as you suggested in uLisp 3.5, and documented it here:

Adding your own functions