|
1 | 1 | #lang at-exp racket/base
|
| 2 | +(module+ test (require rackunit)) |
| 3 | + |
| 4 | +#; #; |
| 5 | +(this is to facilitate |
| 6 | + some testing code |
| 7 | + that appears below) |
| 8 | +(it helps test |
| 9 | + codeblock-from-file) |
2 | 10 |
|
3 | 11 | (provide
|
4 | 12 | goals ;; bulletize goals
|
|
10 | 18 | scribble/example
|
11 | 19 | racket/sandbox
|
12 | 20 | scribble/core
|
13 |
| - scriblib/figure)) |
| 21 | + scriblib/figure) |
| 22 | + codeblock-from-file) |
14 | 23 |
|
15 | 24 | ;; -----------------------------------------------------------------------------
|
16 | 25 | (require
|
17 | 26 | "exercise/ex.rkt"
|
18 | 27 | (for-label racket/base redex/reduction-semantics)
|
| 28 | + (for-syntax racket/base racket/match racket/list |
| 29 | + syntax/parse syntax/strip-context) |
19 | 30 | scribble/manual
|
20 | 31 | scribble/core
|
21 | 32 | scribble/example
|
22 | 33 | racket/sandbox
|
| 34 | + racket/runtime-path |
| 35 | + racket/list |
23 | 36 | scriblib/figure)
|
24 | 37 |
|
25 | 38 |
|
@@ -51,3 +64,104 @@ to the top of your file:
|
51 | 64 | @;%
|
52 | 65 | }
|
53 | 66 | )
|
| 67 | + |
| 68 | +;; codeblock-from-file pulls a specific hunk of lines from |
| 69 | +;; one of the files in code/ and generates a use of `examples` |
| 70 | +;; with that code in it. |
| 71 | +;; |
| 72 | +;; Supply: |
| 73 | +;; - the name of the file as a relative path to the source |
| 74 | +;; location of the use of codeblock-from-file, |
| 75 | +;; - a regular expression that the first line to include must match |
| 76 | +;; - the evaluator to evaluate the program, with #:eval |
| 77 | +;; - the number of expressions that should follow (according to the |
| 78 | +;; rules of read) with #:exp-count, which defaults to 1 |
| 79 | +;; - and a sequence of strings to tack onto the end of the expression |
| 80 | +;; these are treated as if they are extra lines in the file with |
| 81 | +;; #:extra-code |
| 82 | +;; |
| 83 | +;; if the expression begins with define, define-language or a few |
| 84 | +;; other keywords (see the match expression below), then the code |
| 85 | +;; is wrapped with eval:no-prompt (see examples for docs) |
| 86 | +;; the highlighting and linking is based on the for-label bindings |
| 87 | +;; that are at the use of codeblock-from-file |
| 88 | +(define-syntax (codeblock-from-file stx) |
| 89 | + (syntax-parse stx |
| 90 | + [(_ file:string rx-start:regexp #:eval eval |
| 91 | + (~optional (~seq #:exp-count number-of-expressions:integer)) |
| 92 | + (~optional (~seq #:extra-code (extra-code:string ...)))) |
| 93 | + |
| 94 | + #`(examples #:label #f |
| 95 | + #:eval eval |
| 96 | + #,@(get-code (syntax-e #'file) |
| 97 | + (syntax-e #'rx-start) |
| 98 | + (if (attribute number-of-expressions) |
| 99 | + (syntax-e #'number-of-expressions) |
| 100 | + 1) |
| 101 | + (if (attribute extra-code) |
| 102 | + (map |
| 103 | + syntax-e |
| 104 | + (syntax->list #'(extra-code ...))) |
| 105 | + '()) |
| 106 | + stx))])) |
| 107 | + |
| 108 | +(begin-for-syntax |
| 109 | + (define (get-code file rx:start number-of-expressions extra-code stx) |
| 110 | + (define src (syntax-source stx)) |
| 111 | + (define-values (src-dir _1 _2) (split-path src)) |
| 112 | + (define-values (in out) (make-pipe)) |
| 113 | + (define-values (start-line end-line no-prompt?s) |
| 114 | + (get-start-and-end-lines file rx:start |
| 115 | + number-of-expressions |
| 116 | + stx |
| 117 | + src-dir)) |
| 118 | + (call-with-input-file (build-path src-dir file) |
| 119 | + (λ (port) |
| 120 | + (for/list ([l (in-lines port)] |
| 121 | + [i (in-range end-line)] |
| 122 | + #:unless (< i start-line)) |
| 123 | + (displayln l out)))) |
| 124 | + (for ([extra-code-item (in-list extra-code)]) |
| 125 | + (displayln extra-code-item out)) |
| 126 | + (close-output-port out) |
| 127 | + (port-count-lines! in) |
| 128 | + (for/list ([no-prompt? (in-list (append no-prompt?s |
| 129 | + (make-list (length extra-code) |
| 130 | + #f)))]) |
| 131 | + (define e (replace-context stx (read-syntax src in))) |
| 132 | + (if no-prompt? |
| 133 | + `(eval:no-prompt ,e) |
| 134 | + e))) |
| 135 | + |
| 136 | + (define (get-start-and-end-lines file rx:start number-of-expressions stx src-dir) |
| 137 | + (define start-line |
| 138 | + (call-with-input-file (build-path src-dir file) |
| 139 | + (λ (port) |
| 140 | + (define count 0) |
| 141 | + (let/ec escape |
| 142 | + (for ([l (in-lines port)]) |
| 143 | + (when (regexp-match? rx:start l) |
| 144 | + (escape)) |
| 145 | + (set! count (+ count 1))) |
| 146 | + (raise-syntax-error 'codeblock-from-file |
| 147 | + (format "didn't find a line matching ~s" rx:start) |
| 148 | + stx)) |
| 149 | + count))) |
| 150 | + (define-values (no-prompt?s end-line) |
| 151 | + (call-with-input-file (build-path src-dir file) |
| 152 | + (λ (port) |
| 153 | + (port-count-lines! port) |
| 154 | + (for ([i (in-range start-line)]) |
| 155 | + (read-line port)) |
| 156 | + (define no-prompt?s |
| 157 | + (for/list ([i (in-range number-of-expressions)]) |
| 158 | + (define exp (read port)) |
| 159 | + (when (eof-object? exp) (error 'codeblock-from-file "expression #~a not present in file" i)) |
| 160 | + (match exp |
| 161 | + [`(define . ,stuff) #t] |
| 162 | + [`(define-language . ,stuff) #t] |
| 163 | + [`(test-equal . ,stuff) #t] |
| 164 | + [_ #f]))) |
| 165 | + (define-values (line col pos) (port-next-location port)) |
| 166 | + (values no-prompt?s line)))) |
| 167 | + (values start-line end-line no-prompt?s))) |
0 commit comments