; ʞ / interlace.scm
; a scheme library by lexi hale
;
; (interlace) solves an age-old problem. what kind of data structure
; do you use when you need both an aggregate (a list, a vector) of
; items but also to be able to individually refer to those items by
; name? this problem is effectively unsolvable in C and C++ without
; inordinate runtime overhead, and there's no native syntax for it
; in Scheme. however, by rewriting the AST, we can implement a clean,
; performant, low-cost solution ourselves, thanks to the fact that
; (let*) bindings are expressions.
;
; interlace takes a constructor function, e.g. (vector) or (list),
; a list of items, and returns an expression calling that constructor
; on a list of all items defined in it. if an item is proceded by
; an atom that ends in a period (e.g. “name.”), it is defined within
; a (let*) expression, and its name is then included among the
; arguments passed to the constructor function.
;
; the upshot of this is that items can reference other items by name
; *as they are being defined*. for instance:
;
; (define verbs (interlace vector
; talk. (verb "talk" "talking" "talked")
; (phrasal-verb talk "to")
; (phrasal-verb talk "about")
; (verb "say" "saying" "said)))
;
; here, the function to generate a phrasal verb exhibits backreference
; to an existing verb contained alongside it in the vector. note that
; the name bindings are ephemeral; they do not survive the immediate
; context of the constructor.
(define-macro (interlace . body)
; given a list with both named and nameless members, identify
; which is which and instate the vector.
(define (name? term)
; given a symbol, determine wheter it is a name, and if so return
; that name as a string and without the name-marking suffix ‹.›
; otherwise, return #f
(define (coda str) (substring str
(- (string-length str) 1)
(string-length str)))
(if (not (symbol? term)) #f
(let* ([term-string (symbol->string term)]
[final-char (coda term-string)])
(if (not (equal? final-char ".")) #f
(string->symbol(substring term-string 0 (- (string-length term-string) 1)))))))
(define (divide-entries lst @named @nameless)
; given a list, return a pair [ x . y ] such that x is a list
; of named terms ( name . term ) and y is a list of nameless
; terms.
(if (eq? lst '()) (cons @named @nameless)
(let* ([head (car lst)]
[tail (cdr lst)]
[name (name? head)])
(if (eqv? name #f)
; there's no name term; add this to the nameless
; list and move on the next iteration
(divide-entries tail @named (cons head @nameless))
; head is a name, so determine its value, cons them
; together, and add that cons to the list of named
; lists
(let* ([val (car tail)]
[new-tail (cdr tail)]
[named (list name val)])
(divide-entries new-tail
(cons named @named) @nameless))))))
(let* ([structure (car body)]
[-structure-entries (cdr body)]
[-divided-lists (divide-entries -structure-entries '() '())]
[named-terms (car -divided-lists)]
[nameless-terms (cdr -divided-lists)])
`(let* ,(reverse named-terms)
(,structure ,@nameless-terms
,@(map car named-terms)))))