awwx.ws
The latest version of this hack is template5

template3

; turn adjacent characters into a string

(def coalesce-chars (lst)
  (xloop (lst lst a nil)
    (if (no lst)
         (rev a)
         (let (chars rest) (span [is (type _) 'char] lst)
           (if chars
                (next rest (cons (coerce chars 'string) a))
                (next cdr.lst (cons car.lst a)))))))

(def slurp-template-field (name closing port)
  (list name (string (accum a
                       (whiler c (readc port) [or (no _) (is _ closing)]
                         (a c))))))

(def slurp-template (port (o accum))
  (cons 'template
        (coalesce-chars:accum a
          (whiler c (readc port) [or (no _) (is _ #\‡)]
            (a (if (is c #\«)
                    (slurp-template-field 'guillemet #\» port)
                   (is c #\⌊)
                    (slurp-template-field 'floor #\⌋ port)
                    c))))))

(ac-scheme
  (current-readtable
   (make-readtable
    (current-readtable)
    #\‡
    'non-terminating-macro
    (lambda (ch port src line col pos)
      (_slurp-template port)))))

(mac lex1 (template)
  `(tostring ,@(map (fn (x)
                      (if (isa x 'string)
                           `(pr ,x)
                          (and (acons x) (is (car x) 'floor))
                           `(pr ,(read (cadr x)))))
                    (cdr template))))

(mac lex2 (converter template)
  (w/uniq gc
    `(tostring:let ,gc ,converter
       ,@(map (fn (x)
                (if (isa x 'string)
                     `(pr ,x)
                    (and (acons x) (is (car x) 'guillemet))
                     `(pr (,gc ,(read (cadr x))))
                    (and (acons x) (is (car x) 'floor))
                     `(pr ,(read (cadr x)))))
              (cdr template)))))

(mac lex args
  (if (is (len args) 1)
       `(lex1 ,@args)
      (is (len args) 2)
       `(lex2 ,@args)
       (err "lex takes one or two arguments")))

I haven’t really figured out how I want templates to work yet... often I want to be inserting Arc expressions, something like

(let name "Fred"
  ‡Hello «name»‡)

but other times the data I want to insert is already in a table, and it’s a pain to get the data from the table into lexical variables just so I can insert them into the template, so I want something like my previous template2 code.

To give me something to play with, with this hack the reader expands ‡...‡ into a (template ...) form, like this:

arc> (read "‡one «two» ⌊three⌋ four‡")
(template "one " (guillemet "two") " " (floor "three") " four")

Like how the reader expands a comma into an unquote form to be used in a quasiquotation expression, but “unquote” itself is not a function or macro since it is the quasiquotation expander that goes in and finds the unquote forms:

arc> (read ",x")
(unquote x)
arc> (unquote x)
reference to undefined identifier: _unquote

template” isn’t defined to be anything,

arc> ‡hello‡
reference to undefined identifier: _template

but it serves as a handle for other macros to work on. For example, lex causes ⌊...⌋ to be treated as an Arc expression:

arc> (let name "Fred"
       (lex ‡Hello ⌊name⌋‡))
"Hello Fred"

lex takes an optional argument, a conversion function to apply to «...» forms:

arc> (let name "Fred"
       (lex strperl ‡hello(«name»);‡))
"hello('Fred');"

I don’t expect this to be my final version, but now I have some flexibility to play around with.

Get this hack

Using the hackinator:

$ hack \
    ycombinator.com/arc/arc3.1.tar \
    awwx.ws/ac0.patch \
    awwx.ws/ac1.arc \
    awwx.ws/xloop0.arc \
    awwx.ws/span0.arc \
    awwx.ws/template3.arc

unLicense

This code is in the public domain.

Contact me

Twitter: awwx
Email: andrew.wilcox [at] gmail.com