about summary refs log tree commit diff stats
path: root/chicken-scratch.mod.scm
blob: e47ad6737661948078502ac8b20c839ed9bfd2eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
;;; CHICKEN-SCRATCH --- heredocs for CHICKEN
;; written by Case Duckworth <acdw@acdw.net> off an idea by evhan
;; Licensed under BSD-3.  See COPYING for details.

;; CHICKEN has "Multiline string constants with embedded expressions" syntax,
;; which is basically shell here-doc syntax but schemier and with a real
;; programming langugage to embed. evhan's beaker tool (which is great, btw)
;; uses this facility to do a quick-and-dirty templating for wiki generation. I
;; realized that I could use the same facility for the same kind of
;; heredoc-style templating I have done in various other SSGs like unk and
;; vienna, but with scheme. Thus, CHICKEN-SCRATCH was born.

;; USAGE

;; `expand-string' is the main entry point to this module. It takes a string and
;; returns a string with all #( ... ) forms expanded according to the CHICKEN
;; rules. `expand-port' is a port version of `expand-string'.

;; To enable truly invisible definitions within the expanded string, the `def'
;; macro is provided which performs a `set!' on its variables, then returns a
;; string guaranteed not to be in the input string, which is then filtered out
;; in the expanded string.

;; Finally, to enable CHICKEN-SCRATCH to be used in a shebang, if the first line
;; of the input string begins with #!, it's deleted from the input.

(module chicken-scratch
    (expand-string
     expand-port
     def
     %def/replacer)

  (import scheme
          (chicken base)
          (only (chicken io)
                read-string)
          (only (chicken irregex)
                irregex-replace
                irregex-replace/all
                irregex-search)
          (only (chicken port)
                make-concatenated-port)
          (only (chicken random)
                pseudo-random-real))

  (define %def/replacer (make-parameter #f))

  (define (expand-string str)
    (parameterize ((%def/replacer (random-string-not-in str)))
      (let* ((delim (random-string-not-in str))
             (template (make-concatenated-port
                        (open-input-string (string-append "#<#" delim "\n"))
                        (open-input-string (irregex-replace "^#!.*\n" str ""))
                        (open-input-string (string-append "\n" delim "\n"))))
             (expanded (open-output-string))
             (output (begin
                       (display (eval (read template)) expanded)
                       (get-output-string expanded))))
        (irregex-replace/all `(seq ,(%def/replacer) (* "\n"))
                             output
                             ""))))

  (define (expand-port #!optional port)
    (let ((port (or port (current-input-port))))
      (expand-string (read-string #f port))))

  (define-syntax def
    (syntax-rules ()
      ((def var val)
       ;; I think this only works in CHICKEN.
       (begin (set! var val)
              (%def/replacer)))))

  (define (random-string-not-in str)
    (let ((attempt (number->string (pseudo-random-real))))
      (if (irregex-search attempt str)
          (random-string-not-in str)
          attempt))))