summaryrefslogtreecommitdiff
path: root/src/xyz/euandreh/heredoc.scm
blob: ba7b72735457c4b91f9fbc5293a3ed16b89c214a (plain) (blame)
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
(define-module (xyz euandreh heredoc)
  #:use-module ((srfi srfi-1) #:prefix s1:)
  #:export (reader
            enable-syntax))

(define consumable-chars
  '(#\space #\tab))

(define (extract-single-line-indentation chars)
  (s1:fold (lambda (curr acc)
             (let* ((prev (s1:first acc))
                    (halted? (s1:second acc))
                    (n (s1:third acc))
                    (non-blank? (not (member curr consumable-chars)))
                    (changed? (and (not (null? prev))
                                   (not (equal? curr prev)))))
               (cond
                 (halted?    acc)
                 (non-blank? (list prev #t n))
                 (changed?   (list prev #t n))
                 (#:else     (list curr #f (+ 1 n))))))
           '(#nil #f 0)
           chars))

(define (extract-line-indentations lines)
  (map (compose (lambda (triple)
                  (list (s1:first triple)
                        (s1:third triple)))
                extract-single-line-indentation
                string->list)
       lines))

(define (maximum-indentation lines)
  (let* ((line-indentations (extract-line-indentations lines))
         (chars (map s1:first line-indentations))
         (different-indents? (not (= 1 (length (s1:delete-duplicates chars))))))
    (if different-indents?
      0
      (apply min (map s1:second line-indentations)))))

(define (trim-indentation s)
  (let* ((lines (string-split s #\newline))
         (trim-n (maximum-indentation
                  (filter (lambda (s) (not (equal? "" s)))
                          lines))))
    (string-join
     (map (lambda (line)
            (if (equal? "" line)
              line
              (substring line trim-n)))
          lines)
     "\n")))

(define (non-quote-chars? chars)
  (let ((non-quote-chars (filter (lambda (c)
                                   (not (equal? #\" c)))
                                 chars)))
    (< 0 (length non-quote-chars))))

(define (reader _char port)
  (let ((should-trim? #f)
        (chars '()))
    (do ((curr (read-char port)
               (read-char port)))
        ((equal? #\newline curr))
      (set! chars (cons curr chars)))
    (when (and (not (null? chars))
               (equal? #\- (car chars)))
      (set! should-trim? #t)
      (set! chars (cdr chars)))
    (let ((non-quote-chars (non-quote-chars? chars)))
      (when non-quote-chars
        (error
         (format
          #f
          "Invalid characters at the beginning of the multiline reader: ~s"
          (reverse non-quote-chars)))))
    (let* ((quote-count (+ 1 (length chars)))
           (quote-n 0)
           (output '()))
      (while #t
        (let ((curr (read-char port)))
          (when (eof-object? curr)
            (error "EOF while reading #\"\"# multiline string"))
          (set! output (cons curr output))
          (set! quote-n
                (cond
                  ((and (>= quote-n quote-count)
                        (equal? #\# curr))
                   (break))
                  ((equal? #\" curr) (+ 1 quote-n))
                  (#:else 0)))))
      (let ((s (list->string
                (reverse
                 (s1:drop output (+ 1 quote-count))))))
        (if should-trim?
          (trim-indentation s)
          s)))))

(define-macro (enable-syntax)
  `(eval-when (expand load eval)
     (read-hash-extend #\" (@ (xyz euandreh heredoc) reader))))