(texinfo docbook): informaltable is a block element.
[bpt/guile.git] / module / texinfo.scm
CommitLineData
47f3ce52
AW
1;;;; (texinfo) -- parsing of texinfo into SXML
2;;;;
be52f329 3;;;; Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
47f3ce52
AW
4;;;; Copyright (C) 2004, 2009 Andy Wingo <wingo at pobox dot com>
5;;;; Copyright (C) 2001,2002 Oleg Kiselyov <oleg at pobox dot com>
6;;;;
7;;;; This file is based on SSAX's SSAX.scm.
8;;;;
9;;;; This library is free software; you can redistribute it and/or
10;;;; modify it under the terms of the GNU Lesser General Public
11;;;; License as published by the Free Software Foundation; either
12;;;; version 3 of the License, or (at your option) any later version.
13;;;;
14;;;; This library is distributed in the hope that it will be useful,
15;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17;;;; Lesser General Public License for more details.
18;;;;
19;;;; You should have received a copy of the GNU Lesser General Public
20;;;; License along with this library; if not, write to the Free Software
21;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22\f
23;;; Commentary:
24;;
25;; @subheading Texinfo processing in scheme
26;;
27;; This module parses texinfo into SXML. TeX will always be the
28;; processor of choice for print output, of course. However, although
29;; @code{makeinfo} works well for info, its output in other formats is
30;; not very customizable, and the program is not extensible as a whole.
31;; This module aims to provide an extensible framework for texinfo
32;; processing that integrates texinfo into the constellation of SXML
33;; processing tools.
34;;
35;; @subheading Notes on the SXML vocabulary
36;;
37;; Consider the following texinfo fragment:
38;;
39;;@example
40;; @@deffn Primitive set-car! pair value
41;; This function...
42;; @@end deffn
43;;@end example
44;;
45;; Logically, the category (Primitive), name (set-car!), and arguments
46;; (pair value) are ``attributes'' of the deffn, with the description as
47;; the content. However, texinfo allows for @@-commands within the
48;; arguments to an environment, like @code{@@deffn}, which means that
49;; texinfo ``attributes'' are PCDATA. XML attributes, on the other hand,
50;; are CDATA. For this reason, ``attributes'' of texinfo @@-commands are
51;; called ``arguments'', and are grouped under the special element, `%'.
52;;
53;; Because `%' is not a valid NCName, stexinfo is a superset of SXML. In
54;; the interests of interoperability, this module provides a conversion
55;; function to replace the `%' with `texinfo-arguments'.
56;;
57;;; Code:
58
59;; Comparison to xml output of texinfo (which is rather undocumented):
60;; Doesn't conform to texinfo dtd
61;; No DTD at all, in fact :-/
62;; Actually outputs valid xml, after transforming %
63;; Slower (although with caching the SXML that problem can go away)
64;; Doesn't parse menus (although menus are shite)
65;; Args go in a dedicated element, FBOFW
66;; Definitions are handled a lot better
67;; Does parse comments
68;; Outputs only significant line breaks (a biggie!)
69;; Nodes are treated as anchors, rather than content organizers (a biggie)
70;; (more book-like, less info-like)
71
72;; TODO
73;; Integration: help, indexing, plain text
74
75(define-module (texinfo)
76 #:use-module (sxml simple)
77 #:use-module (sxml transform)
78 #:use-module (sxml ssax input-parse)
79 #:use-module (srfi srfi-1)
0c65f52c 80 #:use-module (srfi srfi-11)
47f3ce52
AW
81 #:use-module (srfi srfi-13)
82 #:export (call-with-file-and-dir
83 texi-command-specs
84 texi-command-depth
85 texi-fragment->stexi
86 texi->stexi
87 stexi->sxml))
88
89;; Some utilities
90
91(define (parser-error port message . rest)
05c29c5a 92 (apply throw 'parser-error port message rest))
47f3ce52
AW
93
94(define (call-with-file-and-dir filename proc)
95 "Call the one-argument procedure @var{proc} with an input port that
96reads from @var{filename}. During the dynamic extent of @var{proc}'s
97execution, the current directory will be @code{(dirname
98@var{filename})}. This is useful for parsing documents that can include
99files by relative path name."
100 (let ((current-dir (getcwd)))
101 (dynamic-wind
102 (lambda () (chdir (dirname filename)))
103 (lambda ()
104 (call-with-input-file (basename filename) proc))
105 (lambda () (chdir current-dir)))))
106
47f3ce52
AW
107;;========================================================================
108;; Reflection on the XML vocabulary
109
110(define texi-command-specs
111 #;
112"A list of (@var{name} @var{content-model} . @var{args})
113
114@table @var
115@item name
116The name of an @@-command, as a symbol.
117
118@item content-model
119A symbol indicating the syntactic type of the @@-command:
120@table @code
121@item EMPTY-COMMAND
122No content, and no @code{@@end} is coming
123@item EOL-ARGS
124Unparsed arguments until end of line
125@item EOL-TEXT
126Parsed arguments until end of line
127@item INLINE-ARGS
128Unparsed arguments ending with @code{#\\@}}
129@item INLINE-TEXT
130Parsed arguments ending with @code{#\\@}}
be52f329
AW
131@item INLINE-TEXT-ARGS
132Parsed arguments ending with @code{#\\@}}
47f3ce52
AW
133@item ENVIRON
134The tag is an environment tag, expect @code{@@end foo}.
135@item TABLE-ENVIRON
136Like ENVIRON, but with special parsing rules for its arguments.
137@item FRAGMENT
138For @code{*fragment*}, the command used for parsing fragments of
139texinfo documents.
140@end table
141
142@code{INLINE-TEXT} commands will receive their arguments within their
143bodies, whereas the @code{-ARGS} commands will receive them in their
144attribute list.
145
146@code{EOF-TEXT} receives its arguments in its body.
147
148@code{ENVIRON} commands have both: parsed arguments until the end of
149line, received through their attribute list, and parsed text until the
150@code{@@end}, received in their bodies.
151
152@code{EOF-TEXT-ARGS} receives its arguments in its attribute list, as in
153@code{ENVIRON}.
154
31d59769
AW
155In addition, @code{ALIAS} can alias one command to another. The alias
156will never be seen in parsed stexinfo.
157
47f3ce52
AW
158There are four @@-commands that are treated specially. @code{@@include}
159is a low-level token that will not be seen by higher-level parsers, so
160it has no content-model. @code{@@para} is the paragraph command, which
161is only implicit in the texinfo source. @code{@@item} has special
162syntax, as noted above, and @code{@@entry} is how this parser treats
163@code{@@item} commands within @code{@@table}, @code{@@ftable}, and
164@code{@@vtable}.
165
166Also, indexing commands (@code{@@cindex}, etc.) are treated specially.
167Their arguments are parsed, but they are needed before entering the
168element so that an anchor can be inserted into the text before the index
169entry.
170
171@item args
172Named arguments to the command, in the same format as the formals for a
173lambda. Only present for @code{INLINE-ARGS}, @code{EOL-ARGS},
be52f329 174@code{INLINE-TEXT-ARGS}, @code{ENVIRON}, @code{TABLE-ENVIRON} commands.
47f3ce52
AW
175@end table"
176 '(;; Special commands
177 (include #f) ;; this is a low-level token
178 (para PARAGRAPH)
179 (item ITEM)
180 (entry ENTRY . heading)
181 (noindent EMPTY-COMMAND)
182 (*fragment* FRAGMENT)
183
184 ;; Inline text commands
185 (*braces* INLINE-TEXT) ;; FIXME: make me irrelevant
186 (bold INLINE-TEXT)
187 (sample INLINE-TEXT)
188 (samp INLINE-TEXT)
189 (code INLINE-TEXT)
190 (kbd INLINE-TEXT)
191 (key INLINE-TEXT)
192 (var INLINE-TEXT)
193 (env INLINE-TEXT)
194 (file INLINE-TEXT)
195 (command INLINE-TEXT)
196 (option INLINE-TEXT)
197 (dfn INLINE-TEXT)
198 (cite INLINE-TEXT)
199 (acro INLINE-TEXT)
47f3ce52
AW
200 (email INLINE-TEXT)
201 (emph INLINE-TEXT)
202 (strong INLINE-TEXT)
203 (sample INLINE-TEXT)
204 (sc INLINE-TEXT)
205 (titlefont INLINE-TEXT)
206 (asis INLINE-TEXT)
207 (b INLINE-TEXT)
208 (i INLINE-TEXT)
209 (r INLINE-TEXT)
210 (sansserif INLINE-TEXT)
211 (slanted INLINE-TEXT)
212 (t INLINE-TEXT)
213
214 ;; Inline args commands
215 (value INLINE-ARGS . (key))
216 (ref INLINE-ARGS . (node #:opt name section info-file manual))
217 (xref INLINE-ARGS . (node #:opt name section info-file manual))
218 (pxref INLINE-ARGS . (node #:opt name section info-file manual))
31d59769 219 (url ALIAS . uref)
47f3ce52
AW
220 (uref INLINE-ARGS . (url #:opt title replacement))
221 (anchor INLINE-ARGS . (name))
222 (dots INLINE-ARGS . ())
223 (result INLINE-ARGS . ())
224 (bullet INLINE-ARGS . ())
225 (copyright INLINE-ARGS . ())
226 (tie INLINE-ARGS . ())
227 (image INLINE-ARGS . (file #:opt width height alt-text extension))
228
be52f329
AW
229 ;; Inline parsed args commands
230 (acronym INLINE-TEXT-ARGS . (acronym #:opt meaning))
231
47f3ce52
AW
232 ;; EOL args elements
233 (node EOL-ARGS . (name #:opt next previous up))
234 (c EOL-ARGS . all)
235 (comment EOL-ARGS . all)
236 (setchapternewpage EOL-ARGS . all)
237 (sp EOL-ARGS . all)
238 (page EOL-ARGS . ())
239 (vskip EOL-ARGS . all)
240 (syncodeindex EOL-ARGS . all)
241 (contents EOL-ARGS . ())
242 (shortcontents EOL-ARGS . ())
243 (summarycontents EOL-ARGS . ())
244 (insertcopying EOL-ARGS . ())
245 (dircategory EOL-ARGS . (category))
246 (top EOL-ARGS . (title))
247 (printindex EOL-ARGS . (type))
406524ea 248 (paragraphindent EOL-ARGS . (indent))
47f3ce52
AW
249
250 ;; EOL text commands
251 (*ENVIRON-ARGS* EOL-TEXT)
252 (itemx EOL-TEXT)
253 (set EOL-TEXT)
254 (center EOL-TEXT)
255 (title EOL-TEXT)
256 (subtitle EOL-TEXT)
257 (author EOL-TEXT)
258 (chapter EOL-TEXT)
259 (section EOL-TEXT)
260 (appendix EOL-TEXT)
261 (appendixsec EOL-TEXT)
262 (unnumbered EOL-TEXT)
263 (unnumberedsec EOL-TEXT)
264 (subsection EOL-TEXT)
265 (subsubsection EOL-TEXT)
266 (appendixsubsec EOL-TEXT)
267 (appendixsubsubsec EOL-TEXT)
268 (unnumberedsubsec EOL-TEXT)
269 (unnumberedsubsubsec EOL-TEXT)
270 (chapheading EOL-TEXT)
271 (majorheading EOL-TEXT)
272 (heading EOL-TEXT)
273 (subheading EOL-TEXT)
274 (subsubheading EOL-TEXT)
275
276 (deftpx EOL-TEXT-ARGS . (category name . attributes))
277 (defcvx EOL-TEXT-ARGS . (category class name))
278 (defivarx EOL-TEXT-ARGS . (class name))
279 (deftypeivarx EOL-TEXT-ARGS . (class data-type name))
280 (defopx EOL-TEXT-ARGS . (category class name . arguments))
281 (deftypeopx EOL-TEXT-ARGS . (category class data-type name . arguments))
282 (defmethodx EOL-TEXT-ARGS . (class name . arguments))
283 (deftypemethodx EOL-TEXT-ARGS . (class data-type name . arguments))
284 (defoptx EOL-TEXT-ARGS . (name))
285 (defvrx EOL-TEXT-ARGS . (category name))
286 (defvarx EOL-TEXT-ARGS . (name))
287 (deftypevrx EOL-TEXT-ARGS . (category data-type name))
288 (deftypevarx EOL-TEXT-ARGS . (data-type name))
289 (deffnx EOL-TEXT-ARGS . (category name . arguments))
290 (deftypefnx EOL-TEXT-ARGS . (category data-type name . arguments))
291 (defspecx EOL-TEXT-ARGS . (name . arguments))
292 (defmacx EOL-TEXT-ARGS . (name . arguments))
293 (defunx EOL-TEXT-ARGS . (name . arguments))
294 (deftypefunx EOL-TEXT-ARGS . (data-type name . arguments))
295
296 ;; Indexing commands
297 (cindex INDEX . entry)
298 (findex INDEX . entry)
299 (vindex INDEX . entry)
300 (kindex INDEX . entry)
301 (pindex INDEX . entry)
302 (tindex INDEX . entry)
303
304 ;; Environment commands (those that need @end)
305 (texinfo ENVIRON . title)
306 (ignore ENVIRON . ())
307 (ifinfo ENVIRON . ())
308 (iftex ENVIRON . ())
309 (ifhtml ENVIRON . ())
310 (ifxml ENVIRON . ())
311 (ifplaintext ENVIRON . ())
312 (ifnotinfo ENVIRON . ())
313 (ifnottex ENVIRON . ())
314 (ifnothtml ENVIRON . ())
315 (ifnotxml ENVIRON . ())
316 (ifnotplaintext ENVIRON . ())
317 (titlepage ENVIRON . ())
318 (menu ENVIRON . ())
319 (direntry ENVIRON . ())
320 (copying ENVIRON . ())
321 (example ENVIRON . ())
322 (smallexample ENVIRON . ())
323 (display ENVIRON . ())
324 (smalldisplay ENVIRON . ())
325 (verbatim ENVIRON . ())
326 (format ENVIRON . ())
327 (smallformat ENVIRON . ())
328 (lisp ENVIRON . ())
329 (smalllisp ENVIRON . ())
330 (cartouche ENVIRON . ())
331 (quotation ENVIRON . ())
332
333 (deftp ENVIRON . (category name . attributes))
334 (defcv ENVIRON . (category class name))
335 (defivar ENVIRON . (class name))
336 (deftypeivar ENVIRON . (class data-type name))
337 (defop ENVIRON . (category class name . arguments))
338 (deftypeop ENVIRON . (category class data-type name . arguments))
339 (defmethod ENVIRON . (class name . arguments))
340 (deftypemethod ENVIRON . (class data-type name . arguments))
341 (defopt ENVIRON . (name))
342 (defvr ENVIRON . (category name))
343 (defvar ENVIRON . (name))
344 (deftypevr ENVIRON . (category data-type name))
345 (deftypevar ENVIRON . (data-type name))
346 (deffn ENVIRON . (category name . arguments))
347 (deftypefn ENVIRON . (category data-type name . arguments))
348 (defspec ENVIRON . (name . arguments))
349 (defmac ENVIRON . (name . arguments))
350 (defun ENVIRON . (name . arguments))
351 (deftypefun ENVIRON . (data-type name . arguments))
352
353 (table TABLE-ENVIRON . (formatter))
354 (itemize TABLE-ENVIRON . (formatter))
355 (enumerate TABLE-ENVIRON . (start))
356 (ftable TABLE-ENVIRON . (formatter))
357 (vtable TABLE-ENVIRON . (formatter))))
358
359(define command-depths
360 '((chapter . 1) (section . 2) (subsection . 3) (subsubsection . 4)
361 (top . 0) (unnumbered . 1) (unnumberedsec . 2)
362 (unnumberedsubsec . 3) (unnumberedsubsubsec . 4)
363 (appendix . 1) (appendixsec . 2) (appendixsection . 2)
364 (appendixsubsec . 3) (appendixsubsubsec . 4)))
365(define (texi-command-depth command max-depth)
366 "Given the texinfo command @var{command}, return its nesting level, or
367@code{#f} if it nests too deep for @var{max-depth}.
368
369Examples:
370@example
05c29c5a
AW
371 (texi-command-depth 'chapter 4) @result{} 1
372 (texi-command-depth 'top 4) @result{} 0
373 (texi-command-depth 'subsection 4) @result{} 3
374 (texi-command-depth 'appendixsubsec 4) @result{} 3
375 (texi-command-depth 'subsection 2) @result{} #f
47f3ce52
AW
376@end example"
377 (let ((depth (and=> (assq command command-depths) cdr)))
378 (and depth (<= depth max-depth) depth)))
379
380;; The % is for arguments
381(define (space-significant? command)
382 (memq command
383 '(example smallexample verbatim lisp smalllisp menu %)))
384
385;; Like a DTD for texinfo
386(define (command-spec command)
dc7a9cef
AW
387 (let ((spec (assq command texi-command-specs)))
388 (cond
389 ((not spec)
390 (parser-error #f "Unknown command" command))
391 ((eq? (cadr spec) 'ALIAS)
392 (command-spec (cddr spec)))
393 (else
394 spec))))
47f3ce52
AW
395
396(define (inline-content? content)
be52f329
AW
397 (case content
398 ((INLINE-TEXT INLINE-ARGS INLINE-TEXT-ARGS) #t)
399 (else #f)))
47f3ce52
AW
400
401
402;;========================================================================
403;; Lower-level parsers and scanners
404;;
405;; They deal with primitive lexical units (Names, whitespaces, tags) and
406;; with pieces of more generic productions. Most of these parsers must
407;; be called in appropriate context. For example, complete-start-command
408;; must be called only when the @-command start has been detected and
409;; its name token has been read.
410
411;; Test if a string is made of only whitespace
412;; An empty string is considered made of whitespace as well
413(define (string-whitespace? str)
414 (or (string-null? str)
415 (string-every char-whitespace? str)))
416
417;; Like read-text-line, but allows EOF.
418(define read-eof-breaks '(*eof* #\return #\newline))
419(define (read-eof-line port)
420 (if (eof-object? (peek-char port))
421 (peek-char port)
422 (let* ((line (next-token '() read-eof-breaks
423 "reading a line" port))
424 (c (read-char port))) ; must be either \n or \r or EOF
425 (if (and (eq? c #\return) (eq? (peek-char port) #\newline))
426 (read-char port)) ; skip \n that follows \r
427 line)))
428
47f3ce52
AW
429(define (skip-whitespace port)
430 (skip-while '(#\space #\tab #\return #\newline) port))
431
432(define (skip-horizontal-whitespace port)
433 (skip-while '(#\space #\tab) port))
434
435;; command ::= Letter+
436
437;; procedure: read-command PORT
438;;
439;; Read a command starting from the current position in the PORT and
440;; return it as a symbol.
441(define (read-command port)
442 (let ((first-char (peek-char port)))
443 (or (char-alphabetic? first-char)
444 (parser-error port "Nonalphabetic @-command char: '" first-char "'")))
445 (string->symbol
446 (next-token-of
447 (lambda (c)
448 (cond
449 ((eof-object? c) #f)
450 ((char-alphabetic? c) c)
451 (else #f)))
452 port)))
453
454;; A token is a primitive lexical unit. It is a record with two fields,
455;; token-head and token-kind.
456;;
457;; Token types:
458;; END The end of a texinfo command. If the command is ended by },
459;; token-head will be #f. Otherwise if the command is ended by
460;; @end COMMAND, token-head will be COMMAND. As a special case,
461;; @bye is the end of a special @texinfo command.
462;; START The start of a texinfo command. The token-head will be a
463;; symbol of the @-command name.
464;; INCLUDE An @include directive. The token-head will be empty -- the
465;; caller is responsible for reading the include file name.
466;; ITEM @item commands have an irregular syntax. They end at the
467;; next @item, or at the end of the environment. For that
468;; read-command-token treats them specially.
469
470(define (make-token kind head) (cons kind head))
471(define token? pair?)
472(define token-kind car)
473(define token-head cdr)
474
475;; procedure: read-command-token PORT
476;;
477;; This procedure starts parsing of a command token. The current
478;; position in the stream must be #\@. This procedure scans enough of
479;; the input stream to figure out what kind of a command token it is
480;; seeing. The procedure returns a token structure describing the token.
481
482(define (read-command-token port)
483 (assert-curr-char '(#\@) "start of the command" port)
484 (let ((peeked (peek-char port)))
485 (cond
486 ((memq peeked '(#\! #\. #\? #\@ #\\ #\{ #\}))
487 ;; @-commands that escape characters
488 (make-token 'STRING (string (read-char port))))
489 (else
490 (let ((name (read-command port)))
491 (case name
492 ((end)
493 ;; got an ending tag
494 (let ((command (string-trim-both
495 (read-eof-line port))))
496 (or (and (not (string-null? command))
497 (string-every char-alphabetic? command))
498 (parser-error port "malformed @end" command))
499 (make-token 'END (string->symbol command))))
500 ((bye)
501 ;; the end of the top
502 (make-token 'END 'texinfo))
503 ((item)
504 (make-token 'ITEM 'item))
505 ((include)
506 (make-token 'INCLUDE #f))
507 (else
508 (make-token 'START name))))))))
509
510;; procedure+: read-verbatim-body PORT STR-HANDLER SEED
511;;
512;; This procedure must be called after we have read a string
513;; "@verbatim\n" that begins a verbatim section. The current position
514;; must be the first position of the verbatim body. This function reads
515;; _lines_ of the verbatim body and passes them to a STR-HANDLER, a
516;; character data consumer.
517;;
518;; The str-handler is a STR-HANDLER, a procedure STRING1 STRING2 SEED.
519;; The first STRING1 argument to STR-HANDLER never contains a newline.
520;; The second STRING2 argument often will. On the first invocation of the
521;; STR-HANDLER, the seed is the one passed to read-verbatim-body
522;; as the third argument. The result of this first invocation will be
523;; passed as the seed argument to the second invocation of the line
524;; consumer, and so on. The result of the last invocation of the
525;; STR-HANDLER is returned by the read-verbatim-body. Note a
526;; similarity to the fundamental 'fold' iterator.
527;;
528;; Within a verbatim section all characters are taken at their face
529;; value. It ends with "\n@end verbatim(\r)?\n".
530
531;; Must be called right after the newline after @verbatim.
532(define (read-verbatim-body port str-handler seed)
533 (let loop ((seed seed))
534 (let ((fragment (next-token '() '(#\newline)
535 "reading verbatim" port)))
536 ;; We're reading the char after the 'fragment', which is
537 ;; #\newline.
538 (read-char port)
539 (if (string=? fragment "@end verbatim")
540 seed
541 (loop (str-handler fragment "\n" seed))))))
542
543;; procedure+: read-arguments PORT
544;;
545;; This procedure reads and parses a production ArgumentList.
546;; ArgumentList ::= S* Argument (S* , S* Argument)* S*
547;; Argument ::= ([^@{},])*
548;;
549;; Arguments are the things in braces, i.e @ref{my node} has one
550;; argument, "my node". Most commands taking braces actually don't have
551;; arguments, they process text. For example, in
552;; @emph{@strong{emphasized}}, the emph takes text, because the parse
553;; continues into the braces.
554;;
555;; Any whitespace within Argument is replaced with a single space.
556;; Whitespace around an Argument is trimmed.
557;;
558;; The procedure returns a list of arguments. Afterwards the current
559;; character will be after the final #\}.
560
561(define (read-arguments port stop-char)
562 (define (split str)
563 (read-char port) ;; eat the delimiter
564 (let ((ret (map (lambda (x) (if (string-null? x) #f x))
565 (map string-trim-both (string-split str #\,)))))
566 (if (and (pair? ret) (eq? (car ret) #f) (null? (cdr ret)))
567 '()
568 ret)))
569 (split (next-token '() (list stop-char)
570 "arguments of @-command" port)))
571
572;; procedure+: complete-start-command COMMAND PORT
573;;
574;; This procedure is to complete parsing of an @-command. The procedure
575;; must be called after the command token has been read. COMMAND is a
576;; TAG-NAME.
577;;
578;; This procedure returns several values:
579;; COMMAND: a symbol.
580;; ARGUMENTS: command's arguments, as an alist.
581;; CONTENT-MODEL: the content model of the command.
582;;
583;; On exit, the current position in PORT will depend on the CONTENT-MODEL.
584;;
585;; Content model Port position
586;; ============= =============
587;; INLINE-TEXT One character after the #\{.
be52f329 588;; INLINE-TEXT-ARGS One character after the #\{.
47f3ce52
AW
589;; INLINE-ARGS The first character after the #\}.
590;; EOL-TEXT The first non-whitespace character after the command.
591;; ENVIRON, TABLE-ENVIRON, EOL-ARGS, EOL-TEXT
592;; The first character on the next line.
593;; PARAGRAPH, ITEM, EMPTY-COMMAND
594;; The first character after the command.
595
596(define (arguments->attlist port args arg-names)
597 (let loop ((in args) (names arg-names) (opt? #f) (out '()))
598 (cond
599 ((symbol? names) ;; a rest arg
600 (reverse (if (null? in) out (acons names in out))))
601 ((and (not (null? names)) (eq? (car names) #:opt))
602 (loop in (cdr names) #t out))
603 ((null? in)
604 (if (or (null? names) opt?)
605 (reverse out)
606 (parser-error port "@-command expected more arguments:"
607 args arg-names names)))
608 ((null? names)
609 (parser-error port "@-command didn't expect more arguments:" in))
610 ((not (car in))
611 (or (and opt? (loop (cdr in) (cdr names) opt? out))
612 (parser-error "@-command missing required argument"
613 (car names))))
614 (else
615 (loop (cdr in) (cdr names) opt?
be52f329
AW
616 (acons (car names)
617 (if (list? (car in)) (car in) (list (car in)))
618 out))))))
47f3ce52
AW
619
620(define (parse-table-args command port)
621 (let* ((line (string-trim-both (read-text-line port)))
622 (length (string-length line)))
623 (define (get-formatter)
624 (or (and (not (zero? length))
625 (eq? (string-ref line 0) #\@)
626 (let ((f (string->symbol (substring line 1))))
627 (or (inline-content? (cadr (command-spec f)))
628 (parser-error
629 port "@item formatter must be INLINE" f))
630 f))
05c29c5a 631 (parser-error port "Invalid @item formatter" line)))
47f3ce52
AW
632 (case command
633 ((enumerate)
634 (if (zero? length)
635 '()
636 `((start
637 ,(if (or (and (eq? length 1)
638 (char-alphabetic? (string-ref line 0)))
639 (string-every char-numeric? line))
640 line
641 (parser-error
642 port "Invalid enumerate start" line))))))
643 ((itemize)
644 `((bullet
645 ,(or (and (eq? length 1) line)
646 (and (string-null? line) '(bullet))
647 (list (get-formatter))))))
648 (else ;; tables of various varieties
649 `((formatter (,(get-formatter))))))))
650
651(define (complete-start-command command port)
652 (define (get-arguments type arg-names stop-char)
653 (arguments->attlist port (read-arguments port stop-char) arg-names))
654
655 (let* ((spec (command-spec command))
dc7a9cef 656 (command (car spec))
47f3ce52
AW
657 (type (cadr spec))
658 (arg-names (cddr spec)))
659 (case type
660 ((INLINE-TEXT)
661 (assert-curr-char '(#\{) "Inline element lacks {" port)
662 (values command '() type))
663 ((INLINE-ARGS)
664 (assert-curr-char '(#\{) "Inline element lacks {" port)
665 (values command (get-arguments type arg-names #\}) type))
be52f329
AW
666 ((INLINE-TEXT-ARGS)
667 (assert-curr-char '(#\{) "Inline element lacks {" port)
668 (values command '() type))
47f3ce52
AW
669 ((EOL-ARGS)
670 (values command (get-arguments type arg-names #\newline) type))
671 ((ENVIRON ENTRY INDEX)
672 (skip-horizontal-whitespace port)
673 (values command (parse-environment-args command port) type))
674 ((TABLE-ENVIRON)
675 (skip-horizontal-whitespace port)
676 (values command (parse-table-args command port) type))
677 ((EOL-TEXT)
678 (skip-horizontal-whitespace port)
679 (values command '() type))
680 ((EOL-TEXT-ARGS)
681 (skip-horizontal-whitespace port)
682 (values command (parse-eol-text-args command port) type))
683 ((PARAGRAPH EMPTY-COMMAND ITEM FRAGMENT)
684 (values command '() type))
685 (else ;; INCLUDE shouldn't get here
686 (parser-error port "can't happen")))))
687
688;;-----------------------------------------------------------------------------
689;; Higher-level parsers and scanners
690;;
691;; They parse productions corresponding entire @-commands.
692
693;; Only reads @settitle, leaves it to the command parser to finish
694;; reading the title.
695(define (take-until-settitle port)
696 (or (find-string-from-port? "\n@settitle " port)
697 (parser-error port "No \\n@settitle found"))
698 (skip-horizontal-whitespace port)
699 (and (eq? (peek-char port) #\newline)
700 (parser-error port "You have a @settitle, but no title")))
701
702;; procedure+: read-char-data PORT EXPECT-EOF? STR-HANDLER SEED
703;;
704;; This procedure is to read the CharData of a texinfo document.
705;;
706;; text ::= (CharData | Command)*
707;;
708;; The procedure reads CharData and stops at @-commands (or
709;; environments). It also stops at an open or close brace.
710;;
711;; port
712;; a PORT to read
713;; expect-eof?
714;; a boolean indicating if EOF is normal, i.e., the character
715;; data may be terminated by the EOF. EOF is normal
716;; while processing the main document.
717;; preserve-ws?
718;; a boolean indicating if we are within a whitespace-preserving
719;; environment. If #t, suppress paragraph detection.
720;; str-handler
721;; a STR-HANDLER, see read-verbatim-body
722;; seed
723;; an argument passed to the first invocation of STR-HANDLER.
724;;
725;; The procedure returns two results: SEED and TOKEN. The SEED is the
726;; result of the last invocation of STR-HANDLER, or the original seed if
727;; STR-HANDLER was never called.
728;;
729;; TOKEN can be either an eof-object (this can happen only if expect-eof?
730;; was #t), or a texinfo token denoting the start or end of a tag.
731
732;; read-char-data port expect-eof? preserve-ws? str-handler seed
733(define read-char-data
734 (let* ((end-chars-eof '(*eof* #\{ #\} #\@ #\newline)))
735 (define (handle str-handler str1 str2 seed)
736 (if (and (string-null? str1) (string-null? str2))
737 seed
738 (str-handler str1 str2 seed)))
739
740 (lambda (port expect-eof? preserve-ws? str-handler seed)
741 (let ((end-chars ((if expect-eof? identity cdr) end-chars-eof)))
742 (let loop ((seed seed))
743 (let* ((fragment (next-token '() end-chars "reading char data" port))
744 (term-char (peek-char port))) ; one of end-chars
745 (cond
746 ((eof-object? term-char) ; only if expect-eof?
747 (values (handle str-handler fragment "" seed) term-char))
748 ((memq term-char '(#\@ #\{ #\}))
749 (values (handle str-handler fragment "" seed)
750 (case term-char
751 ((#\@) (read-command-token port))
752 ((#\{) (make-token 'START '*braces*))
753 ((#\}) (read-char port) (make-token 'END #f)))))
754 ((eq? term-char #\newline)
755 ;; Always significant, unless directly before an end token.
756 (let ((c (peek-next-char port)))
757 (cond
758 ((eof-object? c)
759 (or expect-eof?
760 (parser-error port "EOF while reading char data"))
761 (values (handle str-handler fragment "" seed) c))
762 ((eq? c #\@)
763 (let* ((token (read-command-token port))
764 (end? (eq? (token-kind token) 'END)))
765 (values
766 (handle str-handler fragment (if end? "" " ") seed)
767 token)))
768 ((and (not preserve-ws?) (eq? c #\newline))
769 ;; paragraph-separator ::= #\newline #\newline+
770 (skip-while '(#\newline) port)
771 (skip-horizontal-whitespace port)
772 (values (handle str-handler fragment "" seed)
773 (make-token 'PARA 'para)))
774 (else
775 (loop (handle str-handler fragment
776 (if preserve-ws? "\n" " ") seed)))))))))))))
777
778; procedure+: assert-token TOKEN KIND NAME
779; Make sure that TOKEN is of anticipated KIND and has anticipated NAME
780(define (assert-token token kind name)
781 (or (and (token? token)
782 (eq? kind (token-kind token))
783 (equal? name (token-head token)))
784 (parser-error #f "Expecting @end for " name ", got " token)))
785
786;;========================================================================
787;; Highest-level parsers: Texinfo to SXML
788
789;; These parsers are a set of syntactic forms to instantiate a SSAX
790;; parser. The user tells what to do with the parsed character and
791;; element data. These latter handlers determine if the parsing follows a
792;; SAX or a DOM model.
793
794;; syntax: make-command-parser fdown fup str-handler
795
796;; Create a parser to parse and process one element, including its
797;; character content or children elements. The parser is typically
798;; applied to the root element of a document.
799
800;; fdown
801;; procedure COMMAND ARGUMENTS EXPECTED-CONTENT SEED
802;;
803;; This procedure is to generate the seed to be passed to handlers
804;; that process the content of the element. This is the function
805;; identified as 'fdown' in the denotational semantics of the XML
806;; parser given in the title comments to (sxml ssax).
807;;
808;; fup
809;; procedure COMMAND ARGUMENTS PARENT-SEED SEED
810;;
811;; This procedure is called when parsing of COMMAND is finished.
812;; The SEED is the result from the last content parser (or from
813;; fdown if the element has the empty content). PARENT-SEED is the
814;; same seed as was passed to fdown. The procedure is to generate a
815;; seed that will be the result of the element parser. This is the
816;; function identified as 'fup' in the denotational semantics of
817;; the XML parser given in the title comments to (sxml ssax).
818;;
819;; str-handler
820;; A STR-HANDLER, see read-verbatim-body
821;;
822
823;; The generated parser is a
824;; procedure COMMAND PORT SEED
825;;
826;; The procedure must be called *after* the command token has been read.
827
828(define (read-include-file-name port)
829 (let ((x (string-trim-both (read-eof-line port))))
830 (if (string-null? x)
831 (error "no file listed")
832 x))) ;; fixme: should expand @value{} references
833
834(define (sxml->node-name sxml)
835 "Turn some sxml string into a valid node name."
836 (let loop ((in (string->list (sxml->string sxml))) (out '()))
837 (if (null? in)
838 (apply string (reverse out))
839 (if (memq (car in) '(#\{ #\} #\@ #\,))
840 (loop (cdr in) out)
841 (loop (cdr in) (cons (car in) out))))))
842
843(define (index command arguments fdown fup parent-seed)
844 (case command
845 ((deftp defcv defivar deftypeivar defop deftypeop defmethod
846 deftypemethod defopt defvr defvar deftypevr deftypevar deffn
847 deftypefn defspec defmac defun deftypefun)
848 (let ((args `((name ,(string-append (symbol->string command) "-"
849 (cadr (assq 'name arguments)))))))
850 (fup 'anchor args parent-seed
851 (fdown 'anchor args 'INLINE-ARGS '()))))
852 ((cindex findex vindex kindex pindex tindex)
853 (let ((args `((name ,(string-append (symbol->string command) "-"
854 (sxml->node-name
855 (assq 'entry arguments)))))))
856 (fup 'anchor args parent-seed
857 (fdown 'anchor args 'INLINE-ARGS '()))))
858 (else parent-seed)))
859
860(define (make-command-parser fdown fup str-handler)
861 (lambda (command port seed)
862 (let visit ((command command) (port port) (sig-ws? #f) (parent-seed seed))
863 (let*-values (((command arguments expected-content)
864 (complete-start-command command port)))
865 (let* ((parent-seed (index command arguments fdown fup parent-seed))
866 (seed (fdown command arguments expected-content parent-seed))
867 (eof-closes? (or (memq command '(texinfo para *fragment*))
868 (eq? expected-content 'EOL-TEXT)))
869 (sig-ws? (or sig-ws? (space-significant? command)))
870 (up (lambda (s) (fup command arguments parent-seed s)))
871 (new-para (lambda (s) (fdown 'para '() 'PARAGRAPH s)))
872 (make-end-para (lambda (p) (lambda (s) (fup 'para '() p s)))))
873
874 (define (port-for-content)
875 (if (eq? expected-content 'EOL-TEXT)
876 (call-with-input-string (read-text-line port) identity)
877 port))
878
879 (cond
880 ((memq expected-content '(EMPTY-COMMAND INLINE-ARGS EOL-ARGS INDEX
881 EOL-TEXT-ARGS))
882 ;; empty or finished by complete-start-command
883 (up seed))
884 ((eq? command 'verbatim)
885 (up (read-verbatim-body port str-handler seed)))
886 (else
887 (let loop ((port (port-for-content))
888 (expect-eof? eof-closes?)
889 (end-para identity)
890 (need-break? (and (not sig-ws?)
891 (memq expected-content
892 '(ENVIRON TABLE-ENVIRON
893 ENTRY ITEM FRAGMENT))))
894 (seed seed))
895 (cond
896 ((and need-break? (or sig-ws? (skip-whitespace port))
897 (not (memq (peek-char port) '(#\@ #\})))
898 (not (eof-object? (peek-char port))))
899 ;; Even if we have an @, it might be inline -- check
900 ;; that later
901 (let ((seed (end-para seed)))
902 (loop port expect-eof? (make-end-para seed) #f
903 (new-para seed))))
904 (else
905 (let*-values (((seed token)
906 (read-char-data
907 port expect-eof? sig-ws? str-handler seed)))
908 (cond
909 ((eof-object? token)
910 (case expect-eof?
911 ((include #f) (end-para seed))
912 (else (up (end-para seed)))))
913 (else
914 (case (token-kind token)
915 ((STRING)
916 ;; this is only @-commands that escape
917 ;; characters: @}, @@, @{ -- new para if need-break
918 (let ((seed ((if need-break? end-para identity) seed)))
919 (loop port expect-eof?
920 (if need-break? (make-end-para seed) end-para) #f
921 (str-handler (token-head token) ""
922 ((if need-break? new-para identity)
923 seed)))))
924 ((END)
925 ;; The end will only have a name if it's for an
926 ;; environment
927 (cond
928 ((memq command '(item entry))
929 (let ((spec (command-spec (token-head token))))
930 (or (eq? (cadr spec) 'TABLE-ENVIRON)
931 (parser-error
932 port "@item not ended by @end table/enumerate/itemize"
933 token))))
934 ((eq? expected-content 'ENVIRON)
935 (assert-token token 'END command)))
936 (up (end-para seed)))
937 ((ITEM)
938 (cond
939 ((memq command '(enumerate itemize))
940 (up (visit 'item port sig-ws? (end-para seed))))
941 ((eq? expected-content 'TABLE-ENVIRON)
942 (up (visit 'entry port sig-ws? (end-para seed))))
943 ((memq command '(item entry))
944 (visit command port sig-ws? (up (end-para seed))))
945 (else
946 (parser-error
947 port "@item must be within a table environment"
948 command))))
949 ((PARA)
950 ;; examine valid paragraphs?
951 (loop port expect-eof? end-para (not sig-ws?) seed))
952 ((INCLUDE)
953 ;; Recurse for include files
954 (let ((seed (call-with-file-and-dir
955 (read-include-file-name port)
956 (lambda (port)
957 (loop port 'include end-para
958 need-break? seed)))))
959 (loop port expect-eof? end-para need-break? seed)))
960 ((START) ; Start of an @-command
961 (let* ((head (token-head token))
dc7a9cef
AW
962 (spec (command-spec head))
963 (head (car spec))
964 (type (cadr spec))
47f3ce52
AW
965 (inline? (inline-content? type))
966 (seed ((if (and inline? (not need-break?))
967 identity end-para) seed))
968 (end-para (if inline?
969 (if need-break? (make-end-para seed)
970 end-para)
971 identity))
972 (new-para (if (and inline? need-break?)
973 new-para identity)))
974 (loop port expect-eof? end-para (not inline?)
975 (visit head port sig-ws? (new-para seed)))))
976 (else
977 (parser-error port "Unknown token type" token))))))))))))))))
978
979;; procedure: reverse-collect-str-drop-ws fragments
980;;
981;; Given the list of fragments (some of which are text strings), reverse
982;; the list and concatenate adjacent text strings. We also drop
983;; "unsignificant" whitespace, that is, whitespace in front, behind and
984;; between elements. The whitespace that is included in character data
985;; is not affected.
986(define (reverse-collect-str-drop-ws fragments)
987 (cond
988 ((null? fragments) ; a shortcut
989 '())
990 ((and (string? (car fragments)) ; another shortcut
991 (null? (cdr fragments)) ; remove single ws-only string
992 (string-whitespace? (car fragments)))
993 '())
994 (else
995 (let loop ((fragments fragments) (result '()) (strs '())
996 (all-whitespace? #t))
997 (cond
998 ((null? fragments)
999 (if all-whitespace?
1000 result ; remove leading ws
1001 (cons (apply string-append strs) result)))
1002 ((string? (car fragments))
1003 (loop (cdr fragments) result (cons (car fragments) strs)
1004 (and all-whitespace?
1005 (string-whitespace? (car fragments)))))
1006 (else
1007 (loop (cdr fragments)
1008 (cons
1009 (car fragments)
1010 (cond
1011 ((null? strs) result)
1012 (all-whitespace?
1013 (if (null? result)
1014 result ; remove trailing whitespace
1015 (cons " " result))); replace interstitial ws with
1016 ; one space
1017 (else
1018 (cons (apply string-append strs) result))))
1019 '() #t)))))))
1020
be52f329
AW
1021(define (parse-inline-text-args port spec text)
1022 (let lp ((in text) (cur '()) (out '()))
1023 (cond
1024 ((null? in)
1025 (if (and (pair? cur)
1026 (string? (car cur))
1027 (string-whitespace? (car cur)))
1028 (lp in (cdr cur) out)
1029 (let ((args (reverse (if (null? cur)
1030 out
1031 (cons (reverse cur) out)))))
1032 (arguments->attlist port args (cddr spec)))))
1033 ((pair? (car in))
1034 (lp (cdr in) (cons (car in) cur) out))
1035 ((string-index (car in) #\,)
1036 (let* ((parts (string-split (car in) #\,))
1037 (head (string-trim-right (car parts)))
1038 (rev-tail (reverse (cdr parts)))
1039 (last (string-trim (car rev-tail))))
1040 (lp (cdr in)
1041 (if (string-null? last) cur (cons last cur))
1042 (append (cdr rev-tail)
1043 (cons (reverse (if (string-null? head) cur (cons head cur)))
1044 out)))))
1045 (else
1046 (lp (cdr in)
1047 (cons (if (null? cur) (string-trim (car in)) (car in)) cur)
1048 out)))))
1049
47f3ce52
AW
1050(define (make-dom-parser)
1051 (make-command-parser
1052 (lambda (command args content seed) ; fdown
1053 '())
1054 (lambda (command args parent-seed seed) ; fup
dc7a9cef
AW
1055 (let* ((seed (reverse-collect-str-drop-ws seed))
1056 (spec (command-spec command))
1057 (command (car spec)))
be52f329
AW
1058 (if (eq? (cadr spec) 'INLINE-TEXT-ARGS)
1059 (cons (list command (cons '% (parse-inline-text-args #f spec seed)))
1060 parent-seed)
1061 (acons command
1062 (if (null? args) seed (acons '% args seed))
1063 parent-seed))))
47f3ce52
AW
1064 (lambda (string1 string2 seed) ; str-handler
1065 (if (string-null? string2)
1066 (cons string1 seed)
1067 (cons* string2 string1 seed)))))
1068
1069(define parse-environment-args
1070 (let ((parser (make-dom-parser)))
1071 ;; duplicate arguments->attlist to avoid unnecessary splitting
1072 (lambda (command port)
dc7a9cef
AW
1073 (let* ((args (cdar (parser '*ENVIRON-ARGS* port '())))
1074 (spec (command-spec command))
1075 (command (car spec))
1076 (arg-names (cddr spec)))
47f3ce52
AW
1077 (cond
1078 ((not arg-names)
1079 (if (null? args) '()
1080 (parser-error port "@-command doesn't take args" command)))
1081 ((eq? arg-names #t)
1082 (list (cons 'arguments args)))
1083 (else
1084 (let loop ((args args) (arg-names arg-names) (out '()))
1085 (cond
1086 ((null? arg-names)
1087 (if (null? args) (reverse! out)
1088 (parser-error port "@-command didn't expect more args"
1089 command args)))
1090 ((symbol? arg-names)
1091 (reverse! (acons arg-names args out)))
1092 ((null? args)
1093 (parser-error port "@-command expects more args"
1094 command arg-names))
1095 ((and (string? (car args)) (string-index (car args) #\space))
1096 => (lambda (i)
1097 (let ((rest (substring/shared (car args) (1+ i))))
1098 (if (zero? i)
1099 (loop (cons rest (cdr args)) arg-names out)
1100 (loop (cons rest (cdr args)) (cdr arg-names)
1101 (cons (list (car arg-names)
1102 (substring (car args) 0 i))
1103 out))))))
1104 (else
1105 (loop (cdr args) (cdr arg-names)
1106 (if (and (pair? (car args)) (eq? (caar args) '*braces*))
1107 (acons (car arg-names) (cdar args) out)
1108 (cons (list (car arg-names) (car args)) out))))))))))))
1109
1110(define (parse-eol-text-args command port)
1111 ;; perhaps parse-environment-args should be named more
1112 ;; generically.
1113 (parse-environment-args command port))
1114
1115;; procedure: texi-fragment->stexi STRING
1116;;
1117;; A DOM parser for a texinfo fragment STRING.
1118;;
1119;; The procedure returns an SXML tree headed by the special tag,
1120;; *fragment*.
1121
1122(define (texi-fragment->stexi string-or-port)
1123 "Parse the texinfo commands in @var{string-or-port}, and return the
1124resultant stexi tree. The head of the tree will be the special command,
1125@code{*fragment*}."
1126 (define (parse port)
1127 (postprocess (car ((make-dom-parser) '*fragment* port '()))))
1128 (if (input-port? string-or-port)
1129 (parse string-or-port)
1130 (call-with-input-string string-or-port parse)))
1131
1132;; procedure: texi->stexi PORT
1133;;
1134;; This is an instance of a SSAX parser above that returns an SXML
1135;; representation of the texinfo document ready to be read at PORT.
1136;;
1137;; The procedure returns an SXML tree. The port points to the
1138;; first character after the @bye, or to the end of the file.
1139
1140(define (texi->stexi port)
1141 "Read a full texinfo document from @var{port} and return the parsed
1142stexi tree. The parsing will start at the @code{@@settitle} and end at
1143@code{@@bye} or EOF."
1144 (let ((parser (make-dom-parser)))
1145 (take-until-settitle port)
1146 (postprocess (car (parser 'texinfo port '())))))
1147
1148(define (car-eq? x y) (and (pair? x) (eq? (car x) y)))
1149(define (make-contents tree)
1150 (define (lp in out depth)
1151 (cond
1152 ((null? in) (values in (cons 'enumerate (reverse! out))))
1153 ((and (pair? (cdr in)) (texi-command-depth (caadr in) 4))
1154 => (lambda (new-depth)
1155 (let ((node-name (and (car-eq? (car in) 'node)
1156 (cadr (assq 'name (cdadar in))))))
1157 (cond
1158 ((< new-depth depth)
1159 (values in (cons 'enumerate (reverse! out))))
1160 ((> new-depth depth)
1161 (let ((out-cdr (if (null? out) '() (cdr out)))
1162 (out-car (if (null? out) (list 'item) (car out))))
1163 (let*-values (((new-in new-out) (lp in '() (1+ depth))))
1164 (lp new-in
1165 (cons (append out-car (list new-out)) out-cdr)
1166 depth))))
1167 (else ;; same depth
1168 (lp (cddr in)
1169 (cons
1170 `(item (para
1171 ,@(if node-name
1172 `((ref (% (node ,node-name))))
1173 (cdadr in))))
1174 out)
1175 depth))))))
1176 (else (lp (cdr in) out depth))))
1177 (let*-values (((_ contents) (lp tree '() 1)))
1178 `((chapheading "Table of Contents") ,contents)))
1179
1180(define (trim-whitespace str trim-left? trim-right?)
1181 (let* ((left-space? (and (not trim-left?)
1182 (string-prefix? " " str)))
1183 (right-space? (and (not trim-right?)
1184 (string-suffix? " " str)))
1185 (tail (append! (string-tokenize str)
1186 (if right-space? '("") '()))))
1187 (string-join (if left-space? (cons "" tail) tail))))
1188
1189(define (postprocess tree)
1190 (define (loop in out state first? sig-ws?)
1191 (cond
1192 ((null? in)
1193 (values (reverse! out) state))
1194 ((string? (car in))
1195 (loop (cdr in)
1196 (cons (if sig-ws? (car in)
1197 (trim-whitespace (car in) first? (null? (cdr in))))
1198 out)
1199 state #f sig-ws?))
1200 ((pair? (car in))
1201 (case (caar in)
1202 ((set)
1203 (if (null? (cdar in)) (error "@set missing arguments" in))
1204 (if (string? (cadar in))
1205 (let ((i (string-index (cadar in) #\space)))
1206 (if i
1207 (loop (cdr in) out
1208 (acons (substring (cadar in) 0 i)
1209 (cons (substring (cadar in) (1+ i)) (cddar in))
1210 state)
1211 #f sig-ws?)
1212 (loop (cdr in) out (acons (cadar in) (cddar in) state)
1213 #f sig-ws?)))
1214 (error "expected a constant to define for @set" in)))
1215 ((value)
1216 (loop (fold-right cons (cdr in)
1217 (or (and=>
1218 (assoc (cadr (assq 'key (cdadar in))) state) cdr)
1219 (error "unknown value" (cdadar in) state)))
1220 out
1221 state #f sig-ws?))
1222 ((copying)
1223 (loop (cdr in) out (cons (car in) state) #f sig-ws?))
1224 ((insertcopying)
1225 (loop (fold-right cons (cdr in)
1226 (or (cdr (assoc 'copying state))
1227 (error "copying isn't set yet")))
1228 out
1229 state #f sig-ws?))
1230 ((contents)
1231 (loop (cdr in) (fold cons out (make-contents tree)) state #f sig-ws?))
1232 (else
1233 (let*-values (((kid-out state)
1234 (loop (car in) '() state #t
1235 (or sig-ws? (space-significant? (caar in))))))
1236 (loop (cdr in) (cons kid-out out) state #f sig-ws?)))))
1237 (else ; a symbol
1238 (loop (cdr in) (cons (car in) out) state #t sig-ws?))))
1239
1240 (call-with-values
1241 (lambda () (loop tree '() '() #t #f))
1242 (lambda (out state) out)))
1243
1244;; Replace % with texinfo-arguments.
1245(define (stexi->sxml tree)
1246 "Transform the stexi tree @var{tree} into sxml. This involves
1247replacing the @code{%} element that keeps the texinfo arguments with an
1248element for each argument.
1249
1250FIXME: right now it just changes % to @code{texinfo-arguments} -- that
1251doesn't hang with the idea of making a dtd at some point"
1252 (pre-post-order
1253 tree
1254 `((% . ,(lambda (x . t) (cons 'texinfo-arguments t)))
1255 (*text* . ,(lambda (x t) t))
1256 (*default* . ,(lambda (x . t) (cons x t))))))
1257
1258;;; arch-tag: 73890afa-597c-4264-ae70-46fe7756ffb5
1259;;; texinfo.scm ends here