SC, an s-expression based front end to C
yuasa.kuis.kyoto-u.ac.jp
yuasa.kuis.kyoto-u.ac.jp
I write a lot of embedded microcontroller code (usually AVR but some ARM) and have written my own preprocessor that compiles lisp code between slash-star-L-star and star-L-star-slash tags (sorry, HN changed the characters into formats, I think you should divine what I mean), appending standard out into the C source, which is then compiled by gcc as normal. I have Lisp In Small Pieces and have intended to write something like this, but the time hasn't been worth it yet.
"Why?" you may ask. A couple reasons:
* If you use clock dividers to get a particular frequency, then change the main clock, you have to make sure you changed every instance in the code to a new divider ratio. I wrote functions that automatically generate the C code which correctly programs the timer, uarts, etc.
* Most programmers automatically generate their lookup tables, but they have to rerun the program then cut-and-paste the new values into the C source. Then you have to make sure the length is correct, etc. With the code right there, you can just change a single number and recompile, being assured the new table will be correct.
One way I've used Lisp is to generate code in either an if-else form or switch-case form. If you have a sparse lookup table (say, 30 values out of 256 map to a byte) you don't want to allocate space for those useless 226 values, so you could write out each by hand. Instead, I maintain a list of pairs at the beginning of the file (along with all the other constants) and I wrote a CL function that converts them to C code. Sometimes using if-elses results in a smaller binary verses switch-case, and sometimes the other way. I only have to change two words and recompile to test the result both ways. Doing this by hand for each statement... yuck. But I saved over 100 bytes on my last project, enough to get a necessary feature working before shipping.
I use emacs, so when I edit code inside the lisp preprocessor tags, I type 'M-x lisp-mode' and press enter. Then emacs properly indents the lines and I can use SLIME to execute the preprocessor functions right there. Sometimes I need to add the comment:
;|
as the first line because the lisp mode catches a pipe in the C source (CL block comment) and formats everything incorrectly. Conversely, I sometimes have to place this after a Lisp section: //'
...for the single quote (even inside a comment) freaks out the emacs reader. Not sure how this works in emacs23 yet.Basically, I use Lisp to remove as many assumptions from the code as possible. As a result, my devices work just the way I say they will -- contrary to the other programmer here. Sadly, my coworkers think waterfall is a genius new idea they came up with. It's quite frustrating because I'd still like to learn from others, but I fear going somewhere where I'd be forced to follow a bad methodology (at least here I have the freedom to follow what I think are best practices, on the projects I develop/lead/design).
Back to the lisp preprocessor: When I call 'make' each source file is attached to the standard input of a lisp process launched off a file of <processor_name>.lisp, and the output is written into <original_file>.c.cpp-lisp.c (yes, very ugly, but it works). The gcc is run as normal, with the output written back to <original_file>.o to clean up the file structure.
One problem I ran into is CL up-casing all input. Fortunately, avr-libc has all constants as uppercase, and most of my preprocessor passes through strings unchanged (if it makes sense), but I have had many times when I had to work around this. The project above should allow these problems to be solved easily.
Atmel releases all the register information (names, bit names, read/write/both, etc) in an XML file. The next step in upgrading my preprocessor is to automatically generate mapping functions from generic names to specific names from this XML file (which is first processed by xml-to-sexp). This function lets me write something like this:
(set-bit 'RXENn :sequence COMPUTER_UART)
and then change the zero to one in (defparameter COMPUTER_UART 0)
to use the second uart instead of the first. If this was done using pure C, either a search and replace for RXEN0->RXEN1 (and you'd have to check that you don't use any other UART registers, nor in any other files, etc) or you have series of #define's at the top of the file like #define COMPUTER_UART_RX_ENABLE() ... Finally, the other major problem is changing output pins. As the AVR is 8-bit, the output pins are in groups of eight. Normally, you would have code like this: #define LED_PIN PC1
...
PORTC |= (1<<LED_PIN);
but, what if you change LED_PIN to be PB0? Now it is on PORTB so you must change everywhere you write it. So you do: #define LED_PIN PB0
#define LED_PIN_PORT PORTB
#define SET_LED_PIN() LED_PIN_PORT |= (1<<LED_PIN)
...
SET_LED_PIN();
well, then you need CLEAR_LED_PIN(), and SET_LED_PIN_AS_OUTPUT() and SET_LED_PIN_AS_INPUT()... all of sudden you want one little constant and you get five extra garbage lines of boilerplate. Instead, my mapping function knows the port of PB0 is PORTB so you only need one line: (defparameter LED_PIN 'PB0)
Now, if you end up needing more direct control, you can still use the Lisp code to generate assertions.Anyway, all the problems in these two posts are some of the most commons ones brought up to the forums at avrfreaks.net and that I've experienced, so I wrote these tools to mitigate them. Unfortunately, it is too gross (in the dark corners you can find some of my first real CL code!) and too poorly designed (should be automatically generated based off Atmel XML files) to be released to the public, but the proof is in the pudding: I came back to a six-month-old project, made what could have been major changes (we were upgrading the product) in less than a day and everything worked the first compile and passed all the "QA" tests.
* As others pointed, ability to use powerful macros are huge relief compared to the crude macro of C.
* I can manipulate a piece of C code (in S-expr) programmatically. For example, I can automate VM instruction fusion (create a piece of code that combines two or more basic VM instructions).
* I can mix Scheme code and C code more naturally, allowing me to micro-tune inner loop and/or reducing overhead of FFI.
Of course, if you have really good native Lisp compiler on your target platform, you won't need this kind of stuff.
Quote from their website: BitC is a new systems programming language. It seeks to combine the flexibility, safety, and richness of Standard ML or Haskell with the low-level expressiveness of C.
SC sounds fun!
Imagine writting CUDA/OpenCL kernels in Scheme-Lisp way :)
That's what they said about Lisp itself. Let's hope it's just as true.