This repository has been archived on 2022-08-10. You can view files and clone it, but cannot push or open issues or pull requests.
chez-openbsd/ta6ob/examples/template.ss

859 lines
37 KiB
Scheme
Raw Normal View History

2022-08-09 23:28:25 +02:00
#!chezscheme
;;; Copyright 1984-2017 Cisco Systems, Inc.
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;;
;;; http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.
;;; include-template: a simple yet powerful extensible templating mechanism
;;;
;;; Authors: Andrew W. Keep and R. Kent Dybvig
;;;
;;; The syntax (include-template <filename>) expands into an expression whose
;;; value is a string containing the contents of the file named by <filename>,
;;; except each occurrence of @@ within the file is replaced by @, and each
;;; occurrence of @(<scheme expression>) is replaced with the value of
;;; <scheme expression>. <filename> must be a string literal, and the value
;;; of each <scheme expression> must be a string. The file named by <filename>
;;; must be present at expand time and need not be present at run time.
;;;
;;; The template system can also be extended using the syntax:
;;;
;;; (define-template-extension <template-pattern> <output-pattern>)
;;;
;;; where:
;;;
;;; <template-pattern> -> (<initial-pattern> <additional-pattern>*)
;;; | <non-template-pattern>
;;; <initial-pattern> -> <keyword> (<syntax-pattern>*) <template-id>
;;; | <keyword> <template-id>
;;; <additional-pattern> -> <initial-pattern>
;;; | (<keyword> (<syntax-pattern>*) <template-id>) ...
;;; | (<keyword> <template-id>) ...
;;; | (optional <keyword> (<syntax-pattern>*)
;;; <template-id>)
;;; | (optional <keyword> <template-id>)
;;; <non-template-pattern> -> <keyword> (<syntax-pattern>*)
;;; <keyword> -> <initial-kw-character> <kw-character>
;;; <initial-kw-character> -> a - z | A - Z | ! | $ | % | & | * | / | : | <
;;; | = | > | ? | ^ | _ | ~
;;; <kw-character> -> <initial-kw-character> | 0 - 9 | - | + | .
;;;
;;; <output-pattern> is treated as a quasisyntax template,
;;; <syntax-pattern> is a syntax-rules pattern and,
;;; <template-id> is any valid scheme identifier.
;;;
;;; For <template-patterns> using a <non-template-pattern> a new @<keyword>
;;; form is created and when the @<keyword>(<syntax-pattern>*) is encountered
;;; in a template, it is immediately replaced with the <output-pattern>.
;;;
;;; For instance an extension that converts numbers to strings can be
;;; implemented as:
;;;
;;; (define-template-extension (num (e)) (number->string e))
;;;
;;; When @num(10) is encountered in a template, the string "10" is generated.
;;;
;;; For <template-patterns> with an <inital-pattern> each <keyword> in the
;;; <template-pattern> the define-template-extension will create a new syntax
;;; form @<keyword> along with an @end<keyword> for the <keyword> from the
;;; <initial-pattern>. When include-template encounters an
;;; @<initial-keyword> ... @end<initial-keyword> pattern it will match the
;;; <syntax-pattern>* (when supplied) against the following scheme expression
;;; and match the template expressions found between the @<keywords> to the
;;; matching <template-id> bindings.
;;;
;;; For example, we can add a "for" loop extension as:
;;;
;;; (define-template-extension (for ([x e] [xs es] ...) tmpl)
;;; (let ([t e])
;;; (apply string-append (map (lambda (x xs ...) tmpl) t es ...))))
;;;
;;; (for (exprs ...) tmpl)
;;; =>
;;; @for (lambda (...)
;;; (let ([t (read-scheme k...)])
;;; (cons (incomplete-node @for t) ...)))
;;; @endfor (lambda (...)
;;; ---
;;; (let loop ([t '()] [tmpl '()] ...)
;;; (cond
;;; [(and (incomplete-node? (car stack)) (eq? (incomplete-node-kw (car stack)) '@for))
;;; (with-syntax (['([x e] [xs es] ...) (incomplete-node-stx (car stack))])
;;; '(let ([t e])
;;; (apply string-append (map (lambda (x xs ...) tmpl) t es ..))))
;;;
;;; In a template if: @for([x '("a" "b" "c")]) got @(t). @endfor
;;; is encountered, it will print the string " got a. got b. got c. " for
;;; this expression (which will be produced by the code:
;;; (apply string-append
;;; (map (lambda (x) (string-append " got " x ". ") '("a" "b" "c"))))
;;;
;;; For a more complex example see the @if/@elif/@else/@endif example at
;;; the end of the library.
;;;
;;; Additional examples are embedded within the tests below #!eof.
;;; The (template-helpers) library supplies scheme procedures that are used at
;;; macro-expansion time by both the include-template and
;;; define-template-extension macros.
(library (template-helpers)
(export
incomplete-node? make-incomplete-node
incomplete-node-type incomplete-node-e* incomplete-node-bfp
source-string source-error
read-scheme initial-id-char? id-char?
make-string-buffer extend-string-buffer! extract-string-and-reset!
open-positional-string-output-port
strip-blanks)
(import (chezscheme))
(define (source-string sfd bfp)
(call-with-values
(lambda () (locate-source sfd bfp))
(case-lambda
[() (format "at char position ~s of ~a" bfp
(source-file-descriptor-path sfd))]
[(path lp cp) (format "at line ~s, char ~s of ~a" lp cp
(source-file-descriptor-path sfd))])))
(define (source-error sfd bfp msg . args)
(errorf 'include-template "~? ~a" msg args (source-string sfd bfp)))
(define (read-scheme k ip sfd bfp)
(let-values ([(x new-bfp) (get-datum/annotations ip sfd bfp)])
(let ([x (if (annotation? x) (annotation-expression x) x)])
(values (datum->syntax k x) new-bfp))))
(define-record-type incomplete-node (nongenerative) (fields type e* bfp))
(define (initial-id-char? c)
(or (char<=? #\a c #\z) (char<=? #\A c #\Z)
(char=? c #\!) (char<=? #\$ c #\&)
(char=? c #\*) (char=? c #\/)
(char=? c #\:) (char<=? #\< c #\?)
(char=? c #\^) (char=? c #\_) (char=? c #\~)))
(define (id-char? c)
(or (initial-id-char? c) (char<=? #\0 c #\9)
(char=? c #\-) (char=? c #\+) (char=? c #\.)))
(define-record-type string-buffer (nongenerative)
(fields (mutable n) (mutable str))
(protocol (lambda (new) (lambda () (new 0 (make-string 16))))))
(define (get-buffer tb n required-capacity)
(let* ([str (string-buffer-str tb)] [len (string-length str)])
(if (fx< (fx- len n) required-capacity)
(let ([new-str (make-string (fx* 2 (fx+ len required-capacity)))])
(string-copy! str 0 new-str 0 n)
(string-buffer-str-set! tb new-str)
new-str)
str)))
(define (extend-string-buffer! tb c)
(let ([n (string-buffer-n tb)])
(string-set! (get-buffer tb n 1) n c)
(string-buffer-n-set! tb (fx+ n 1))))
(define (append-to-string-buffer! tb str)
(let ([n (string-buffer-n tb)] [len (string-length str)])
(string-copy! (get-buffer tb n len) n str 0 len)
(string-buffer-n-set! tb (fx+ n len))))
(define (extract-string-and-reset! tb)
(let ([str (substring (string-buffer-str tb) 0 (string-buffer-n tb))])
(string-buffer-n-set! tb 0)
str))
(define (open-positional-string-output-port)
(define-record-type position (nongenerative)
(fields (mutable line) (mutable column))
(protocol (lambda (new) (lambda () (new 1 1)))))
(let ([tb (make-string-buffer)] [pos (make-position)])
(define (w! str start cnt)
(let* ([n (string-buffer-n tb)]
[buf (get-buffer tb n cnt)]
[end (fx+ start cnt)])
(let loop! ([i start] [n n] [line (position-line pos)] [column (position-column pos)])
(if (fx= i end)
(begin
(position-line-set! pos line)
(position-column-set! pos column)
(string-buffer-n-set! tb n))
(let ([c (string-ref str i)])
(string-set! buf n c)
(if (char=? c #\newline)
(loop! (fx+ i 1) (fx+ n 1) (fx+ line 1) 1)
(loop! (fx+ i 1) (fx+ n 1) line (fx+ column 1)))))))
cnt)
(define (gp) (string-buffer-n tb))
(let ([op (make-custom-textual-output-port "positional-string-output-port" w! gp #f #f)])
(define (line) (flush-output-port op) (position-line pos))
(define (column) (flush-output-port op) (position-column pos))
(define (p) (flush-output-port op) (extract-string-and-reset! tb))
(values op p line column))))
;; scan forward for blanks, and if it leads you to a new-line, strip
;; the previous blanks back to the new line.
(define (finish-strip ip stack bfp start-bfp)
(let ([node-to-strip (car stack)])
(if (string? node-to-strip)
(let loop ([i (string-length node-to-strip)])
(if (fx= i 0)
(values (cdr stack) bfp)
(let ([i (fx- i 1)])
(let ([c (string-ref node-to-strip i)])
(cond
[(char=? c #\newline)
(values (cons (substring node-to-strip 0 (fx+ i 1)) (cdr stack)) bfp)]
[(char-whitespace? c) (loop i)]
[else (set-port-position! ip start-bfp) (values stack start-bfp)])))))
(begin (set-port-position! ip start-bfp) (values stack start-bfp)))))
(define (strip-blanks ip stack start-bfp)
(let gather-blanks ([bfp start-bfp])
(let ([c (read-char ip)])
(cond
[(eof-object? c) (finish-strip ip stack bfp start-bfp)]
[(char=? c #\newline) (finish-strip ip stack (fx+ bfp 1) start-bfp)]
[(char-whitespace? c) (gather-blanks (fx+ bfp 1))]
[else (set-port-position! ip start-bfp) (values stack start-bfp)])))))
(library (template)
(export include-template define-template-extension optional @if @elif @else @endif @for @endfor @num)
(import (chezscheme) (template-helpers))
(define-syntax optional (lambda (x) (syntax-violation #f "misplaced aux keyword" x)))
(define check-string-and-indent
(lambda (s at indent)
(unless (string? s)
(errorf 'include-template "unexpected non-string value ~s of expression ~s" s at))
(if (= indent 0)
s
(let ([ip (open-string-input-port s)])
(let ([first-line (get-line ip)])
(if (eof-object? first-line)
s
(let-values ([(op p) (open-string-output-port)])
(display first-line op)
(let ([indent (make-string indent #\space)])
(let loop ()
(let ([line (get-line ip)])
(if (eof-object? line)
(begin
(when (char=? (string-ref s (fx- (string-length s) 1)) #\newline) (newline op))
(p))
(begin
(newline op)
(display indent op)
(display line op)
(loop)))))))))))))
(define-syntax include-template
(lambda (x)
(define (process-template-file r fn k)
(let* ([bip (open-file-input-port fn)]
[sfd (make-source-file-descriptor fn bip #t)]
[ip (transcoded-port bip (native-transcoder))]
[tb (make-string-buffer)])
(define (s0 a bfp)
(let ([c (read-char ip)])
(cond
[(eof-object? c)
(close-input-port ip)
(reverse (cons (extract-string-and-reset! tb) a))]
[(char=? c #\@) (s1 a (+ bfp 1))]
[else (extend-string-buffer! tb c) (s0 a (+ bfp 1))])))
(define (s1 a bfp)
(let ([c (read-char ip)])
(cond
[(eof-object? c) (source-error sfd bfp "expected open paren or @ following @")]
[(eqv? c #\@) (extend-string-buffer! tb c) (s0 a (+ bfp 1))]
[(eqv? c #\()
(unread-char c ip)
(let-values ([(e* new-bfp) (read-scheme k ip sfd bfp)])
(syntax-case e* ()
[(e)
(s0
(cons*
#`(check-string-and-indent e #,(source-string sfd bfp) (fx- (column) 1))
(extract-string-and-reset! tb)
a)
new-bfp)]
[else (source-error sfd bfp "found more than one expression within @(---)")]))]
[(initial-id-char? c)
(let ([str (extract-string-and-reset! tb)])
(extend-string-buffer! tb #\@)
(extend-string-buffer! tb c)
(s2 (cons str a) (+ bfp 1) bfp))]
[else (source-error sfd bfp "expected open paren or @ following @")])))
(define (s2 a bfp token-start-bfp)
(let ([c (read-char ip)])
(cond
[(eof-object? c) (close-input-port ip) (finish-identifier a bfp token-start-bfp)]
[(id-char? c) (extend-string-buffer! tb c) (s2 a (+ bfp 1) token-start-bfp)]
[else (unread-char c ip) (finish-identifier a bfp token-start-bfp)])))
(define (finish-identifier a bfp token-bfp)
(let* ([token (extract-string-and-reset! tb)]
[@kw (datum->syntax k (string->symbol token))]
[p (r @kw)])
(unless p (source-error sfd token-bfp "unrecognized token ~a" token))
(call-with-values (lambda () (p k ip sfd a bfp token-bfp)) s0)))
(s0 '() 0)))
(syntax-case x ()
[(k fn)
(string? (datum fn))
(lambda (r)
(with-syntax ([(e ...) (process-template-file r (datum fn) #'k)])
#'(let ([filename fn])
(let-values ([(op p line column) (open-positional-string-output-port)])
(display e op) ...
(p)))))])))
(define-syntax define-template-extension
(lambda (x)
(define who 'define-template-extension)
(define (make-prefix-id prefix kw)
(datum->syntax kw
(string->symbol
(string-append prefix (symbol->string (syntax->datum kw))))))
(define build-matcher
(case-lambda
[(kw)
(with-syntax ([kw kw] [@kw (make-prefix-id "@" kw)])
#'[@kw (lambda (k ip sfd stack bfp token-bfp)
(let-values ([(stack bfp) (strip-blanks ip stack bfp)])
(values (cons (make-incomplete-node 'kw #f token-bfp) stack) bfp)))])]
[(kw expr)
(with-syntax ([kw kw] [@kw (make-prefix-id "@" kw)] [(expr ...) expr])
#'[@kw (lambda (k ip sfd stack bfp token-bfp)
(let-values ([(e* new-bfp) (read-scheme k ip sfd bfp)])
(syntax-case e* ()
[(expr ...)
(let-values ([(stack new-bfp) (strip-blanks ip stack new-bfp)])
(values (cons (make-incomplete-node 'kw e* token-bfp) stack) new-bfp))]
[_ (source-error sfd token-bfp "expected @~s~s syntax, but got @~s~s"
'kw '(expr ...) 'kw (syntax->datum e*))])))])]))
(define (check-id id)
(let* ([str (symbol->string (syntax->datum id))]
[len (string-length str)])
(unless (and (> len 0) (initial-id-char? (string-ref str 0))
(let loop ([len len])
(or (= len 0)
(let ([len (- len 1)])
(and (id-char? (string-ref str len)) (loop len))))))
(syntax-violation who "invalid template keyword" id))))
(define (check-unique! type ids)
(let loop ([ids ids])
(syntax-case ids ()
[(id rest ...)
(if (memq (datum id) (datum (rest ...)))
(syntax-violation who (format "one or more ~a used more than once" type) #'id #'(rest ...))
(loop #'(rest ...)))]
[() (void)])))
(define (check-syntax-unique! type maybe-expr*)
(check-unique! type
(let f ([stx maybe-expr*] [ids '()])
(syntax-case stx ()
[id (and (identifier? #'id) (not (memq (datum id) '(... unquote quote)))) (cons #'id ids)]
[(a . d) (f #'a (f #'d ids))]
[_ ids]))))
(define (build-check kw tmpl x)
#`(unless #,(if x #`(and #,x #,tmpl) tmpl)
(source-error sfd token-bfp "found ~s without required ~s" token '#,kw)))
(define (build-initial-values bindings list?*)
(fold-right (lambda (binding list? init-val**)
(cons
(if list?
(make-list (length binding) #''())
(make-list (length binding) #'#f))
init-val**))
'() bindings list?*))
(define (build-bodies list?* tmpls updates bindings)
(let f ([list?* list?*] [tmpls tmpls] [updates updates] [bindings bindings] [rbindings '()])
(if (null? list?*)
'()
(with-syntax ([(checks ...)
(if (car list?*)
'()
#`((when #,(car tmpls)
(source-error token-bfp "found more @~s than expected" type))))]
[((args ...) ...) (fold-left (lambda (args binding) (cons binding args))
(cons (car updates) (cdr bindings)) rbindings)])
(cons #'(begin checks ... (loop (cdr stack) '() args ... ...))
(f (cdr list?*) (cdr tmpls) (cdr updates) (cdr bindings) (cons (car bindings) rbindings)))))))
(define (process-template output pat)
(define (squawk type)
(syntax-violation who (format "extension cannot start with ~s keyword" type) pat))
(syntax-case pat (optional)
[((optional kw (expr ...) tmpl) . rest)
(and (identifier? #'kw) (identifier? #'tmpl))
(squawk 'optional)]
[((optional kw tmpl) . rest)
(and (identifier? #'kw) (identifier? #'tmpl))
(squawk 'optional)]
[((kw (expr ...) tmpl) dots . rest)
(and (eq? (datum dots) '...) (identifier? #'kw) (identifier? #'tmpl))
(squawk 'list)]
[((kw tmpl) dots . rest)
(and (eq? (datum dots) '...) (identifier? #'kw) (identifier? #'tmpl))
(squawk 'optional)]
[(kw (expr ...) tmpl . rest)
(and (identifier? #'kw) (identifier? #'tmpl))
(process-rest output #'kw #'rest
(list (build-matcher #'kw #'(expr ...)))
#'([tmpl #`(string-append #,@rstack)]
[(expr ...) (incomplete-node-e* item)]))]
[(kw tmpl . rest)
(and (identifier? #'kw) (identifier? #'tmpl))
(process-rest output #'kw #'rest (list (build-matcher #'kw))
#'([tmpl #`(string-append #,@rstack)]))]
[(kw (expr ...))
(with-syntax ([@kw (make-prefix-id "@" #'kw)] [output output])
#'([@kw (lambda (k ip sfd stack bfp token-bfp)
(let-values ([(e* new-bfp) (read-scheme k ip sfd bfp)])
(syntax-case e* ()
[(expr ...) (values (cons #`output stack) new-bfp)]
[_ (source-error sfd token-bfp "expected @~s~s syntax, but got @~s~s"
'kw '(expr ...) 'kw (syntax->datum e*))])))]))]
[(kw)
(with-syntax ([@kw (make-prefix-id "@" #'kw)] [output output])
#'([@kw (lambda (k ip sfd stack bfp indent token-bfp)
(values (cons #`output stack) bfp indent))]))]))
(define (process-rest output first-kw rest as* matches)
(let f ([pat rest]
[as* as*]
[checks '()]
[kws '()]
[tmpls '()]
[list?* '()]
[bindings '()]
[updates '()]
[exprs '()]
[matches matches])
(syntax-case pat (optional)
[((optional kw (expr ...) tmpl) . rest)
(and (identifier? #'kw) (identifier? #'tmpl))
(with-syntax ([(t) (generate-temporaries '(t))])
(f #'rest
(cons (build-matcher #'kw #'(expr ...)) as*) checks
(cons #'kw kws) (cons #'tmpl tmpls) (cons #f list?*)
(cons (list #'tmpl #'t) bindings)
(cons (list #'#`(string-append #,@rstack) #'(incomplete-node-e* item)) updates)
(cons #'(expr ...) exprs) (cons* #'[tmpl tmpl] #'[(expr ...) #'t] matches)))]
[((optional kw tmpl) . rest)
(and (identifier? #'kw) (identifier? #'tmpl))
(f #'rest
(cons (build-matcher #'kw) as*) checks
(cons #'kw kws) (cons #'tmpl tmpls) (cons #f list?*) (cons (list #'tmpl) bindings)
(cons (list #'#`(string-append #,@rstack)) updates)
(cons #f exprs) (cons #'[tmpl tmpl] matches))]
[((kw (expr ...) tmpl) dots . rest)
(and (eq? (datum dots) '...) (identifier? #'kw) (identifier? #'tmpl))
(with-syntax ([(t*) (generate-temporaries '(t*))])
(f #'rest
(cons (build-matcher #'kw #'(expr ...)) as*) checks
(cons #'kw kws) (cons #'tmpl tmpls) (cons #t list?*) (cons (list #'tmpl #'t*) bindings)
(cons (list #'(cons #`(string-append #,@rstack) tmpl) #'(cons (incomplete-node-e* item) t*)) updates)
(cons #'(expr ...) exprs) (cons* #'[(tmpl (... ...)) tmpl] #'[((expr ...) (... ...)) t*] matches)))]
[((kw tmpl) dots . rest)
(and (eq? (datum dots) '...) (identifier? #'kw) (identifier? #'tmpl))
(f #'rest
(cons (build-matcher #'kw) as*) checks
(cons #'kw kws) (cons #'tmpl tmpls) (cons #t list?*) (cons (list #'tmpl) bindings)
(cons (list #'(cons #`(string-append #,@rstack) tmpl)) updates)
(cons #f exprs) (cons* #'[(tmpl (... ...)) tmpl] matches))]
[(kw (expr ...) tmpl . rest)
(and (identifier? #'kw) (identifier? #'tmpl))
(with-syntax ([(t) (generate-temporaries '(t))])
(f #'rest
(cons (build-matcher #'kw #'(expr ...)) as*)
(cons (build-check #'kw #'tmpl #'t) checks)
(cons #'kw kws) (cons #'tmpl tmpls) (cons #f list?*) (cons (list #'tmpl #'t) bindings)
(cons (list #'#`(string-append #,@rstack) #'(incomplete-node-e* item)) updates)
(cons #'(expr ...) exprs) (cons* #'[tmpl tmpl] #'[(expr ...) #'t] matches)))]
[(kw tmpl . rest)
(and (identifier? #'kw) (identifier? #'tmpl))
(f #'rest
(cons (build-matcher #'kw) as*)
(cons (build-check #'kw #'tmpl #f) checks)
(cons #'kw kws) (cons #'tmpl tmpls) (cons #f list?*) (cons (list #'tmpl) bindings)
(cons (list #'#`(string-append #,@rstack)) updates)
(cons #f exprs) (cons #'[tmpl tmpl] matches))]
[()
(begin
(for-each check-id kws)
(check-unique! "keyword" kws)
(check-unique! "template bindings" tmpls)
(check-syntax-unique! "scheme syntax matching expressions" exprs)
(cons
(with-syntax ([startkw first-kw]
[endkw (make-prefix-id "end" first-kw)]
[@endkw (make-prefix-id "@end" first-kw)]
[output output]
[(matches ...) matches]
[(checks ...) checks]
[((x ...) ...) bindings]
[((init-val ...) ...) (build-initial-values bindings list?*)]
[(kw ...) kws]
[(body ...) (build-bodies list?* tmpls updates bindings)])
#'[@endkw (lambda (k ip sfd stack bfp token-bfp)
(let-values ([(stack bfp) (strip-blanks ip stack bfp)])
(let loop ([stack stack] [rstack '()] [x init-val] ... ...)
(if (null? stack)
(source-error sfd token-bfp "found @~s with no initial @~s" 'endkw 'startkw)
(let ([item (car stack)])
(if (incomplete-node? item)
(let ([type (incomplete-node-type item)])
(case type
[(startkw) checks ...
(with-syntax (matches ...)
(values (cons #`output (cdr stack)) bfp))]
[(kw) body] ...
[else (source-error sfd token-bfp
"found unexpected @~s (~a) instead of expected @~s before @~s"
type (source-string sfd (incomplete-node-bfp item)) 'startkw 'endkw)]))
(loop (cdr stack) (cons item rstack) x ... ...)))))))])
as*))]
[_ (syntax-violation who "unrecognized pattern" pat)])))
(syntax-case x ()
[(_ pat output)
(with-syntax ([([@kw proc] ...) (process-template #'output #'pat)])
#'(begin (define-syntax @kw (make-compile-time-value proc)) ...))])))
(define-template-extension (num (e)) (number->string e))
(define-template-extension (for ([binding e] [bindings es] ...) tmpl)
(with-output-to-string
(lambda ()
(for-each (lambda (binding bindings ...) (display tmpl)) e es ...))))
(define-template-extension (if (expr) tmpl (elif (exprs) tmpls) ... (optional else else-tmpl))
(if expr
tmpl
#,(let f ([exprs #'(exprs ...)] [tmpls #'(tmpls ...)])
(if (null? exprs)
(or #'else-tmpl #'"")
(with-syntax ([expr (car exprs)] [tmpl (car tmpls)] [else (f (cdr exprs) (cdr tmpls))])
#'(if expr
tmpl
else)))))))
#!eof
-------- saving remainder of file to /tmp/t and running /tmp/t should produce only "end of tests" --------
#!/bin/tcsh
cat >! /tmp/spam.h << END
extern void @(name)(void);
END
cat >! /tmp/spam.c << END
#include <stdio.h>
@((include-template "/tmp/spam.h"))
void @(name)() {
@(name)();
}
END
scheme -q << END
(import (template))
(unless (equal?
(let ([name "bob"]) (include-template "/tmp/spam.c"))
"#include <stdio.h>\n\nextern void bob(void);\n\n\nvoid bob() {\n bob();\n}\n")
(error #f "test 1 failed"))
END
cat >! /tmp/spam.c << END
(import (template))
(unless (equal?
(guard (c [else (with-output-to-string (lambda () (display-condition c)))])
(expand '(let ([name "bob"]) (include-template "/tmp/spam.c"))))
"Exception in get-datum/annotations: unexpected end-of-file reading list at line 6, char 4 of /tmp/spam.c")
(error #f "test 2 failed"))
END
cat >! /tmp/spam.c << END
#include <stdio.h>
@((include-template "/tmp/spam.h"))
void @(name)() {
@(name)();
@
}
END
scheme -q << END
(import (template))
(unless (equal?
(guard (c [else (with-output-to-string (lambda () (display-condition c)))])
(expand '(let ([name "bob"]) (include-template "/tmp/spam.c"))))
"Exception in include-template: expected open paren or @ following @ at line 7, char 4 of /tmp/spam.c")
(error #f "test 3 failed"))
END
cat >! /tmp/spam.c << END
#include <stdio.h>
@((include-template "/tmp/spam.h"))
void @(name)() {
@(name)();
}
END
echo -n "@" >> /tmp/spam.c
scheme -q << END
(import (template))
(unless (equal?
(guard (c [else (with-output-to-string (lambda () (display-condition c)))])
(expand '(let ([name "bob"]) (include-template "/tmp/spam.c"))))
"Exception in include-template: expected open paren or @ following @ at line 8, char 2 of /tmp/spam.c")
(error #f "test 4 failed"))
END
cat >! /tmp/spam.c << END
#include <stdio.h>
@((include-template #xGO! "/tmp/spam.h"))
void @(name)() {
@(name)();
}
END
echo -n "@" >> /tmp/spam.c
scheme -q << END
(import (template))
(unless (equal?
(guard (c [else (with-output-to-string (lambda () (display-condition c)))])
(expand '(let ([name "bob"]) (include-template "/tmp/spam.c"))))
"Exception in get-datum/annotations: invalid number syntax #xGO! at line 3, char 21 of /tmp/spam.c")
(error #f "test 5 failed"))
END
cat >! /tmp/spam.c << END
#include <stdio.h>
@((include-template))
void @(name)() {
@(name)();
}
END
scheme -q << END
(import (template))
(unless (equal?
(guard (c [else (with-output-to-string (lambda () (display-condition c)))])
(expand '(let ([name "bob"]) (include-template "/tmp/spam.c"))))
"Exception: invalid syntax (include-template) at line 3, char 3 of /tmp/spam.c")
(error #f "test 6 failed"))
END
cat >! /tmp/spam.c <<END
#include <stdio.h>
/* function: @(name)
*
* @@param: @(name)
*/
@((include-template "/tmp/spam.h"))
void @(name)() {
@(name)();
}
END
scheme -q << END
(import (template))
(unless (equal? (let ([name "bob"]) (include-template "/tmp/spam.c"))
"#include <stdio.h>\n\n/* function: bob\n *\n * @param: bob\n */\nextern void bob(void);\n\n\nvoid bob() {\n bob();\n}\n")
(error #f "test 7 failed"))
END
cat >! /tmp/rockets <<END
Test
@for([x xs])
@num(x) ...
@endfor
Blast off!
END
scheme -q << END
(import (template))
(unless (equal? (let ([xs '(3 2 1)]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\n")
(error #f "test 8 failed"))
END
cat >> /tmp/rockets <<END
@if((= y 0))
Again!
@elif((= y 1))
Rockets are awesome!
@elif((= y 2))
Explosions!
@else
That was fun.
@endif
END
scheme -q << END
(import (template))
(unless (equal? (let ([xs '(3 2 1)] [y 0]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\nAgain!\n")
(error #f "test 9 failed"))
END
scheme -q << END
(import (template))
(unless (equal? (let ([xs '(3 2 1)] [y 1]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\nRockets are awesome!\n")
(error #f "test 10 failed"))
END
scheme -q << END
(import (template))
(unless (equal? (let ([xs '(3 2 1)] [y 2]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\nExplosions!\n")
(error #f "test 11 failed"))
END
scheme -q << END
(import (template))
(unless (equal? (let ([xs '(3 2 1)] [y 3]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\nThat was fun.\n")
(error #f "test 12 failed"))
END
cat >> /tmp/rockets <<END
@join(" and " [a as])@(a)@endjoin
END
scheme -q << END
(import (template))
(define-template-extension (join (?str [binding ls-expr] [bindings ls-exprs] ...) tmpl)
#,(with-syntax ([(t ts ...) (generate-temporaries #'(binding bindings ...))])
#'(with-output-to-string
(lambda ()
(let ([str ?str] [t ls-expr] [ts ls-exprs] ...)
(if (null? t)
""
(let loop ([t t] [ts ts] ...)
(let ([binding (car t)] [bindings (car ts)] ... [t (cdr t)] [ts (cdr ts)] ...)
(display tmpl)
(unless (null? t)
(display str)
(loop t ts ...))))))))))
(unless (equal? (let ([xs '(3 2 1)] [y 3] [as '()]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\nThat was fun.\n")
(error #f "test 13 failed"))
END
scheme -q << END
(import (template))
(define-template-extension (join (?str [binding ls-expr] [bindings ls-exprs] ...) tmpl)
#,(with-syntax ([(t ts ...) (generate-temporaries #'(binding bindings ...))])
#'(with-output-to-string
(lambda ()
(let ([str ?str] [t ls-expr] [ts ls-exprs] ...)
(if (null? t)
""
(let loop ([t t] [ts ts] ...)
(let ([binding (car t)] [bindings (car ts)] ... [t (cdr t)] [ts (cdr ts)] ...)
(display tmpl)
(unless (null? t)
(display str)
(loop t ts ...))))))))))
(unless (equal? (let ([xs '(3 2 1)] [y 3] [as '("a")]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\nThat was fun.\na")
(error #f "test 14 failed"))
END
scheme -q << END
(import (template))
(define-template-extension (join (?str [binding ls-expr] [bindings ls-exprs] ...) tmpl)
#,(with-syntax ([(t ts ...) (generate-temporaries #'(binding bindings ...))])
#'(with-output-to-string
(lambda ()
(let ([str ?str] [t ls-expr] [ts ls-exprs] ...)
(if (null? t)
""
(let loop ([t t] [ts ts] ...)
(let ([binding (car t)] [bindings (car ts)] ... [t (cdr t)] [ts (cdr ts)] ...)
(display tmpl)
(unless (null? t)
(display str)
(loop t ts ...))))))))))
(unless (equal? (let ([xs '(3 2 1)] [y 3] [as '("a" "b")]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\nThat was fun.\na and b")
(error #f "test 15 failed"))
END
scheme -q << END
(import (template))
(define-template-extension (join (?str [binding ls-expr] [bindings ls-exprs] ...) tmpl)
#,(with-syntax ([(t ts ...) (generate-temporaries #'(binding bindings ...))])
#'(with-output-to-string
(lambda ()
(let ([str ?str] [t ls-expr] [ts ls-exprs] ...)
(if (null? t)
""
(let loop ([t t] [ts ts] ...)
(let ([binding (car t)] [bindings (car ts)] ... [t (cdr t)] [ts (cdr ts)] ...)
(display tmpl)
(unless (null? t)
(display str)
(loop t ts ...))))))))))
(unless (equal? (let ([xs '(3 2 1)] [y 3] [as '("a" "b" "c")]) (include-template "/tmp/rockets"))
"Test\n3 ...\n2 ...\n1 ...\nBlast off!\nThat was fun.\na and b and c")
(error #f "test 16 failed"))
END
cat >! /tmp/indent-test.c << END
#include <stdio.h>
int main(int argc, char *argv[]) {
@(body)
}
END
scheme -q << END
(import (template))
(unless (equal? (let ([body "printf(\"Hello, world!\\\\n\");\nprintf(\"So... uh, what's going on?\\\\n\");\nprintf(\"Well, goodbye then.\\\\n\");"])
(include-template "/tmp/indent-test.c"))
"#include <stdio.h>\n\nint main(int argc, char *argv[]) {\n printf(\"Hello, world!\\\\n\");\n printf(\"So... uh, what's going on?\\\\n\");\n printf(\"Well, goodbye then.\\\\n\");\n}\n")
(error #f "test 17 failed"))
END
cat >! /tmp/hygeine << END
Hygiene test:
@for([t i*])
got @(t) .
@endfor
@for([x j*])
got @(x) and t is @(t).
@endfor
@let([outer-t t])
@for([x i*] [t j*])
got @(x) and @(t) and @(outer-t).
@endfor
@endlet
END
scheme -q << END
(import (template))
(let ()
(define-template-extension (for ([x e] [xs es] ...) tmpl)
(let ([t e])
(apply string-append (map (lambda (x xs ...) tmpl) t es ...))))
(define-template-extension (let ([x e] [xs es] ...) tmpl)
(let ([x e] [xs es] ...) tmpl))
(unless (equal? (let ([t "10"] [i* '("1" "2" "3")] [j* '("a" "b" "c")])
(include-template "/tmp/hygeine"))
"Hygiene test:\n\n got 1 .\n got 2 .\n got 3 .\n\n got a and t is 10.\n got b and t is 10.\n got c and t is 10.\n\n got 1 and a and 10.\n got 2 and b and 10.\n got 3 and c and 10.\n")
(error #f "test 18 failed")))
END
cat >! /tmp/indent << END
This is to test indents:
simple indent: @(x)
nested indents: @(y) @(x)
END
scheme -q << END
(import (template))
(unless (equal? (let ([x "a\nb\nc\n"] [y "x\ny\nz"])
(include-template "/tmp/indent"))
"This is to test indents:\n simple indent: a\n b\n c\n\n\n nested indents: x\n y\n z a\n b\n c\n\n")
(error #f "test 19 failed"))
END
echo "end of tests"