about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorCase Duckworth2024-06-15 21:17:03 -0500
committerCase Duckworth2024-06-15 21:17:03 -0500
commit703e9e93087d32364087a0ebc9e315869b70ff7c (patch)
treede5cfdd1a687dbe68686929497e870fad5f28800
parentWrite executable (diff)
downloadjimmy-703e9e93087d32364087a0ebc9e315869b70ff7c.tar.gz
jimmy-703e9e93087d32364087a0ebc9e315869b70ff7c.zip
Update things
-rw-r--r--.gitignore8
-rw-r--r--Makefile66
-rw-r--r--README.gmi32
-rw-r--r--bin/jimmy.scm2
-rw-r--r--lib/emit.scm96
-rw-r--r--lib/html.scm9
-rw-r--r--lib/read.scm10
-rw-r--r--lib/util.scm29
-rw-r--r--repl.scm2
-rw-r--r--tests/run.scm46
10 files changed, 173 insertions, 127 deletions
diff --git a/.gitignore b/.gitignore index d163863..1489dda 100644 --- a/.gitignore +++ b/.gitignore
@@ -1 +1,7 @@
1build/ \ No newline at end of file 1*.build.sh
2*.import.scm
3*.install.sh
4*.link
5*.o
6*.so
7jimmy \ No newline at end of file
diff --git a/Makefile b/Makefile index d2a1003..2e0612e 100644 --- a/Makefile +++ b/Makefile
@@ -1,61 +1,17 @@
1# Automatically generated by scramble
2
3NAME = jimmy 1NAME = jimmy
4CSC = /usr/bin/csc 2CSI = csi -setup-mode -s
5CSC_OPTIONS = -setup-mode -host -I $(PWD) -C -I$(BUILD)
6CSC_LIB_OPTIONS = -D compiling-extension -emit-all-import-libraries -dynamic -regenerate-import-libraries
7CSC_OPTIONS_EXTRA = -X utf8 -X module-declarations
8CSI = /usr/bin/csi
9BUILD = $(PWD)/build
10TESTS = $(PWD)/tests 3TESTS = $(PWD)/tests
11TEST_ENV = env BUILD=$(BUILD) TESTS=$(TESTS) 4TEST_ENV = env BUILD=$(BUILD) TESTS=$(TESTS) TEST_USE_ANSI=0
12TEST_ENV_EXTRA = TEST_USE_ANSI=0 5ARTEFACTS = *.build.sh *.install.sh *.import.scm *.so *.link *.o
13 6
14.PHONY: all test clean install uninstall 7.PHONY: build test clean install uninstall
15all: build/jimmy.util.so build/jimmy.read.so build/jimmy.emit.so build/jimmy.html.so build/jimmy.wrap.so build/jimmy.main.so build/jimmy 8build:
16test: all 9 chicken-install -n
17 cd $(BUILD) && $(TEST_ENV) $(TEST_ENV_EXTRA) $(CSI) -setup-mode -s $(TESTS)/run.scm $(NAME) 10test: build
11 $(TEST_ENV) $(CSI) $(TESTS)/run.scm $(NAME)
18clean: 12clean:
19 -rm -rf $(BUILD) *.build.sh *.install.sh $(NAME) *.import.scm *.so *.link *.static.o 13 -rm -rf $(ARTEFACTS) $(NAME)
20install: 14install:
21 chicken-install -s 15 chicken-install -s
22uninstall: 16uninstall:
23 chicken-uninstall -s 17 chicken-uninstall -s $(NAME)
24
25# jimmy
26
27build/jimmy.util.so: lib/util.scm
28 @mkdir -p $(BUILD)
29 $(CSC) $(CSC_OPTIONS) $(CSC_LIB_OPTIONS) $(CSC_OPTIONS_EXTRA) $< -o $@
30 @test -f jimmy.util.import.scm &&mv jimmy.util.import.scm $(BUILD)/||true
31
32build/jimmy.read.so: lib/read.scm lib/util.scm
33 @mkdir -p $(BUILD)
34 $(CSC) $(CSC_OPTIONS) $(CSC_LIB_OPTIONS) $(CSC_OPTIONS_EXTRA) $< -o $@
35 @test -f jimmy.read.import.scm &&mv jimmy.read.import.scm $(BUILD)/||true
36
37build/jimmy.emit.so: lib/emit.scm lib/util.scm
38 @mkdir -p $(BUILD)
39 $(CSC) $(CSC_OPTIONS) $(CSC_LIB_OPTIONS) $(CSC_OPTIONS_EXTRA) $< -o $@
40 @test -f jimmy.emit.import.scm &&mv jimmy.emit.import.scm $(BUILD)/||true
41
42build/jimmy.html.so: lib/html.scm lib/util.scm lib/emit.scm
43 @mkdir -p $(BUILD)
44 $(CSC) $(CSC_OPTIONS) $(CSC_LIB_OPTIONS) $(CSC_OPTIONS_EXTRA) $< -o $@
45 @test -f jimmy.html.import.scm &&mv jimmy.html.import.scm $(BUILD)/||true
46
47build/jimmy.wrap.so: lib/wrap.scm lib/util.scm lib/emit.scm
48 @mkdir -p $(BUILD)
49 $(CSC) $(CSC_OPTIONS) $(CSC_LIB_OPTIONS) $(CSC_OPTIONS_EXTRA) $< -o $@
50 @test -f jimmy.wrap.import.scm &&mv jimmy.wrap.import.scm $(BUILD)/||true
51
52build/jimmy.main.so: lib/main.scm lib/util.scm lib/emit.scm lib/read.scm lib/wrap.scm
53 @mkdir -p $(BUILD)
54 $(CSC) $(CSC_OPTIONS) $(CSC_LIB_OPTIONS) $(CSC_OPTIONS_EXTRA) $< -o $@
55 @test -f jimmy.main.import.scm &&mv jimmy.main.import.scm $(BUILD)/||true
56
57build/jimmy: bin/jimmy.scm lib/main.scm
58 @mkdir -p $(BUILD)
59 $(CSC) $(CSC_OPTIONS) $(CSC_OPTIONS_EXTRA) $< -o $@
60 @test -f jimmy.import.scm &&mv jimmy.import.scm $(BUILD)/||true
61
diff --git a/README.gmi b/README.gmi index a3e10fd..88dde93 100644 --- a/README.gmi +++ b/README.gmi
@@ -38,12 +38,12 @@ offers a normalization function.
38 tags do not stretch over source line breaks, and there is no check as to 38 tags do not stretch over source line breaks, and there is no check as to
39 whether they're properly nested. 39 whether they're properly nested.
40 40
41### Incompatible changes 41### Incompatible changes (UNIMPLEMENTED)
42 42
43This section should be as short as possible, of course. They're incompatible in 43This section should be as short as possible, of course. They're incompatible in
44that a complying gemtext reader will interpret their meanings as different from 44that a complying gemtext reader will interpret their meanings as different from
45what jimmy text outputs. These changes are behind a flag (TODO!) and can be 45what jimmy text outputs. These changes are behind a flag and can be disabled at
46disabled at run-time. 46run-time.
47 47
48* Lines beginning with two or more spaces are automatically joined with the 48* Lines beginning with two or more spaces are automatically joined with the
49 previous line on output. 49 previous line on output.
@@ -52,6 +52,7 @@ disabled at run-time.
52 52
53Jimmy requires CHICKEN 5.3+ as well as the following eggs: 53Jimmy requires CHICKEN 5.3+ as well as the following eggs:
54 54
55* args
55* module-declarations 56* module-declarations
56* utf8 57* utf8
57 58
@@ -59,7 +60,30 @@ To install, simply run `make install`. You can uninstall with `make uninstall`.
59 60
60## Using jimmy 61## Using jimmy
61 62
62TODO 63You can run jimmy on the command line as a filter or on a file. Run it like
64this:
65
66```
67jimmy [OPTIONS...] [FILE]
68```
69
70FILE, if present, is the file to read; otherwise read standard input.
71
72The available options comprise
73
74* *-t, --to=FORMAT* --- Translate the input to FORMAT, one of `gemini`, `html`,
75 or a filename with format specifications in it (see OUTPUT FORMATS, below).
76* *-n, --no-extensions* --- Don't enable any of the breaking extensions to
77 gemtext outlined above (NOT IMPLEMENTED)
78* *-T, --template=TEMPLATE* --- Wrap the generated text in TEMPLATE using
79 metadata from the document (see TEMPLATES, below).
80* -h, --help --- Show the help text and exit
81
82### Output Formats
83
84
85
86### Templates
63 87
64## License 88## License
65 89
diff --git a/bin/jimmy.scm b/bin/jimmy.scm index 17e12ba..07bad7a 100644 --- a/bin/jimmy.scm +++ b/bin/jimmy.scm
@@ -23,7 +23,7 @@
23 (lambda () 23 (lambda ()
24 (print "Usage: " (car (argv)) " [OPTIONS...] [FILE]") 24 (print "Usage: " (car (argv)) " [OPTIONS...] [FILE]")
25 (newline) 25 (newline)
26 (print (args:usage options)) 26 (print (args:usage opts))
27 (print "Report bugs to acdw@acdw.net."))) 27 (print "Report bugs to acdw@acdw.net.")))
28 (exit exit-code)) 28 (exit exit-code))
29 29
diff --git a/lib/emit.scm b/lib/emit.scm index 2a8ab97..546ec5c 100644 --- a/lib/emit.scm +++ b/lib/emit.scm
@@ -2,8 +2,10 @@
2 2
3(import scheme (chicken base) 3(import scheme (chicken base)
4 (chicken format) 4 (chicken format)
5 (chicken io)
5 (chicken irregex) 6 (chicken irregex)
6 (chicken port) 7 (chicken port)
8 (chicken process)
7 (chicken string) 9 (chicken string)
8 (only utf8-srfi-13 string-join) 10 (only utf8-srfi-13 string-join)
9 (jimmy util)) 11 (jimmy util))
@@ -15,9 +17,14 @@
15 (with-output-to-string 17 (with-output-to-string
16 (lambda () (emit doc)))) 18 (lambda () (emit doc))))
17 19
20;;; Change these for different output types
21
22(define-public output-type
23 (make-parameter 'gemini))
24
18(define-public formats 25(define-public formats
19 (make-parameter 26 (make-parameter
20 ;; (TYPE (line . LINE-FMT) (stanza . STANZA-FMT) (inline . INLINE-FMT)) 27 ;; (EL (line . LINE-FMT) (stanza . STANZA-FMT) (inline . INLINE-FMT))
21 '((para (line . "~A") 28 '((para (line . "~A")
22 (stanza . "~A~%~%")) 29 (stanza . "~A~%~%"))
23 (verb (line . "~A~%") 30 (verb (line . "~A~%")
@@ -36,35 +43,60 @@
36 (hdr3 (line . "### ~A~%") 43 (hdr3 (line . "### ~A~%")
37 (stanza . "~A~%"))))) 44 (stanza . "~A~%")))))
38 45
46(define-public set-formats! formats)
47
39(define-public filters 48(define-public filters
40 (make-parameter 49 (make-parameter
41 ;; (TYPE (line . LINE-FILTER) (stanza . STANZA-FILTER)) 50 ;; (EL (line . LINE-FILTER) (stanza . STANZA-FILTER))
42 ;; line-filter : (lambda (list-of-strs) ...) -> list-of-strs (for format) 51 ;; line-filter : (lambda (list-of-strs) ...) -> list-of-strs (for format)
43 ;; stanza-filter : (lambda (list-of-strs) ...) -> str 52 ;; stanza-filter : (lambda (list-of-strs) ...) -> str
44 `((verb (line . ,identity) 53 `((verb
45 (stanza . ,join-lines)) 54 (stanza . ,(o ensure-newline (cut string-join <> "\n"))))
46 (default 55 (default
47 (line . ,identity) 56 (line . ,identity)
48 (stanza . ,flush-lines-left))))) 57 (stanza . ,flush-lines-left)))))
49 58
59(define-public set-filters! filters)
60
61;;; Implementation
62
50(define (format-line line el) 63(define (format-line line el)
51 (cond 64 (cond
52 ((string? (car line)) ; regular stanza line 65 ((string? (car line)) ; regular stanza line
53 (sprintf* (get-format el 'line) 66 (format/filter el 'line line))
54 ((get-filter el 'line) line)))
55 ((symbol? (car line)) ; inline element 67 ((symbol? (car line)) ; inline element
56 (sprintf* (get-format (car line) 'inline) 68 (format/filter (car line) '(inline . line) (cdr line)))
57 ((get-filter (car line) 'line) (cdr line))))
58 (else (error "Malformed line" line)))) 69 (else (error "Malformed line" line))))
59 70
60(define (format-stanza stanza) 71(define (format-stanza stanza)
61 (let* ((type (car stanza)) 72 (let* ((el (caar stanza))
62 (data (cdr stanza))
63 (text (map (lambda (ln) 73 (text (map (lambda (ln)
64 (format-line ln type)) 74 (format-line ln el))
65 data))) 75 (cdr stanza))))
66 (sprintf (get-format type 'stanza) 76 (case el
67 ((get-filter type 'stanza) text)))) 77 ((verb) (format-verb stanza))
78 (else
79 (format/filter el 'stanza text)))))
80
81(define (format-verb stanza)
82 (let ((el (car stanza))
83 (text (apply append (cdr stanza))))
84 (with-output-to-string
85 (lambda ()
86 (cond
87 ((and (pair? (cdr el))
88 (equal? (cadr el) "|"))
89 ;; special case: pipe to an external process
90 (let ((cmdline (cddr el)))
91 (if (find-command (car cmdline) #;<TODO:JIMMY_PATH>)
92 (receive (in out pid)
93 (process (car cmdline) (cdr cmdline)
94 `(("JIMMY_OUTPUT" . ,(->string (output-type)))))
95 (display (ensure-newline text) out)
96 (read-string #f in)))))
97 (else ; verbatim baby
98 (printf (get-format 'verb 'stanza)
99 ((get-filter 'verb 'stanza) text))))))))
68 100
69;;; Utilities 101;;; Utilities
70 102
@@ -77,18 +109,28 @@
77(define (get-format el scope) 109(define (get-format el scope)
78 (or (get-from (formats) el scope) 110 (or (get-from (formats) el scope)
79 "")) 111 ""))
112
80(define (get-filter el scope) (get-from (filters) el scope)) 113(define (get-filter el scope) (get-from (filters) el scope))
81 114
82(define (sprintf* fmt lis) 115(define (format/filter el scope text)
83 (let loop ((num (length (irregex-extract "~[aA]" fmt))) 116 (define (sprintf* fmt lis)
84 (lis lis) 117 (let loop ((num (length (irregex-extract "~[aA]" fmt)))
85 (out '())) 118 (lis (if (list? lis) lis (list lis)))
86 (cond 119 (out '()))
87 ((null? lis) 120 (cond
88 (apply sprintf fmt (reverse out))) 121 ((null? lis)
89 ((= 1 num) 122 (apply sprintf fmt (reverse out)))
90 (loop 0 '() (cons (string-join lis) out))) 123 ((= 1 num)
91 (else 124 (loop 0 '() (cons (string-join lis) out)))
92 (loop (- num 1) 125 (else
93 (cdr lis) 126 (loop (- num 1)
94 (cons (car lis) out)))))) 127 (cdr lis)
128 (cons (car lis) out))))))
129
130 (define-values (format-scope filter-scope)
131 (if (pair? scope)
132 (values (car scope) (cdr scope))
133 (values scope scope)))
134
135 (sprintf* (get-format el format-scope)
136 ((get-filter el filter-scope) text)))
diff --git a/lib/html.scm b/lib/html.scm index 07cd921..26cdff4 100644 --- a/lib/html.scm +++ b/lib/html.scm
@@ -3,7 +3,10 @@
3(import scheme (chicken base) 3(import scheme (chicken base)
4 (chicken irregex) 4 (chicken irregex)
5 (jimmy emit) 5 (jimmy emit)
6 (jimmy util)) 6 (jimmy util)
7 utf8-srfi-13)
8
9(output-type 'html)
7 10
8(define (escape-entities s) 11(define (escape-entities s)
9 (irregex-replace/all "[&<>]" s 12 (irregex-replace/all "[&<>]" s
@@ -24,7 +27,7 @@
24 (char->tag "_" "i") 27 (char->tag "_" "i")
25 (char->tag "`" "code")) s)) 28 (char->tag "`" "code")) s))
26 29
27(formats 30(set-formats!
28 '((para (line . "~a~%") 31 '((para (line . "~a~%")
29 (stanza . "<p>~% ~a</p>~%")) 32 (stanza . "<p>~% ~a</p>~%"))
30 (verb (line . "~a~%") 33 (verb (line . "~a~%")
@@ -43,7 +46,7 @@
43 (hdr3 (line . "~a") 46 (hdr3 (line . "~a")
44 (stanza . "<h3>~a</h3>~%")))) 47 (stanza . "<h3>~a</h3>~%"))))
45 48
46(filters 49(set-filters!
47 `((verb (line . ,identity) 50 `((verb (line . ,identity)
48 (stanza . ,join-lines)) 51 (stanza . ,join-lines))
49 (link (line . ,(lambda (ln) 52 (link (line . ,(lambda (ln)
diff --git a/lib/read.scm b/lib/read.scm index 1b611bb..f84b3a5 100644 --- a/lib/read.scm +++ b/lib/read.scm
@@ -26,7 +26,7 @@
26 (def (cdr (assoc 'default line-types)))) 26 (def (cdr (assoc 'default line-types))))
27 (cond 27 (cond
28 ((null? lin) def) ; empty line 28 ((null? lin) def) ; empty line
29 ((assoc (car lin) line-types) => cdr) ; a line type exists 29 ((assoc (car lin) line-types) => cdr) ; a known line type
30 (else def)))) ; otherwise ... 30 (else def)))) ; otherwise ...
31 31
32(define (parse-lines lines doc) 32(define (parse-lines lines doc)
@@ -48,10 +48,8 @@
48 ;;;; FIXME: I think this necessitates a special emit-verbatim 48 ;;;; FIXME: I think this necessitates a special emit-verbatim
49 ;;;; function. 49 ;;;; function.
50 (parse-verbatim (cdr lines) doc '() 50 (parse-verbatim (cdr lines) doc '()
51 #; (if (< 1 (length words)) 51 ;;; FIXME
52 (cons 'verb (cdr words)) 52 (cons 'verb (cdr words))))
53 'verb)
54 'verb))
55 (else ; another line type 53 (else ; another line type
56 (apply parse-stanza lines doc '() (line-type words))))))) 54 (apply parse-stanza lines doc '() (line-type words)))))))
57 55
@@ -67,7 +65,7 @@
67 65
68(define (parse-stanza lines doc stanza st-type 66(define (parse-stanza lines doc stanza st-type
69 #!optional (st-inlines '()) (st-words cdr)) 67 #!optional (st-inlines '()) (st-words cdr))
70 (define (close-stanza) (cons (cons st-type (reverse stanza)) doc)) 68 (define (close-stanza) (cons (cons (list st-type) (reverse stanza)) doc))
71 (if (null? lines) ; end of document 69 (if (null? lines) ; end of document
72 (parse-lines lines (close-stanza)) 70 (parse-lines lines (close-stanza))
73 (let* ((ln (car lines)) 71 (let* ((ln (car lines))
diff --git a/lib/util.scm b/lib/util.scm index c71c600..f42878b 100644 --- a/lib/util.scm +++ b/lib/util.scm
@@ -2,8 +2,12 @@
2 2
3 (import scheme (chicken base) 3 (import scheme (chicken base)
4 (chicken condition) 4 (chicken condition)
5 (only (chicken irregex) irregex-replace/all) 5 (chicken file)
6 (chicken string)) 6 (chicken irregex)
7 (chicken process-context)
8 (chicken string)
9 (srfi 1)
10 utf8-srfi-13)
7 11
8 (define-syntax define-public 12 (define-syntax define-public
9 (syntax-rules () 13 (syntax-rules ()
@@ -34,9 +38,6 @@
34 ((list? (cdr kv)) 38 ((list? (cdr kv))
35 (apply alist-walk (cdr kv) (cdr keys))))))) 39 (apply alist-walk (cdr kv) (cdr keys)))))))
36 40
37 (define (string-join ss #!optional (sep " "))
38 (string-intersperse ss sep))
39
40 (define (flush-lines-left lines) 41 (define (flush-lines-left lines)
41 (irregex-replace/all '(: bol (* space)) 42 (irregex-replace/all '(: bol (* space))
42 (string-join lines) "")) 43 (string-join lines) ""))
@@ -44,6 +45,24 @@
44 (define (join-lines lines) 45 (define (join-lines lines)
45 (apply string-append lines)) 46 (apply string-append lines))
46 47
48 (define (find-command command . dirs)
49 (define (find-command-in-dir dir)
50 (and (directory-exists? dir)
51 (find-files dir
52 limit: 0
53 test: `(: (* any) "/" ,command eos))))
54 (define path+
55 (append (string-split (get-environment-variable "PATH") ":") dirs))
56 (define found
57 (filter file-executable?
58 (apply append (filter-map find-command-in-dir path+))))
59 (if (pair? found) (car found) #f))
60
61 (define (ensure-newline str)
62 (if (string-suffix? "\n" str)
63 str
64 (string-append str "\n")))
65
47 ) 66 )
48 67
49 68
diff --git a/repl.scm b/repl.scm index 28c66b7..eb1c010 100644 --- a/repl.scm +++ b/repl.scm
@@ -2,8 +2,6 @@
2 2
3(import (chicken file)) 3(import (chicken file))
4 4
5(for-each load (glob "build/*"))
6
7(import (jimmy util) 5(import (jimmy util)
8 (jimmy emit) 6 (jimmy emit)
9 (jimmy read) 7 (jimmy read)
diff --git a/tests/run.scm b/tests/run.scm index 49da815..cd5acba 100644 --- a/tests/run.scm +++ b/tests/run.scm
@@ -21,29 +21,29 @@
21 21
22(define test-file (make-pathname (list test-dir) "test" "gmi")) 22(define test-file (make-pathname (list test-dir) "test" "gmi"))
23(define expected-doc 23(define expected-doc
24 '((meta ("title" "a" "test" "document") 24 '(((meta) ("title" "a" "test" "document")
25 ("date" "2024-05-13T03:02:45Z") 25 ("date" "2024-05-13T03:02:45Z")
26 ("uuid" "b3daebf1-440b-4828-a4d9-9089c7bd7c61")) 26 ("uuid" "b3daebf1-440b-4828-a4d9-9089c7bd7c61"))
27 (hdr1 ("a" "test" "document" "of" "some" "kind")) 27 ((hdr1) ("a" "test" "document" "of" "some" "kind"))
28 (para ("here" "is" "a" "test" "document.") 28 ((para) ("here" "is" "a" "test" "document.")
29 ("it" "has" "paragraphs") 29 ("it" "has" "paragraphs")
30 (link "example.com" "with" "links!") 30 (link "example.com" "with" "links!")
31 ("and" "other" "things.")) 31 ("and" "other" "things."))
32 (hdr2 ("a" "code" "example")) 32 ((hdr2) ("a" "code" "example"))
33 (verb ("for (a=1;a<=4;a++) {") ("\tprintf(\"%d\\n\", a);") ("}")) 33 ((verb) ("for (a=1;a<=4;a++) {") ("\tprintf(\"%d\\n\", a);") ("}"))
34 (hdr3 ("other" "examples")) 34 ((hdr3) ("other" "examples"))
35 (quot ("a" "blockquote" "is" "a" "quote") ("that" "is" "blocky.")) 35 ((quot) ("a" "blockquote" "is" "a" "quote") ("that" "is" "blocky."))
36 (list ("list" "1") ("list" "2") ("list" "3")) 36 ((list) ("list" "1") ("list" "2") ("list" "3"))
37 (link ("example.com" "link" "list" "1") 37 ((link) ("example.com" "link" "list" "1")
38 ("example.com" "link" "list" "2") 38 ("example.com" "link" "list" "2")
39 ("example.com" "link" "list" "3")) 39 ("example.com" "link" "list" "3"))
40 (para ("ok," "now" "for" "another" "test:") 40 ((para) ("ok," "now" "for" "another" "test:")
41 ("will" "*strong*" "in-line" "text" "be" "converted?") 41 ("will" "*strong*" "in-line" "text" "be" "converted?")
42 ("as" "well" "as" "`code`," "_emph_" "and" "such?") 42 ("as" "well" "as" "`code`," "_emph_" "and" "such?")
43 ("what" "if" "*i" "_nest_" "them*") 43 ("what" "if" "*i" "_nest_" "them*")
44 ("what" "if" "*i" "_nest" "them*" "wrong_" "?") 44 ("what" "if" "*i" "_nest" "them*" "wrong_" "?")
45 ("what" "about" "*breaking" "them") 45 ("what" "about" "*breaking" "them")
46 ("over" "two" "lines?*")))) 46 ("over" "two" "lines?*"))))
47(define actual-doc (with-input-from-file test-file parse)) 47(define actual-doc (with-input-from-file test-file parse))
48 48
49(test "read" expected-doc actual-doc) 49(test "read" expected-doc actual-doc)