marți, 19 ianuarie 2010

Primitives, preprocessor macros and unit tests

Before adding the usual syntactic constructs in the interpreter, I decided to implement the procedures that will be available in the language, so it is rich enough from the start. While some of these can be written in scheme itself, I opted to write them in C and make them available as primitive operations.

I'm using the R5RS as a guideline for what needs to be there. Since I don't have a full numeric tower but only fixnums (I will probably add floats later), I only implemented the integer predicates and operations. I will also probably need to add vectors. Here's a list of what's available so far:
Equivalence predicates
eq?
Numerical operations
number?, integer?, =, <, >, <=, >=, zero?, positive?, negative?, odd?, even?, max, min, +, *, -, /, abs
Booleans
not, boolean?
Pairs and Lists
pair?, cons, car, cdr, set-car!, set-cdr!, caar, cadr, cdar... cddddr, null?, list?, list, length, append, reverse, list-tail, list-ref
Symbols
symbol?, symbol->string, string->symbol
Characters
char?, char=?, char?, char<=?, char>=? (and the case insensitive version of these), char-alphabetic?, char-numeric?, char-whitespace?, char-upper-case?, char-lower-case?, char->integer, integer->char, char-upcase, char-downcase
Strings
string?, make-string, string, string-length, string-ref, string-set!, string=?, string?, string<=?, string>=? (and their -ci counterparts)
Phew. But there's a couple more to add :-)

There is one particular implementation note for the comparison predicates on integers, characters and string. I wrote them as preprocessor macros. Here's a gist:



I used macros not so much to save typing, but to avoid copy-pasting mistakes. These functions have the same structure, they only differ in what is being tested. Poor man's higher order procedures. Note the "identity" case-folding macro for the case sensitive versions.

The strings weren't so easy, particularly at early morning hours. The added complication is that two character-wise equal strings up to the length of the shortest string, have an additional ordering imposed by the length of the strings.

Enter unit tests. I'm somewhat of a heretic these days, as I don't usually write unit tests on a regular basis. If you write in C, they tend to become extremely verbose for what amounts to some very simple tests, and they slow me down.

This however is a perfect example of a situation where I find them useful: mindlessly adding small bits of code with limited functional goals, until the final product is complete and can be tested.

In this particular situation, it was very convenient to be able to test primitives as I was adding them, so I put together a small perl script to run the 73 simple tests and 303 primitive tests. As opposed to writing C unit tests, these were really easy to add.

I count 6 commits that fix primitive bugs and one for the reader. In reality, there were more like 10 bugs, not 6.

Niciun comentariu:

Trimiteți un comentariu