procgen  interlace.scm at [275f19cd9e]

File lib/interlace.scm artifact a8f59872e4 part of check-in 275f19cd9e


; ʞ / 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)
	(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)])
		  (print "TERMSTRING:" term-string)
		  (print "CHAR:" final-char)
		  (if (not (equal? final-char ".")) #f
			  (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)))))