Notes on Implementing List Ops in the Interpreter
26 January 2009
Using Lists of Values will allow all lists to be handled uniformly, as far as list operations go. It may be useful to define a ValueList specialization of List, with ListElemData typedef'd as Value, and appropriate specializations of the List functions.
A list Value struct has the following data fields:
LoR = RVAL or LVAl, depending on context tag = ListTag type = pointer to full list TypeStruct size = TypeSize(List*), i.e., always the size of just a pointer val.ListVal = pointer to C List structureThis is a Lisp-style (Java-style) implementation of lists, as pointers to dynamically-allocated data. So, in any interpreter memory pool, a list value is allocated with only a pointer's worth of storage. This means there are no array-like list values, with inline storage and offsets for the elements.
Based on this list representation, here are some notes about implementing list operations:
Operator | Description | Implementation Notes |
_ | _ | . sp .2v |
[e1,...,en] | construction (elementwise) | Start by building a new List Value struct. Then build the list value with NewList, followed by a loop of PutList(interExpr(ei)) for each element expression. Return the new Value. |
[e1 .. en] | construction (inclusive range) | Like elementwise construction, but for the range e1-en. The type checker ensures that the type of e1 and en are integer. |
L[n] | selection (nth, from 1) | Use GetNthList to fetch the Value at the nth position in the list, and return that Value. |
L[m..n] | selection (mth - nth) | Like range construction, but using a loop with GetNthList to get the elements at list positions m through n. Return the new list. |
+ | concatenation | Use the C function ListConcat. That function takes two lists, but FMSL list contat (with '+') is overloaded to allow three cases: (1) both args are lists (the way ListConcat is implemented) (2) first arg is an element, second arg is list (3) vice versa of case 2 For cases 2 and 3, the type checker ensures that the element arg is the correct type, i.e., it's of the base type of the list. In the interp, you'll need to wrap the element arg into a list, by creating a new List Value. Then you can call the ListConcat function. As explained in the documentation of list.h, ListConcat is the non-mutating version of list construction. It's OK to use the mutating PutList in the initial list construction, but the non-mutating ListConcat needs to be used for the FMSL list concat, in order for it to have non-mutating semantics. |
- | deletion | Use the C functions InListWithFunction, SubList, ConcatList. We can't use the DelListNth function, because FMSL list deletion is non-mutating. For example, if l is an FMSL list of integer = [1,2,3,4], then the expression l - 3 returns [1,2,4]. Like list concat, it's a non-mutating function, meaning the value of l is not affected by the delete. The args for FMSL deletion are not overloaded like list concat. The first arg to delete must be a list and the second arg must be an element of the list's basetype. The type checker ensures this. There aren't three overloads like there are for list concat. So the implementation of the delete op starts by searching the list using InListWithFunction. If that returns a non-zero value, then a non-mutating version of DelListNth is performed. The code looks like this, for the list value l and delete position n: ConcatLists(SubList(l, 1, n-1), SubList(l, n+1, ListLen(l))); DelNthList is not itself called, but replaced with this chunk of code. It's the standard delete idiom for a functional language. Instead of mutating l by removing the nth element, a new list is created by concatenating the first n-1 elements of l, with the n+1st through the last element of l. I noted above that the implementation of list delete should use InListWithFunction, not just InList. The discussion immediately below abut the FMSL in operator clarifies this. I.e., it explains the function to use with InListWithFunction. |
in | membership | Use the list function InListWithFunction. The deal is that all equality checks in FMSL must be deep. In Lisp terms, equality in FMSL is implemented as equal, not eq (if you happen to recall that distinction). As explained in the list.h documentation, there are two c functions that implement membership -- InList and InListWithFunction. When you used InListWithFunction, you supply a function that performs the deep equality test for two list elements. In the case of a Value struct as the list element type, the equals function uses the Valtag to determine how to compare the values. For the atomic types, it can use ==. I.e., the IntVal, RealVal, BoolVal can be compared with ==. String compare is used for StringVals. The equals function for lists works recursively, with == for atomic list elements and recursive descent into sublists. The testing example in int-list-test has the basic idea, in the example EqualsFunc. |
# | length | Use the ListLen function. |