Extract unroff-1.0.tar.gz

This commit is contained in:
Lassi Kortela 2023-02-13 15:45:50 +02:00
commit d8bfced2b7
58 changed files with 10224 additions and 0 deletions

26
COPYRIGHT Normal file
View File

@ -0,0 +1,26 @@
Copyright 1995 Oliver Laumann. All rights reserved.
Not derived from licensed software.
Permission is granted to use, copy, perform, display, modify, and
redistribute this software (including derivative works based on it) in
any form, subject to the following restrictions:
i) Source distributions must be accompanied by this entire copyright
notice.
ii) Binary distributions must include the acknowledgement ``This
product includes software developed by Oliver Laumann'' in the
documentation or other materials provided with the distribution.
iii) The origin of this software must not be misrepresented, either
by explicit claim or by omission.
iv) Altered versions must be plainly marked as such in the source
and documentation and must not be misrepresented as being the
original software.
v) The author is not deemed to have made any representations as to
the suitability of this software for any purpose nor is held
responsible for any defects of this software.
THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE.
Berlin, March 23, 1995
Oliver Laumann <net@cs.tu-berlin.de> # $Revision: 1.2 $

96
INSTALL Normal file
View File

@ -0,0 +1,96 @@
Installation Guide for the `unroff' Source Distribution
-------------------------------------------------------
Requirements: you need Elk 2.2 or Elk 3.0 and an ANSI C compiler
to install unroff from the source distribution. If you don't have
Elk and don't want to install it, you may want to obtain the binary
unroff distribution instead.
You can obtain Elk 3.0 in the World Wide Web at
http://www.informatik.uni-bremen.de/~net/elk
and from a number of FTP servers including these:
ftp://ftp.x.org/contrib/devel_tools/elk-3.0.tar.gz
ftp://ftp.uni-bremen.de/pub/programming/languages/scheme/elk/elk-3.0.tar.gz
o If you are still using Elk 2.2, you may have to apply a small patch to
your Elk installation. The patch is in the file src/elk-2.2-patch.
Assuming you have unpacked unroff under /usr/local/src/unroff-1.0,
change to the directory where the Elk "src" directory is located and call:
patch < /usr/local/src/unroff-1.0/src/elk-2.2-patch
If the patch program detects a `Reverse patch', the patch is not
required and you are done. If the patch succeeded, recompile the
interpreter by calling "make" and "make install" in the directory "src".
o Go to the sub-directory "src" below the directory where you unpacked
unroff:
cd /usr/local/src/unroff-1.0/src
and edit the Makefile. Check the site and compiler dependencies at the
beginning of the Makefile and modify them if necessary.
(Don't worry about DIR= if you just want to test unroff; you can
override the directory later by setting the environment variable
UNROFF_DIR before calling unroff.)
o Call "make depend" and then "make".
o You may want to remove the minimal Elk runtime environment contained
in the directory "elk" and replace it by a symbolic link to your
site's Elk runtime directory (i.e. the directory with sub-directories
"scm" and "obj").
o Test unroff and the HTML back-end included in the distribution.
For example, change to the directory "doc" and run "make" to convert
the manual pages and the Programmer's Manual to HTML. Then view the
resulting .html files with your favorite WWW browser.
Test the supplementary -ms features by calling (still in "doc"):
unroff -ms document=test split=1 hyper.scm manual.ms
This creates several files beginning with "test". Load test.html
into your WWW browser; observe the automatically generated table of
contents and the hypertext links embedded in the document. When
finished, you may want to "rm test*".
You can proceed by using unroff with a few troff documents of your
own. For example, try to convert a large manual page:
unroff -man /usr/man/man1/csh.1
Don't worry if this displays numerous warning messages. unroff
usually produces good results even if many low-level troff requests
are ignored. Check csh.1.html with your WWW browser.
o Read the documentation located in the directory "doc", in particular
the manual pages.
You can convert the troff files to HTML and view them with your
WWW browser (as explained in the previous step), or typeset them
using your local troff and send the output to the printer, or read
them online using nroff or man.
o You may want to place a number of default settings into an initialization
file ".unroff" in your home directory. There is a sample init file
"sample.unroff" in the directory "misc". If you decide to use this
file, replace the string "net@cs.tu-berlin.de" by your e-mail address.
o If you find unroff useful, you may want to install it in a central
directory at your site. If so, install the executable, the contents
of the directory "scm", and the manual pages.
If you have any questions, problems, or suggestions send a message to
net@cs.tu-berlin.de.
# $Revision: 1.6 $

1
PATCHLEVEL Normal file
View File

@ -0,0 +1 @@
2

133
README Normal file
View File

@ -0,0 +1,133 @@
This is version 1.0 of unroff.
Unroff is a Scheme-based, programmable, extensible troff translator
with a back-end for the Hypertext Markup Language. Unroff is free
software and is distributed both as source and as precompiled binaries.
* Overview
Unroff reads and parses UNIX troff documents and translates the embedded
markup into a different format. Neither the actual output format nor
any knowledge about particular troff macro sets (-man, -ms, etc.) are
hard-wired into unroff. Instead, the translation process is controlled
by a set of user-supplied procedures written in the Scheme programming
language.
Translation rules for new output formats and troff macro packages can
be added easily by providing a corresponding set of Scheme procedures
(a `back-end'). Version 1.0 of unroff includes back-ends for translating
documents using the `man' and `ms' macros into the Hypertext Markup
Language (HTML) version 2.0. Additional requests facilitate use of
arbitrary hypertext links in troff documents.
* unroff and troff
In contrast to conventional troff `converters' (usually Perl scripts
some of which process nroff output) unroff includes a full troff parser
and closely mimics the troff processing engine.
This enables unroff to handle user-defined macros, strings, and
number registers, nested if-else requests, arbitrary fonts and font
positions, low-level formatting requests such as \l, \c, and \h, and
idiosyncrasies such as troff `copy mode' and the subtle differences
between request and macro invocations.
Unroff has adopted a number of groff extensions, among them long names
for macros, strings, number registers, and special characters, and the
escape sequences \$@ and \$*.
* unroff and Scheme
Unroff uses Elk, the Scheme-based Extension Language Kit, to achieve
programmability. It includes a full Scheme language implementation
with the usual amenities such as garbage collection, interactive
programming and testing, and dynamic loading. Standard Scheme has
been augmented by a set of new Scheme data types and primitives that
aid in writing new unroff back-ends.
A new troff request and an extension to the `.ig' request allow for
Scheme code to be embedded in troff documents; the code is then
evaluated on the fly as the documents are processed by unroff.
Unroff may be viewed as a prototype for hybrid applications that use
Scheme (in particular Elk) as their extension language. Approximately
half of its source consists of portable ANSI C code, and the other
half is written in Scheme and can be configured and tailored easily
without the need to recompile unroff. Authors of Elk-based applications
are encouraged to look into the source code or reuse parts of it for
their own projects.
As the time-critical Scheme primitives provided of unroff have been
coded in (efficient) C, its performance comes close to that of nroff
processing the same troff input.
* unroff and hypertext
troff documents that were originally written without intentions to
make them available in the World Wide Web (such as UNIX manual pages)
can easily be translated to the Hypertext Markup Language using the
predefined HTML back-ends.
As unroff closely simulates ordinary troff, even large or complex
documents (like technical reports or theses with many user-defined
macros) can be translated to HTML automatically without having to
add any structural cues to the documents.
The `-man' support has been tested with several hundred vendor- and
user-supplied manual pages and has produced good results in all but
less than a dozen cases (a few manual pages were found to make excessive
use of low-level troff constructs or to include tbl output verbatim).
* Managing hypertext documents with troff
Authors can benefit from unroff not only as a converter for existing
documents, but also when writing new documents that must exist both
in high-quality paper form and in the World Wide Web as hypertext.
Rather than writing hypertext documents directly in HTML (which is
cumbersome for long or complex texts), authors can continue using
ordinary troff together with the usual preprocessors and macro packages.
Unroff is then employed to produce the WWW form, while troff is used
in the normal way to typeset the same text, producing the printed
version (or, using nroff, an ASCII version if desired).
In this way authors of hypertext documents can take full advantage of
the usual troff facilities such as user-defined macros, conditional text,
tables, equations, and drawings, automatic table of contents generation,
footnotes/endnotes, indexes, etc., none of which are available when
composing documents directly in plain HTML.
Two new troff requests for embedding hypertext links in troff documents
are provided by the unroff HTML back-end. Arbitrary forward and backward
references using symbolic labels (rather than actual file names) among
groups of troff source files are supported. Another new request and
another extension to `.ig' allow for HTML code to be embedded directly
in troff documents. The hypertext capabilities are demonstrated by
the troff source of the Programmer's Manual that is part of the unroff
distribution.
* Availability
The source distribution of unroff 1.0 as well as binary distributions
(with full Scheme source code and documentation) are available under:
http://www.informatik.uni-bremen.de/~net/unroff/unroff.html#dist
You can obtain Elk 3.0 from the same WWW server at:
http://www.informatik.uni-bremen.de/~net/elk
Elk 3.0 is also available from a number of FTP servers including these:
ftp://ftp.x.org/contrib/devel_tools/elk-3.0.tar.gz
ftp://ftp.uni-bremen.de/pub/programming/languages/scheme/elk/elk-3.0.tar.gz
--
Oliver Laumann <net@cs.tu-berlin.de> # $Revision: 1.4 $

34
doc/Makefile Normal file
View File

@ -0,0 +1,34 @@
# $Revision: 1.7 $
SHELL = /bin/sh
UNROFF = unroff
TROFF = \
unroff.1\
unroff-html.1\
unroff-html-man.1\
unroff-html-ms.1\
manual.ms
HTML = \
unroff.1.html\
unroff-html.1.html\
unroff-html-man.1.html\
unroff-html-ms.1.html\
manual.html
ALL = $(HTML)
all: $(ALL)
.SUFFIXES: .1 .1.html
.1.1.html:
$(UNROFF) -man $<
manual.html: manual.ms
$(UNROFF) -ms document=manual hyper.scm $?
clean:
rm -f $(ALL)

1737
doc/manual.ms Normal file

File diff suppressed because it is too large Load Diff

19
doc/tmac.hyper Normal file
View File

@ -0,0 +1,19 @@
.\" tmac.hyper: These macros implement hypertext links in troff, that is,
.\" they basically do nothing. See scm/misc/hyper.scm for the macros as
.\" implemented by unroff.
.
.ds pk tmac.hyper
.
.de Er
.tm \\*(pk: \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
..
.
.de Ha
.if !\\n(.$=2 .Er too few arguments for .Ha
\\$2\c
..
.
.de Hr
.if \\n(.$<1 .Er too few arguments for .Hr
.if !'\\$1'-symbolic' .if !'\\$1'-url' \\$1
..

218
doc/unroff-html-man.1 Normal file
View File

@ -0,0 +1,218 @@
.\" $Revision: 1.6 $
.ds Ve 1.0
.\"
.de Ex
.RS
.nf
.nr sf \\n(.f
.if !\\n(.U \{\
. ft B
. if n .sp
. if t .sp .5 \}
..
.de Ee
.if !\\n(.U \{\
. ft \\n(sf
. if n .sp
. if t .sp .5 \}
.fi
.RE
..
.\"
.de Sd
.ds Dt \\$2
..
.\"
.Sd $Date: 1995/08/23 12:07:31 $
.TH unroff-html-man 1 "\*(Dt"
.SH NAME
unroff-html-man \- back-end to translate manual pages to HTML 2.0
.SH SYNOPSIS
.B unroff
[
.B \-fhtml
] [
.B \-man
] [
.IR file " | " option...\&
]
.SH OVERVIEW
When called with the
.B \-fhtml
and
.B \-man
options, the troff translator
.I unroff
loads the back-end for converting UNIX manual pages to the Hypertext
Markup Language (HTML) version 2.0.
.LP
Please read
.BR unroff (1)
first for an overview of the Scheme-based, programmable troff translator
and for a description of the generic options that exist in
addition to
.B \-f
and
.BR \-m .
The translation of basic troff requests, special characters,
escape sequences, etc. as well as the HTML-specific options
are described in
.BR unroff-html (1).
For information about extending and programming
.I unroff
also refer to the
.IR "Unroff Programmer's Manual" .
.SH OPTIONS
The
.B \-man
extension provides one new keyword/value option in addition to
those listed in
.BR unroff (1)
and
.BR unroff-html (1):
.TP
.BR do-signature " (boolean)"
If set to 1, a signature is appended to each output file.
The signature is composed of a horizontal rule and a one-line
message consisting of version information and date and time.
The default value of this option is 1.
.SH DESCRIPTION
.I unroff
reads and parses its input files (each containing a UNIX manual
page); the HTML output is written to a separate output file
for each input file.
The name of an output file is obtained by appending the
suffix \*(lq.html\*(rq to the name of the corresponding input
file.
Any
.B document
option is ignored if input files are named in the command line.
As usual, the special file name
.RB ` \- '
can be used to interpolate standard input.
.LP
If no file name is given in the command line, a manual page
is read from standard input and sent to standard output,
unless the
.B document
option is given, in which case the HTML output is written
to the specified file (with \*(lq.html\*(rq appended).
Example:
this call to
.I unroff
translates two manual pages and creates two corresponding output files,
.B cc.1.html
and
.BR send.2.html :
.Ex
unroff \-fhtml \-man /usr/man/man1/cc.1 /usr/man/man2/send.2
.Ee
.LP
The following
.B \-man
macros are recognized and translated (in addition to any user-defined macros):
.LP
.nf
.if !\n(.U .ta 8n 16n 24n 32n 40n 48n 56n
.TH .SH .SS .I .B .SB .SM
.BI .BR .IB .IR .RB .RI .TP
.IP .HP .RS .RE .LP .PP .P
.fi
.LP
In addition, the following Sun-specific macros are silently
ignored (.TX generates an informational message containing
its argument):
.LP
.nf
.TX .IX .DT .PD .UC
.fi
.LP
The following predefined troff strings are recognized
(\e*S expands to the empty string):
.LP
.nf
\e*R \e*S \e*(lq \e*(rq
.fi
.LP
The title of each HTML document generated is obtained by calling
the primitive
.I substitute
(as explained in the Programmer's Manual) with the value of the option
.B title
and the first and second arguments passed to the initial call to
.BR .TH .
Thus, the specifiers \*(lq%1%\*(rq and \*(lq%2%\*(rq can be used
in the option to interpolate the command (or whatever is documented
in the manual page) and the section number.
If
.B title
has not been specified, the string \*(lqManual page for %1%(%2%)\*(rq
is taken.
As generating the HTML title element is deferred until the call to
.BR .TH ,
any macros or other troff requests that produce output must not be
used before the initial
.BR .TH .
.LP
HTML header elements <h2> and <h3> are created for
.B .SH
and
.B .SS
requests.
The markup created for the initial NAME section differs in that the
contents of the section (usually a single line) is itself placed
inside a header element.
.LP
The font switching macros are based on changes to the fonts `R',
`I', and `B', as explained under FONTS in
.BR unroff-html (1).
Of course, this fails if the fonts (which are mounted on startup)
are unmounted by explicit
.B .fp
requests.
As HTML is lacking the concept of text size, the macro
.B .SB
is just an alias for
.BR .B ,
and
.B .SM
simply echoes its arguments.
.LP
The translation rules for
.B .TP
and
.B .IP
employ a heuristic to determine whether to generate a definition
list or an unordered list:
if the first in a sequence of tagged/indented paragraph macros is
called with a tag consisting of the special character \e(bu, a
definition list is begun, otherwise an unordered list.
Subsequent invocations cause the list style to change if appropriate.
Use of tagged paragraphs inside non-filled (pre-formatted) text
violates the HTML definition and should be avoided.
A warning message is printed in this and other questionable situations.
.LP
As hanging tags cannot be realized with HTML 2.0,
a kludge is used for the
.B .HP
(hanging paragraph) macro:
the macro starts a definition list (as does the ordinary
.BR .TP
macro), and everything up to the next request that causes a break
is placed inside the definition tag.
This method obviously fails if no break occurs in subsequent lines,
but it works for the common, idiomatic use of hanging paragraphs
in manual pages.
.SH "SEE ALSO"
.BR unroff (1),
.BR unroff-html (1),
.BR troff (1),
.BR man "(5 or 7)."
.LP
Unroff Programmer's Manual.
.LP
http://www.informatik.uni-bremen.de/~net/unroff
.LP
Berners-Lee, Connolly, et al.,
HyperText Markup Language Specification\(em2.0,
Internet Draft, Internet Engineering Task Force.

324
doc/unroff-html-ms.1 Normal file
View File

@ -0,0 +1,324 @@
.\" $Revision: 1.8 $
.ds Ve 1.0
.\"
.de Ex
.RS
.nf
.nr sf \\n(.f
.if !\\n(.U \{\
. ft B
. if n .sp
. if t .sp .5 \}
..
.de Ee
.if !\\n(.U \{\
. ft \\n(sf
. if n .sp
. if t .sp .5 \}
.fi
.RE
..
.\"
.de Sd
.ds Dt \\$2
..
.\"
.Sd $Date: 1995/08/23 12:07:31 $
.TH unroff-html-ms 1 "\*(Dt"
.SH NAME
unroff-html-ms \- back-end to translate `ms' documents to HTML 2.0
.SH SYNOPSIS
.B unroff
[
.B \-fhtml
] [
.B \-ms
] [
.IR file " | " option...\&
]
.SH OVERVIEW
When called with the
.B \-fhtml
and
.B \-ms
options, the troff translator
.I unroff
loads the back-end for converting \*(lqms\*(rq documents to the Hypertext
Markup Language (HTML) version 2.0.
.LP
Please read
.BR unroff (1)
first for an overview of the Scheme-based, programmable troff translator
and for a description of the generic options that exist in
addition to
.B \-f
and
.BR \-m .
The translation of basic troff requests, special characters,
escape sequences, etc. as well as the HTML-specific options
are described in
.BR unroff-html (1).
For information about extending and programming
.I unroff
also refer to the
.IR "Unroff Programmer's Manual" .
.SH OPTIONS
The
.B \-ms
extension provides a number of keyword/value options in addition to
those listed in
.BR unroff (1)
and
.BR unroff-html (1):
.TP
.BR signature " (string)"
If non-empty, the value of this option together with a <hr> tag is
appended to each HTML output file created.
The
.I substitute
Scheme primitive (as described in the Programmer's Manual) is
applied to the value of the option, so that date, time, environment
variables, etc. can be interpolated.
.TP
.BR split " (integer)"
This option specifies whether to split the output document into
individual files for each major section.
If a positive integer
.I level
is assigned to the option, a new output file is opened for each
numbered header
.RB ( .NH
request) with a level equal to or numerically less than
.IR level .
Use of this feature requires that the
.B document
option has bee set, as otherwise the HTML document is sent
to standard output.
The default value is 0, i.\|e. all sections will be written to
a single file.
.TP
.BR toc " (boolean)"
If true, a table of contents with a hypertext link for each section
is generated automatically and inserted after the front matter
(title, author information, abstract) and before the first section.
Use of this feature requires a non-zero value for the
.B split
option.
The default is to produce a table of contents if
.B split
is non-zero.
.TP
.BR toc-header " (string)"
This option defines the contents of the <h2> header element prepended to
an automatically generated table of contents.
Its value is subject to a call to
.IR substitute .
The default is the string \*(lqTable of Contents\*(rq.
.TP
.BR pp-indent " (integer)"
The number of non-breakable spaces (as specified by the predefined
Scheme variable
.IR nbsp )
to generate for a paragraph created by the
.B .PP
macro.
The default is 3.
This option, as well as
.BR signature ,
is typically set in the user-preferences file
.BR ~/.unroff ,
or in a document-specific Scheme file or at the beginning of
the document proper.
.TP
.BR footnotes-header " (string)"
The contents of the <h2> header element prepended to the footnotes
section that is appended to the document if any footnotes were used,
and that also appears in the automatically generated table of contents.
As with all string option listed in this section, the
.I substitute
primitive is applied to the option's value.
The default is the string \*(lqFootnotes\*(rq.
.TP
.BR footnote-reference " (string)"
This option controls the text generated by each use of the variable
\&`\e**', which produces a footnote (hypertext) reference.
Its value is passed to a call to
.I substitute
with the current footnote number as another argument, so that the
specifier \*(lq%1%\*(rq can be used to interpolate the footnote
number.
The default is the string \*(lq[note %1%]\*(rq.
.TP
.BR footnote-anchor " (string)"
This options specifies the footnote reference that appears at the
beginning of each footnote proper if
.B .FS
was called without an argument.
The option's value is passed to a call to
.I substitute
with the footnote number generated by the last use of `\e**' as
another argument.
The default is \*(lq[%1%]\*(rq.
.SH FILES
.I unroff
reads and parses an \*(rqms\*(lq document composed of one ore more
input files.
As usual, the special file name
.RB ` \- '
can be used to interpolate standard input.
If no file name is given in the command line,
.I unroff
reads from standard input.
.LP
The resulting HTML document is sent to standard output, unless a
file name prefix is assigned to the
.B document
option.
In the latter case, the
.B split
option controls splitting of the output into separate files at
section boundaries as described under OPTIONS above.
A number of other features, such as footnotes, also require
that the
.B document
option is supplied, as separate output files are created for them
(regardless of the value of
.BR split ).
In any case, the name of each output file consists of the value of
.BR document ,
followed by an optional suffix, followed by the extension \*(lq.html\*(rq.
.SH EXAMPLE
To translate an \*(lqms\*(rq document composed of several
input files,
.I unroff
could be invoked like this:
.Ex
.if n \{unroff \-fhtml \-ms document=thesis split=2\e
intro.ms 1.ms 2.ms 3.ms app.ms\}
.if !n unroff \-fhtml \-ms document=thesis split=2 intro.ms 1.ms 2.ms 3.ms app.ms
.Ee
The names of all output files will have the prefix \*(lqthesis\*(rq,
and the resulting HTML document will be split into separate files
at each level 1 section or level 2 section.
.SH DESCRIPTION
The following
.B \-ms
macros are translated (in addition to any user-defined macros):
.LP
.nf
.if !\n(.U .ta 8n 16n 24n 32n 40n 48n 56n
.AB .AE .AI .AU .B .B1 .B2
.BD .BX .CD .DE .DS .FA .FE
.FS .I .ID .IP .LD .LG .LP
.NH .PP .PX .QP .R .RE .RS
.RT .SH .SM .TL .UL .UX .XA
.XE .XS
.fi
.LP
These predefined strings and number registers are recognized:
.LP
.nf
\e*- \e*(DY \e*(MO \e*Q \e*U \en(PN
.fi
.LP
In addition, a number of macros are either silently ignored
or cause a warning to be printed, because their function either
cannot be mapped to HTML 2.0 elements or assumes a page
structure:
.LP
.nf
.AM .BT .CM .CT .DA .EF .EH
.HD .KE .KF .KS .ND .NL .OF
.OH .P1 .PT .TM .MC .1C .2C
.fi
.LP
The font switching macros are based on changes to the fonts `R',
`I', and `B', as explained under FONTS in
.BR unroff-html (1).
Of course, this fails if the fonts (which are mounted on startup)
are unmounted by explicit
.B .fp
requests.
.LP
Upper or lower case letters are accepted as section numbers by
.B .NH
when the argument ``S'' is used to set new section numbers.
This is useful for appendices and similar constructs.
.LP
The translation rule for
.B .IP
employs a heuristic to determine whether to generate a definition
list or an unordered list:
if the first in a sequence of indented paragraph macros is
called with a tag consisting of one of the special character \e(bu
or \e(sq, a definition list is begun, otherwise an unordered list.
Since
.RI exdented[ sic ]
paragraphs cannot be expressed in HTML 2.0, a warning
message is printed when a call to the macro
.B .XP
is encountered.
.LP
All footnotes are concatenated and placed in a separate output file,
and a corresponding section (with a user-defined header) holding
the footnotes is appended to the document automatically.
Use of the string `\e**' generates a hypertext link to the beginning
of the footnote created by the next call to
.B .FS
and
.BR .FE .
The actual text generated by using `\e**' as well as the footnote
reference that appears in the footnote proper are controlled by
two options as explained under OPTIONS above.
A warning message is printed on termination if `\e**' has been
used but a corresponding footnote was not seen.
As an alternative to `\e**', the new request
.B .FA
can be used to produce a footnote anchor together with a hypertext
link; the anchor is the argument to the macro
(however, `\e**' itself must not be used in a call to
.BR .FA ).
.LP
Likewise, a hypertext reference is created for each use of the
table of contents macros
.BR .XS
and
.BR .XE
(optionally accompanied by calls to
.BR .XA ).
.SH "SEE ALSO"
.BR unroff (1),
.BR unroff-html (1),
.BR troff (1),
.BR ms "(5 or 7)."
.LP
Unroff Programmer's Manual.
.LP
http://www.informatik.uni-bremen.de/~net/unroff
.LP
Berners-Lee, Connolly, et al.,
HyperText Markup Language Specification\(em2.0,
Internet Draft, Internet Engineering Task Force.
.SH BUGS
The macro
.B .UL
is currently mapped to a call to
.BR .I ,
as underlining is not supported by the HTML back-end of
.I unroff
\*(Ve.
.LP
Footnote references and requests such as
.B .sp
that cause non-character-level markup to be generated must not
be used inside a numbered header.
.LP
When creating a hypertext anchor for
.B .XS
and
.BR .XE ,
there is nothing to put inside the <a> element;
therefore a non-breaking space is used.
.LP
Changing the number register format of `NH' to get roman or alphabetic
section numbers does not work, obviously.

671
doc/unroff-html.1 Normal file
View File

@ -0,0 +1,671 @@
.\" $Revision: 1.12 $
.ds Ve 1.0
.\"
.de Ex
.RS
.nf
.nr sf \\n(.f
.if !\\n(.U \{\
. ft B
. if n .sp
. if t .sp .5 \}
..
.de Ee
.if !\\n(.U \{\
. ft \\n(sf
. if n .sp
. if t .sp .5 \}
.fi
.RE
..
.\"
.de Sd
.ds Dt \\$2
..
.\"
.Sd $Date: 1995/08/23 12:07:31 $
.TH unroff-html 1 "\*(Dt"
.SH NAME
unroff-html \- HTML 2.0 back-end for the programmable troff translator
.SH SYNOPSIS
.B unroff
[
.B \-fhtml
] [
.BI \-m package
] [
.IR file " | " option...\&
]
.SH OVERVIEW
When called with the
.B \-fhtml
option,
.I unroff
loads the back-end for the Hypertext Markup Language (HTML) version 2.0.
Please read
.BR unroff (1)
first for an overview of the Scheme-based, programmable troff translator
and for a description of the generic options that exist in
addition to
.B \-f
and
.BR \-m .
For information about extending and programming
.I unroff
also refer to the
.IR "Unroff Programmer's Manual" .
.LP
.I unroff
is usually invoked with an additional
.BI \-m package
option (such as
.B \-ms
or
.BR \-man )
to load the translation rules for the troff macros and other elements
defined by the macro package that is used to typeset the document.
If no
.B \-m
option is supplied, only the standard troff requests, special characters,
escape sequences, etc. are recognized and translated to HTML by
.I unroff
as described in this manual.
.SH OPTIONS
The following HTML-specific options can be specified in the command
line after the generic options.
See
.BR unroff (1)
for a general description of keyword/value options and their types
and for a list of options that are not specific to the target language.
.TP
.BR title " (string)"
The value to be used for the <title> element in HTML output files.
This option may be ignored by the code implementing a specific
macro set, e.\|g. when special rules are employed to derive the title
from the contents of the troff input files.
Whether or not this option is required also depends on the specific
.B \-m
option used, but it may be omitted if no
.B \-m
option is given.
.TP
.BR document " (string)"
The prefix used for the names of all output files.
May be ignored depending on the macro package that has been selected.
.TP
.BR mail-address " (string)"
The caller's mail address; may be used for \*(lqmailto:\*(rq URLs,
in particular for the \*(lqhref\*(rq attribute of the <link>
element that is usually generated.
.TP
.BR tt-preformat " (boolean)"
If 1, font changes to a font that is mapped to the <tt> element
are honored inside non-filled text (as described below).
The default is 0, i.\|e. the font changes will be recorded, but no
corresponding HTML tags will be emitted for them.
.TP
.BR handle-eqn " (string)"
.TP
.BR handle-tbl " (string)"
.TP
.BR handle-pic " (string)"
These options specify how equations, tables, and pictures encountered
in the troff input are processed.
Possible values are \*(lqcopy\*(rq to include the raw eqn, tbl, or
pic commands as pre-formatted text, \*(lqtext\*(rq to run the
respective troff preprocessor (eqn, tbl, or pic) and include its output
as pre-formatted text, or \*(lqgif\*(rq to convert the preprocessor
output to a GIF image and include it in the HTML document as
an inline image.
The default is \*(lqtext\*(rq for
.BR handle-tbl ,
\*(lqgif\*(rq for the other options.
See DESCRIPTION below for more information.
.TP
.BR eqn " (string)"
.TP
.BR tbl " (string)"
.TP
.BR pic " (string)"
These options specify the programs to invoke as the eqn, tbl,
and pic preprocessors.
The defaults are site-dependent.
.TP
.BR troff-to-text " (string)"
.TP
.BR troff-to-gif " (string)"
The programs to invoke for converting the output of a troff preprocessor
to plain text or to a GIF image.
The default values are site-dependent.
See DESCRIPTION below for more information on these options.
.SH FILES
If no
.B \-m
option is supplied,
.I unroff
reads the specified input files and sends the HTML document to
standard output, unless the
.B document
option is given, in which case its value together
with the suffix \*(lq.html\*(rq is used as the name of an
output file.
If no input files are specified, input is taken from standard input.
The output is enclosed by the usual HTML boiler-plate (<html>, <head>,
and <body> elements), a <title> element with the specified title
(or the value of
.B document
if no title has been given, or a default title if both are omitted),
a <link> element with rev= and href= attributes if
.B mail-address
has been set, and any pending end tags are generated on end of input.
.LP
Note that this is the default action that is performed in the
rare case when no macro package name has been specified, i.\|e. when
processing \*(lqbare\*(rq troff input.
Somewhat different rules may apply when processing, for
example, a group of UNIX manual pages
.RB ( \-man ).
.LP
See
.BR unroff (1)
for a list of Scheme files that are loaded on startup.
.SH DESCRIPTION
.SS "OUTPUT TRANSLATIONS"
The characters `<', `>', and `&' are replaced by the entities
`&lt;', `&gt;', and `&amp;' on output.
In addition, the quote character is mapped to `&quot;' where
appropriate.
New mappings can be added by means of the
.I defchar
Scheme primitive as explained in the Programmer's Manual.
.SS COMMENTS
each troff comment is translated to a corresponding HTML tag
followed by a newline; empty comments are ignored.
Comments are also ignored when appearing inside a macro body.
.SS "ESCAPE SEQUENCES"
The following is a list of troff escape sequences that are recognized
and the HTML output generated for them.
Any escape sequence that does not appear in the list
expands to the character after the escape character, and
a warning is printed in this case.
New definitions can be added and the predefined mappings can
be replaced by calling the
.I defescape
Scheme primitive in the user's initialization file, in a user-supplied
Scheme file, in a document, or on a site-wide basis by modifying
the file
.B scm/html/common.scm
in the installation directory.
.LP
.nf
.if !\n(.U .ta 8n 16n 24n 32n 40n 48n 56n
\e& nothing
\e- -
\e| nothing
\e^ nothing
\e\e \e
\e' '
\e` `
\e" rest of line as HTML comment tag
\e% nothing
\e{ conditional input begin
\e} conditional input end
\e* contents of string
\espace space
\e0 space
\ec nothing; eats following newline
\ee \e
\es nothing
\eu nothing, prints warning
\ed nothing, prints warning
\ev nothing, prints warning
\eo its argument, prints warning
\ez its argument, prints warning
\ek sets specified register to zero
\eh appropriate number of spaces for positive argument
\ew length of argument in units
\el repeats specified character, or <hr>
\en contents of number register
\ef see description of fonts below
.fi
.SS "SPECIAL CHARACTERS"
The following special characters are mapped to their equivalent
ISO-Latin 1 entities:
.LP
.nf
\e(12 \e(14 \e(34 \e(*b \e(*m \e(+- \e(:A
\e(:O \e(:U \e(:a \e(:o \e(:u \e(A: \e(Cs
\e(O: \e(Po \e(S1 \e(S2 \e(S3 \e(U: \e(Ye
\e(a: \e(bb \e(cd \e(co \e(ct \e(de \e(di
\e(es \e(hy \e(mu \e(no \e(o: \e(r! \e(r?
\e(rg \e(sc \e(ss \e(tm \e(u:
.fi
.LP
Heuristics have to be used for the following special characters:
.LP
.nf
\e(** *
\e(-> ->
\e(<- <-
\e(<= <=
\e(== ==
\e(>= >=
\e(Fi ffi
\e(Fl ffl
\e(aa '
\e(ap ~
\e(br |
\e(bu + (prints a warning)
\e(bv |
\e(ci O
\e(dd *** (prints a warning)
\e(dg ** (prints a warning)
\e(em --
\e(en -
\e(eq =
\e(ff ff
\e(fi fi
\e(fl fl
\e(fm '
\e(ga `
\e(lh <=
\e(lq ``
\e(mi -
\e(or |
\e(pl +
\e(rh =>
\e(rq ''
\e(ru _
\e(sl /
\e(sq o (prints a warning)
\e(ul _
\e(~= ~
.fi
.LP
A warning is printed to standard error output for any special
character not mentioned in this section.
To add new definitions, and to customize existing ones, the
.I defspecial
Scheme primitive can be used.
.SS "NON-FILLED TEXT"
The
.B .nf
and
.B .fi
troff requests generate pairs of <pre> and </pre> tags.
Nested requests are treated correctly, and currently
active character formatting elements such as <i> (resulting
from troff font changes) are temporarily disabled while
the <pre> or </pre> is emitted.
A warning is printed if a \*(lqtab\*(rq character is encountered
within filled text.
.SS FONTS
The `\ef' escape sequence and the requests
.B .ft
(change current font) and
.B .fp
(mount font at font position) are supported in the usual way,
both with numeric font positions as well as font names and
the special name `P' to denote the previous font.
The font position of the currently active font is available
through the read-only number register `.f'.
Initially, the font `R' is mounted on font positions 1 and 4,
font `I' on font position 2, and font `B' on position 3.
.LP
To map troff font names to HTML character formatting elements,
the \f2define-font\fP Scheme procedure is called with the name
of a troff font to be used in documents, and
HTML start and end tags to be emitted when changing to this font,
or when changing
.I from
this font to another font, respectively.
Whether <tt> and </tt> is generated inside non-filled (pre-formatted)
text for fixed-width fonts is controlled by the option
.BR tt-preformat .
The following calls to
.I define-font
are evaluated on startup:
.LP
.nf
.if !\n(.U \{\
. ft C
. ps -1
. vs -1 \}
(define-font "R" "" "")
(define-font "I" '<i> '</i>)
(define-font "B" '<b> '</b>)
(define-font "C" '<tt> '</tt>)
(define-font "CW" '<tt> '</tt>)
(define-font "CO" '<i> '</i>) ; kludge for Courier-Oblique
.if !\n(.U \{\
. ft
. ps
. vs \}
.fi
.LP
Site administrators may add definitions here for fonts used
at their site.
Users can define mappings for new fonts by placing corresponding
definitions in their documents or document-specific Scheme files.
.SS "OTHER TROFF REQUESTS"
The
.B .br
request generates a <br> tag.
.LP
.B .sp
requires a positive argument and is mapped to the appropriate number
of <p> tags (or newline characters inside non-filled/pre-formatted
text).
Likewise, the request
.BR .ti ,
when called with a positive indent, produces a <br> followed by the
appropriate number of non-breakable spaces.
.LP
The
.B .tl
requests justs emits the title parts delimited by spaces.
It is impossible to preserve the meaning of this request
in HTML 2.0.
.LP
The horizontal line drawing escape sequence `\el' just repeats
the specified character (or underline as default) to draw
a line.
If the given length looks like it could be the line length
(that is, if it exceeds a certain value), a <hr> tag
is produced instead.
Example:
.LP
.nf
\el'5c\e&-'
\el'60'
.fi
.LP
The first of these two requests
would produce a line of 20 dashes, while the second
request would generate a <hr> tag (the '\e&' is required
because the dash could be interpreted as a continuation of
the numeric expression).
.LP
Centering
.RB ( .ce )
is simulated by producing a <br> at the end of each line, as
this functionality is not supported by HTML 2.0.
.LP
The following requests are silently ignored; as the corresponding
functions cannot be expressed in HTML 2.0 or are controlled by
the client.
Ignoring these requests most likely does no harm.
.LP
.nf
.ad .bp .ch .fl .hw .hy .lg
.na .ne .nh .ns .pl .ps .rs
.vs .wh
.fi
.LP
All troff requests not mentioned in this section by default
cause a warning message to be printed to standard error output,
except for these basic requests which have their usual
semantics:
.LP
.nf
.am .as .de .ds .ec .el .ie
.if .ig .nr .rm .rr .so .tm
.fi
.LP
The
.I defrequest
Scheme primitive is used to associate an event handling procedure
with a request as documented in the Programmer's Manual.
.SS "END OF SENTENCE"
The sequence \*(lq<tt>space</tt>\*(rq is produced at the end of
each sentence to provide additional space, except inside non-filled text.
A sentence is defined a sequence of characters followed by
a period, a question mark, or an exclamation mark, followed
by a newline.
The usual convention to suppress end-of-sentence recognition
by adding the escape sequence `\e&' is correctly implemented by
.IR unroff .
To change the end-of-sentence function, the
.I sentence-event
can be redefined from within Scheme code as described in
the Programmer's Manual.
.SS "SCALE INDICATORS"
As the notions of vertical spacing, character width, device
resolution, etc. do not exist in HTML, the scaling for the
usual troff scale indicators is defined once on startup and
then remains constant.
For simplicity, the scaling usually employed by
.BR nroff (1)
is taken.
.SS "EQUATIONS, TABLES, PICTURES"
Interpretation of embedded eqn, tbl, and pic preprocessor input
is controlled by the options
.BR handle-eqn ,
.BR handle-tbl ,
and
.B handle-pic
(see OPTIONS above).
These options affect the input lines from a starting
.BR .EQ ,
.BR .TS ,
or
.B .PS
request up to and including the matching
.BR .EN ,
.BR .TE ,
or
.B .PE
request, as well as text surrounded by the current eqn
inline equation delimiters.
Each of the options can have one the following values:
.TP
.B copy
The preprocessor input (including the enclosing requests) is
placed inside <pre> and </pre>.
If assigned to the option
.BR handle-eqn ,
inline equations are rendered in the font currently mounted
on font position 2.
.TP
.B text
The input is sent to the respective preprocessor (as specified
by the options
.BR eqn ,
.BR tbl ,
or
.BR pic ),
and its result is piped to the shell command referred to by the
option
.BR troff-to-text ,
which typically involves a call to
.BR nroff (1)
or an equivalent command.
As with \*(lqcopy\*(rq, the result is then placed inside
<pre> and </pre>, unless the source is an inline equation.
.IP
The value of
.B troff-to-text
is filtered through a call to the
.I substitute
Scheme primitive with the name of an output file as its argument;
this file name can be referenced from within the option's value
by the substitute specifier \*(lq%1%\*(rq (see the Programmer's
Manual for a description of
.I substitute
and a list of substitute specifiers).
Here is a typical value for the
.B troff-to-text
option:
.Ex
"groff \-Tascii | col \-b | sed '/^[ \et]*$/d' > %1%"
.Ee
.TP
.B gif
Input lines are preprocessed as described under \*(lqtext\*(rq, and
the result is piped to the shell command named by the option
.BR troff-to-gif .
The latter is subject to a call to
.I substitute
with the name of a temporary file (which may be used to store intermediate
PostScript output) and the name of the output file where the resulting
GIF image must be stored.
The entire preprocessor input is replaced by an <img> element with
a reference to the GIF file and a suitable \*(lqalt=\*(rq attribute.
Unless processing an inline equation, the <img> element is
surrounded by <p> tags.
.IP
The names of the files containing the GIF images are generated
from the value of the
.B document
option, a sequence number, and the suffix \*(lq.gif\*(rq.
Therefore, the
.B document
option must have been set when using the \*(lqgif\*(rq method,
otherwise a warning is printed and the preprocessor input
is skipped.
.LP
In any case, the output of a call to eqn is ignored if the
input consists of calls to \*(lqdelim\*(rq or \*(lqdefine\*(rq
and empty lines exclusively.
When processing eqn input, calls to \*(lqdelim\*(rq are intercepted by
.I unroff
to record changes of the inline equation delimiters.
.SS "HYPERTEXT LINKS"
The facilities for embedding arbitrary hypertext links in troff
documents are still experimental in this version of
.I unroff
and thus are likely to change in future releases.
To use them, mention the file name \*(lqhyper.scm\*(rq in the
command line before any troff source files.
At the beginning of the first troff file, source the file
\*(lqtmac.hyper\*(rq from the directory \*(lqdoc\*(rq like this:
.LP
.nf
.if !\en(.U .so tmac.hyper
.fi
.LP
The request
.B .Hr
can then be used to create a hypertext link.
Its usage is:
.LP
.nf
.Hr -url URL anchor-text [suffix]
.Hr -symbolic label anchor-text [suffix]
.Hr troff-text
.fi
.LP
The first two forms are recognized by
.I unroff
and the third form is recognized by troff.
The first form is used for links pointing to external resources,
and the second one is used for forward or backward links referencing
anchors defined in a file belonging to the same document.
An anchor is placed in the document by calling the request
.BR .Ha :
.LP
.nf
.Ha label anchor-text
.fi
.LP
The label specified in a call to
.B .Ha
can then be used in calls to
.BR ".Hr -symbolic" .
All symbolic references must have been resolved at the end of the document.
The \*(lqanchor-text\*(rq is placed between the tags <a> and </a>;
\*(lqsuffix\*(rq is appended to the closing </a> if present.
\*(lqtroff-text\*(rq is just formatted in the normal way.
Quotes must be used if any of the arguments contains spaces.
.LP
Use of the hypertext facilities is demonstrated by the troff source
of the Programmer's Manual that is included in the
.I unroff
distribution.
.SH "SCHEME PROCEDURES"
The following Scheme procedures, macros, and variables are defined
by the HTML 2.0 back-end and can be used from within user-supplied
Scheme code:
.TP
(\f2define-font name start-tag end-tag\fP)
Associates a HTML start tag and end tag (symbols) with a troff
font name (string) as explained under FONTS above.
The font name can then be used in
.BR .fp ,
.BR .ft ,
and `\ef' requests.
.TP
(\f2reset-font\fP)
Resets both the current and previous font to the font mounted
on position 1.
.TP
\f2current-font\fP
.TP
\f2previous-font\fP
These variables hold the current and previous font as
(integer) font positions.
.TP
(\f2with-font-preserved\fP . \f2body\fP)
This macro can be used to temporarily change to font \*(lqR\*(rq,
evaluate \f2body\fP, and revert to the font that has been
active when the form was entered.
The macro returns a string that can be output using the
primitive \f2emit\fP or returned from an event procedure.
.TP
(\f2preform enable?\fP)
If the argument is #t, pre-formatted text is enabled, otherwise disabled.
.TP
\f2preform?\fP
This boolean variable holds #t if pre-formatted text is enabled,
#f otherwise.
.TP
(\f2with-preform-preserved\fP . \f2body\fP)
A macro that can be used to temporarily disable pre-formatted
text, evaluate \f2body\fP, and then re-enable it if appropriate.
The macro expands to a string that must be output or returned from
an event procedure.
.TP
(\f2parse-unquote string\fP)
Temporarily establishes an output translation to map the quote
character to \*(lq&quot;\*(rq, applies \f2parse\fP (explained
in the Programmer's Manual) to its argument, and returns the result.
.TP
(\f2center n\fP)
Centers the next \f2n\fP input lines (see description of
.B .ce
under TROFF REQUESTS above).
If \f2n\fP is zero, centering is stopped.
.TP
\f2nbsp\fP
A Scheme variable that holds a string interpreted as a non-breaking
space by HTML clients.
.SH "SEE ALSO"
.BR unroff (1),
.BR unroff-html-man (1),
.BR unroff-html-ms (1);
.br
.BR troff (1),
.BR nroff (1),
.BR groff (1),
.BR eqn (1),
.BR tbl (1),
.BR pic (1).
.LP
Unroff Programmer's Manual.
.LP
http://www.informatik.uni-bremen.de/~net/unroff
.LP
Berners-Lee, Connolly, et al.,
HyperText Markup Language Specification\(em2.0,
Internet Draft, Internet Engineering Task Force.
.SH BUGS
The `\espace' escape sequence should be mapped to the &#160; entity
(non-breaking space), but this entity is not supported by a number
of HTML clients.
.LP
Only the font positions 1 to 9 can currently be used.
There should be no limit.
.LP
The extra space generated for end of sentence should be configurable.
.LP
Underlining should be supported.

682
doc/unroff.1 Normal file
View File

@ -0,0 +1,682 @@
.\" $Revision: 1.16 $
.ds Ve 1.0
.\"
.de Ex
.RS
.nf
.nr sf \\n(.f
.if !\\n(.U \{\
. ft B
. if n .sp
. if t .sp .5 \}
..
.de Ee
.if !\\n(.U \{\
. ft \\n(sf
. if n .sp
. if t .sp .5 \}
.fi
.RE
..
.\"
.de Sd
.ds Dt \\$2
..
.\"
.Sd $Date: 1995/08/23 12:07:31 $
.TH unroff 1 "\*(Dt"
.SH NAME
unroff \- programmable, extensible troff translator
.SH SYNOPSIS
.B unroff
[
.BI \-f format
] [
.BI \-m package
] [
.BI \-h heapsize
] [
.B \-C
]
.if n .ti +0.5i
[
.B \-t
] [
.IR file " | " option...\&
]
.SH OVERVIEW
.I unroff
reads and parses documents with embedded troff markup
and translates them to a different format\(emtypically
to a different markup language such as SGML.
The actual output format is not hard-wired into
.IR unroff ;
instead, the translation is performed by a set of user-supplied rules
and functions written in the
.I Scheme
programming language.
.I unroff
employs the Extension Language Kit
.I Elk
to achieve programmability based on the Scheme language:
a fully-functional Scheme interpreter is embedded in the translator.
.LP
The documents that can be processed by
.I unroff
are not restricted to a specific troff macro set.
Translation rules for a new macro package can be added by supplying
a set of corresponding Scheme procedures (a \*(lqback-end\*(rq).
Predefined sets of such procedures exist for a number of combinations
of target language and troff macro package:
.I unroff
\*(Ve supports translation to the \*(lqHypertext Markup Language\*(rq
(HTML) version 2.0 for the
.B \-man
and
.B \-ms
macro packages as well as \*(lqbare\*(rq troff (see
.BR unroff-html (1),
.BR unroff-html-man (1),
and
.BR unroff-html-ms (1)
for a description).
.LP
Unlike conventional troff conversion tools,
.I unroff
includes a full troff parser and can therefore handle user-defined
macros, strings, and number registers, nested if-else requests
(with text blocks enclosed by `\e{' and `\e}' escape sequences), arbitrary
fonts and font positions, troff \*(lqcopy mode\*(rq, low-level formatting
requests such as `\el' and '\eh', and the subtle
differences between request and macro invocations that are inherent
in the troff processing model.
.I unroff
has adopted a number of troff extensions introduced by
.IR groff ,
among them long names for macros, strings, number registers, and
special characters, and the `\e$@' and `\e$*' escape sequences.
.LP
.I unroff
interprets its input stream as a sequence of \*(lqevents\*(rq.
Events include the invocation of a troff request or macro, the use of a
troff escape sequence or special character, a troff string
or number register reference, end of sentence, start
of a new input file, and so on.
For each event encountered
.I unroff
invokes a Scheme procedure associated with that event.
Some types of events require a procedure that returns a string (or an
object that can be coerced into a string),
which is then interpolated into the input or output stream;
for other types of events, the event procedures are just called
for their side-effects.
.LP
The set of Scheme procedures to be used by
.I unroff
is determined by the output format and the name of the troff
macro package.
In addition, users can supply event procedures for their own macro
definitions (or replace existing ones) in form of a simple Scheme
program passed to
.I unroff
along with the troff input files; Scheme code can even be directly
embedded in the troff input as described below.
.LP
The full capabilities of
.IR unroff
and the Scheme primitives required to write extensions or support
for new output formats are described in the
.IR "Unroff Programmer's Manual" .
.SH "GENERIC OPTIONS"
.TP
.BI \-f format
Specifies the output format into which the troff input files are
translated.
If no
.B \-f
option is given, a default output format is used (for
.I unroff
version \*(Ve the default is
.B \-f\c
.IR html ).
This default can be overridden by setting the
.SB UNROFF_FORMAT
environment variable.
.TP
.BI \-m name
Specifies the name of the macro package that would be used by ordinary
troff to typeset the document.
In contrast to troff
.I unroff
does not actually load the macro package.
Instead, the specified name\-in combination with the specified output
format\-selects a set of Scheme files providing the procedure definitions
that control the translation process (see
.B FILES
below).
Therefore a corresponding
.B tmac
file need not exist for a given
.B \-m
option.
.TP
.BI \-h heapsize
This option can be used to specify a non-standard heap size (in Kbytes)
for the Scheme interpreter included in
.IR unroff ;
see
.BR elk (1).
.TP
.B \-C
Enables troff compatibility mode.
In compatibility mode certain
.I groff
extensions such as long names are not recognized.
.TP
.B \-t
Enables test mode.
Instead of processing troff input files,
.I unroff
enters an interactive Scheme top-level.
This can be useful to interactively experiment with the Scheme
primitives defined by
.I unroff
or to test or debug user-defined Scheme procedures.
.SH "KEYWORD/VALUE OPTIONS"
In addition to the generic options, a set of output-format-specific
options can be set from the command line and from within troff and
Scheme input files.
When specified on the command line, these options have the form
.Ex
\f2option\fP=\f2value\fP
.Ee
where the format of
.I value
depends on the
.I type
of the option.
For example, most output formats defines an option
.B document
whose value is used as a prefix for all output files created during
the translation.
The option is assigned a value by specifying a token such as
.Ex
document=thesis
.Ee
on the command line.
This option's value is interpreted as a plain string, i.\|e.\&
its type is
.BR string .
.LP
The Scheme back-ends and user-supplied extensions can define their
own option types, but at least the following types are recognized:
.TP 10n
.B integer
the option value is composed of an optional sign and an (arbitrary)
string of digits
.TP 10n
.B boolean
the option value must either be the character 1 (true) or the
character 0 (false)
.TP 10n
.B character
a single character must be specified as the option value
.TP 10n
.B string
an arbitrary string of characters can be specified
.TP 10n
.B dynstring
\*(lqdynamic string\*(rq; the option value is either
.RS
.TP
.I string
to assign a string to the option in the normal way, or
.TP
.BI + string
to append the characters after the plus sign
to the option's current value, or
.TP
.BI \- string
to remove the characters after the minus sign from the
option's current value.
.RE
.LP
These extension-specific options must appear after the generic
.I unroff
options and may be mixed with the file name arguments.
As the option assignments and specified input files are processed in
order, the value given for an option is in effect for all the input
files that appear on the command line to the right of the option.
.LP
The exact set of keyword/value options is determined by the
Scheme code loaded for a given combination of output format
and macro package name and is described in the corresponding
manuals.
The following few options can always be set, regardless of the
actual output format:
.TP
.BR include-files " (boolean)"
If true,
.B .so
requests are executed by
.I unroff
in the normal way (that is, the named input file is read and
parsed), otherwise
.B .so
requests are ignored.
The default value is 1.
.TP
.BR if-true " (dynstring)"
the specified characters are assigned to (appended to, removed from)
the set of one-character conditions that are regarded as true
by the
.B .if
and
.B .ie
requests.
The default value is "to".
.TP
.BR if-false " (dynstring)"
like
.BR if-true ;
specifies the one-character conditions regarded as false.
The default value is "ne".
.SH FILES
.SS "INPUT FILES"
On startup,
.I unroff
loads the Scheme source files that control the translation process.
All these files are loaded from subdirectories of a site-specific
\*(lqlibrary directory\*(rq, typically something like
.BR /usr/local/lib/unroff .
The directory is usually chosen by the system administrator when
installing the software and can be overridden by setting the
.SB UNROFF_DIR
environment variable.
The path names mentioned in the following are relative to this
library directory.
.LP
The first Scheme file loaded is
.B scm/troff.scm
which contains basic definitions such as the built-in options
and option types, implementations for troff requests that are
not output-format specific, and utility functions to be used
by the back-ends or by user-supplied extensions.
Next, the file
.BI scm/ format /common.scm
is loaded, where
.I format
is the value of the option
.B \-f
as given on the command line (or its default value).
The file implements the translation of the basic troff
requests, escape sequences, and special characters, etc.
The code dealing with macro invocations is loaded from
.BI scm/ format / package .scm
where
.I package
is the value of the option
.B \-m
with the letter `m' prepended.
.LP
Finally, the file
.B .unroff
is loaded from the caller's home directory if present.
Arbitrary Scheme code can be placed in this initialization file.
It is typically used to assign values to package-specific
keyword/value options according to the user's preferences
(by means of the
.I set-option!
Scheme primitive as explained in the Programmer's Manual).
.LP
When the initial files have been loaded, any troff input files specified
in the command line are read and parsed.
The special file name
.RB ` \- '
can be used to indicate standard input (usually in combination with
ordinary file names).
If no file name is given,
.I unroff
reads from standard input.
.LP
In addition to troff input files, file containing Scheme code can
be mentioned in the command line.
Scheme files (which by convention end in
.BR .scm )
are loaded into the Scheme interpreter and usually contain
used-defined Scheme procedures to translate specific macros or
to replace existing procedures, or other user-supplied extensions
of any kind.
Scheme files named in the command line (or loaded explicitly from
within other files) are resolved against the directory
.B scm/misc/
which may hold site-specific extensions or other supplementary
packages.
troff files and Scheme files can be mixed freely in the command line.
.SS "OUTPUT FILES"
Whether
.I unroff
sends its output to standard output or produces one or more output
files is not hard-wired but determined by the combination of output
format and macro package.
Generally, if no troff input files are specified, output is directed
to standard output, but this rule is not mandatory and may
be overridden by specific back-ends.
The
.B document
option is usually honored, although other rules may be employed to
determine the names of output files (for example, the extension
that implements
.B \-man
for a given output format may derive the name of the output file
for a manual page from the input file name; see
.BR unroff-html-man (1)).
.LP
If
.I unroff
is interrupted or quits early, any output files produced so far may be
incomplete or may contain wrong or inconsistent data, because
several passes may be required to complete an output file (for example,
to resolve cross references between a set of files), or because
an output file is not necessarily produced as a whole, but
.I unroff
may work on several files simultaneously.
.SH EXAMPLES
.LP
To translate a troff document composed of two files and written with the
\*(lqms\*(rq macro package to HTML 2.0,
.I unroff
might be called like this:
.Ex
unroff \-fhtml \-ms doc.tr doc.tr
.Ee
Two options specific to the combination of
.B \-fhtml
and
.B \-ms
might be added to specify a prefix for output files and to have
the resulting output split into separate files after each section
(see
.BR unroff-html-ms (1)):
.Ex
unroff \-fhtml \-ms document=out/ split=1 doc.tr doc.tr
.Ee
Additional features may be loaded from Scheme files specified in the
command line, e.\|g.\&
.B hyper.scm
which implements general Hypertext requests (and gets loaded from
.BR scm/misc/ )
and a user-supplied file in the current directory providing translation
rules for user-defined troff macros:
.Ex
.ne 2
unroff \-fhtml \-ms document=out/ split=1 hyper.scm doc.scm\e
doc.tr doc.tr
.Ee
.SH "TROFF SUPPORT AND EXTENSIONS"
As
.I unroff
translates troff input into another language rather than typesetting
the text in the usual way, its processing model necessarily differs
from that of conventional troff.
For a detailed description refer to the Programmer's Manual.
.LP
In brief,
.I unroff
copies characters from input to output, optionally performing
target-language-specific character translations.
For each request or macro invocation, string or number register
reference, special character, escape sequence, sentence end, or
.BR eqn (1)
inline equation encountered in the input stream,
.I unroff
checks whether an \*(lqevent value\*(rq has been specified by
the Scheme code (user-supplied or part of the back-end).
An event value is either a plain string, which is then treated as
if it had been part of the input stream, or a Scheme procedure,
which is then invoked and must in turn return a string.
The Scheme procedures are passed arguments, e.\|g. the macro
or request arguments in case of a procedure attached to a macro
or request, or an escape sequence argument for functions such as
`\ef' or `\ew'.
.LP
If no event value has been associated with a particular macro,
string, or number register,
.I unroff
checks whether a definition has been supplied in the normal way,
i.\|e. by means of
.BR .de ,
.BR .ds ,
or
.BR .nr .
In this case, the value of the macro, string, or register is
interpolated as done by ordinary troff.
If no definition can be found, a fallback definition is looked up
as a last resort; and if everything fails, a warning is printed
and the event is ignored.
Similarly, event procedures are invoked at end of input line,
when an input file is opened or closed, at program start and
termination, and for each option specified in the command line;
but these procedures are called solely for their side-effects
(i.\|e. the return values are ignored).
.LP
Most Scheme procedures just emit the target language's representation
of the event with which they are associated.
Other procedures perform various kinds of bookkeeping; the procedure
associated with the
.B .de
request, for example, puts the text following
aside for later expansion, and the event procedures attached to
the requests
.B .ds
and
.B .nr
and to the escape sequences `\e*' and `\en'
implement troff strings and number registers.
This way, even basic troff functions need not be hard-wired and can
be altered or replaced freely without recompiling
.IR unroff .
.LP
The rule that an event value associated with a macro has precedence
over the actual macro definition accommodates higher-level,
structure-oriented target languages (such as SGML).
While the micro-formatting contained in a typical
.B \-ms
macro definition, for example, makes sense to an ordinary typesetting
program, it is usually impossible to infer the macro's
.I structural
function from it (new paragraph, quotation, etc.).
On the other hand, troff documents often define a few additional,
simple macros that just serve as an abbreviation for a sequence
of predefined macros; in this case event procedures need not
specified, as
.I unroff
will then perform normal macro expansion.
.LP
.I unroff
usually takes care to not rescan the characters returned by event
procedures as if their results had been normal input, because
most event procedures already return code in the target language rather
than troff input that can be rescanned.
This, however, cannot always be avoided; for example, if a troff string
reference occurs at macro definition time (because `\e*' is used rather
than `\e\e*'), the string value ends up in the macro body and will still
be rescanned when the macro is invoked.
A few other pitfalls caused by differences in the processing models of
troff and
.I unroff
are listed in the BUGS section below.
.LP
The scaling performed for the usual troff scale indicators
can be manipulated by a calling a Scheme primitive from within
the Scheme code implementing a particular back-end.
.SS "NEW TROFF REQUESTS"
To aid transparent output of code in the target language and
evaluation of inline Scheme code,
.I unroff
supports two new requests and two extensions to the
.B .ig
(ignore input lines) troff request.
.LP
If
.B .ig
is called with the symbol
.B >>
as its first argument, all input lines up to (but not including)
the terminating
.B .>>
are sent to the current output file.
Example:
when translating to the Hypertext Markup Language, the construct
could be used to emit literal HTML code like this:
.Ex
.ne 6
\&.ig >>
<address>
Bart Simpson<br>
Springfield
</address>
\&.>>
.Ee
.LP
To produce a single line of output, the new request
.B .>>
can be used as in this HTML example:
.Ex
\&.>> "<code>result = i+1;</code>"
.Ee
.LP
If the
.B .ig
request is called with the argument
.BR ##,
everything up to the terminating
.B .##
is passed to the Scheme interpreter for evaluation.
This allows users to embed Scheme code in a troff document which
is executed when the document is processed by
.IR unroff .
One use of this construct is to provide a Scheme event procedure
for a user-defined macro by placing the corresponding Scheme
definition in the same source file right below the troff macro definition.
Similarly, the request
.B .##
can be used to evaluate a short S-expression; all arguments to
the request are concatenated and then passed to the Scheme
interpreter.
.LP
Note that inline Scheme code is a potentially dangerous feature,
as a document received by someone else may contain embedded code
that does something unexpected when the file is processed by
.I unroff
(but it is probably not more dangerous than the standard troff
.B .pi
request or the
.B .sy
request of
.IR ditroff ).
.LP
.I unroff
defines the following new read-only number registers:
.TP
.B .U
This register always expand to 1.
It can be used by macros to determine whether the document is
being processed by
.IR unroff .
.TP
.B .C
Expands to 1 if troff compatibility mode has been enabled
by using the option
.BR \-C ,
to 0 otherwise.
.LP
The following new escape sequences are available in a macro
body during macro expansion:
.TP
.B $0
The name of the current macro.
.TP
.B $*
The concatenation of all arguments, separated by spaces.
.TP
.B $@
The concatenation of all arguments, separated by spaces, and
with each argument enclosed by double quotes.
.LP
The names of strings, macros, number registers, and fonts may be of
any length.
As in
.IR groff ,
square brackets can be used for names of arbitrary length:
.Ex
\ef[font] \e*[string] \en[numreg] ...
.Ee
.LP
There is no limit on the number of macro arguments, and the following
syntax can be used to reference the 10th, 11th, etc. macro argument:
.Ex
\e$(12 \e$[12] \e$[123]
.Ee
.LP
Unless troff compatibility mode has been enabled, the arguments to the
.IR groff -specific
escape sequences `\eA', `\eC', '\eL', '\eN', '\eR', '\eV', '\eY',
and '\eZ' are recognized and parsed, so that event procedures
can be implemented correctly for these escape sequences.
.SH "SEE ALSO"
.BR unroff-html (1),
.BR unroff-html-man (1),
.BR unroff-html-ms (1);
.br
.BR troff (1),
.BR groff (1);
.BR elk (1).
.LP
Unroff Programmer's Manual.
.LP
http://www.informatik.uni-bremen.de/~net/unroff
.SH AUTHOR
Oliver Laumann, net@cs.tu-berlin.de
.SH BUGS
A number of low-level formatting features of troff (such as the
absolute position indicator in numerical expressions)
are not yet supported by
.I unroff
version \*(Ve, which is not critical for higher-level,
structure-oriented target languages such as the Hypertext
Markup Language.
.LP
Diversions are not supported, although specific back-ends are
free to add this functionality.
.LP
Special characters are not treated right in certain contexts;
in particular, special characters may not be used in place
of plain characters where the characters act as some kind of
delimiter as in
.Ex
\&.if \e(bsfoo\e(bsbar\e(bs ...
.Ee
.LP
Spaces in an
.B .if
condition do not work; e.\|g. the following fails:
.Ex
\&.if ' ' ' ...
.Ee
.LP
Conditional input is subject to string and number register
expansion even if the corresponding if-condition evaluates to false.
.LP
There are no number register formats, i.\|e. the request
.B .af
does not work.
.LP
The set of punctuation marks that indicate end of sentence
should be configurable.
.LP
Empty input lines and leading space should trigger a special
event, so that their break semantics can be implemented correctly.
.LP
A comment in a line by itself currently does not generate a
blank line.

4
elk/README Normal file
View File

@ -0,0 +1,4 @@
This directory holds a minimal, self-contained Elk runtime environment.
All the files have been copied from the Elk 3.0 distribution. If you
have Elk installed at your site, you can replace this directory by
a symbolic link to your site's Elk runtime directory.

212
elk/scm/debug.scm Normal file
View File

@ -0,0 +1,212 @@
;;; -*-Scheme-*-
;;;
;;; A simple debugger (improvements by Thomas M. Breuel <tmb@ai.mit.edu>).
(define (backtrace . args)
(if (> (length args) 1)
(error 'backtrace "too many arguments"))
(if (not (null? args))
(if (not (eq? (type (car args)) 'control-point))
(error 'backtrace "argument must be a control point")))
(let ((trace (apply backtrace-list args)))
(if (null? args)
(set! trace (cdddr trace)))
(show-backtrace trace 0 999999)))
(define (show-backtrace trace start-frame end-frame)
(define (rjust n x)
(let* ((y (string-append (make-string n #\space) x))
(l (string-length y)))
(substring y (- l n) l)))
(let ((maxlen 28))
(let loop ((frames (list-tail trace start-frame)) (num start-frame))
(if (or (null? frames) (>= num end-frame)) #v
(let ((frame (car frames)))
(let* ((func
(format #f "~s" (vector-ref frame 0)))
(indent
(- maxlen (+ 5 (string-length func)))))
(display (rjust 4 (number->string num)))
(display " ")
(display func)
(if (negative? indent)
(begin
(newline)
(set! indent maxlen)))
(do ((i indent (1- i)))
((> 0 i))
(display " ")))
(fluid-let
((print-depth 2)
(print-length 3))
(display (vector-ref frame 1)))
(newline))
(loop (cdr frames) (1+ num))))))
(define (show-environment env)
(fluid-let
((print-length 2)
(print-depth 2))
(do ((f (environment->list env) (cdr f)))
((null? f))
(do ((b (car f) (cdr b)))
((null? b))
(format #t "~s\t~s~%" (caar b) (cdar b)))
(print '-------)))
#v)
(define inspect)
(let ((frame)
(trace)
(help-text
'("q -- quit inspector"
"f -- print current frame"
"u -- go up one frame"
"d -- go down one frame"
"^ -- go to top frame"
"$ -- go to bottom frame"
"g <n> -- goto to n-th frame"
"e -- eval expressions in environment"
"p -- pretty-print procedure"
"v -- show environment"
"<n> -- pretty-print n-th argument"
"b -- show backtrace starting at current frame"
"t -- show top of bracktrace starting at current frame"
"z -- show and move top of backtrace starting at current frame"
"o -- obarray information")))
(define (inspect-command-loop)
(let ((input) (done #f))
(display "inspect> ")
(set! input (read))
(case input
(q
(set! done #t))
(?
(for-each
(lambda (msg)
(display msg)
(newline))
help-text))
(f
(print-frame))
(^
(set! frame 0)
(print-frame))
($
(set! frame (1- (length trace)))
(print-frame))
(u
(if (zero? frame)
(format #t "Already on top frame.~%")
(set! frame (1- frame))
(print-frame)))
(d
(if (= frame (1- (length trace)))
(format #t "Already on bottom frame.~%")
(set! frame (1+ frame))
(print-frame)))
(g
(set! input (read))
(if (integer? input)
(set! frame
(cond ((negative? input) 0)
((>= input (length trace)) (1- (length trace)))
(else input)))
(format #t "Frame number must be an integer.~%")))
(v
(show-environment (vector-ref (list-ref trace frame) 2)))
(e
(format #t "Type ^D to return to Inspector.~%")
(let loop ()
(display "eval> ")
(set! input (read))
(if (not (eof-object? input))
(begin
(write (eval input
(vector-ref (list-ref trace frame) 2)))
(newline)
(loop))))
(newline))
(p
(pp (vector-ref (list-ref trace frame) 0))
(newline))
(z
(show-backtrace trace frame (+ frame 10))
(set! frame (+ frame 9))
(if (>= frame (length trace)) (set! frame (1- (length trace)))))
(t
(show-backtrace trace frame (+ frame 10)))
(b
(show-backtrace trace frame 999999))
(o
(let ((l (map length (oblist))))
(let ((n 0))
(for-each (lambda (x) (set! n (+ x n))) l)
(format #t "~s symbols " n)
(format #t "(maximum bucket: ~s).~%" (apply max l)))))
(else
(cond
((integer? input)
(let ((args (vector-ref (list-ref trace frame) 1)))
(if (or (< input 1) (> input (length args)))
(format #t "No such argument.~%")
(pp (list-ref args (1- input)))
(newline))))
((eof-object? input)
(set! done #t))
(else
(format #t "Invalid command. Type ? for help.~%")))))
(if (not done)
(inspect-command-loop))))
(define (print-frame)
(format #t "~%Frame ~s of ~s:~%~%" frame (1- (length trace)))
(let* ((f (list-ref trace frame)) (args (vector-ref f 1)))
(format #t "Procedure: ~s~%" (vector-ref f 0))
(format #t "Environment: ~s~%" (vector-ref f 2))
(if (null? args)
(format #t "No arguments.~%")
(fluid-let
((print-depth 2)
(print-length 3))
(do ((i 1 (1+ i)) (args args (cdr args))) ((null? args))
(format #t "Argument ~s: ~s~%" i (car args))))))
(newline))
(define (find-frame proc)
(let loop ((l trace) (i 0))
(cond ((null? l) -1)
((eq? (vector-ref (car l) 0) proc) i)
(else (loop (cdr l) (1+ i))))))
(set! inspect
(lambda ()
(set! trace (backtrace-list))
(set! trace (cddr trace))
(do ((t trace (cdr t)) (f 1 (1+ f))) ((null? t))
(if (not (null? (vector-ref (car t) 1)))
(let ((last (last-pair (vector-ref (car t) 1))))
(if (not (null? (cdr last)))
(begin
(format #t
"[inspector: fixing improper arglist in frame ~s]~%" f)
(set-cdr! last (cons (cdr last) '())))))))
(set! frame (find-frame error-handler))
(if (negative? frame)
(set! frame 0))
(format #t "Inspector (type ? for help):~%")
(let loop ()
(if (call-with-current-continuation
(lambda (control-point)
(push-frame control-point)
(inspect-command-loop)
#f))
(begin
(pop-frame)
(loop))))
(newline)
(pop-frame)
(let ((next-frame (car rep-frames)))
(next-frame #t)))))

81
elk/scm/initscheme.scm Normal file
View File

@ -0,0 +1,81 @@
;;; -*-Scheme-*-
;;;
;;; Initialization code for the Elk interpreter kernel.
;;;
;;; This file is loaded on startup before the toplevel (or the file
;;; supplied along with the -l option) is loaded.
;;;
;;; If a garbage collection is triggered while loading this file,
;;; it is regarded as an indication that the heap size is too small
;;; and an error message is printed.
;;; Primitives that are part of the core functionality but are not
;;; implemented in C. This is a bad thing, because extension or
;;; application writers should be able to rely on P_Expt().
(define (expt x y)
(define (square x) (* x x))
(define (integer-expt b n)
(cond ((= n 0) 1)
((negative? n) (/ 1 (integer-expt b (abs n))))
((even? n) (square (integer-expt b (/ n 2))))
(else (* b (integer-expt b (- n 1))))))
(cond ((zero? x) (if (zero? y) 1 0))
((integer? y) (integer-expt x y))
(else (exp (* (log x) y)))))
;;; Synonyms:
(define call/cc call-with-current-continuation)
;;; Backwards compatibility. These procedures are really obsolete;
;;; please do not use them any longer.
(define (close-port p)
(if (input-port? p) (close-input-port p) (close-output-port p)))
(define (void? x) (eq? x (string->symbol "")))
(define (re-entrant-continuations?) #t)
;;; Useful macros (these were loaded by the standard toplevel in
;;; earlier versions of Elk). They shouldn't really be here, but
;;; it's too late...
(define (expand form)
(if (or (not (pair? form)) (null? form))
form
(let ((head (expand (car form))) (args (expand (cdr form))) (result))
(if (and (symbol? head) (bound? head))
(begin
(set! result (macro-expand (cons head args)))
(if (not (equal? result form))
(expand result)
result))
(cons head args)))))
(define-macro (unwind-protect body . unwind-forms)
`(dynamic-wind
(lambda () #f)
(lambda () ,body)
(lambda () ,@unwind-forms)))
(define-macro (while test . body)
`(let loop ()
(cond (,test ,@body (loop)))))
(define-macro (when test . body)
`(cond (,test ,@body)))
(define-macro (unless test . body)
`(when (not ,test) ,@body))
(define-macro (multiple-value-bind vars form . body)
`(apply (lambda ,vars ,@body) ,form))

117
elk/scm/pp.scm Normal file
View File

@ -0,0 +1,117 @@
;;; -*-Scheme-*-
;;;
;;; Trivial pretty-printer
(provide 'pp)
(define pp)
(let ((max-pos 55) (pos 0) (tab-stop 8))
(put 'lambda 'special #t)
(put 'macro 'special #t)
(put 'define 'special #t)
(put 'define-macro 'special #t)
(put 'define-structure 'special #t)
(put 'fluid-let 'special #t)
(put 'let 'special #t)
(put 'let* 'special #t)
(put 'letrec 'special #t)
(put 'case 'special #t)
(put 'call-with-current-continuation 'long #t)
(put 'quote 'abbr "'")
(put 'quasiquote 'abbr "`")
(put 'unquote 'abbr ",")
(put 'unquote-splicing 'abbr ",@")
(set! pp (lambda (x)
(set! pos 0)
(cond ((eq? (type x) 'compound)
(set! x (procedure-lambda x)))
((eq? (type x) 'macro)
(set! x (macro-body x))))
(fluid-let ((garbage-collect-notify? #f))
(pp-object x))
#v))
(define (flat-size s)
(fluid-let ((print-length 50) (print-depth 10))
(string-length (format #f "~a" s))))
(define (pp-object x)
(if (or (null? x) (pair? x))
(pp-list x)
(if (void? x)
(display "#v")
(write x))
(set! pos (+ pos (flat-size x)))))
(define (pp-list x)
(if (and (pair? x)
(symbol? (car x))
(string? (get (car x) 'abbr))
(= 2 (length x)))
(let ((abbr (get (car x) 'abbr)))
(display abbr)
(set! pos (+ pos (flat-size abbr)))
(pp-object (cadr x)))
(if (> (flat-size x) (- max-pos pos))
(pp-list-vertically x)
(pp-list-horizontally x))))
(define (pp-list-vertically x)
(maybe-pp-list-vertically #t x))
(define (pp-list-horizontally x)
(maybe-pp-list-vertically #f x))
(define (maybe-pp-list-vertically vertical? list)
(display "(")
(set! pos (1+ pos))
(if (null? list)
(begin
(display ")")
(set! pos (1+ pos)))
(let ((pos1 pos))
(pp-object (car list))
(if (and vertical?
(or
(and (pair? (car list))
(not (null? (cdr list))))
(and (symbol? (car list))
(get (car list) 'long))))
(indent-newline (1- pos1)))
(let ((pos2 (1+ pos)) (key (car list)))
(let tail ((flag #f) (l (cdr list)))
(cond ((pair? l)
(if flag
(indent-newline
(if (and (symbol? key) (get key 'special))
(1+ pos1)
pos2))
(display " ")
(set! pos (1+ pos)))
(pp-object (car l))
(tail vertical? (cdr l)))
(else
(cond ((not (null? l))
(display " . ")
(set! pos (+ pos 3))
(if flag (indent-newline pos2))
(pp-object l)))
(display ")")
(set! pos (1+ pos)))))))))
(define (indent-newline x)
(newline)
(set! pos x)
(let loop ((i x))
(cond ((>= i tab-stop)
(display "\t")
(loop (- i tab-stop)))
((> i 0)
(display " ")
(loop (1- i)))))))

110
elk/scm/toplevel.scm Normal file
View File

@ -0,0 +1,110 @@
;;; -*-Scheme-*-
;;;
;;; Read-eval-print loop and error handler
(autoload 'pp 'pp.scm)
(autoload 'apropos 'apropos.scm)
(autoload 'sort 'qsort.scm)
(autoload 'describe 'describe.scm)
(autoload 'backtrace 'debug.scm)
(autoload 'inspect 'debug.scm)
(define ?)
(define ??)
(define ???)
(define !)
(define !!)
(define !!!)
(define &)
(define (rep-loop env)
(define input)
(define value)
(let loop ()
(set! ??? ??)
(set! ?? ?)
(set! ? &)
;;; X Windows hack
(if (and (bound? 'display-flush-output) (bound? 'dpy) (display? dpy))
(display-flush-output dpy))
(if (> rep-level 0)
(display rep-level))
(display "> ")
(set! input (read))
(set! & input)
(if (not (eof-object? input))
(begin
(set! value (eval input env))
(set! !!! !!)
(set! !! !)
(set! ! value)
(write value)
(newline)
(loop)))))
(define rep-frames)
(define rep-level)
(set! interrupt-handler
(lambda ()
(format #t "~%\7Interrupt!~%")
(let ((next-frame (car rep-frames)))
(next-frame #t))))
(define-macro (push-frame control-point)
`(begin
(set! rep-frames (cons ,control-point rep-frames))
(set! rep-level (1+ rep-level))))
(define-macro (pop-frame)
'(begin
(set! rep-frames (cdr rep-frames))
(set! rep-level (1- rep-level))))
(define (error-print error-msg)
(format #t "~s: " (car error-msg))
(apply format `(#t ,@(cdr error-msg)))
(newline))
(set! error-handler
(lambda error-msg
(error-print error-msg)
(let loop ((intr-level (enable-interrupts)))
(if (positive? intr-level)
(loop (enable-interrupts))))
(let loop ()
(if (call-with-current-continuation
(lambda (control-point)
(push-frame control-point)
(rep-loop (the-environment))
#f))
(begin
(pop-frame)
(loop))))
(newline)
(pop-frame)
(let ((next-frame (car rep-frames)))
(next-frame #t))))
(define top-level-environment (the-environment))
(define (top-level)
(let loop ()
;;; Allow GC to free old rep-frames when we get here on "reset":
(set! rep-frames (list top-level-control-point))
(if (call-with-current-continuation
(lambda (control-point)
(set! rep-frames (list control-point))
(set! top-level-control-point control-point)
(set! rep-level 0)
(rep-loop top-level-environment)
#f))
(loop))))
(define (the-top-level)
(top-level)
(newline)
(exit))
(the-top-level)

14
misc/sample.unroff Normal file
View File

@ -0,0 +1,14 @@
;;;; -*-Scheme-*-
;;;;
;;;; ~/.unroff -- initializations for unroff
(define signature
(concat "Markup created by <em>%progname%</em> %version%," nbsp nbsp
"%monthname+% %day%, %year%," nbsp nbsp "net@cs.tu-berlin.de"))
(eval-if-mode (* html)
(set-option! 'mail-address "net@cs.tu-berlin.de"))
(eval-if-mode (ms html)
(set-option! 'title "An `ms' document")
(set-option! 'signature signature))

597
scm/html/common.scm Normal file
View File

@ -0,0 +1,597 @@
;;;; -*-Scheme-*-
;;;;
;;;; $Revision: 1.20 $
;;;;
;;;; Common definitions for HTML output format
;;; --------------------------------------------------------------------------
;;; Configurable, site-specific definitions.
(define-option 'troff-to-gif 'string
"groff -ms > %1%; /usr/www/lib/latex2html/pstogif %1% -out %2%")
(define-option 'troff-to-text 'string
"groff -Tlatin1 -P-b -P-u |sed '/^[ \t]*$/d' > %1%")
(define-option 'tbl 'string 'gtbl)
(define-option 'eqn 'string 'geqn)
(define-option 'pic 'string 'gpic)
;; A non-breaking space that is really non-breaking even in broken browsers:
(define nbsp "&#160;<tt> </tt>")
;;; --------------------------------------------------------------------------
;;; Options.
(define-option 'title 'string #f) ; May be used for <title>
(define-option 'mail-address 'string #f) ; May be used for `mailto:'
(define-option 'document 'string #f) ; Prefix for output file(s)
(define-option 'tt-preformat 'boolean #f) ; do <tt>-changes inside .nf/.fi
(define-option 'handle-eqn 'string "gif") ; gif/text/copy
(define-option 'handle-tbl 'string "text") ;
(define-option 'handle-pic 'string "gif") ;
;;; --------------------------------------------------------------------------
;;; Preformatted text.
(define preform? #f)
(define (preform on?)
(cond ((and on? (not preform?))
(defsentence #f)
(with-font-preserved
(begin (set! preform? #t) "<pre>\n")))
((and (not on?) preform?)
(defsentence sentence-event)
(with-font-preserved
(begin (set! preform? #f) "</pre>\n")))
(else "")))
(defrequest 'nf (lambda _ (preform #t)))
(defrequest 'fi (lambda _ (preform #f)))
(define-macro (with-preform-preserved . body)
`(let (($p preform?))
(concat (preform #f) ,@body (preform $p))))
(defchar #\tab
(lambda (c)
(if (not preform?) (surprise "tab outside .nf/.fi")) c))
;;; --------------------------------------------------------------------------
;;; Silently ignoring these requests probably will not harm. There is
;;; nothing sensible we can do.
(defrequest 'ne "")
(defrequest 'hw "")
(defrequest 'nh "")
(defrequest 'hy "")
(defrequest 'lg "")
(defrequest 'ps "")
(defrequest 'vs "")
(defrequest 'pl "")
(defrequest 'bp "")
(defrequest 'ns "")
(defrequest 'rs "")
(defrequest 'wh "")
(defrequest 'ch "")
(defrequest 'fl "")
(defrequest 'na "")
(defrequest 'ad "")
;;; --------------------------------------------------------------------------
;;; Basic escape sequences and special characters.
(defescape #\c "") ; swallows its character argument
(defescape #\& "")
(defescape #\- #\-)
(defescape #\| "")
(defescape #\^ "")
(defescape #\space #\space) ; should be &#160; (doesn't work in Mosaic)
(defescape #\0 #\space)
(defescape #\s "")
(defescape #\e #\\)
(defescape #\\ #\\)
(defescape #\' #\')
(defescape #\` #\`)
(defescape #\% "")
(defescape ""
(lambda (c . _)
(warn "escape sequence `\\~a' expands to `~a'" c c)
(translate c)))
(defspecial 'em "--")
(defspecial 'en #\-)
(defspecial 'mi #\-)
(defspecial 'pl #\+) ; plus
(defspecial 'lq "``")
(defspecial 'rq "''")
(defspecial '** #\*)
(defspecial 'bv #\|) ; bold vertical (what is this?)
(defspecial 'hy "&#173;") ; `soft hyphen'
(defspecial 'co "&#169;") ; copyright
(defspecial 'ap #\~) ; approximates
(defspecial '~= #\~)
(defspecial 'cd "&#183;") ; centered dot
(defspecial 'de "&#176;") ; degree
(defspecial '>= "&gt;=")
(defspecial '<= "&lt;=")
(defspecial 'eq #\=)
(defspecial '== "==")
(defspecial 'mu "&#215;") ; multiplication
(defspecial 'tm "&#174;")
(defspecial 'rg "&#174;")
(defspecial '*m "&#181;") ; mu
(defspecial '*b "&#223;") ; beta (#223 is German sharp-s actually)
(defspecial 'aa #\') ; acute accent
(defspecial 'ga #\`) ; grave accent
(defspecial 'br #\|) ; vertical box rule
(defspecial 'or #\|)
(defspecial 'sl #\/)
(defspecial 'ru #\_)
(defspecial 'ul #\_)
(defspecial 'ci #\O)
(defspecial "14" "&#188;")
(defspecial "12" "&#189;")
(defspecial "34" "&#190;")
(defspecial 'es "&#216;")
(defspecial '+- "&#177;")
(defspecial 'sc "&#167;")
(defspecial 'fm #\') ; foot mark
(defspecial 'lh "&lt;=")
(defspecial 'rh "=&gt;")
(defspecial '-> "-&gt;")
(defspecial '<- "&lt;-")
(defspecial 'no "&#172;") ; negation
(defspecial 'di "&#247;") ; division
(defspecial 'ss "&#223;")
(defspecial ':a "&#228;")
(defspecial 'a: "&#228;")
(defspecial ':o "&#246;")
(defspecial 'o: "&#246;")
(defspecial ':u "&#252;")
(defspecial 'u: "&#252;")
(defspecial ':A "&#196;")
(defspecial 'A: "&#196;")
(defspecial ':O "&#214;")
(defspecial 'O: "&#214;")
(defspecial ':U "&#220;")
(defspecial 'U: "&#220;")
(defspecial 'ct "&#162;") ; cent
(defspecial 'Po "&#163;") ; pound
(defspecial 'Cs "&#164;") ; currency sign
(defspecial 'Ye "&#165;") ; yen
(defspecial 'ff "ff")
(defspecial 'fi "fi")
(defspecial 'fl "fl")
(defspecial 'Fi "ffi")
(defspecial 'Fl "ffl")
(defspecial 'S1 "&#185;")
(defspecial 'S2 "&#178;")
(defspecial 'S3 "&#179;")
(defspecial 'bb "&#166;") ; broken bar
(defspecial 'r! "&#161;") ; reverse exclamation mark
(defspecial 'r? "&#191;") ; reverse question mark
(defspecial 'bu (lambda _ (warn "rendering \\(bu as `+'") #\+))
(defspecial 'sq (lambda _ (warn "rendering \\(sq as `o'") #\o))
(defspecial 'dg (lambda _ (warn "rendering \\(dg as `**'") "**"))
(defspecial 'dd (lambda _ (warn "rendering \\(dd as `***'") "***"))
;;; --------------------------------------------------------------------------
;;; Local motion requests and related stuff (mostly ignored).
(define (motion-ignored request . _)
(warn "local motion request \\~a ignored" request))
(defescape #\u motion-ignored)
(defescape #\d motion-ignored)
(defescape #\v motion-ignored)
(define (motion-no-effect request arg)
(warn "local motion request \\~a has no effect" request)
(parse arg))
(defescape #\o motion-no-effect)
(defescape #\z motion-no-effect)
(defescape #\k
(lambda (k reg)
((requestdef 'nr) 'nr reg "0" "")))
(defescape #\h
(lambda (h arg)
(let* ((x (parse arg))
(n (get-hunits (parse-expression x 0 #\m))))
(if (negative? n)
(warn "\\h with negative argument ignored")
(make-string n #\space)))))
(defescape #\w
(lambda (w s)
(let ((scale (get-scaling #\m))
(len (string-length (parse s))))
(number->string (quotient (* len (car scale)) (cdr scale))))))
;; Heuristic: generate <hr> if length could be line length, else
;; repeat specified character:
(defescape #\l
(lambda (l s)
(let* ((p (parse-expression-rest s '(0 . "") #\m))
(n (get-hunits (car p)))
(c (parse (cdr p))))
(if (>= n line-length)
"<hr>"
(repeat-string n (if (eqv? c "") "_" c))))))
;;; --------------------------------------------------------------------------
;;; Output translations for HTML special characters.
(defchar #\< "&lt;")
(defchar #\> "&gt;")
(defchar #\& "&amp;")
;;; Like parse, but also take char of `"':
(define (parse-unquote s)
(let ((old (defchar #\" "&quot;")))
(begin1 (parse s) (defchar #\" old))))
;;; --------------------------------------------------------------------------
;;; Font handling.
(define font-table (make-table 100))
(define (define-font name open close)
(table-store! font-table name (cons open close)))
(define-font "R" "" "")
(define-font "I" '<i> '</i>)
(define-font "B" '<b> '</b>)
(define-font "C" '<tt> '</tt>)
(define-font "CW" '<tt> '</tt>)
(define-font "CO" '<i> '</i>) ; a kludge for Courier-Oblique
(define font-positions (make-vector 10 #f))
(define (find-font f start)
(cond
((= start (vector-length font-positions)) #f)
((equal? (vector-ref font-positions start) f) start)
(else (find-font f (1+ start)))))
(define (font->position f)
(let* ((m (find-font f 1)) (n (if m m (find-font #f 1))))
(cond
(n (mount-font n f) n)
(else
(warn "no free font position for font ~a" f) #f))))
(define (get-font-name name)
(cond
((table-lookup font-table name) name)
(else (warn "unknown font: ~a" name) "R")))
(define (mount-font i name)
(if (and (>= i 1) (< i (vector-length font-positions)))
(vector-set! font-positions i (get-font-name name))
(warn "invalid font position: `~a'" i)))
(mount-font 1 "R")
(mount-font 2 "I")
(mount-font 3 "B")
(mount-font 4 "R")
(defrequest 'fp
(lambda (fp where name)
(if (not (string->number where))
(warn "invalid font position `~a' in .fp" where)
(mount-font (string->number where) name) "")))
(define previous-font 1)
(define current-font 1)
(define (reset-font)
(concat (change-font 1) (change-font 1))) ; current and previous
(define (change-font-at i)
(cond
((or (< i 1) (>= i (vector-length font-positions)))
(warn "invalid font position: `~a'" i))
((vector-ref font-positions i)
(let ((o (table-lookup font-table
(vector-ref font-positions current-font)))
(n (table-lookup font-table (vector-ref font-positions i))))
(set! previous-font current-font)
(set! current-font i)
(if (and preform? (not (option 'tt-preformat)))
(concat (if (eq? (cdr o) '</tt>) "" (cdr o))
(if (eq? (car n) '<tt>) "" (car n)))
(concat (cdr o) (car n)))))
(else (warn "no font mounted at position ~a" i))))
(define (change-font f)
(cond
((number? f)
(change-font-at f))
((string->number f)
(change-font-at (string->number f)))
((string=? f "P")
(change-font-at previous-font))
(else
(let ((n (font->position (get-font-name f))))
(if n (change-font-at n) "")))))
(defrequest 'ft
(lambda (ft font)
(change-font (if (eqv? font "") "P" font))))
(defescape #\f (requestdef 'ft))
(defnumreg '.f (lambda _ (number->string current-font)))
(define-macro (with-font-preserved . body)
`(let (($f current-font))
(concat (change-font "R") ,@body (change-font $f))))
;;; --------------------------------------------------------------------------
;;; tbl, eqn, pic.
(define (copy-preprocess for-eqn? proc-1 proc-2 stop inline)
(cond
(inline
(emit inline #\newline stop)
(filter-eqn-line inline))
(else
(let loop ((x (read-line-expand))
(use-output? (not for-eqn?)))
(cond ((eof-object? x) use-output?)
(else
(proc-1 (proc-2 x))
(if (string=? x stop)
use-output?
(loop (read-line-expand)
(or (not for-eqn?) (filter-eqn-line x))))))))))
(define troff-to-gif
(let ((image-seqnum 1))
(lambda (processor start stop what args inline)
(let ((docname (option 'document)))
(if (not docname)
(begin
(warn "~a skipped, because no `document' option given" what)
(if (not inline)
(skip-lines stop))
"")
(let* ((num (number->string image-seqnum))
(psname (concat docname #\- num ".ps"))
(gifname (concat docname #\- num ".gif"))
(ref (concat "<img src=\"" gifname
"\" alt=\"[" what "]\">\n"))
(use-output? #f))
(++ image-seqnum)
(with-output-to-stream
(substitute (concat #\| (option processor)
#\| (option 'troff-to-gif)) psname gifname)
(emit start #\space (apply spread args) #\newline)
(set! use-output? (copy-preprocess (eq? processor 'eqn)
emit identity stop inline)))
(remove-file psname)
(if use-output?
(if inline ref (concat "<p>" ref "<p>\n"))
(remove-file gifname) "")))))))
(define (troff-to-text processor start stop what args inline)
(let* ((tmpname (substitute "%tmpname%"))
(use-output? #f))
(with-output-to-stream
(substitute (concat #\| (option processor) #\| (option 'troff-to-text))
tmpname)
(emit start #\space (apply spread args) #\newline)
(set! use-output? (copy-preprocess (eq? processor 'eqn)
emit identity stop inline)))
(let ((text (translate (stream->string tmpname))))
(remove-file tmpname)
(if use-output?
(if inline
(with-font-preserved (concat (change-font 2) text))
(concat (preform #t) text (preform #f)))
""))))
(define (troff-to-preform processor start stop what args inline)
(cond
(inline (with-font-preserved (concat (change-font 2) inline)))
(else
(emit (preform #t) start #\space (apply spread args) #\newline)
(copy-preprocess (eq? processor 'eqn) emit translate stop)
(preform #f))))
(define (troff-select-method option-name)
(let ((method (option option-name)))
(cond ((string=? method "gif") troff-to-gif)
((string=? method "text") troff-to-text)
((string=? method "copy") troff-to-preform)
(else
(warn "bad value `~a' for ~a, assuming `text'" method option-name)
troff-to-text))))
(defmacro 'TS
(lambda (TS . args)
((troff-select-method 'handle-tbl) 'tbl ".TS" ".TE\n" "table" args #f)))
(defmacro 'EQ
(lambda (EQ . args)
((troff-select-method 'handle-eqn) 'eqn ".EQ" ".EN\n" "equation" args #f)))
(defmacro 'PS
(lambda (PS . args)
((troff-select-method 'handle-pic) 'pic ".PS" ".PE\n" "picture" args #f)))
(defmacro 'TE "")
(defmacro 'EN "")
(defmacro 'PE "")
(defequation
(lambda (eqn)
((troff-select-method 'handle-eqn) 'eqn ".EQ" ".EN\n" "equation" '() eqn)))
;;; --------------------------------------------------------------------------
;;; Miscellaneous troff requests.
(defrequest 'br
(lambda _
(if (positive? lines-to-center) "" "<br>\n")))
(defrequest 'sp
(lambda (sp num)
(let ((n (if (eqv? num "") 1 (get-vunits (parse-expression num 0 #\v)))))
(cond
((negative? n)
(warn ".sp with negative spacing ignored"))
(preform?
(repeat-string n "\n"))
((zero? n)
"<br>\n")
(else
(with-font-preserved (repeat-string n "<p>\n")))))))
(defrequest 'ti
(lambda (ti num)
(let ((n (if (eqv? num "") 0 (get-hunits (parse-expression num 0 #\m)))))
(if (negative? n)
(warn ".ti with negative indent ignored")
(concat "<br>\n" (repeat-string n nbsp))))))
;;; There is no reasonable way to create markup for .tl; just emit the
;;; argument:
(defrequest 'tl
(lambda (tl s)
(let* ((p (parse s))
(t (parse-triple p)))
(cond
(t
(spread (car t) (cadr t) (cddr t) #\newline))
((eqv? s "")
"")
(else
(warn "badly formed .tl argument: `~a'" p))))))
;;; Until HTML can center, at least generate a <br> after each line:
(defrequest 'ce
(lambda (ce num)
(let ((n (if (eqv? num "") 1 (string->number num))))
(if n
(center (round n))
(warn ".ce argument `~a' not understood" num)))))
(define lines-to-center 0)
(define (center n)
(set! lines-to-center n)
(defevent 'line 50 (if (positive? n) center-processor #f))
"")
(define (center-processor c)
(if (positive? (-- lines-to-center))
(if (eqv? c #\newline)
(emit "<br>\n")))
(if (not (positive? lines-to-center))
(center 0)))
;;; --------------------------------------------------------------------------
;;; Other definitions.
;;; Suppress comment if writing to a buffer, because in this case the
;;; output is likely to be re-read later (e.g. it may be a macro):
(defescape #\"
(lambda (_ x)
(let ((c (string-prune-right x "\n" x))
(old (defchar #\tab #f)))
(if (and (not (eqv? c "")) (not (stream-buffer? (output-stream))))
(emit "<!-- " (translate c) " -->\n"))
(defchar #\tab old)
#\newline)))
;;; Extra white space at end of sentence:
(define sentence-event
(lambda (c)
(concat c "<tt> </tt>\n")))
(defsentence sentence-event)
;;; Emit standardized output file prolog:
(define (emit-HTML-prolog)
(let ((mailto (option 'mail-address)))
(emit "<html>\n<head>\n")
(emit "<!-- This file has been generated by "
(substitute "%progname% %version%, %date% %time%. -->\n")
"<!-- Do not edit! -->\n")
(if mailto (emit "<link rev=\"made\" href=\"mailto:" mailto "\">\n"))))
;;; Define a scaling for the usual scaling indicators. Note that the
;;; vertical spacing and character width will never change; and the
;;; device's vertical/horizontal resolution is 1.
(define inch 240) ; units per inch
(set-scaling! #\i inch 1)
(set-scaling! #\c (* 50 inch) 127)
(set-scaling! #\P inch 6) ; Pica
(set-scaling! #\m inch 10)
(set-scaling! #\n inch 10)
(set-scaling! #\p inch 72)
(set-scaling! #\v inch 7)
;;; Convert from units back to ems and Vs:
(define (get-hunits x)
(let ((s (get-scaling #\m)))
(if x (inexact->exact (/ (* x (cdr s)) (car s))) x)))
(define (get-vunits x)
(let ((s (get-scaling #\v)))
(if x (inexact->exact (/ (* x (cdr s)) (car s))) x)))
;;; Fake line length:
(define line-length 65)
(defnumreg '.l "1560") ; 65 ems

26
scm/html/m.scm Normal file
View File

@ -0,0 +1,26 @@
;;;; -*-Scheme-*-
;;;;
;;;; $Revision: 1.4 $
;;;;
;;;; Definitions for HTML output format to be loaded when no -mfoo
;;;; option has been given.
;;; --------------------------------------------------------------------------
;;; Simple default start and exit handler.
(defevent 'start 10
(lambda _
(let* ((docname (option 'document))
(title (option 'title))
(t (if title title (if docname docname "(untitled)"))))
(if docname
(set-output-stream! (open-output-stream (concat docname ".html"))))
(emit-HTML-prolog)
(emit "<title>" (translate t) "</title>\n</head>\n<body>\n"))))
(defevent 'exit 10
(lambda _
(emit (change-font "R") (preform #f))
(emit "</body>\n</html>\n")
(close-stream (set-output-stream! #f))))

316
scm/html/man.scm Normal file
View File

@ -0,0 +1,316 @@
;;;; -*-Scheme-*-
;;;;
;;;; $Revision: 1.18 $
;;;;
;;;; `man' specific definitions for HTML output format
;;; --------------------------------------------------------------------------
;;; Options.
(define-option 'do-signature 'boolean #t)
;;; --------------------------------------------------------------------------
;;; Miscellaneous definitions.
(defstring 'R "&#174;") ; trademark
(defstring 'S "") ; change to default point size
(defstring 'lq "``")
(defstring 'rq "''")
(define-font 'L '<b> '</b>) ; whatever font L is supposed to be...
;;; --------------------------------------------------------------------------
;;; Bookkeeping for .TH, for requests that occur in pairs, etc.
(define-pair header header? "<h2>\n" "<hr></h2>\n")
(define-pair tag-para tag-para? "<dl>\n" "</dl>\n")
(define-pair list-para list-para? "<ul>\n" "</ul>\n")
(define-pair hang-para hang-para? "<dt>" "<dd>\n")
(define (reset-everything)
(concat
(reset-font)
(center 0)
(header #f)
(preform #f)
(hang-para #f)
(tag-para #f)
(list-para #f)))
(define-nested-pair indent indent-level "<dl><dt><dd>\n" "</dl>\n")
;;; --------------------------------------------------------------------------
;;; File prolog and epilog functions.
(defevent 'prolog 10
(lambda (pathname filename)
(if (not (string=? filename "stdin"))
(set-option! 'document filename))
(let ((docname (option 'document)))
(if docname
(set-output-stream! (open-output-stream (concat docname ".html"))))
(emit-HTML-prolog))))
(defevent 'epilog 10
(lambda _
(complain-if-no-title)
(emit (reset-everything) (indent 0))
(if (option 'do-signature)
(emit
(substitute
"<p><hr>\nMarkup created by <em>%progname%</em> %version%,")
nbsp nbsp
(substitute "%monthname+% %day%, %year%.\n")))
(emit "</body>\n</html>\n")
(close-stream (set-output-stream! #f))))
;;; --------------------------------------------------------------------------
;;; Title, section, subsection.
(define title-seen? #f)
(define (complain-if-no-title)
(if (not title-seen?)
(quit "manual page must begin with .TH request")))
(defmacro 'TH
(lambda (TH what section . _)
(let ((title (option 'title)))
(set! title-seen? #t)
(concat "<title>"
(substitute (if title title "Manual page for %1%(%2%)")
(translate what) (translate section))
"</title>\n</head>\n<body>\n"))))
(defmacro 'SH
(lambda (SH first . rest)
(complain-if-no-title)
(emit (reset-everything) (indent 0))
(if (string=? first "NAME")
(header #t)
(concat "<h2>" (parse (apply spread first rest)) "</h2>\n"))))
(defmacro 'SS
(lambda (SS . args)
(complain-if-no-title)
(emit (reset-everything) (indent 0))
(cond
((null? args)
(defevent 'line 11
(lambda _ (emit "</h3>\n") (defevent 'line 11 #f)))
(emit "<h3>"))
(else
(concat "<h3>" (parse (apply spread args)) "</h3>\n")))))
;;; --------------------------------------------------------------------------
;;; Font switching requests.
;;;
;;; Both with-font and with-fonts include a terminating newline in the
;;; parsing, because people are using .I xxx\c and .BR xxx\c etc., and
;;; end-of-sentence must be detected in situations like .BR send(2) .
(define (with-font font words)
(let ((old current-font))
(cond
((null? words)
(defevent 'line 10
(lambda _ (emit (change-font old)) (defevent 'line 10 #f)))
(emit (change-font font) #\newline))
(else
(concat (change-font font)
(parse (apply spread words) #\newline)
(change-font old))))))
(defmacro 'I (lambda (I . args) (with-font "I" args)))
(defmacro 'B (lambda (B . args) (with-font "B" args)))
(defmacro 'SB (requestdef 'B))
(defmacro 'SM
(lambda (SM . words)
(if (null? words) "" (parse (apply spread words) #\newline))))
(define (with-fonts f1 f2 words)
(define (recurse f1 f2 words)
(if (null? words)
""
(concat (change-font f1)
(parse (concat (car words)
(if (null? (cdr words)) #\newline "")))
(recurse f2 f1 (cdr words)))))
(let ((old current-font))
(concat (recurse f1 f2 words) (change-font old))))
(defmacro 'BI (lambda (BI . args) (with-fonts "B" "I" args)))
(defmacro 'BR (lambda (BR . args) (with-fonts "B" "R" args)))
(defmacro 'IB (lambda (IB . args) (with-fonts "I" "B" args)))
(defmacro 'IR (lambda (IR . args) (with-fonts "I" "R" args)))
(defmacro 'RB (lambda (RB . args) (with-fonts "R" "B" args)))
(defmacro 'RI (lambda (RB . args) (with-fonts "R" "I" args)))
;;; --------------------------------------------------------------------------
;;; Indented paragraphs with labels.
;;;
;;; A heuristic is used to determine whether to emit a bulleted list
;;; or a tagged list: .TP with \(bu in the next input line and
;;; .IP with \(bu as argument both start a bulleted list. Of course, in
;;; case the style changes later, we have a problem and may want to end
;;; the current list and begin a new one with the new style.
(define (next-para-TP)
(cond
(tag-para?
(defevent 'line 12
(lambda _ (emit (reset-font) "<dd>\n") (defevent 'line 12 #f)))
(emit "<dt>"))
(else
"<li>")))
(define (next-para-IP arg)
(cond
(tag-para?
(if (null? arg)
"<dt><dd><p>\n"
(concat "<dt>" (parse (car arg)) "<dd>\n")))
((or (null? arg) (string=? (car arg) "\\(bu"))
"<li>\n")
(else
(warn ".IP `arg' in a list that was begun as non-tagged")
(concat "<li>" (parse (car arg)) "<br>\n"))))
(defmacro 'TP
(lambda _
(emit (reset-font) (hang-para #f))
(if preform?
(begin
(surprise ".TP inside .nf/.fi") #\newline)
(let ((next (read-line)))
(if (eof-object? next) (set! next #\newline))
(cond
((string=? next "\\(bu\n")
(cond
(tag-para?
(emit (tag-para #f) (list-para #t))) ; change style
(else
(emit (list-para #t)))))
(else
(unread-line next)
(cond
(list-para?
(emit (list-para #f) (tag-para #t))) ; change style
(else
(emit (tag-para #t))))))
(next-para-TP)))))
(defmacro 'IP
(lambda (IP . arg)
(emit (reset-font) (hang-para #f))
(if preform?
(begin
(surprise ".IP inside .nf/.fi")
(if (not (null? arg)) (concat (parse (car arg)) #\newline) #\newline))
(if (or tag-para? list-para?)
(next-para-IP arg)
(cond
((and (not (null? arg)) (string=? (car arg) "\\(bu"))
(emit (list-para #t))
(set! arg '()))
(else
(emit (tag-para #t))))
(next-para-IP arg)))))
;;; A hanging indent cannot be achieved in HTML. Therefore we have to
;;; kludge .HP by beginning a `tag-para' and putting everything up to
;;; the next line break between the <dt> and <dd>.
(defmacro 'HP
(lambda _
(emit (reset-font) (hang-para #f))
(cond
(preform?
(surprise ".HP inside .nf/.fi") #\newline)
(else
(if list-para? (emit (list-para #f))) ; change style
(concat (tag-para #t) (hang-para #t))))))
;;; --------------------------------------------------------------------------
;;; Relative indent.
(define (relative-indent request . _)
(if preform?
(surprise ".RS/.RE inside .nf/.fi"))
(emit (reset-font) (hang-para #f) (tag-para #f) (list-para #f))
(with-preform-preserved
(indent (if (string=? request "RS") '+ '-))))
(defmacro 'RS relative-indent)
(defmacro 'RE relative-indent)
;;; --------------------------------------------------------------------------
;;; Paragraphs.
(define (paragraph . _)
(concat (reset-everything) "<p>\n"))
(defmacro 'LP paragraph)
(defmacro 'PP paragraph)
(defmacro 'P paragraph)
;;; --------------------------------------------------------------------------
;;; Miscellaneous break-causing requests (must end .HP paragraph).
(defrequest 'sp
(let ((orig (requestdef 'sp)))
(lambda (sp num)
(concat (hang-para #f) (orig sp num)))))
(defrequest 'bp (lambda _ (hang-para #f)))
(defrequest 'ti
(let ((orig (requestdef 'ti)))
(lambda (ti num)
(concat (hang-para #f) (orig ti num)))))
;;; Kludge: Suppress <br> immediately after `hang-para' to avoid excessive
;;; white space
(defrequest 'br
(lambda _
(if hang-para?
(hang-para #f)
(concat (hang-para #f) "<br>\n"))))
;;; --------------------------------------------------------------------------
;;; Bogus or SunOS-specific stuff.
(defmacro 'TX
(lambda (TX name . id)
(concat "[a manual with the abbreviation " (parse name) "]"
(if (null? id) "" (car id)) #\newline)))
(defmacro 'IX "")
(defmacro 'DT "")
(defmacro 'PD "")
(defmacro 'UC "")

628
scm/html/ms.scm Normal file
View File

@ -0,0 +1,628 @@
;;;; -*-Scheme-*-
;;;;
;;;; $Revision: 1.14 $
;;;;
;;;; `ms' specific definitions for HTML output format
;;; --------------------------------------------------------------------------
;;; Options.
(define-option 'signature 'string "")
(define-option 'split 'integer 0)
(define-option 'toc 'boolean #t)
(define-option 'toc-header 'string "Table of Contents")
(define-option 'pp-indent 'integer 3)
(define-option 'footnotes-header 'string "Footnotes")
(define-option 'footnote-reference 'string "[note %1%]")
(define-option 'footnote-anchor 'string "[%1%]")
;;; --------------------------------------------------------------------------
;;; Predefined strings and number registers.
(defstring 'Q "``")
(defstring 'U "''")
(defstring '- "--") ; em-dash
(defstring 'MO (substitute "%monthname+%"))
(defstring 'DY (substitute "%monthname+% %day%, %year%"))
(defnumreg 'PN #\0)
;;; --------------------------------------------------------------------------
;;; General bookkeeping.
(define split-sections? #f) ; #t if `split' option is positive
(define-pair abstract abstract? "" "<hr>\n")
(define-pair title title? "<h1>\n" "</h1>\n")
(define-pair secthdr secthdr? "<h2>\n" "</h2>\n")
(define-pair tag-para tag-para? "<dl>\n" "</dl>\n")
(define-pair list-para list-para? "<ul>\n" "</ul>\n")
(define-pair quoted quoted? "<blockquote>\n" "</blockquote>\n")
(define (reset-everything)
(emit
(reset-font)
(center 0)
(quoted #f)
(secthdr #f)
(preform #f)
(tag-para #f)
(list-para #f)
(reset-title-features))
(header-processor #f))
(define-nested-pair indent indent-level "<dl><dt><dd>\n" "</dl>\n")
;;; --------------------------------------------------------------------------
;;; Manage HTML output files.
(define HTML-streams '())
(define (push-HTML-stream file-suffix title-suffix)
(let* ((docname (option 'document))
(title (option 'title))
(t (concat (if title title docname) title-suffix))
(fn (if file-suffix (concat docname file-suffix ".html") #f))
(s (if fn (open-output-stream fn) #f)))
(close-stream (set-output-stream! #f))
(set-output-stream! s)
(list-push! HTML-streams fn)
(emit-HTML-prolog)
(emit "<title>" (translate t) "</title>\n</head><body>\n")))
(define (pop-HTML-stream)
(if (not (eqv? (option 'signature) ""))
(emit "<p><hr>\n" (substitute (option 'signature))) #\newline)
(emit "</body>\n</html>\n")
(list-pop! HTML-streams)
(close-stream (set-output-stream! #f))
(if (and (not (null? HTML-streams)) (car HTML-streams))
(set-output-stream! (append-output-stream (car HTML-streams)))))
;;; --------------------------------------------------------------------------
;;; Callback procedure called by hyper.scm when creating hypertext anchor.
(define (query-anchor request label)
(lambda (op)
(case op
(allowed? #t)
(emit-anchor? #t)
(filename
(if (not (stream-file? (output-stream)))
(car HTML-streams)
(stream-target (output-stream)))))))
;;; --------------------------------------------------------------------------
;;; Generate hypertext reference and anchor.
(define (make-href type index contents)
(let* ((docname (option 'document))
(file
(case type
((section toc) (car HTML-streams))
(footnote (if split-sections? (concat docname "-notes.html") "")))))
(format #f "<a href=\"~a#~a~a\">~a" file type index
(if contents (concat contents "</a>\n") ""))))
(define (make-anchor type index contents)
(format #f "<a name=\"~a~a\">~a</a>" type index contents))
;;; --------------------------------------------------------------------------
;;; Automatically generated TOC.
(define auto-toc-entry
(let ((last-level 0))
(lambda (anchor entry level labelnum)
(with-output-appended-to-stream "[autotoc]"
(emit (repeat-string (- level last-level) "<ul>")
(repeat-string (- last-level level) "</ul>"))
(set! last-level level)
(if (positive? level)
(emit "<li>" (make-href 'section labelnum anchor) entry))))))
(define (auto-toc-spill)
(auto-toc-entry "" "" 0 0)
(let ((toc (stream->string "[autotoc]")))
(if (not (eqv? toc ""))
(emit "<h2>" (substitute (option 'toc-header)) "</h2>\n" toc))))
;;; --------------------------------------------------------------------------
;;; Start and exit event functions.
(defevent 'start 10
(lambda _
(set! split-sections? (positive? (option 'split)))
(let ((docname (option 'document)))
(if (not (or docname (option 'title)))
(quit "you must set either document= or title="))
(if (and split-sections? (not docname))
(quit "you must set document= for non-zero `split'"))
(push-HTML-stream (if docname "" #f) ""))))
(defevent 'exit 10
(lambda _
(reset-everything)
(emit (indent 0))
(footnote-processor 'spill)
(do () ((null? (cdr HTML-streams))) (pop-HTML-stream))
(if (option 'toc)
(auto-toc-spill))
(pop-HTML-stream)))
;;; --------------------------------------------------------------------------
;;; Title features, abstract.
(define got-title? #f)
(define (reset-title-features)
(concat (title #f)
(begin1 (if got-title? "<hr>\n" "") (set! got-title? #f))))
(defmacro 'TL
(lambda _
(cond
(got-title?
(warn ".TL is only allowed once"))
(else
(reset-everything)
(set! got-title? #t)
(title #t)))))
(defmacro 'AU
(lambda _
(emit (title #f) "<p>\n" (change-font 2))
(center 999)))
(defmacro 'AI
(lambda _
(emit (title #f) "<br>\n" (change-font 1))
(center 999)))
(defmacro 'AB
(lambda (AB . args)
(reset-everything)
(abstract #t)
(cond ((null? args)
"<h2>ABSTRACT</h2>\n<p>\n")
((string=? (car args) "no")
"<p>\n")
(else
(concat "<h2>" (parse (car args)) "</h2>\n<p>\n")))))
(defmacro 'AE
(lambda _
(cond (abstract? (reset-everything) (abstract #f))
(else (warn ".AE without preceding .AB")))))
;;; --------------------------------------------------------------------------
;;; Numbered sections.
(define sections (list 0))
(define (increment-section! s n)
(if (positive? n)
(increment-section! (cdr s) (1- n))
(set-car! s (if (char? (car s))
(integer->char (modulo (1+ (char->integer (car s))) 256))
(1+ (car s))))
(set-cdr! s '())))
(define (section-number s n)
(if (zero? n)
""
(format #f "~a.~a" (car s) (section-number (cdr s) (1- n)))))
(define (verify-section-number s)
(cond ((eqv? s "") #f)
((string->number s) (string->number s))
((char-alphabetic? (string-ref s 0)) (string-ref s 0))
(else #f)))
(define (numbered-section args)
(cond
((null? args)
(increment-section! sections 0)
(defstring 'SN (section-number sections 1))
1)
((string=? (car args) "S")
(cond
((null? (cdr args))
(warn ".NH with `S' argument but no numbers")
1)
(else
(let ((new (map verify-section-number (cdr args))))
(if (memq #f new)
(warn "bad section number in .NH request")
(set! sections new))
(defstring 'SN (section-number new (length new)))
(length new)))))
(else
(let ((level (string->number (car args))))
(if (not level)
(begin
(warn "~a is not a valid section level" (car args))
(set! level 1)))
(if (< (length sections) level)
(append! sections (make-list (- level (length sections)) 0)))
(increment-section! sections (1- level))
(defstring 'SN (section-number sections level))
level))))
(defmacro 'NH
(lambda (NH . args)
(reset-everything)
(emit (indent 0))
(let ((level (numbered-section args)))
(if (and split-sections? (<= level (option 'split)))
(let* ((sect (stringdef 'SN))
(suff (concat #\- (string-prune-right sect "." sect))))
(push-HTML-stream suff (concat ", section " sect))))
(header-processor #t level))))
(define header-processor
(let ((stream #f) (inside? #f) (seq 1) (level 0))
(lambda (enter? . arg)
(cond
((and enter? (not inside?))
(set! level (car arg))
(set! stream (set-output-stream! (open-output-stream "[header]"))))
((and inside? (not enter?))
(close-stream (set-output-stream! stream))
(let ((hdr (stream->string "[header]"))
(sectno (stringdef 'SN)))
(cond
((and split-sections? (option 'toc))
(auto-toc-entry (concat sectno #\space) hdr level seq)
(emit "<h2>" (make-anchor 'section seq sectno)))
(else
(emit "<h2>" sectno)))
(emit nbsp hdr "</h2>\n")
(++ seq))))
(set! inside? enter?)
"")))
;;; --------------------------------------------------------------------------
;;; Font switching and related requests.
(define (with-font font . args)
(let ((old current-font))
(cond
((null? args)
(concat (change-font font) #\newline))
((null? (cdr args))
(concat (change-font font) (parse (car args) #\newline)
(change-font old)))
(else
(concat (change-font font) (parse (car args)) (change-font old)
(parse (cadr args) #\newline))))))
(defmacro 'I with-font)
(defmacro 'B with-font)
(defmacro 'R with-font)
(defmacro 'UL (lambda (UL) (with-font "I"))) ; <u> doesn't work
(defmacro 'SM
(lambda (SM . words)
(if (null? words) "" (parse (apply spread words) #\newline))))
(defmacro 'LG (requestdef 'SM))
;;; --------------------------------------------------------------------------
;;; Indented paragraph with optional label.
(define (indented-paragraph IP . arg)
(define (non-tagged? s)
(or (null? s) (member (car s) '("\\(bu" "\\(sq" "\\-"))))
(emit (reset-font) (secthdr #f) (reset-title-features))
(header-processor #f)
(cond
(preform?
(surprise ".IP inside .nf/.fi")
(if (not (null? arg)) (concat (parse (car arg)) #\newline) #\newline))
(tag-para?
(if (null? arg)
"<dt><dd><p>\n"
(concat "<dt>" (parse (car arg)) "<dd>\n")))
(list-para?
(cond
((non-tagged? arg)
"<li>\n")
(else
(warn ".IP `arg' in a list that was begun as non-tagged")
(concat "<li>" (parse (car arg)) "<br>\n"))))
((non-tagged? arg)
(concat (list-para #t) (indented-paragraph IP)))
(else
(concat (tag-para #t) (indented-paragraph IP (car arg))))))
(defmacro 'IP indented-paragraph)
;;; --------------------------------------------------------------------------
;;; Relative indent.
(define (relative-indent request . _)
(if preform?
(surprise ".RS/.RE inside .nf/.fi"))
(emit (reset-font) (tag-para #f) (list-para #f))
(with-preform-preserved
(indent (if (string=? request "RS") '+ '-))))
(defmacro 'RS relative-indent)
(defmacro 'RE relative-indent)
;;; --------------------------------------------------------------------------
;;; Displays.
(define display-saved-font #f)
(define inside-display? #f)
(define indented-display? #f)
(define (display-start type)
(if (or (string=? type "C") (string=? type "B"))
(begin
(warn "display type ~a not supported (using I)" type)
(set! type "I")))
(cond
((or (not (= (string-length type) 1))
(not (memq (string-ref type 0) '(#\I #\L #\C #\B))))
(warn "illegal display type `~a'" type))
(inside-display?
(warn "nested display ignored"))
(preform?
(warn "display inside .nf/.fi ignored"))
(else
(set! display-saved-font current-font)
(emit (reset-font))
(set! indented-display? (string=? type "I"))
(if indented-display?
(emit (indent '+)))
(set! inside-display? #t)
(preform #t))))
(defmacro 'DS
(lambda (DS . args)
(display-start (if (null? args) "I" (car args)))))
(defmacro 'ID (lambda _ (display-start "I")))
(defmacro 'LD (lambda _ (display-start "L")))
(defmacro 'CD (lambda _ (display-start "C")))
(defmacro 'BD (lambda _ (display-start "B")))
(defmacro 'DE
(lambda _
(cond
((not inside-display?)
(warn ".DE without matching display start"))
(else
(set! inside-display? #f)
(emit
(with-font-preserved
(preform #f)
(if indented-display? (indent '-) ""))
(change-font display-saved-font))
""))))
;;; --------------------------------------------------------------------------
;;; Footnotes.
;; Generating \[***] for \** allows us to defer creating the anchor from
;; string expansion time to output time. Otherwise we couldn't use <...>.
(defstring '* "\\[***]")
(define **-count 0)
(defspecial '***
(lambda _
(++ **-count)
(footnote-anchor (substitute (option 'footnote-reference)
(number->string **-count)))))
(define next-footnote 0)
(define (footnote-anchor sym)
(++ next-footnote)
(with-font-preserved
(concat (change-font 1) (make-href 'footnote next-footnote sym))))
;; New request to generate a footnote anchor; an alternative to \**.
;; Should be followed by .FS. Do not use `.FA \**'.
(defmacro 'FA
(lambda (FA arg) (footnote-anchor (parse arg))))
(define footnote-processor
(let ((stream #f) (inside? #f))
(lambda (op . arg)
(case op
(begin
(cond
(inside?
(surprise "nested .FS"))
(else
(set! inside? #t)
(set! stream (set-output-stream!
(append-output-stream "[footnotes]")))
(emit "<p>\n")
(let ((anchor
(cond ((not (null? arg))
(parse (car arg)))
((positive? **-count)
(substitute (option 'footnote-anchor)
(number->string **-count)))
(else #f))))
(if anchor
(emit "<b>" (make-anchor 'footnote next-footnote anchor)
"</b>" nbsp))))))
(end
(cond
(inside?
(set! inside? #f)
(close-stream (set-output-stream! stream)))
(else (warn ".FE without matching .FS"))))
(spill
(if inside? (quit "unterminated footnote at end of document"))
(let ((contents (stream->string "[footnotes]"))
(hdr (substitute (option 'footnotes-header))))
(cond
((not (eqv? contents ""))
(if split-sections?
(push-HTML-stream "-notes" ", footnotes"))
(cond ((and split-sections? (option 'toc))
(auto-toc-entry hdr "" 1 0)
(emit "<h2>" (make-anchor 'section 0 hdr)))
(else (emit "<h2>" hdr)))
(emit "</h2>\n" contents))
((positive? next-footnote)
(warn "footnote anchor used, but no .FS"))))))
"")))
(defmacro 'FS
(lambda (FS . arg)
(apply footnote-processor 'begin arg)))
(defmacro 'FE
(lambda _ (footnote-processor 'end)))
;;; --------------------------------------------------------------------------
;;; TOC macros.
(define toc-processor
(let ((stream #f) (inside? #f) (seq 1))
(lambda (op . arg)
(case op
(begin
(cond
(inside?
(surprise "nested .XS"))
(else
(set! inside? #t)
(emit (make-anchor 'toc seq "&#160;") #\newline)
(set! stream (set-output-stream! (append-output-stream "[toc]")))
(if (>= (length arg) 2)
(emit
(repeat-string
(get-hunits (parse-expression (cadr arg) 0 #\n)) nbsp)))
(if (option 'document)
(emit (make-href 'toc seq #f)))
(++ seq))))
(end
(cond
(inside?
(set! inside? #f)
(if (option 'document) (emit "</a>\n"))
(emit "<br>\n")
(close-stream (set-output-stream! stream)))
(else (warn ".XE or .XA without matching .XS"))))
(spill
(if inside? (quit "unterminated .XE"))
(if (or (null? arg) (not (string=? (car arg) "no")))
(emit "<h2>Table of Contents</h2>\n"))
(emit (stream->string "[toc]"))))
"")))
(defmacro 'XS
(lambda (XS . arg)
(apply toc-processor 'begin arg)))
(defmacro 'XE (lambda _ (toc-processor 'end)))
(defmacro 'XA (lambda _ (toc-processor 'end) (toc-processor 'begin)))
(defmacro 'PX
(lambda (PX . arg)
(apply toc-processor 'spill arg)))
;;; --------------------------------------------------------------------------
;;; Paragraphs of various kinds.
(define-macro (define-paragraph request . body)
`(defmacro ,request (lambda _ (reset-everything) ,@body)))
(define-paragraph 'LP "<p>\n")
(define-paragraph 'PP (concat "<p>\n"
(repeat-string (option 'pp-indent) nbsp)))
(define-paragraph 'QP (quoted #t))
(define-paragraph 'SH (secthdr #t))
(define-paragraph 'RT)
;;; --------------------------------------------------------------------------
;;; Requests that must be ignored, either because the function cannot
;;; be expressed in HTML or because they assume a page structure.
(defmacro 'AM "") ; better accents
(defmacro 'BT "") ; bottom title
(defmacro 'CM "") ; cut mark between pages
(defmacro 'CT "") ; chapter title
(defmacro 'DA "") ; force date at page bottom
(defmacro 'EF "") ; even footer
(defmacro 'EH "") ; even header
(defmacro 'HD "") ; optional page header
(defmacro 'KE "") ; keep end
(defmacro 'KF "") ; floating keep
(defmacro 'KS "") ; keep
(defmacro 'ND "") ; no date in footer
(defmacro 'NL "") ; reset point size to normal
(defmacro 'OF "") ; odd footer
(defmacro 'OH "") ; odd header
(defmacro 'P1 "") ; print header on 1st page
(defmacro 'PT "") ; page title
(defmacro 'TM "") ; UCB thesis mode
(defmacro 'BX ; boxed word
(lambda (BX word)
(parse word #\newline)))
(define (multi-column-ignored request . _)
(warn "multi-column request .~a not supported" request))
(defmacro 'MC multi-column-ignored)
(defmacro '1C multi-column-ignored)
(defmacro '2C multi-column-ignored)
;;; --------------------------------------------------------------------------
;;; Anachronisms, kludges, etc.
(defmacro 'UX "UNIX")
(defmacro 'B1 "<hr>\n")
(defmacro 'B2 "<hr>\n")

166
scm/misc/hyper.scm Normal file
View File

@ -0,0 +1,166 @@
;;;; -*-Scheme-*-
;;;;
;;;; $Revision: 1.5 $
;;;;
;;;; General-purpose hypertext requests
;;;;
;;;; This implementation is undocumented and is likely to change in
;;;; future relases.
(if (not (string=? (substitute "%format%") "html"))
(quit "hypertext functions require -fhtml"))
;;; --------------------------------------------------------------------------
;;; Data structures.
(define ht-anchors '())
(define ht-references '())
(define anchor-name car)
(define anchor-location cdr)
(define (anchor-create name location)
(cons name location))
(define ref-filename car)
(define ref-offset cadr)
(define ref-location caddr)
(define ref-name cadddr)
(define (ref-unresolved? x)
(eqv? (ref-location x) ""))
(define (set-ref-location! x l)
(set-car! (cddr x) l))
(define (ref-create filename offset name)
(list filename offset "" name))
;;; --------------------------------------------------------------------------
;;; Figure out if hypertext request is allowed at a given place and the
;;; filename to use in hypertext anchor.
(define (default-query-anchor request label)
(lambda (op)
(case op
(allowed? #t)
(emit-anchor? #t)
(filename (stream-target (output-stream))))))
(define (ht-querier request label)
(let ((q (if (bound? 'query-anchor) query-anchor default-query-anchor)))
(q request label)))
;;; --------------------------------------------------------------------------
;;; Create hypertext anchor.
;;;
;;; .Ha label anchor-text
(defmacro 'Ha
(lambda (Ha name contents)
(let* ((q (ht-querier '.Ha name))
(location (q 'filename)))
(cond
((not (q 'allowed?))
"")
((assoc name ht-anchors)
(warn ".Ha with duplicate anchor name `~a'" name))
(else
(resolve-ht-reference name location)
(list-push! ht-anchors (anchor-create name location))
(if (q 'emit-anchor?)
(concat (format #f "<a name=\"~a\">~a</a>" (parse-unquote name)
(parse contents)))
""))))))
(define (resolve-ht-reference name location)
(let loop ((x ht-references))
(cond
((not (null? x))
(if (and (ref-unresolved? (car x))
(string=? (ref-name (car x)) name))
(set-ref-location! (car x) location))
(loop (cdr x))))))
;;; --------------------------------------------------------------------------
;;; Create hypertext reference.
;;;
;;; .Hr -url url anchor-text [suffix]
;;; .Hr -symbolic label anchor-text [suffix]
;;; .Hr troff-text
(defmacro 'Hr
(lambda (Hr type . args)
(cond
((string=? type "-url")
(if (< (length args) 2)
(warn "too few arguments for .Hr")
(concat
(format #f "<a href=\"~a\">~a</a>~a"
(parse-unquote (car args))
(parse (cadr args))
(if (null? (cddr args))
#\newline
(parse (caddr args) #\newline))))))
((string=? type "-symbolic")
(if (< (length args) 2)
(warn "too few arguments for .Hr")
(let* ((ref (car args))
(q (ht-querier '.Hr ref))
(filename (q 'filename))
(a (assoc ref ht-anchors))
(location (if a (cdr a) "")))
(cond
((not (q 'allowed?))
"")
((and (not a) (not (output-stream)))
(warn ".Hr forward reference requires `document' option"))
(else
(emit "<a href=\"")
(if (not a)
(list-push! ht-references
(ref-create filename (stream-position (output-stream))
ref)))
(concat (if (string=? filename location) "" location)
#\# (parse-unquote ref) "\">"
(parse (cadr args)) "</a>"
(if (null? (cddr args))
#\newline
(parse (caddr args) #\newline))))))))
(else ""))))
;;; --------------------------------------------------------------------------
;;; Complain about unresolved references and weed out references that
;;; point into the file where they appear; then do the file insertions.
(define (check-ht-references)
(let loop ((x ht-references) (new '()))
(cond
((null? x)
new)
((ref-unresolved? (car x))
(warn "unresolved hypertext reference `~a' in file ~a"
(ref-name (car x)) (ref-filename (car x)))
(loop (cdr x) new))
((string=? (ref-filename (car x)) (ref-location (car x)))
(loop (cdr x) new))
(else
(loop (cdr x) (cons (car x) new))))))
(define (insert-ht-references)
(let ((refs (check-ht-references)))
(file-insertions refs)))
(defevent 'exit 90 insert-ht-references)

541
scm/troff.scm Normal file
View File

@ -0,0 +1,541 @@
;;;; -*-Scheme-*-
;;;;
;;;; $Revision: 1.22 $
;;;;
;;;; Basic initializations
;;; --------------------------------------------------------------------------
;;; Define minimal reset, interrupt handler, and error handlers.
(if (call-with-current-continuation
(lambda (c)
(set! top-level-control-point c) #f))
(exit 1))
(define (interrupt-handler) (exit 1))
(define (error-handler . args)
(let ((port (error-port)))
(format port "~a: ~s: " (substitute "%progname%") (car args))
(apply format port (cdr args))
(newline port)
(exit 1)))
;;; --------------------------------------------------------------------------
;;; Procedures to print an error message and quit and to print warnings.
(define (quit msg . args)
(let ((port (error-port)))
(display (substitute "%progname%:%filepos% ") port)
(apply format port msg args)
(newline port))
(exit 1))
(define (warn msg . args)
(let ((port (error-port)))
(display (substitute "%progname%:%filepos% warning: ") port)
(apply format port msg args)
(newline port)
"")) ; return "" to assist use in event functions
(define (surprise msg)
(warn (concat msg " may not work as expected")))
;;; --------------------------------------------------------------------------
;;; Miscellaneous utilities.
(define-macro (++ var) `(set! ,var (1+ ,var)))
(define-macro (-- var) `(set! ,var (1- ,var)))
(define (identity x) x)
(define (copy-apply reader . procedures)
(define (apply-all val procs)
(if (null? procs)
val
((car procs) (apply-all val (cdr procs)))))
(let loop ((x (reader)))
(cond ((eof-object? x) "")
(else
(apply-all x procedures)
(loop (reader))))))
(define-macro (list-push! list elem)
`(set! ,list (cons ,elem ,list)))
(define-macro (list-pop! list)
`(set! ,list (cdr ,list)))
(define-macro (list-clear! list)
`(set! ,list '()))
(define (skip-lines stop)
(let ((x (read-line-expand)))
(cond ((eof-object? x)
(warn "end-of-stream while skipping input"))
((not (string=? x stop))
(skip-lines stop)))))
;;; Assist setting of options in initialization file:
(define-macro (eval-if-mode mode . body)
(if (and (pair? mode)
(= (length mode) 2)
(symbol? (car mode))
(symbol? (cadr mode)))
(let ((tmac (car mode)) (format (cadr mode)))
`(cond
((and (or (eq? ',tmac '*)
(eq? ',tmac (string->symbol (substitute "m%macros%"))))
(or (eq? ',format '*)
(eq? ',format (string->symbol (substitute "%format%")))))
,@body)))
(error 'eval-if-mode "badly formed mode argument: `~a'" mode)))
;;; Macro to define a function and a predicate to manage requests that
;;; come in pairs, such as .fi/.nf.
(define-macro (define-pair func inside enter leave)
`(begin
(define ,inside #f)
(define (,func on)
(begin1
(if on
(if ,inside "" ,enter)
(if ,inside ,leave ""))
(set! ,inside on)))))
;;; Like define-pair, but for nested pairs.
(define-macro (define-nested-pair func level enter leave)
`(begin
(define ,level 0)
(define (,func op)
(case op
(0 (begin1 (repeat-string ,level ,leave) (set! ,level 0)))
(+ (++ ,level) ,enter)
(- (if (zero? ,level)
""
(-- ,level) ,leave))))))
;;; --------------------------------------------------------------------------
;;; Options.
(define option-types (make-table 10))
(define option-table (make-table 100))
(define (define-option-type name check1 msg1 convert check2 msg2)
(table-store! option-types name (list check1 msg1 convert check2 msg2)))
(define (define-option name type initial)
(if (not (table-lookup option-types type))
(quit "bad type `~a' for define-option" type))
(table-store! option-table name (cons initial type)))
(define (option-setter as-event?)
(lambda (name value)
(let* ((opt (table-lookup option-table name))
(t (if opt (table-lookup option-types (cdr opt)) #f))
(err (lambda (msg) (quit "option `~a' requires ~a as value"
name msg))))
(if opt
(let ((val value))
(if as-event?
(begin
(if (not ((car t) val)) (err (cadr t)))
(set! val ((caddr t) (car opt) val))))
(if (not ((cadddr t) val)) (err (car (cddddr t))))
(set-car! opt val))
(quit "undefined option: `~a'" name)))))
(defevent 'option 0 (option-setter #t))
(define set-option! (option-setter #f))
(define (option name)
(let ((opt (table-lookup option-table name)))
(if opt (car opt) (quit "undefined option: `~a'" name))))
(define-option-type 'integer
string? ""
(lambda (old new) (string->number new))
integer? "an integer")
(define-option-type 'boolean
(lambda (x) (member x '("0" "1"))) "0 or 1"
(lambda (old new) (string=? new "1"))
boolean? "a boolean")
(define-option-type 'character
(lambda (x) (= (string-length x) 1)) "a character"
(lambda (old new) (string-ref new 0))
char? "a character")
(define-option-type 'string
string? ""
(lambda (old new) new)
string? "a string")
(define-option-type 'dynstring
string? ""
string-compose
string? "a string")
;;; --------------------------------------------------------------------------
;;; Utilities for working with streams.
(define (with-i/o name proc opener setter!)
(let* ((new (opener name)) (old (setter! new)) (result (proc)))
(setter! old)
(close-stream new)
result))
(define-macro (with-output-to-stream name . body)
`(with-i/o ,name (lambda () ,@body) open-output-stream set-output-stream!))
(define-macro (with-output-appended-to-stream name . body)
`(with-i/o ,name (lambda () ,@body) append-output-stream set-output-stream!))
(define-macro (with-input-from-stream name . body)
`(with-i/o ,name (lambda () ,@body) open-input-stream set-input-stream!))
;;; --------------------------------------------------------------------------
;;; Basic troff requests that are not output format specific.
(defrequest 'tm
(lambda (tm arg)
(display arg (error-port))
(newline (error-port))))
(define-option 'include-files 'boolean #t)
(defrequest 'so
(lambda (so fn)
(cond
((eqv? fn "")
(warn "missing filename for .so"))
((option 'include-files)
(with-input-from-stream fn
(copy-apply read-line-expand parse-line)))
(else ""))))
(defrequest 'ec
(lambda (ec c)
(cond
((eqv? c "")
(set-escape! #\\))
((= (string-length c) 1)
(set-escape! (string-ref c 0)))
(else
(warn "non-character argument for .ec")
(set-escape! #\\)))))
(defrequest 'rm
(lambda (rm . names)
(for-each
(lambda (x)
(defrequest x #f)
(defstring x #f))
names) ""))
;;; --------------------------------------------------------------------------
;;; Inline Scheme code execution; transparent output.
(define \##-env (the-environment))
(define (\##-eval expr) (eval expr \##-env))
(defrequest 'ig
(lambda (ig delim)
(define (copy-exec stop what)
(let loop ((s (read-line)))
(cond ((eof-object? s)
(warn "end-of-stream during ~a" what))
((not (string=? s stop))
(emit s)
(loop (read-line))))))
(cond
((string=? delim "##")
(with-output-to-stream '[##]
(copy-exec ".##\n" "inline Scheme execution"))
(let ((p (open-input-string (stream->string '[##]))))
(copy-apply (lambda () (read p)) \##-eval)))
((string=? delim ">>")
(copy-exec ".>>\n" "transparent output"))
(else
(skip-lines (concat #\. (if (eqv? delim "") #\. delim) #\newline))))
""))
(defrequest '\##
(lambda (\## sexpr)
(let ((p (open-input-string sexpr)))
(copy-apply (lambda () (read p)) \##-eval))))
(defrequest '>>
(lambda (>> code) (emit code #\newline)))
;;; --------------------------------------------------------------------------
;;; User-defined macros.
(define arg-stack '())
(defescape '$
(lambda ($ n)
(let ((i (string->number n)))
(cond
((not i)
(cond
((string=? n "*")
(if (null? arg-stack) "" (apply spread (cdar arg-stack))))
((string=? n "@")
(let loop ((a (if (null? arg-stack) '() (cdar arg-stack))))
(cond ((null? a)
"")
((null? (cdr a))
(concat #\" (car a) #\"))
(else
(concat #\" (car a) #\" #\space (loop (cdr a)))))))
(else
(warn "invalid $ argument `~a'" n))))
((or (null? arg-stack) (>= i (length (car arg-stack))))
"")
(else (list-ref (car arg-stack) i))))))
(defnumreg '.$
(lambda _
(number->string (if (null? arg-stack) 0 (1- (length (car arg-stack)))))))
(define (macro-buffer-name s) (concat "[." s "]"))
(define (expand-macro . args)
(list-push! arg-stack args)
(with-input-from-stream (macro-buffer-name (car args))
(copy-apply read-line-expand parse-line parse-copy-mode))
(list-pop! arg-stack) "")
(define (copy-macro-body)
(let* ((s (read-line-expand))
(t (if (eof-object? s) #f (parse-copy-mode s))))
(cond ((not t)
(warn "end-of-stream during macro definition"))
((not (string=? t "..\n"))
(emit t)
(copy-macro-body)))))
(defrequest 'de
(lambda (de name)
(cond ((eqv? name "")
(warn "missing name for .de"))
(else
(with-output-to-stream (macro-buffer-name name)
(copy-macro-body))
(defmacro name expand-macro) ""))))
(defrequest 'am
(lambda (am name)
(cond ((eqv? name "")
(warn "missing name for .am"))
(else
(with-output-appended-to-stream (macro-buffer-name name)
(copy-macro-body))
(defmacro name expand-macro) ""))))
;;; --------------------------------------------------------------------------
;;; if, if-else, else.
(defescape #\{ "")
(defescape #\} "")
(defrequest "\\}" "") ; do not complain about .\}
(define-option 'if-true 'dynstring "to")
(define-option 'if-false 'dynstring "ne")
(define if-stack '())
(define (if-request request condition rest)
(let* ((doit? #f)
(c (string-prune-left condition "!" condition))
(len (string-length c))
(neg? (not (eq? c condition))))
(cond
((and (= len 1) (char-alphabetic? (string-ref c 0)))
(cond
((substring? c (option 'if-true))
(set! doit? #t))
((substring? c (option 'if-false)))
(else (warn "unknown if-condition `~a'" c))))
((and (> len 0) (char-expression-delimiter? (string-ref c 0)))
(let ((x (parse-expression c #f #\u)))
(if x (set! doit? (not (zero? x))))))
(else
(let ((pair (parse-pair c)))
(if pair
(set! doit? (string=? (car pair) (cdr pair)))
(warn "if-condition `~a' not understood" c)))))
(cond
((eq? neg? doit?)
(unread-line (concat rest #\newline))
(skip-group))
(else
(unread-line (hack-if-argument rest))))
(if (string=? request "ie")
(list-push! if-stack (not (eq? neg? doit?))))
""))
;; Some people like to write .if requests such as
;; .if t \{\
;; .foo
;; This causes the string "\{.foo" to be passed to .if, as the first line
;; is a continuation line. So let's strip the initial \{. What a hack.
(define (hack-if-argument s)
(string-prune-left s "\\{" s))
(defrequest 'if if-request)
(defrequest 'ie if-request)
(defrequest 'el
(lambda (_ rest)
(cond
((null? if-stack)
(warn ".el without matching .ie request"))
((car if-stack)
(unread-line (concat rest #\newline))
(skip-group)
(list-pop! if-stack))
(else
(unread-line (hack-if-argument rest))
(list-pop! if-stack)))
""))
;;; --------------------------------------------------------------------------
;;; Number registers.
(define numreg-table (make-table 65536))
(defrequest 'nr
(lambda (nr name val incr)
(cond
((eqv? name "")
(warn "missing name for .nr"))
((eqv? val "")
(warn "missing value for .nr"))
(else
(let* ((old (table-lookup numreg-table name))
(v (parse val))
(n (parse-expression v #f #\u))
(add? (string-prune-left v "+" #f))
(i (if (eqv? incr "")
#f
(parse-expression (parse incr) #f #\u))))
(cond
((not n) "")
(old
(set-car! old (if (or add? (negative? n)) (+ (car old) n) n))
(if i
(set-cdr! old i)))
(else
(table-store! numreg-table name (cons n (if i i 0))))))))
""))
(defescape 'n
(lambda (_ name . sign)
(let ((val (table-lookup numreg-table name)))
(cond
(val
(if (not (null? sign))
(case (car sign)
(#\+ (set-car! val (+ (car val) (cdr val))))
(#\- (set-car! val (- (car val) (cdr val))))))
(number->string (car val)))
(else (warn "undefined number register: `~a'" name) "0")))))
(defrequest 'rr
(lambda (rr . names)
(for-each
(lambda (x)
(defnumreg x #f)
(table-remove! numreg-table x))
names) ""))
;;; Predefined number registers
(defnumreg 'dw
(lambda _
(number->string (1+ (string->number (substitute "%weekdaynum%"))))))
(defnumreg 'dy (lambda _ (substitute "%day%")))
(defnumreg 'mo (lambda _ (substitute "%month%")))
(defnumreg 'yr (lambda _ (substring (substitute "%year%") 2 4)))
(defnumreg '.C (lambda _ (if (troff-compatible?) #\1 #\0)))
(defnumreg '% #\0)
(defnumreg '.z "")
(defnumreg '.U #\1)
;;; --------------------------------------------------------------------------
;;; Strings. Note that user-defined strings are re-scanned (strings
;;; defined via `defstring' aren't, because they may contain anything).
(defrequest 'ds
(lambda (ds name val)
(if (eqv? name "")
(warn "missing name for .ds")
(let ((v (string-prune-left val "\"" val)))
(defstring name (lambda _ (parse-expand v)))))
""))
(defrequest 'as
(lambda (as name val)
(if (eqv? name "")
(warn "missing name for .as")
(let* ((f (stringdef name))
(s (if f (if (string? f) f (f)) ""))
(new (concat s (string-prune-left val "\"" val))))
(defstring name (lambda _ (parse-expand new)))))
""))
(defescape '*
(lambda (_ name)
(warn "undefined string: `~a'" name)))
;;; --------------------------------------------------------------------------
;;; Now we are done with the definitions.
;;;
;;; Load the output-format-specific Scheme code and the macro-package-
;;; specific Scheme code.
(load (substitute "%directory%/scm/%format%/common.scm"))
(load (substitute "%directory%/scm/%format%/m%macros%.scm"))
(set! garbage-collect-notify? #f)
(append! load-path (list (substitute "%directory%/scm/misc")))

93
src/Makefile Normal file
View File

@ -0,0 +1,93 @@
# $Revision: 1.12 $
### You need a C compiler that compiles ANSI C code.
CC = gcc
CFLAGS = -Wall -pedantic -O
### If you need additional linker flags add them here.
LDFLAGS =
### The directory where the Elk installation resides on your system.
ELKDIR = /usr/elk
### Additional libraries. You may want to insert the output of the
### shell-script $(ELKDIR)/lib/ldflags here.
LIBS = -lm
### The makedepend program (it's usually installed with the X11 binaries).
MAKEDEP = makedepend
### The directory under which you will install the Scheme files.
DIR = /usr/local/lib/unroff
### The default output format.
FORMAT = html
### End of configurable variables.
### -------------------------------------------------------------------------
SHELL = /bin/sh
INCLUDE = -I$(ELKDIR)/include
ELK = $(ELKDIR)/lib/module.o
DEFS = -DDEFAULT_DIR=\"$(DIR)\" -DDEFAULT_FORMAT=\"$(FORMAT)\"
CTAGS = ctags -t -w
SOURCES = \
args.c\
buffer.c\
error.c\
event.c\
expr.c\
gcroot.c\
insert.c\
malloc.c\
parse.c\
prim.c\
scmtable.c\
stream.c\
subst.c\
table.c\
unroff.c
OBJECTS = \
args.o\
buffer.o\
error.o\
event.o\
expr.o\
gcroot.o\
insert.o\
malloc.o\
parse.o\
prim.o\
scmtable.o\
stream.o\
subst.o\
table.o\
unroff.o\
$(ELK)
ALL = unroff
all: $(ALL)
.c.o:
$(CC) $(CFLAGS) $(INCLUDE) $(DEFS) -c $<
clean:
rm -f *.o $(ALL)
tags ctags: $(SOURCES)
$(CTAGS) $(SOURCES)
unroff: $(OBJECTS)
$(CC) $(LDFLAGS) -o $@ $(OBJECTS) $(LIBS)
depend: Makefile $(SOURCES)
$(MAKEDEP) $(INCLUDE) $(SOURCES)
# DO NOT DELETE THIS LINE -- make depend depends on it.

72
src/args.c Normal file
View File

@ -0,0 +1,72 @@
/* $Revision: 1.5 $
*/
/* Functions that maintain a properly GC-linked list of Scheme
* arguments. The parser collects arguments to an event procedure
* using args_add(); the code that eventually calls the event
* procedure calls args_get() to obtain a list of arguments that
* can directly be passed to Funcall().
*
* args_clear() -- clears the list
* args_add(obj) -- appends an object to the list
* args_get() -- returns the list
* args_num() -- returns the current length of the list
*/
#include "unroff.h"
static Object args, last, rest;
static int num;
void args_clear(void) {
if (Nullp(last)) {
if (!Nullp(rest))
args = rest;
} else
P_Setcdr(last, rest);
last = Null;
num = 0;
}
void args_add(Object x) {
Object tmp;
GC_Node;
if (Nullp(last)) {
last = args;
} else {
if (Nullp(Cdr(last))) {
GC_Link(x);
tmp = Cons(False, Null);
GC_Unlink;
Cdr(last) = tmp;
}
last = Cdr(last);
}
Car(last) = x;
rest = Cdr(last);
num++;
}
Object args_get(void) {
if (Nullp(last)) {
rest = args;
return Null;
}
rest = Cdr(last);
P_Setcdr(last, Null);
return args;
}
int args_num(void) {
return num;
}
void init_args(void) {
rest = Cons(False, Null);
args = last = Null;
Global_GC_Link(args);
Global_GC_Link(last);
Global_GC_Link(rest);
}

8
src/args.h Normal file
View File

@ -0,0 +1,8 @@
/* $Revision: 1.2 $
*/
void args_clear(void);
void args_add(Object);
Object args_get(void);
int args_num(void);
void init_args(void);

68
src/buffer.c Normal file
View File

@ -0,0 +1,68 @@
/* $Revision: 1.7 $
*/
/* `Buffer' is a dynamically growing, general-purpose buffer data
* structure.
*
* buffer_new(size) -- returns new buffer with initial size `size'
* or some default size if argument is zero
* buffer_delete(b) -- marks a buffer as unused
* buffer_clear(b) -- empties a buffer
* buffer_puts(b,data,len) -- appends data to a buffer, growing it if
* necessary
* buffer_putc(b,c) -- appends a character to a buffer
*/
#include "unroff.h"
static int sizeinc_linear(Buffer *);
static Buffer *first_free;
Buffer *buffer_new(int size) {
Buffer *p;
if (size == 0)
size = 512;
if (first_free) { /* reuse a buffer if there is one */
p = first_free;
first_free = p->next;
if (p->max < size) {
free(p->data);
p->data = safe_malloc(p->max = size);
}
} else {
p = safe_malloc(sizeof *p);
p->data = safe_malloc(size);
p->increment = p->max = size;
p->sizeinc_func = sizeinc_linear;
}
buffer_clear(p);
return p;
}
void buffer_delete(Buffer *p) {
assert(p->size >= 0);
p->size = -1;
p->next = first_free;
first_free = p;
}
void buffer_grow(Buffer *p) {
p->max = p->sizeinc_func(p);
p->data = safe_realloc(p->data, p->max);
}
void buffer_puts(Buffer *p, char *s, int size) {
assert(size >= 0);
while (p->size + size > p->max)
buffer_grow(p);
if (size == 0)
return;
memcpy(p->data+p->size, s, size);
p->size += size;
}
static int sizeinc_linear(Buffer *p) {
return p->max + p->increment;
}

26
src/buffer.h Normal file
View File

@ -0,0 +1,26 @@
/* $Revision: 1.4 $
*/
typedef struct _buffer {
char *data;
int increment;
int size;
int max;
int (*sizeinc_func)(struct _buffer *);
struct _buffer *next;
} Buffer;
Buffer *buffer_new(int);
void buffer_delete(Buffer *);
void buffer_grow(Buffer *);
void buffer_puts(Buffer *, char *, int);
#define buffer_need_grow(p) ((p)->size == (p)->max)
#define buffer_clear(p) ((p)->size = 0)
#define buffer_putc(p, c) {\
if (buffer_need_grow(p))\
buffer_grow(p);\
(p)->data[(p)->size++] = (c);\
}

33
src/config.h Normal file
View File

@ -0,0 +1,33 @@
/* $Revision: 1.9 $
*/
/* Environment variable specifing library directory:
*/
#define DEFAULT_DIR_ENV "UNROFF_DIR"
/* Environment variable specifing default output format:
*/
#define DEFAULT_FORMAT_ENV "UNROFF_FORMAT"
/* User-supplied initialization file loaded from home directory:
*/
#define RC_FILE ".unroff"
/* File to load if option -t is given:
*/
#define TEST_TOPLEVEL "toplevel.scm"
#define MAJOR_VERSION 1
#define MINOR_VERSION 0
/* Hack: __GNUC_MINOR__ was introduced together with __attribute__ */
#ifdef __GNUC_MINOR__
# define NORETURN __attribute__ ((noreturn))
#else
# define NORETURN
#endif

53
src/elk-2.2-patch Normal file
View File

@ -0,0 +1,53 @@
*** 1.59 1994/01/24 16:03:30
--- src/main.c 1995/02/01 17:06:20
***************
*** 90,95 ****
--- 90,98 ----
struct stat st;
extern int errno;
char foo;
+ #ifdef NOMAIN
+ # define foo (av[0][0])
+ #endif
#ifdef CAN_DUMP
bzero (unused, 1); /* see comment above */
***************
*** 207,213 ****
* the load-path, so that -p can be used.
*/
Error_Tag = "scheme-init";
! initfile = Safe_Malloc (strlen (SCM_DIR) + 1 + sizeof (INITFILE));
sprintf (initfile, "%s/%s", SCM_DIR, INITFILE);
if (stat (initfile, &st) == -1 && errno == ENOENT)
file = Make_String (INITFILE, sizeof(INITFILE)-1);
--- 210,216 ----
* the load-path, so that -p can be used.
*/
Error_Tag = "scheme-init";
! initfile = Safe_Malloc (strlen (SCM_DIR) + 1 + sizeof (INITFILE) + 1);
sprintf (initfile, "%s/%s", SCM_DIR, INITFILE);
if (stat (initfile, &st) == -1 && errno == ENOENT)
file = Make_String (INITFILE, sizeof(INITFILE)-1);
***************
*** 220,227 ****
Error_Tag = "top-level";
#ifdef NOMAIN
! if ((loadfile = toplevel) == 0)
return;
#endif
if (loadfile == 0)
loadfile = "toplevel.scm";
--- 223,233 ----
Error_Tag = "top-level";
#ifdef NOMAIN
! if ((loadfile = toplevel) == 0) {
! Interpreter_Initialized = 1;
! GC_Debug = debug;
return;
+ }
#endif
if (loadfile == 0)
loadfile = "toplevel.scm";

91
src/error.c Normal file
View File

@ -0,0 +1,91 @@
/* $Revision: 1.12 $
*/
#include "unroff.h"
static char *progname = "(whoami?)";
void set_progname(char *s) {
char *p;
progname = (p = strrchr(s, '/')) ? p+1 : s;
}
char *get_progname(void) {
return progname;
}
void warn(char *fmt, ...) {
va_list args;
fprintf(stderr, "%s: ", progname);
if (istream_is_open())
fprintf(stderr, "%s:%lu: ", curr_istream_target(), curr_istream_lno());
fprintf(stderr, "warning: ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
fprintf(stderr, ".\n");
}
void fatal_error(char *fmt, ...) {
va_list args;
fprintf(stderr, "%s: ", progname);
va_start(args, fmt);
vfprintf(stderr, fmt, args);
fprintf(stderr, ".\n");
exit(1);
}
static char *strerr(void) {
extern int sys_nerr;
extern char *sys_errlist[];
return errno > 0 && errno < sys_nerr ?
sys_errlist[errno] : "unknown error";
}
void read_error(char *s) {
fatal_error("reading %s: %s", s, strerr());
}
void write_error(char *s) {
fatal_error("writing %s: %s", s, strerr());
}
void open_error(char *fn) {
fatal_error("%s: %s", fn, strerr());
}
char *printable_string(char *s, int len) {
static Buffer *bp;
char c[4];
if (!bp)
bp = buffer_new(0);
buffer_clear(bp);
for ( ; len > 0; len--, s++) {
if (isprint(UCHAR(*s)) || *s == ' ') {
buffer_putc(bp, *s);
} else {
buffer_putc(bp, '\\');
switch (*s) {
case '\n':
buffer_putc(bp, 'n'); break;
case '\t':
buffer_putc(bp, 't'); break;
case '\b':
buffer_putc(bp, 'b'); break;
default:
sprintf(c, "%03o", *s);
buffer_puts(bp, c, 3);
}
}
}
buffer_putc(bp, '\0');
return bp->data;
}
char *printable_char(char c) {
return printable_string(&c, 1);
}

20
src/error.h Normal file
View File

@ -0,0 +1,20 @@
/* $Revision: 1.7 $
*/
void set_progname(char *);
char *get_progname(void);
void warn(char *, ...);
void fatal_error(char *, ...) NORETURN;
void read_error(char *) NORETURN;
void write_error(char *) NORETURN;
void open_error(char *) NORETURN;
/* Redefined to add NORETURN
*/
extern Primitive_Error() NORETURN;
char *printable_string(char *, int);
char *printable_char(char);

375
src/event.c Normal file
View File

@ -0,0 +1,375 @@
/* $Revision: 1.15 $
*/
/* Scheme primitives that define and query event procedures, and
* functions to lookup and execute events.
*/
#include "unroff.h"
#define NUM_VEC_EVENTS 6
static Table *requests, *numregs, *specials, *escapes, *chars, *others;
static Table *fallbacks;
static Object Key_Equation, Key_Sentence;
Object eventsvec;
static SYMDESCR event_syms[] = {
{ "line", EV_LINE },
{ "prolog", EV_PROLOG },
{ "epilog", EV_EPILOG },
{ "option", EV_OPTION },
{ "start", EV_START },
{ "exit", EV_EXIT },
{ 0, 0 }
};
static char *event_names[] = {
"line event",
"prolog event",
"epilog event",
"option event",
"start event",
"exit event",
"request",
"macro",
"string",
"number register",
"special character",
"inline equation",
"sentence event",
"escape sequence",
"character event",
};
static Object make_event_object(Object x) {
switch (TYPE(x)) {
case T_Null:
case T_Compound:
case T_String:
case T_Character:
case T_Symbol:
return x;
case T_Primitive:
Primitive_Error("event function must be a compound procedure");
default:
if (EQ(x, False))
return x;
Primitive_Error("invalid event value argument");
}
}
static Object store_event(Table *tp, char *key, int size, Object obj,
char flags) {
Elem *oldp;
Object ret = False;
if ((oldp = table_lookup(tp, key, size)) != 0)
ret = get_object(oldp->obj);
if (!Nullp(obj))
if (EQ(obj, False))
table_remove(tp, key, size);
else
table_store(tp, key, size, obj, (unsigned long)flags);
return ret;
}
static Object def_indexed(Table *tp, Object key, Object obj, char flags,
char *code) {
if (TYPE(key) == T_Symbol)
key = SYMBOL(key)->name;
else if (TYPE(key) != T_String)
Wrong_Type_Combination(key, "string or symbol");
if (STRING(key)->size == 0) {
if (*code != 'r' && *code != 'p')
Primitive_Error("event key must be of non-zero length");
return store_event(fallbacks, code, 1, make_event_object(obj), flags);
}
return store_event(tp, STRING(key)->data, STRING(key)->size,
make_event_object(obj), flags);
}
static Object p_defrequest(Object key, Object obj) {
return def_indexed(requests, key, obj, 0, "r");
}
static Object p_defmacro(Object key, Object obj) {
return def_indexed(requests, key, obj, RQ_MACRO, "m");
}
static Object p_defstring(Object key, Object obj) {
return def_indexed(requests, key, obj, RQ_STRING, "s");
}
static Object p_defnumreg(Object key, Object obj) {
return def_indexed(numregs, key, obj, 0, "n");
}
static Object p_defspecial(Object key, Object obj) {
return def_indexed(specials, key, obj, 0, "p");
}
static Object p_defequation(Object obj) {
return def_indexed(others, Key_Equation, obj, 0, 0);
}
static Object p_defsentence(Object obj) {
return def_indexed(others, Key_Sentence, obj, 0, 0);
}
static Object p_requestdef(Object key) {
return def_indexed(requests, key, Null, 0, "r");
}
static Object p_macrodef(Object key) {
return def_indexed(requests, key, Null, 0, "m");
}
static Object p_stringdef(Object key) {
return def_indexed(requests, key, Null, 0, "s");
}
static Object p_numregdef(Object key) {
return def_indexed(numregs, key, Null, 0, "n");
}
static Object p_specialdef(Object key) {
return def_indexed(specials, key, Null, 0, "p");
}
static Object p_equationdef(void) {
return def_indexed(others, Key_Equation, Null, 0, 0);
}
static Object p_sentencedef(void) {
return def_indexed(others, Key_Sentence, Null, 0, 0);
}
static Object def_char_event(Table *tp, Object key, Object obj) {
char c;
switch (TYPE(key)) {
case T_Character:
c = CHAR(key); break;
case T_Symbol:
key = SYMBOL(key)->name;
/* fall through */
case T_String:
if (tp == escapes && STRING(key)->size == 0)
return store_event(fallbacks, "e", 1, make_event_object(obj), 0);
if (STRING(key)->size != 1)
goto err;
c = STRING(key)->data[0];
break;
default: err:
Primitive_Error("cannot coerce argument to character");
}
return store_event(tp, &c, 1, make_event_object(obj), 0);
}
Object p_defescape(Object key, Object obj) {
return def_char_event(escapes, key, obj);
}
Object p_defchar(Object key, Object obj) {
return def_char_event(chars, key, obj);
}
Object p_escapedef(Object key) {
return def_char_event(escapes, key, Null);
}
Object p_chardef(Object key) {
return def_char_event(chars, key, Null);
}
Elem *event_lookup(Event e, char *key, int size) {
Elem *p;
if (key && size == 0) {
switch(e) {
case EV_REQUEST:
return table_lookup(fallbacks, "r", 1); /* not yet */
case EV_MACRO:
return table_lookup(fallbacks, "m", 1); /* not yet */
case EV_SPECIAL:
return table_lookup(fallbacks, "p", 1);
case EV_ESCAPE:
return table_lookup(fallbacks, "e", 1);
default:
assert(0);
}
} else {
switch (e) {
case EV_REQUEST:
return ((p = table_lookup(requests, key, size)) &&
!(p->flags & RQ_MACRO)) ? p : 0;
case EV_MACRO:
return ((p = table_lookup(requests, key, size)) &&
(p->flags & RQ_MACRO)) ? p : 0;
case EV_STRING:
return table_lookup(requests, key, size);
case EV_NUMREG:
return table_lookup(numregs, key, size);
case EV_SPECIAL:
return table_lookup(specials, key, size);
case EV_ESCAPE:
return table_lookup(escapes, key, size);
case EV_CHAR:
assert(size == 1);
return table_lookup(chars, key, size);
case EV_EQUATION:
return table_lookup(others, "e", 1);
case EV_SENTENCE:
return table_lookup(others, "s", 1);
case EV_LINE:
case EV_PROLOG:
case EV_EPILOG:
case EV_OPTION:
case EV_START:
case EV_EXIT:
default:
assert(0);
}
}
return 0; /* shut up gcc -W */
}
static int check_num_args(Object fun, int num, char *evnam) {
char *s = 0;
struct S_Compound *comp = COMPOUND(fun);
if (num < comp->min_args)
s = "few";
else if (comp->max_args >= 0 && num > comp->max_args)
s = "many";
if (s) warn("too %s arguments for %s function", s, evnam);
return !s;
}
char *event_exec(Event e, char *key, int size, int *size_ret, int complain) {
Elem *p = event_lookup(e, key, size);
Object ret;
char *name = event_names[e];
static char c;
if (p) {
if ((e == EV_REQUEST || e == EV_MACRO) && (p->flags & RQ_STRING)) {
warn("cannot execute string as request or macro");
return 0;
}
if (e == EV_STRING && !(p->flags & RQ_STRING)) {
warn("cannot execute request or macro as string");
return 0;
}
ret = get_object(p->obj);
if (TYPE(ret) == T_Compound) {
if (!check_num_args(ret, args_num(), name))
return 0;
ret = Funcall(ret, args_get(), 0);
}
assert(size_ret != 0);
switch (TYPE(ret)) {
case T_Character:
c = CHAR(ret);
*size_ret = 1;
return &c;
case T_Symbol:
ret = SYMBOL(ret)->name; /* fall through */
case T_String:
*size_ret = STRING(ret)->size;
return STRING(ret)->data;
default:
warn("cannot coerce result of %s function to string", name);
return 0;
}
}
if (complain)
warn("no event value for %s `%s'", name, printable_string(key, size));
return 0;
}
char *event_exec_fallback(Event e, char *key, int size, int *size_ret) {
if (!event_lookup(e, key, size) && event_lookup(e, "", 0))
return event_exec(e, "", 0, size_ret, 1);
else
return event_exec(e, key, size, size_ret, 1);
}
static Object store_vec_event(int e, Object pri, Object obj) {
Object ret, v = VECTOR(eventsvec)->data[e];
int p = Get_Integer(pri);
if (p < 0 || p > 99)
Range_Error(pri);
ret = VECTOR(v)->data[p];
if (!Nullp(obj))
VECTOR(v)->data[p] = obj;
return ret;
}
static Object p_defevent(Object event, Object pri, Object obj) {
char e = Symbols_To_Bits(event, 0, event_syms);
if (Truep(obj) && TYPE(obj) != T_Compound)
Wrong_Type_Combination(obj, "compound procedure or #f");
return store_vec_event(e, pri, obj);
}
static Object p_eventdef(Object event, Object pri) {
char e = Symbols_To_Bits(event, 0, event_syms);
return store_vec_event(e, pri, Null);
}
void events_vec_exec(Event e) {
Object func, v;
int i;
for (i = 0; i < 100; i++) {
v = VECTOR(eventsvec)->data[e];
func = VECTOR(v)->data[i];
if (Truep(func) && check_num_args(func, args_num(), event_names[e]))
(void)Funcall(func, args_get(), 0);
}
}
void init_event(void) {
int i;
requests = table_new(256*256);
numregs = table_new(256*256);
specials = table_new(256*256);
escapes = table_new(256);
chars = table_new(256);
others = table_new(10);
fallbacks = table_new(256);
Key_Equation = Make_String("e", 1);
Global_GC_Link(Key_Equation);
Key_Sentence = Make_String("s", 1);
Global_GC_Link(Key_Sentence);
eventsvec = Make_Vector(NUM_VEC_EVENTS, Null);
Global_GC_Link(eventsvec);
for (i = 0; i < NUM_VEC_EVENTS; i++)
VECTOR(eventsvec)->data[i] = Make_Vector(100, False);
Define_Primitive(p_defrequest, "defrequest", 2, 2, EVAL);
Define_Primitive(p_defmacro, "defmacro", 2, 2, EVAL);
Define_Primitive(p_defstring, "defstring", 2, 2, EVAL);
Define_Primitive(p_defnumreg, "defnumreg", 2, 2, EVAL);
Define_Primitive(p_defspecial, "defspecial", 2, 2, EVAL);
Define_Primitive(p_defequation, "defequation", 1, 1, EVAL);
Define_Primitive(p_defsentence, "defsentence", 1, 1, EVAL);
Define_Primitive(p_defescape, "defescape", 2, 2, EVAL);
Define_Primitive(p_defchar, "defchar", 2, 2, EVAL);
Define_Primitive(p_defevent, "defevent", 3, 3, EVAL);
Define_Primitive(p_requestdef, "requestdef", 1, 1, EVAL);
Define_Primitive(p_macrodef, "macrodef", 1, 1, EVAL);
Define_Primitive(p_stringdef, "stringdef", 1, 1, EVAL);
Define_Primitive(p_numregdef, "numregdef", 1, 1, EVAL);
Define_Primitive(p_specialdef, "specialdef", 1, 1, EVAL);
Define_Primitive(p_equationdef, "equationdef", 0, 0, EVAL);
Define_Primitive(p_sentencedef, "sentencedef", 0, 0, EVAL);
Define_Primitive(p_escapedef, "escapedef", 1, 1, EVAL);
Define_Primitive(p_chardef, "chardef", 1, 1, EVAL);
Define_Primitive(p_eventdef, "eventdef", 2, 2, EVAL);
}

33
src/event.h Normal file
View File

@ -0,0 +1,33 @@
/* $Revision: 1.12 $
*/
/* Events:
*/
typedef enum _event {
EV_LINE, /* args: char|#f ret: */
EV_PROLOG, /* args: pathname basename ret: */
EV_EPILOG, /* args: pathname basename ret: */
EV_OPTION, /* args: name value ret: */
EV_START, /* args: ret: */
EV_EXIT, /* args: ret: */
EV_REQUEST, /* args: name args ret: stringable */
EV_MACRO, /* args: name args ret: stringable */
EV_STRING, /* args: name ret: stringable */
EV_NUMREG, /* args: name [sign] ret: stringable */
EV_SPECIAL, /* args: name ret: stringable */
EV_EQUATION, /* args: string ret: stringable */
EV_SENTENCE, /* args: char ret: stringable */
EV_ESCAPE, /* args: char [arg [sign]] ret: stringable */
EV_CHAR /* args: char ret: stringable */
} Event;
/* Flags stored in `requests' table
*/
#define RQ_STRING 1
#define RQ_MACRO 2
void init_event(void);
Elem *event_lookup(Event, char *, int);
char *event_exec(Event, char *, int, int *, int);
char *event_exec_fallback(Event, char *, int, int *);
void events_vec_exec(Event);

235
src/expr.c Normal file
View File

@ -0,0 +1,235 @@
/* $Revision: 1.5 $
*/
/* Scheme primitives that deal with troff numeric expressions.
*/
#include "unroff.h"
#define SCALE_INDICATORS "icPmnpuv"
enum operator {
ADD, SUB, DIV, MUL, MOD, LT, GT, LE, GE, EQ, AND, OR, OOPS
};
static int scale_factor[128];
static int scale_divisor[128];
static Object p_set_scaling(Object scale, Object x, Object y) {
int d, c;
Check_Type(scale, T_Character);
c = CHAR(scale);
if (c == 0 || strchr(SCALE_INDICATORS, c) == 0)
Primitive_Error("invalid scale indicator ~s", scale);
scale_factor[c] = Get_Integer(x);
if ((d = Get_Integer(y)) == 0)
Range_Error(y);
scale_divisor[c] = d;
return Void;
}
static Object p_get_scaling(Object scale) {
int c;
Object ret = Null;
GC_Node;
Check_Type(scale, T_Character);
c = CHAR(scale);
if (c == 0 || strchr(SCALE_INDICATORS, c) == 0)
Primitive_Error("invalid scale indicator ~s", scale);
GC_Link(ret);
ret = Cons(Make_Integer(scale_factor[c]), Null);
Cdr(ret) = Make_Integer(scale_divisor[c]);
GC_Unlink;
return ret;
}
static enum operator get_operator(char **p) {
enum operator op;
switch (**p) {
case '+':
op = ADD; break;
case '-':
op = SUB; break;
case '/':
op = DIV; break;
case '*':
op = MUL; break;
case '%':
op = MOD; break;
case '<':
if ((*p)[1] == '=') {
(*p)++;
op = LE;
} else
op = LT;
break;
case '>':
if ((*p)[1] == '=') {
(*p)++;
op = GE;
} else
op = GT;
break;
case '&':
op = AND; break;
case ':':
op = OR; break;
case '=':
if ((*p)[1] == '=')
(*p)++;
op = EQ;
break;
default:
return OOPS;
}
(*p)++;
return op;
}
static double parse_expr(char **, int, int);
static double get_operand(char **p, int scale, int rest) {
double d;
char *ep;
if (**p == '(') {
(*p)++;
d = parse_expr(p, scale, rest);
if (*p) {
if (**p != ')') *p = 0;
else (*p)++;
}
return d;
}
d = strtod(*p, &ep);
if (ep == *p) {
*p = 0;
return 0;
}
*p = ep;
if (**p && strchr(SCALE_INDICATORS, **p)) {
scale = **p;
(*p)++;
}
return d * scale_factor[scale] / scale_divisor[scale];
}
static double parse_expr(char **p, int scale, int rest) {
double acc, d;
enum operator op;
acc = get_operand(p, scale, rest);
if (*p == 0)
return 0;
while (**p && **p != ')') {
if ((op = get_operator(p)) == OOPS) {
if (rest)
break;
err: *p = 0;
return 0;
}
d = get_operand(p, scale, rest);
if (*p == 0)
return 0;
switch (op) {
case ADD:
acc += d; break;
case SUB:
acc -= d; break;
case DIV:
if (d == 0.0) {
warn("division by zero"); goto err;
}
acc /= d; break;
case MUL:
acc *= d; break;
case MOD:
acc = fmod(acc, d);
if (isnan(acc)) {
warn("division by zero"); goto err;
}
break;
case LT:
acc = acc < d; break;
case GT:
acc = acc > d; break;
case LE:
acc = acc <= d; break;
case GE:
acc = acc >= d; break;
case EQ:
acc = acc == d; break;
case AND:
acc = acc > 0 && d > 0; break;
case OR:
acc = acc > 0 || d > 0; break;
case OOPS:
assert(0);
}
}
if (!finite(acc)) {
warn("expression evaluates to infinity"); goto err;
}
return acc;
}
static Object parse_expression(Object str, Object fail, Object scale,
int rest) {
int c;
char *e, *s;
double d;
Object ret;
Check_Type(scale, T_Character);
c = CHAR(scale);
if (c == 0 || strchr(SCALE_INDICATORS, c) == 0)
Primitive_Error("invalid scale indicator ~s", scale);
e = s = Get_String(str);
d = parse_expr(&s, c, rest);
if (s == 0 || (*s && !rest)) {
warn("invalid expression: `%s'", e);
return fail;
}
ret = P_Inexact_To_Exact(Make_Flonum(d));
if (rest) {
Object x;
GC_Node;
GC_Link(ret);
ret = Cons(ret, Null);
x = Make_String(s, strlen(s));
Cdr(ret) = x;
GC_Unlink;
}
return ret;
}
static Object p_parse_expression(Object str, Object fail, Object scale) {
return parse_expression(str, fail, scale, 0);
}
static Object p_parse_expression_rest(Object str, Object fail, Object scale) {
return parse_expression(str, fail, scale, 1);
}
static Object p_char_expression_delimiter(Object c) {
Check_Type(c, T_Character);
return strchr("0123456789()+-*/%<>=:&.", CHAR(c)) ? True : False;
}
void init_expr(void) {
char *p;
for (p = SCALE_INDICATORS; *p; p++)
scale_factor[(int)*p] = scale_divisor[(int)*p] = 1;
Define_Primitive(p_set_scaling, "set-scaling!", 3, 3, EVAL);
Define_Primitive(p_get_scaling, "get-scaling", 1, 1, EVAL);
Define_Primitive(p_parse_expression, "parse-expression", 3, 3, EVAL);
Define_Primitive(p_parse_expression_rest,
"parse-expression-rest", 3, 3, EVAL);
Define_Primitive(p_char_expression_delimiter,
"char-expression-delimiter?", 1, 1, EVAL);
}

4
src/expr.h Normal file
View File

@ -0,0 +1,4 @@
/* $Revision: 1.1 $
*/
void init_expr(void);

58
src/gcroot.c Normal file
View File

@ -0,0 +1,58 @@
/* $Revision: 1.5 $
*/
#include "unroff.h"
/* Functions to register Scheme objects in a vector that has been added
* to the GC root set to avoid having to deal with weak pointers.
* Code using these functions works with indexes into the vector.
*
* register_object(obj) -- registers an object and returns index
* deregister_object(inx) -- deregisters object specified by index
* get_object(inx) -- returns object specified by index
*/
static int max_objects = 32; /* initial size */
static int num_objects;
static Object objects;
static int inx;
int register_object(Object x) {
Object v;
int n;
GC_Node;
if (num_objects == max_objects) {
max_objects *= 2;
GC_Link(x);
v = Make_Vector(max_objects, Null);
GC_Unlink;
memcpy(VECTOR(v)->data, VECTOR(objects)->data,
num_objects * sizeof(Object));
objects = v;
inx = num_objects;
}
for (n = 0; !Nullp(VECTOR(objects)->data[inx]);
inx++, inx %= max_objects) {
n++;
assert(n < max_objects);
}
VECTOR(objects)->data[inx] = x;
num_objects++;
return inx;
}
void deregister_object(int i) {
VECTOR(objects)->data[i] = Null;
--num_objects;
assert(num_objects >= 0);
}
Object get_object(int i) {
return VECTOR(objects)->data[i];
}
void init_gcroot(void) {
objects = Make_Vector(max_objects, Null);
Global_GC_Link(objects);
}

7
src/gcroot.h Normal file
View File

@ -0,0 +1,7 @@
/* $Revision: 1.1 $
*/
int register_object(Object);
void deregister_object(int);
Object get_object(int);
void init_gcroot(void);

127
src/insert.c Normal file
View File

@ -0,0 +1,127 @@
/* $Revision: 1.6 $
*/
/* The implementation of the Scheme primitive `file-insertions'.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "unroff.h"
static int cmp(const void *p1, const void *p2) {
struct S_String *s1, *s2;
Object o1, o2;
int tmp;
o1 = *(Object *)p1, s1 = STRING(Car(o1));
o2 = *(Object *)p2, s2 = STRING(Car(o2));
if ((tmp = memcmp(s1->data, s2->data,
s1->size < s2->size ? s1->size : s2->size)) == 0)
if (s1->size == s2->size)
return Get_Integer(Car(Cdr(o1))) - Get_Integer(Car(Cdr(o2)));
else
return s1->size - s2->size;
return tmp;
}
#define Finish_File {\
if (fromfn) {\
copy_rest(from, to, fromfn, tofn);\
close(from);\
close(to);\
if (rename(tofn, fromfn) == -1) {\
Saved_Errno = errno;\
Primitive_Error("cannot rename ~s to ~s: ~E", tofn, fromfn);\
}\
free(tofn);\
}\
}
static void copy(int from, int to, char *fromfn, char *tofn,
int len, int off) {
char buf[8192];
int n;
while (len > 0) {
if ((n = read(from, buf, len > 8192 ? 8192 : len)) == -1)
read_error(fromfn);
if (n == 0)
fatal_error("file `%s' too short for insertion at offset %d",
fromfn, off);
if (write(to, buf, n) != n)
write_error(tofn);
len -= n;
}
}
static void copy_rest(int from, int to, char *fromfn, char *tofn) {
char buf[8192];
int n;
while ((n = read(from, buf, 8192)) > 0)
if (write(to, buf, n) != n)
write_error(tofn);
if (n == -1)
read_error(fromfn);
}
static Object p_file_insertions(Object spec) {
Object v, x;
int i, t, from = 0, to = 0; /* make gcc -Wuninitialized happy */
char *currfn, *fromfn, *tofn = 0; /* ditto */
struct stat st;
int off = 0, nextoff; /* ditto for `off' */
struct S_String *ins;
v = P_List_To_Vector(spec);
for (i = VECTOR(v)->size; --i >= 0; ) {
x = VECTOR(v)->data[i];
Check_List(x);
if (Fast_Length(x) < 3 || TYPE(Car(x)) != T_String ||
((t = TYPE(Car(Cdr(x)))) != T_Fixnum && t != T_Bignum) ||
TYPE(Car(Cdr(Cdr(x)))) != T_String)
Primitive_Error("invalid file insertion specification");
}
qsort(VECTOR(v)->data, VECTOR(v)->size, sizeof(Object), cmp);
for (fromfn = 0, i = 0; i < VECTOR(v)->size; i++) {
x = VECTOR(v)->data[i];
currfn = Get_String(Car(x));
if (!fromfn || strcmp(fromfn, currfn) != 0) {
Finish_File;
fromfn = currfn;
if ((from = open(fromfn, O_RDONLY)) == -1)
open_error(fromfn);
(void)fstat(from, &st);
if (st.st_nlink > 1) {
warn("links to `%s' exist; insertion skipped", fromfn);
close(from);
fromfn = 0;
continue;
}
tofn = safe_malloc(strlen(fromfn) + 1 + 4);
sprintf(tofn, "%s.new", fromfn);
if ((to = open(tofn, O_WRONLY|O_CREAT|O_TRUNC, st.st_mode)) == -1)
open_error(tofn);
off = 0;
}
x = Cdr(x);
nextoff = Get_Integer(Car(x));
if (nextoff < 0)
Primitive_Error("invalid insertion offset ~a", Car(x));
x = Cdr(x);
ins = STRING(Car(x));
copy(from, to, fromfn, tofn, nextoff-off, nextoff);
if (write(to, ins->data, ins->size) != ins->size)
write_error(tofn);
off = nextoff;
}
Finish_File;
return Void;
}
void init_insert(void) {
Define_Primitive(p_file_insertions, "file-insertions", 1, 1, EVAL);
}

4
src/insert.h Normal file
View File

@ -0,0 +1,4 @@
/* $Revision: 1.1 $
*/
void init_insert(void);

37
src/malloc.c Normal file
View File

@ -0,0 +1,37 @@
/* $Revision: 1.1 $
*/
#include "unroff.h"
static void (*err_handler)(char *, size_t);
static void default_err_handler(char *, size_t);
void *safe_malloc(size_t n) {
void *p;
if ((p = malloc(n)) == 0) {
(err_handler ? err_handler : default_err_handler)("malloc", n);
exit(1);
}
return p;
}
void *safe_realloc(void *old, size_t n) {
void *p;
if ((p = realloc(old, n)) == 0) {
(err_handler ? err_handler : default_err_handler)("realloc", n);
exit(1);
}
return p;
}
void set_alloc_failure(void (*func)(char *, size_t)) {
err_handler = func;
}
static void default_err_handler(char *what, size_t n) {
fatal_error("cannot %s %lu bytes--virtual memory exhausted",
what, (unsigned long)n);
}

7
src/malloc.h Normal file
View File

@ -0,0 +1,7 @@
/* $Revision: 1.1 $
*/
void *safe_malloc(size_t);
void *safe_realloc(void *, size_t);
void set_alloc_failure(void (*)(char *, size_t));

526
src/parse.c Normal file
View File

@ -0,0 +1,526 @@
/* $Revision: 1.21 $
*/
/* The troff parser. Most of the troff-specific code is in this file
*/
#include "unroff.h"
extern int compatible;
char escape = '\\';
char eqn_delim1, eqn_delim2;
static char control = '.';
static char control_nobreak = '\'';
static unsigned char argspec[256];
static char *sentence_end = ".?!";
/* Styles of escape char arguments:
*/
#define ARG_QUOTED 2 /* \h'xxx', \h|xxx|, ... */
#define ARG_SYMBOL 4 /* \fx \f(xx \f[xxx] */
#define ARG_SIZE 6 /* \sx \s(xx \s[xxx] \sdd */
#define ARG_CHAR 8 /* \zx \z\x \z\(xx \z\[xxx] */
#define ARG_LINE 16 /* \" */
#define ARG_SIGN 1 /* optional sign: \s */
/* parse_char() return values:
*/
#define TOK_CHAR 1
#define TOK_ESC 2
#define skip(c) {\
for ( ; p < ep && *p != c; p++)\
;\
if (p == ep) {\
warn("missing closing `%s' delimiter", printable_char(c)); return 0;\
}\
}
#define check_name(c) if (c) {\
warn("missing escape name"); return 0;\
}
#define check_arg(c) if (c) {\
warn("missing escape sequence argument"); return 0;\
}
#define check_empty(c) if (c) {\
warn("empty `[xxx]' sequence"); return 0;\
}
#define is_request(p) \
((p)->size > 0 && ((p)->data[0] == control ||\
(p)->data[0] == control_nobreak ||\
(p)->data[0] == '.'))
/* Deals with \" \* \n. Returns 0 on error, 1 otherwise.
*/
int parse_expand(Buffer *ip, Buffer *op) {
char sign, *p, *ep, *q, *s, *ret;
int len, size_ret, nl, ev, fallback;
Object str;
buffer_clear(op);
for (p = ip->data, ep = p + ip->size; p < ep; ) {
if (*p == escape && ++p < ep) {
switch (*p) {
case '"':
len = ep-p-1;
nl = p[len] == '\n';
args_clear();
args_add(Make_Char('"'));
args_add(Make_String(p+1, len));
ret = event_exec(EV_ESCAPE, "\"", 1, &size_ret, 1);
if (ret)
buffer_puts(op, ret, size_ret);
else if (nl)
buffer_putc(op, '\n');
p = ep;
break;
case 'n':
case '*':
s = p++;
check_name(p == ep);
if (*s == 'n' && (*p == '+' || *p == '-')) {
sign = *p++;
} else sign = 0;
check_name(p == ep);
switch (*p) {
case '(':
if ((q = ++p) > ep-2) {
warn("escape name truncated"); return 0;
}
len = 2;
p += 2;
break;
case '[':
if (!compatible) {
q = ++p;
skip(']');
check_empty(p == q);
len = p++ - q;
break;
}
default:
check_name((q = p++) == ep);
len = 1;
break;
}
str = Make_String(q, len);
ev = *s == 'n' ? EV_NUMREG : EV_STRING;
fallback = event_lookup(EV_ESCAPE, s, 1) &&
!event_lookup(ev, q, len);
args_clear();
if (fallback)
args_add(Make_Char(*s));
args_add(str);
if (sign)
args_add(Make_Char(sign));
if (fallback)
ret = event_exec(EV_ESCAPE, s, 1, &size_ret, 1);
else
ret = event_exec(ev, q, len, &size_ret, 1);
if (ret)
buffer_puts(op, ret, size_ret);
else
buffer_puts(op, s-1, p-s+1);
break;
default:
buffer_putc(op, escape);
buffer_putc(op, *p);
p++;
break;
}
} else if (isspace(UCHAR(*p))) { /* kill space before comment */
for (s = p+1; s < ep-1 && isspace(UCHAR(*s)); s++)
;
if (s < ep-1 && *s == escape && s[1] == '"')
p = s;
else buffer_putc(op, *p++);
} else buffer_putc(op, *p++);
}
return 1;
}
static int parse_char(char **pp, char *ep, Buffer *op,
int doescape, int doexec, int copymode) {
char sign, *p, *s, *ret;
char *q = 0; /* make gcc -Wuninitialized happy */
int size_ret, tok = 0, len = 0, nl = 0; /* ditto */
unsigned char spec;
p = *pp;
if (*p == escape && doescape && p < ep-1) {
if (copymode) {
if (p[1] != '$') {
if (doexec && p[1] != '.' && p[1] != '\\')
buffer_putc(op, *p);
if (doexec)
buffer_putc(op, p[1]);
*pp = p+2;
return TOK_CHAR;
}
}
switch (*++p) {
case '(':
if (++p > ep-2) {
warn("special character truncated"); return 0;
}
if (doexec) {
args_clear();
args_add(Make_String(p, 2));
if ((ret = event_exec_fallback(EV_SPECIAL, p, 2,
&size_ret)) == 0)
buffer_puts(op, p-2, 4);
else
buffer_puts(op, ret, size_ret);
}
*pp = p+2;
return TOK_CHAR;
case '[':
if (!compatible) {
q = ++p;
skip(']');
check_empty(p == q);
if (doexec) {
args_clear();
args_add(Make_String(q, p-q));
if ((ret = event_exec_fallback(EV_SPECIAL, q, p-q,
&size_ret)) == 0)
buffer_puts(op, q-2, p-q+3);
else
buffer_puts(op, ret, size_ret);
}
*pp = p+1;
return TOK_CHAR;
}
default:
spec = argspec[(unsigned char)*p];
s = p++;
if (spec & ARG_SIGN && p < ep && (*p == '+' || *p == '-')) {
sign = *p++;
} else sign = 0;
switch (spec &= ~ARG_SIGN) {
case ARG_SYMBOL:
case ARG_SIZE:
check_arg(p == ep);
switch (*p) {
case '(':
if ((q = ++p) > ep-2) {
warn("escape sequence argument truncated");
return 0;
}
len = 2;
p += 2;
break;
case '[':
if (!compatible) {
q = ++p;
skip(']');
check_empty(p == q);
len = p++ - q;
break;
}
default:
check_arg((q = p++) == ep);
len = 1;
if (spec == ARG_SIZE && q < ep-1 && *q > '0' && *q < '4'
&& isdigit(UCHAR(*p))) {
len++, p++;
}
break;
}
break;
case ARG_CHAR:
check_arg((q = p) == ep);
if (parse_char(&p, ep, op, doescape, 0, copymode) == 0)
return 0;
len = p - q;
break;
case ARG_LINE:
q = p;
p = ep;
len = ep - q;
nl = ep[-1] == '\n';
break;
case ARG_QUOTED:
if ((q = p++) >= ep) {
warn("missing opening delimiter"); return 0;
}
if (p == ep) {
undelim: warn("missing closing `%s' delimiter", printable_char(*q));
return 0;
}
while ((tok = parse_char(&p, ep, op, doescape, 0, copymode))
!= 0) {
if (tok == TOK_CHAR && p[-1] == *q)
break;
if (p == ep)
goto undelim;
}
if (tok == 0)
return 0;
len = p - ++q - 1;
break;
}
if (doexec) {
args_clear();
args_add(Make_Char(*s));
if (spec) {
args_add(Make_String(q, len));
if (sign)
args_add(Make_Char(sign));
}
ret = event_exec_fallback(EV_ESCAPE, s, 1, &size_ret);
if (ret)
buffer_puts(op, ret, size_ret);
else if (spec & ARG_LINE) {
if (nl) buffer_putc(op, '\n');
} else
buffer_puts(op, s-1, p-s+1);
}
*pp = p;
return TOK_ESC;
}
} else if (doexec && doescape && !copymode &&
eqn_delim1 && *p == eqn_delim1) {
s = ++p;
for ( ; p < ep && *p != eqn_delim2; p++)
;
if (p == ep) {
warn("non-terminated inline equation");
return 0;
}
args_clear();
args_add(Make_String(s, p-s));
ret = event_exec(EV_EQUATION, 0, 0, &size_ret, 0);
if (ret)
buffer_puts(op, ret, size_ret);
else
buffer_puts(op, s-1, p-s+2);
*pp = p+1;
return TOK_ESC;
} else if (doexec && !copymode && event_lookup(EV_CHAR, p, 1)) {
args_clear();
args_add(Make_Char(*p));
ret = event_exec(EV_CHAR, p, 1, &size_ret, 0);
if (ret)
buffer_puts(op, ret, size_ret);
else
buffer_putc(op, *p);
*pp = p+1;
return TOK_CHAR;
} else {
if (doexec)
buffer_putc(op, *p);
*pp = p+1;
return TOK_CHAR;
}
}
/* Deals with \( \[ \x and chardefs. Returns 0 on error, 1 otherwise.
*/
int parse_escape(Buffer *ip, Buffer *op, int doescape, int copymode) {
char *p, *ep;
buffer_clear(op);
p = ip->data;
ep = p + ip->size;
while (p < ep && parse_char(&p, ep, op, doescape, 1, copymode) != 0)
;
return p != 0;
}
static int parse_request(Buffer *ip, Buffer *op) {
char *p, *q, *t, *ep, *ret, *start;
int reqlen, quote, len, size_ret;
int num, macro, minargs = 0, maxargs = 0;
Elem *event;
Object obj;
assert(ip->size > 0);
p = ip->data+1;
ep = ip->data+ip->size;
for ( ; p < ep && (*p == ' ' || *p == '\t'); p++)
;
start = p;
for ( ; p < ep && !isspace(UCHAR(*p)); p++)
if (compatible && p == start+2)
break;
if ((reqlen = p - start) == 0) /* just a period */
return 0;
if ((event = event_lookup(EV_MACRO, start, reqlen)) != 0) {
macro = 1;
} else if ((event = event_lookup(EV_REQUEST, start, reqlen)) != 0) {
macro = 0;
obj = get_object(event->obj);
/*
* The following three lines prevent what I believe is an optimizer
* bug under OSF/1. `obj' is invalid unless it is passed to an
* (arbitrary) function before entering the if-statement.
*/
#ifdef __osf__
(void)P_Not(obj); /* any function... */
#endif
if (TYPE(obj) == T_Compound) {
if ((maxargs = COMPOUND(obj)->max_args - 1) < 0)
maxargs = INT_MAX;
minargs = COMPOUND(obj)->min_args;
}
} else {
warn("no event value for request or macro `%s'",
printable_string(start, reqlen));
return 0;
}
args_clear();
args_add(Make_String(start, reqlen));
for (num = 1; ; num++) {
for ( ; p < ep && isspace(UCHAR(*p)); p++)
;
if (p >= ep)
break;
if (macro && *p == '"') {
quote = 1;
p++;
} else quote = 0;
q = t = p;
while (1) {
if (p == ep || *p == '\n') {
/* If quote==1, a closing delimiter is missing. This is
* not regarded as an error in troff.
*/
break;
}
if (!quote && isspace(UCHAR(*p)) && (macro || num < maxargs))
break;
if (quote && *p == '"') {
if (p < ep-1 && p[1] == '"') /* turn "" into " */
p++;
else
break;
}
if (*p == escape && p < ep-1) {
if (p[1] != '\\')
*t++ = *p;
p++;
}
*t++ = *p++;
}
p++;
len = t-q;
args_add(Make_String(q, len));
}
if (!macro)
while (num++ < minargs)
args_add(Make_String("", 0));
ret = event_exec(macro ? EV_MACRO : EV_REQUEST, start, reqlen,
&size_ret, 1);
if (ret) {
buffer_clear(op);
buffer_puts(op, ret, size_ret);
}
return ret != 0;
}
static int is_sentence_end(Buffer *bp) {
int len = bp->size;
return len > 1 && bp->data[len-1] == '\n' &&
strchr(sentence_end, bp->data[len-2]);
}
static void exec_sentence_end(Buffer *bp) {
int size_ret;
char *ret;
args_clear();
args_add(Make_Char(bp->data[bp->size-2]));
if ((ret = event_exec(EV_SENTENCE, 0, 0, &size_ret, 0)) != 0) {
bp->size -= 2;
buffer_puts(bp, ret, size_ret);
}
}
static void exec_line(Buffer *bp) {
args_clear();
if (bp->size > 0)
args_add(Make_Char(bp->data[bp->size-1]));
else
args_add(False);
events_vec_exec(EV_LINE);
}
void parse_line(Buffer *ip, Buffer *op) {
int do_sentence_end;
if (is_request(ip)) {
if (!parse_request(ip, op))
return;
if (op->size == 0) /* it's not considered an input line */
return;
do_sentence_end = 1;
} else {
do_sentence_end = is_sentence_end(ip);
if (!parse_escape(ip, op, 1, 0))
return;
}
if (do_sentence_end && is_sentence_end(op))
exec_sentence_end(op);
safe_write(op->data, op->size);
exec_line(op);
}
void parse_input(void) {
int eof_seen;
Buffer *b1, *b2;
b1 = buffer_new(0);
b2 = buffer_new(0);
do {
buffer_clear(b1);
if ((eof_seen = safe_readline(b1)) && b1->size == 0)
break;
if (parse_expand(b1, b2))
parse_line(b2, b1);
} while (!eof_seen);
buffer_delete(b1);
buffer_delete(b2);
}
void init_parse(void) {
argspec['b'] = ARG_QUOTED;
argspec['c'] = ARG_CHAR;
argspec['f'] = ARG_SYMBOL;
argspec['h'] = ARG_QUOTED;
argspec['k'] = ARG_SYMBOL;
argspec['l'] = ARG_QUOTED;
argspec['n'] = ARG_SYMBOL|ARG_SIGN;
argspec['o'] = ARG_QUOTED;
argspec['s'] = ARG_SIZE|ARG_SIGN;
argspec['v'] = ARG_QUOTED;
argspec['w'] = ARG_QUOTED;
argspec['x'] = ARG_QUOTED;
argspec['z'] = ARG_CHAR;
argspec['*'] = ARG_SYMBOL;
argspec['$'] = ARG_SYMBOL;
argspec['"'] = ARG_LINE;
if (!compatible) {
argspec['A'] = ARG_QUOTED;
argspec['C'] = ARG_QUOTED;
argspec['L'] = ARG_QUOTED;
argspec['N'] = ARG_QUOTED;
argspec['R'] = ARG_QUOTED;
argspec['V'] = ARG_SYMBOL;
argspec['Y'] = ARG_SYMBOL;
argspec['Z'] = ARG_QUOTED;
}
}

11
src/parse.h Normal file
View File

@ -0,0 +1,11 @@
/* $Revision: 1.8 $
*/
extern char escape;
extern char eqn_delim1, eqn_delim2;
int parse_expand(Buffer *, Buffer *);
int parse_escape(Buffer *, Buffer *, int, int);
void parse_line(Buffer *, Buffer *);
void parse_input(void);
void init_parse(void);

382
src/prim.c Normal file
View File

@ -0,0 +1,382 @@
/* $Revision: 1.19 $
*/
/* The implementation of those Scheme primitives that do not deal with
* events, tables, streams, expressions, substitutions, and file
* insertions.
*/
#include "unroff.h"
static Object error_port;
static Object p_error_port(void) {
return error_port;
}
static Object p_read_line_expand(void) {
Buffer *ip, *op;
Object ret;
ip = buffer_new(0);
op = buffer_new(0);
if (safe_readline(ip) && ip->size == 0) {
ret = Eof;
} else {
(void)parse_expand(ip, op);
ret = Make_String(op->data, op->size);
}
buffer_delete(ip);
buffer_delete(op);
return ret;
}
static Object p_read_line(void) {
Buffer *ip;
Object ret;
ip = buffer_new(0);
if (safe_readline(ip) && ip->size == 0) {
ret = Eof;
} else
ret = Make_String(ip->data, ip->size);
buffer_delete(ip);
return ret;
}
static Object primitive_parse(int ac, Object *av, int what) {
Buffer *ip, *op;
Object ret;
ip = buffer_new(0);
op = buffer_new(0);
while (ac-- > 0) {
Object x = *av++;
switch (TYPE(x)) {
case T_Character:
buffer_putc(ip, CHAR(x));
break;
case T_Symbol:
x = SYMBOL(x)->name; /* fall through */
case T_String:
buffer_puts(ip, STRING(x)->data, STRING(x)->size);
break;
default:
Primitive_Error("cannot coerce argument to string");
}
}
switch (what) {
case 'c': parse_escape(ip, op, 1, 1); break;
case 'p': parse_escape(ip, op, 1, 0); break;
case 't': parse_escape(ip, op, 0, 0); break;
case 'l': parse_line(ip, op); break;
case 'e': parse_expand(ip, op); break;
}
ret = what == 'l' ? Void : Make_String(op->data, op->size);
buffer_delete(ip);
buffer_delete(op);
return ret;
}
static Object p_parse(int ac, Object *av) {
return primitive_parse(ac, av, 'p');
}
static Object p_translate(int ac, Object *av) {
return primitive_parse(ac, av, 't');
}
static Object p_parse_line(int ac, Object *av) {
return primitive_parse(ac, av, 'l');
}
static Object p_parse_copy_mode(int ac, Object *av) {
return primitive_parse(ac, av, 'c');
}
static Object p_parse_expand(int ac, Object *av) {
return primitive_parse(ac, av, 'e');
}
static Object concat(int ac, Object *av, int spread) {
Buffer *op;
Object ret;
op = buffer_new(0);
while (ac-- > 0) {
Object x = *av++;
switch (TYPE(x)) {
case T_Character:
buffer_putc(op, CHAR(x));
break;
case T_Symbol:
x = SYMBOL(x)->name; /* fall through */
case T_String:
buffer_puts(op, STRING(x)->data, STRING(x)->size);
break;
default:
Primitive_Error("cannot coerce argument to string");
}
if (spread && ac > 0)
buffer_putc(op, ' ')
}
ret = Make_String(op->data, op->size);
buffer_delete(op);
return ret;
}
static Object p_concat(int ac, Object *av) {
return concat(ac, av, 0);
}
static Object p_spread(int ac, Object *av) {
return concat(ac, av, 1);
}
static Object p_emit(int ac, Object *av) {
while (ac-- > 0) {
Object x = *av++;
switch (TYPE(x)) {
case T_Character:
safe_write_char(CHAR(x));
break;
case T_Symbol:
x = SYMBOL(x)->name; /* fall through */
case T_String:
safe_write(STRING(x)->data, STRING(x)->size);
break;
default:
Primitive_Error("cannot coerce argument to string");
}
}
return Void;
}
static Object p_shell_command(Object cmd) {
return Make_Integer(system(Get_Strsym(cmd)));
}
static Object p_remove_file(Object fn) {
char *s = Get_Strsym(fn);
if (remove(s)) warn("cannot remove file `%s'", s);
return Void;
}
static Object p_parse_pair(Object x) {
char *p, *ep, *s1, *s2, delim;
Object str, ret;
GC_Node3;
str = ret = False;
GC_Link3(x, str, ret);
Check_Type(x, T_String);
p = STRING(x)->data;
ep = p + STRING(x)->size;
if (p <= ep-3) {
delim = *p++;
for (s1 = p; p < ep && *p != delim; p++)
;
if (p < ep) {
for (s2 = ++p; p < ep && *p != delim; p++)
;
if (p == ep-1) {
str = Make_String(s1, s2-s1-1);
ret = Cons(str, Null);
str = Make_String(s2, p-s2);
Cdr(ret) = str;
}
}
}
GC_Unlink;
return ret;
}
static Object p_parse_triple(Object x) {
char *p, *ep, *s1, *s2, *s3, delim;
Object str, ret;
GC_Node3;
str = ret = False;
GC_Link3(x, str, ret);
Check_Type(x, T_String);
p = STRING(x)->data;
ep = p + STRING(x)->size;
if (p <= ep-4) {
delim = *p++;
for (s1 = p; p < ep && *p != delim; p++)
;
if (p < ep) {
for (s2 = ++p; p < ep && *p != delim; p++)
;
if (p < ep) {
for (s3 = ++p; p < ep && *p != delim; p++)
;
if (p == ep-1) {
str = Make_String(s3, p-s3);
ret = Cons(Null, str);
str = Make_String(s2, s3-s2-1);
Car(ret) = str;
ret = Cons(Null, ret);
str = Make_String(s1, s2-s1-1);
Car(ret) = str;
}
}
}
}
GC_Unlink;
return ret;
}
static Object p_skip_group(void) {
Buffer *ip;
int level = 0;
char *p, *ep;
ip = buffer_new(0);
do {
if (safe_readline(ip) && ip->size == 0) {
warn("end-of-scream while skipping requests");
break;
}
for (p = ip->data, ep = p + ip->size; p < ep-2; p++)
if (*p == escape)
if (*++p == '{') level++;
else if (*p == '}') level--;
buffer_clear(ip);
} while (level > 0);
buffer_delete(ip);
return Void;
}
static Object p_set_escape(Object c) {
Check_Type(c, T_Character);
escape = CHAR(c);
return Void;
}
static Object p_troff_compatible(void) {
extern int compatible;
return compatible ? True : False;
}
static Object p_string_prune_left(Object str, Object pref, Object fail) {
int l1, l2;
Check_Type(str, T_String);
Check_Type(pref, T_String);
l1 = STRING(str)->size, l2 = STRING(pref)->size;
if (l2 <= l1 && memcmp(STRING(str)->data, STRING(pref)->data, l2) == 0)
return Make_String(STRING(str)->data+l2, l1-l2);
return fail;
}
static Object p_string_prune_right(Object str, Object suff, Object fail) {
int l1, l2, l;
Check_Type(str, T_String);
Check_Type(suff, T_String);
l1 = STRING(str)->size, l2 = STRING(suff)->size, l = l1-l2;
if (l >= 0 && memcmp(STRING(str)->data+l, STRING(suff)->data, l2) == 0)
return Make_String(STRING(str)->data, l);
return fail;
}
static Object p_string_compose(Object old, Object new) {
Buffer *bp;
struct S_String *s, *t;
int i;
Object ret;
bp = buffer_new(0);
Check_Type(old, T_String);
Check_Type(new, T_String);
s = STRING(old), t = STRING(new);
if (t->size > 0) {
switch (t->data[0]) {
case '+':
buffer_puts(bp, s->data, s->size);
buffer_puts(bp, t->data+1, t->size-1);
break;
case '-':
for (i = 0; i < s->size; i++)
if (!memchr(t->data, s->data[i], t->size))
buffer_putc(bp, s->data[i]);
break;
default:
buffer_puts(bp, t->data, t->size);
}
}
ret = Make_String(bp->data, bp->size);
buffer_delete(bp);
return ret;
}
static Object p_repeat_string(Object num, Object str) {
Buffer *bp;
Object ret;
int n;
Check_Type(str, T_String);
bp = buffer_new(0);
for (n = Get_Integer(num); n > 0; n--)
buffer_puts(bp, STRING(str)->data, STRING(str)->size);
ret = Make_String(bp->data, bp->size);
buffer_delete(bp);
return ret;
}
static Object p_filter_eqn_line(Object str) {
char *p, *q, *ep;
Check_Type(str, T_String);
p = STRING(str)->data, ep = p + STRING(str)->size;
for ( ; p < ep && isspace(UCHAR(*p)); p++)
;
if (p == ep)
return False;
for (q = p; p < ep && !isspace(UCHAR(*p)); p++)
;
if (p == ep)
return True;
if (p-q == 5 && strncmp(q, "delim", 5) == 0) {
p++;
if (ep-p == 3 && strncmp(p, "off", 3) == 0)
eqn_delim1 = 0;
else if (ep-p >= 2)
eqn_delim1 = *p, eqn_delim2 = p[1];
return False;
}
return p-q == 6 && strncmp(q, "define", 6) == 0 ? False : True;
}
void init_prim(void) {
error_port = Make_Port (0, stderr, Make_String ("stderr", 6));
if (setvbuf(stderr, 0, _IOLBF, BUFSIZ) != 0)
fatal_error("cannot set stderr line buffered");
Global_GC_Link(error_port);
Define_Primitive(p_error_port, "error-port", 0, 0, EVAL);
Define_Primitive(p_read_line, "read-line", 0, 0, EVAL);
Define_Primitive(p_read_line_expand, "read-line-expand", 0, 0, EVAL);
Define_Primitive(p_parse, "parse", 0, MANY, VARARGS);
Define_Primitive(p_parse_copy_mode, "parse-copy-mode", 0, MANY, VARARGS);
Define_Primitive(p_parse_line, "parse-line", 0, MANY, VARARGS);
Define_Primitive(p_parse_expand, "parse-expand", 0, MANY, VARARGS);
Define_Primitive(p_translate, "translate", 0, MANY, VARARGS);
Define_Primitive(p_concat, "concat", 0, MANY, VARARGS);
Define_Primitive(p_spread, "spread", 0, MANY, VARARGS);
Define_Primitive(p_emit, "emit", 0, MANY, VARARGS);
Define_Primitive(p_substitute, "substitute", 1, MANY, VARARGS);
Define_Primitive(p_shell_command, "shell-command", 1, 1, EVAL);
Define_Primitive(p_remove_file, "remove-file", 1, 1, EVAL);
Define_Primitive(p_parse_pair, "parse-pair", 1, 1, EVAL);
Define_Primitive(p_parse_triple, "parse-triple", 1, 1, EVAL);
Define_Primitive(p_skip_group, "skip-group", 0, 0, EVAL);
Define_Primitive(p_set_escape, "set-escape!", 1, 1, EVAL);
Define_Primitive(p_troff_compatible, "troff-compatible?", 0, 0, EVAL);
Define_Primitive(p_string_prune_left, "string-prune-left", 3, 3, EVAL);
Define_Primitive(p_string_prune_right,"string-prune-right", 3, 3, EVAL);
Define_Primitive(p_string_compose, "string-compose", 2, 2, EVAL);
Define_Primitive(p_repeat_string, "repeat-string", 2, 2, EVAL);
Define_Primitive(p_filter_eqn_line, "filter-eqn-line", 1, 1, EVAL);
}

4
src/prim.h Normal file
View File

@ -0,0 +1,4 @@
/* $Revision: 1.1 $
*/
void init_prim(void);

105
src/scmtable.c Normal file
View File

@ -0,0 +1,105 @@
/* $Revision: 1.3 $
*/
/* The implementation of the Scheme type `table' and the primitives
* that work on tables. This file is basically an additional layer
* on top of the code in table.c.
*/
#include "unroff.h"
#define TABLE(x) ((struct s_table *)POINTER(x))
struct s_table {
Object tag;
Table *t;
};
static int T_Table;
static Object p_tablep(Object x) {
return TYPE(x) == T_Table ? True : False;
}
static int table_equal(Object t1, Object t2) {
return EQ(t1, t2);
}
static int table_print(Object x, Object port, int raw, int depth,
int length) {
Printf(port, "#[table %lu]", TABLE(x)->t);
return 0;
}
static Object terminate_table(Object x) {
table_delete(TABLE(x)->t);
return Void;
}
static Object p_make_table(Object size) {
Object t;
int s;
if ((s = Get_Integer(size)) <= 0)
Range_Error(size);
t = Alloc_Object(sizeof(struct s_table), T_Table, 0);
TABLE(t)->tag = Null;
TABLE(t)->t = table_new(s);
Register_Object(t, (GENERIC)0, terminate_table, 0);
return t;
}
static Object table_op(int op, Object t, Object key, Object val) {
Elem *p;
Object ret = Void;
char *data;
int size;
Table *tp;
Check_Type(t, T_Table);
tp = TABLE(t)->t;
if (TYPE(key) == T_Symbol)
key = SYMBOL(key)->name;
else if (TYPE(key) != T_String)
Wrong_Type_Combination(key, "string or symbol");
data = STRING(key)->data;
size = STRING(key)->size;
if (size == 0)
Primitive_Error("key must be of non-zero length");
switch(op) {
case 's':
table_store(tp, data, size, val, 0);
break;
case 'r':
table_remove(tp, data, size);
break;
case 'l':
if ((p = table_lookup(tp, data, size)) == 0)
ret = False;
else
ret = get_object(p->obj);
}
return ret;
}
static Object p_table_store(Object t, Object key, Object val) {
return table_op('s', t, key, val);
}
static Object p_table_remove(Object t, Object key) {
return table_op('r', t, key, Null);
}
static Object p_table_lookup(Object t, Object key) {
return table_op('l', t, key, Null);
}
void init_scmtable(void) {
T_Table = Define_Type(0, "table", NOFUNC, sizeof(struct s_table),
table_equal, table_equal, table_print, NOFUNC);
Define_Primitive(p_tablep, "table?", 1, 1, EVAL);
Define_Primitive(p_make_table, "make-table", 1, 1, EVAL);
Define_Primitive(p_table_store, "table-store!", 3, 3, EVAL);
Define_Primitive(p_table_remove, "table-remove!", 2, 2, EVAL);
Define_Primitive(p_table_lookup, "table-lookup", 2, 2, EVAL);
}

4
src/scmtable.h Normal file
View File

@ -0,0 +1,4 @@
/* $Revision: 1.1 $
*/
void init_scmtable(void);

440
src/stream.c Normal file
View File

@ -0,0 +1,440 @@
/* $Revision: 1.14 $
*/
/* The implementation of the Scheme type `stream' and the primitives
* that work on streams. Additional functions exported by this module:
*
* istream_is_open() -- true if current input stream != #f
* ostream_is_open() -- true if current output stream != #f
* curr_istream_target() -- returns target of current input stream
* curr_istream_lno() -- returns current istream's input line number
* safe_readline(buffer) -- reads line from current istream into buffer;
* signals error if istream is not open
* safe_write_char(c) -- sends character to current ostream or to
* stdout if ostream is #f
* safe_write(data,len) -- same, but writes several characters
*/
#include "unroff.h"
typedef struct _stream {
Object tag;
char open;
char type;
char direction;
char *target;
FILE *fp;
Buffer *bp;
int bs;
unsigned long lno;
unsigned long pos;
Buffer *unread;
int (*readline)(struct _stream *, Buffer *);
void (*write)(struct _stream *, char *, int);
void (*close)(struct _stream *);
} Stream;
static Object buffers;
static Object istream, ostream;
#define STREAM(x) ((Stream *)POINTER(x))
static int T_Stream;
static Object p_streamp(Object x) {
return TYPE(x) == T_Stream ? True : False;
}
static int stream_equal(Object s1, Object s2) {
return EQ(s1, s2);
}
static int stream_print(Object x, Object port, int raw, int depth,
int length) {
Stream *p = STREAM(x);
if (p->open || p->type == 'b')
Printf(port, "#[stream %s]", p->target);
else
Printf(port, "#[stream %lu]", POINTER(x));
return 0;
}
static Object terminate_stream(Object x) {
Stream *p = STREAM(x);
if (p->open && !(p->type == 'b' && p->direction == 'o')) {
free(p->target);
buffer_delete(p->unread);
if (p->type != 'b')
p->close(p);
}
p->open = 0;
return Void;
}
int istream_is_open(void) {
return Truep(istream);
}
char *curr_istream_target(void) {
assert(Truep(istream));
assert(STREAM(istream)->open);
return STREAM(istream)->target;
}
unsigned long curr_istream_lno(void) {
return STREAM(istream)->lno;
}
static int stream_is_active(Object str) {
return EQ(istream, str) || EQ(ostream, str);
}
int safe_readline(Buffer *bp) {
Stream *str;
if (!Truep(istream))
Primitive_Error("no input stream defined");
str = STREAM(istream);
if (str->unread->size > 0) {
buffer_puts(bp, str->unread->data, str->unread->size);
if (bp->data[bp->size-1] != '\n')
buffer_putc(bp, '\n');
buffer_clear(str->unread);
return 0;
}
return str->readline(str, bp);
}
void safe_write_char(char c) {
if (Truep(ostream)) {
STREAM(ostream)->pos++;
STREAM(ostream)->write(STREAM(ostream), &c, 1);
} else if (putc(c, stdout) == EOF)
write_error("stdout");
}
void safe_write(char *data, int len) {
if (len == 0)
return;
if (Truep(ostream)) {
STREAM(ostream)->pos += len;
STREAM(ostream)->write(STREAM(ostream), data, len);
} else if (fwrite(data, len, 1, stdout) == 0)
write_error("stdout");
}
#define is_continuation(p) \
((p)->size > oldsize && (p)->data[(p)->size-1] == escape &&\
!((p)->size > oldsize+1 && (p)->data[(p)->size-2] == escape))
static int readline_buffer(Stream *self, Buffer *bp) {
int oldsize;
int c;
Buffer *sp = self->bp;
assert(self->bs <= sp->size);
if (self->bs == sp->size)
return 1;
oldsize = bp->size;
while (self->bs < sp->size) {
if ((c = sp->data[self->bs++]) == '\n') {
self->lno++;
if (is_continuation(bp)) {
bp->size--;
} else {
buffer_putc(bp, c);
return 0;
}
} else buffer_putc(bp, c);
}
if (bp->size > oldsize) {
buffer_putc(bp, '\n');
self->lno++;
}
return 1;
}
static int readline_file(Stream *self, Buffer *bp) {
int oldsize;
int c;
if (feof(self->fp))
return 1;
oldsize = bp->size;
while ((c = getc(self->fp)) != EOF) {
if (c == '\n') {
self->lno++;
if (is_continuation(bp)) {
bp->size--;
} else {
buffer_putc(bp, c);
return 0;
}
} else buffer_putc(bp, c);
}
if (ferror(self->fp))
read_error(self->target);
if (bp->size > oldsize) {
buffer_putc(bp, '\n');
self->lno++;
}
return 1;
}
static void write_buffer(Stream *self, char *data, int len) {
buffer_puts(self->bp, data, len);
}
static void write_file(Stream *self, char *data, int len) {
if (fwrite(data, len, 1, self->fp) == 0)
write_error(self->target);
}
static void close_file(Stream *self) {
(void)fclose(self->fp);
}
static void close_pipe(Stream *self) {
(void)pclose(self->fp);
}
static Object find_buffer(char *s) {
Object p;
for (p = buffers; !Nullp(p); p = Cdr(p)) {
if (strcmp(STREAM(Car(p))->target, s) == 0)
return Car(p);
}
return Null;
}
static int target_is_buffer(char *s) {
int len = strlen(s);
return len > 1 && s[0] == '[' && s[len-1] == ']';
}
static Object open_stream(Object target, char direction, int append) {
char *t = Get_Strsym(target), *mode;
Stream *p;
Object ret = Null, b = Null;
GC_Node3;
GC_Link3(target, ret, b);
if (target_is_buffer(t)) {
b = find_buffer(t);
if (!Nullp(b)) {
p = STREAM(b);
assert(p->type == 'b');
assert(p->direction == 'o');
if (p->open)
Primitive_Error("stream ~s is already open", b);
if (direction == 'o') {
p->open = 1;
p->lno = p->bs = 0;
if (!append) {
p->pos = 0;
buffer_clear(p->bp);
}
GC_Unlink;
return b;
}
}
}
ret = Alloc_Object(sizeof(Stream), T_Stream, 0);
p = STREAM(ret);
p->tag = Null;
p->open = 1;
p->direction = direction;
p->lno = p->pos = 0;
p->unread = buffer_new(0);
p->target = safe_malloc(strlen(t) + 1);
strcpy(p->target, t);
if (target_is_buffer(t)) {
p->readline = readline_buffer;
p->write = write_buffer; /* no close function */
p->type = 'b';
p->bp = buffer_new(0);
if (direction == 'o') {
buffers = Cons(ret, buffers);
} else {
p->bs = 0;
if (!Nullp(b))
buffer_puts(p->bp, STREAM(b)->bp->data, STREAM(b)->bp->size);
}
} else {
mode = direction == 'i' ? "r" : append ? "a" : "w";
p->readline = readline_file;
p->write = write_file;
if (t[0] == '|') {
char *s;
if ((p->fp = popen(t+1, mode)) == 0)
Primitive_Error("cannot open pipe to ~s", target);
if ((s = strchr(p->target, ' ')) != 0)
*s = 0;
p->close = close_pipe;
p->type = 'p';
} else {
if (direction == 'i' && strcmp(t, "stdin") == 0) {
p->fp = stdin;
} else if ((p->fp = fopen(t, mode)) == 0) {
Saved_Errno = errno;
Primitive_Error("cannot open ~s: ~E", target);
}
p->close = close_file;
p->type = 'f';
}
}
Register_Object(ret, (GENERIC)0, terminate_stream, 0);
GC_Unlink;
return ret;
}
Object p_open_input_stream(Object target) {
return open_stream(target, 'i', 0);
}
static Object p_open_output_stream(Object target) {
return open_stream(target, 'o', 0);
}
static Object p_append_output_stream(Object target) {
return open_stream(target, 'o', 1);
}
Object p_close_stream(Object x) {
if (!Truep(x))
return Void;
Check_Type(x, T_Stream);
if (!STREAM(x)->open)
return Void;
if (stream_is_active(x))
Primitive_Error("stream ~s is still in use", x);
return terminate_stream(x);
}
static Object set_stream(Object x, Object *which) {
Object ret = *which;
Stream *p;
if (Truep(*which) && STREAM(*which)->type != 'b' &&
STREAM(*which)->direction == 'o')
(void)fflush(STREAM(*which)->fp);
if (Truep(x)) {
Check_Type(x, T_Stream);
p = STREAM(x);
if (!p->open)
Primitive_Error("stream ~s has been closed", x);
if (stream_is_active(x))
Primitive_Error("stream ~s is already in use", x);
if (which == &istream && p->direction != 'i')
Primitive_Error("stream ~s is not an input stream", x);
if (which == &ostream && p->direction != 'o')
Primitive_Error("stream ~s is not an output stream", x);
*which = x;
} else {
*which = False;
}
return ret;
}
Object p_set_input_stream(Object x) {
return set_stream(x, &istream);
}
static Object p_set_output_stream(Object x) {
return set_stream(x, &ostream);
}
static Object p_input_stream(void) {
return istream;
}
static Object p_output_stream(void) {
return ostream;
}
static Object p_unread_line(Object str) {
Check_Type(str, T_String);
if (!Truep(istream))
Primitive_Error("no input stream defined");
buffer_puts(STREAM(istream)->unread, STRING(str)->data,
STRING(str)->size);
return Void;
}
#define stream_type_pred(what,t)\
static Object p_stream_##what(Object x) {\
if (Truep(x)) {\
Check_Type(x, T_Stream);\
return STREAM(x)->type == t ? True : False;\
} else return False;\
}
stream_type_pred(buffer, 'b')
stream_type_pred(file, 'f')
stream_type_pred(pipe, 'p')
static Object p_stream_target(Object x) {
if (!Truep(x))
return Make_String("", 0);
Check_Type(x, T_Stream);
return Make_String(STREAM(x)->target, strlen(STREAM(x)->target));
}
static Object p_stream_to_string(Object target) {
Object str, old, ret;
Stream *sp;
Buffer *bp;
str = p_open_input_stream(target);
old = p_set_input_stream(str);
bp = buffer_new(0);
for (sp = STREAM(str); sp->readline(sp, bp) == 0; )
;
(void)p_set_input_stream(old);
(void)p_close_stream(str);
ret = Make_String(bp->data, bp->size);
buffer_delete(bp);
return ret;
}
static Object p_stream_position(Object x) {
if (!Truep(x))
return Make_Integer(0);
Check_Type(x, T_Stream);
return Make_Unsigned_Long(STREAM(x)->pos);
}
void init_stream(void) {
istream = ostream = False;
buffers = Null;
Global_GC_Link(istream);
Global_GC_Link(ostream);
Global_GC_Link(buffers);
T_Stream = Define_Type(0, "stream", NOFUNC, sizeof(Stream),
stream_equal, stream_equal, stream_print, NOFUNC);
Define_Primitive(p_streamp, "stream?", 1, 1, EVAL);
Define_Primitive(p_open_input_stream,
"open-input-stream", 1, 1, EVAL);
Define_Primitive(p_open_output_stream,
"open-output-stream", 1, 1, EVAL);
Define_Primitive(p_append_output_stream,
"append-output-stream", 1, 1, EVAL);
Define_Primitive(p_close_stream, "close-stream", 1, 1, EVAL);
Define_Primitive(p_set_input_stream,
"set-input-stream!", 1, 1, EVAL);
Define_Primitive(p_set_output_stream,
"set-output-stream!", 1, 1, EVAL);
Define_Primitive(p_input_stream, "input-stream", 0, 0, EVAL);
Define_Primitive(p_output_stream, "output-stream", 0, 0, EVAL);
Define_Primitive(p_unread_line, "unread-line", 1, 1, EVAL);
Define_Primitive(p_stream_buffer, "stream-buffer?", 1, 1, EVAL);
Define_Primitive(p_stream_file, "stream-file?", 1, 1, EVAL);
Define_Primitive(p_stream_pipe, "stream-pipe?", 1, 1, EVAL);
Define_Primitive(p_stream_target, "stream-target", 1, 1, EVAL);
Define_Primitive(p_stream_to_string,"stream->string", 1, 1, EVAL);
Define_Primitive(p_stream_position, "stream-position", 1, 1, EVAL);
}

17
src/stream.h Normal file
View File

@ -0,0 +1,17 @@
/* $Revision: 1.5 $
*/
int istream_is_open(void);
int ostream_is_open(void);
char *curr_istream_target(void);
unsigned long curr_istream_lno(void);
int safe_readline(Buffer *);
void safe_write_char(char);
void safe_write(char *, int);
Object p_open_input_stream(Object);
Object p_close_stream(Object);
Object p_set_input_stream(Object);
void init_stream(void);

98
src/subst.c Normal file
View File

@ -0,0 +1,98 @@
/* $Revision: 1.9 $
*/
/* The implementation of the Scheme primitive `substitute'
*/
#include "unroff.h"
Object p_substitute(int ac, Object *av) {
Object str = *av;
static Buffer *fp;
char *p, *ep, *s, *t, *endp;
int len;
long a;
char buf[30];
char *fmt;
time_t now;
extern char *macros, *format, *directory;
Check_Type(str, T_String);
if (!fp)
fp = buffer_new(0);
buffer_clear(fp);
for (p = STRING(str)->data, ep = p + STRING(str)->size; p < ep; p++) {
if (*p == '%') {
for (s = ++p; p < ep && *p != '%'; p++)
;
if (p == ep)
Primitive_Error("missing `%' delimiter");
len = p-s;
t = safe_malloc(len+1); /* Make copy to add \0. C sucks... */
memcpy(t, s, len);
t[len] = 0;
fmt = 0;
if (len == 0)
s = "%";
else if (strcmp(t, "macros") == 0)
s = macros;
else if (strcmp(t, "format") == 0)
s = format;
else if (strcmp(t, "directory") == 0)
s = directory;
else if (strcmp(t, "progname") == 0)
s = get_progname();
else if (strcmp(t, "filepos") == 0) { /* ` file:lno' */
if (istream_is_open()) {
buffer_putc(fp, ' ');
buffer_puts(fp, curr_istream_target(),
strlen(curr_istream_target()));
sprintf(buf, ":%lu:", curr_istream_lno());
s = buf;
} else s = "";
} else if (strcmp(t, "tmpname") == 0) {
s = tmpnam(0);
} else if (strcmp(t, "version") == 0) {
sprintf(buf, "%d.%d", MAJOR_VERSION, MINOR_VERSION);
s = buf;
} else if (strcmp(t, "weekday") == 0) {
fmt = "%a";
} else if (strcmp(t, "weekday+") == 0) {
fmt = "%A";
} else if (strcmp(t, "weekdaynum")== 0) {
fmt = "%w";
} else if (strcmp(t, "monthname") == 0) {
fmt = "%b";
} else if (strcmp(t, "monthname+") == 0) {
fmt = "%B";
} else if (strcmp(t, "day") == 0) {
fmt = "%d";
} else if (strcmp(t, "month") == 0) {
fmt = "%m";
} else if (strcmp(t, "year") == 0) {
fmt = "%Y";
} else if (strcmp(t, "date") == 0) {
fmt = "%x";
} else if (strcmp(t, "time") == 0) {
fmt = "%X";
} else if ((a = strtol(t, &endp, 10)), endp == t+len) {
if (a < 1 || a > ac-1)
Primitive_Error("no argument for %-specifier");
Check_Type(av[a], T_String);
buffer_puts(fp, STRING(av[a])->data, STRING(av[a])->size);
s = "";
} else if ((s = getenv(t)) == 0)
s = "";
if (fmt) {
now = time(0);
if (strftime(buf, sizeof buf, fmt, localtime(&now)) == 0)
s = "";
else s = buf;
}
free(t);
buffer_puts(fp, s, strlen(s));
} else
buffer_putc(fp, *p);
}
return Make_String(fp->data, fp->size);
}

4
src/subst.h Normal file
View File

@ -0,0 +1,4 @@
/* $Revision: 1.2 $
*/
Object p_substitute(int, Object*);

101
src/table.c Normal file
View File

@ -0,0 +1,101 @@
/* $Revision: 1.8 $
*/
/* Simple hash tables.
*
* table_new(size) -- returns a new hash table
* table_delete(t) -- frees a table and its entries
* table_store(t,key,keylen,obj,flags)
* -- stores Scheme object and flags under key
* table_lookup(t,key,keylen) -- returns object stored under key, or 0
* table_remove(t,key,keylen) -- removes entry stored under key
*/
#include "unroff.h"
Table *table_new(int size) {
Table *p;
int i;
assert(size > 0);
p = safe_malloc(sizeof *p);
p->data = safe_malloc(size * sizeof(Elem *));
for (i = 0; i < size; i++)
p->data[i] = 0;
p->size = size;
return p;
}
void table_delete(Table *tp) {
int i;
Elem *p, *q;
for (i = 0; i < tp->size; i++)
for (p = tp->data[i]; p; p = q) {
deregister_object(p->obj);
free(p->data);
q = p->next;
free(p);
}
free(tp->data);
free(tp);
}
/* This function ensures that no collisions can occur in tables of
* size 256^N if all keys are of length <= N (for small values of N).
*/
static unsigned long hash(char *key, int size) {
unsigned long i, j;
assert(size > 0);
for (i = j = 0; j < size; j++)
i = i * 256 + (unsigned char)key[j];
return i;
}
void table_store(Table *tp, char *key, int size, Object obj,
unsigned long flags) {
int i = hash(key, size) % tp->size;
Elem *p;
for (p = tp->data[i]; p; p = p->next) {
if (size == p->size && memcmp(key, p->data, size) == 0) break;
}
if (p) {
deregister_object(p->obj);
} else {
p = safe_malloc(sizeof *p);
p->data = safe_malloc(size);
memcpy(p->data, key, size);
p->size = size;
p->next = tp->data[i];
tp->data[i] = p;
}
p->obj = register_object(obj);
p->flags = flags;
}
void table_remove(Table *tp, char *key, int size) {
int i = hash(key, size) % tp->size;
Elem *p, **pp;
for (pp = &tp->data[i]; (p = *pp); pp = &p->next )
if (size == p->size && memcmp(key, p->data, size) == 0) break;
if (p) {
*pp = p->next;
deregister_object(p->obj);
free(p->data);
free(p);
}
}
Elem *table_lookup(Table *tp, char *key, int size) {
int i = hash(key, size) % tp->size;
Elem *p;
for (p = tp->data[i]; p; p = p->next) {
if (size == p->size && memcmp(key, p->data, size) == 0) return p;
}
return 0;
}

21
src/table.h Normal file
View File

@ -0,0 +1,21 @@
/* $Revision: 1.5 $
*/
typedef struct _elem {
char *data;
int size;
int obj;
unsigned long flags;
struct _elem *next;
} Elem;
typedef struct _table {
Elem **data;
int size;
} Table;
Table *table_new(int);
void table_delete(Table *);
void table_store(Table *, char *, int, Object, unsigned long);
void table_remove(Table *, char *, int);
Elem *table_lookup(Table *, char *, int);

44
src/test.ms Normal file
View File

@ -0,0 +1,44 @@
.LP
Here comes an EQ/EN pair that just serves defining the delimiters
for inline equations (we will use the dollar sign for this purpose).
No eqn-output should not appear here, and no hypertext reference
should be generated if we are creating GIF files.
.EQ
delim $$
.EN
.LP
Now lets typeset a real equation, again using EQ and EN.
The equation should either be displayed as preformatted text or as
an inline GIF image:
.EQ
size 18 { a+b over 2c = 1 }
.EN
Finally an inline equation such as $size 16 { sqrt a+b }$ which uses the
delimiters defined at the start of this document.
It should either be typeset in Italics or appear as a GIF image.
.LP
Now lets test tables.
Here is a simple table that uses the \f2box\fP attribute and
has two header rows, two columns, and three data rows:
.TS
box, tab(~);
c s
c | c
n n.
Price Development
_
Year~Price
_
1992~$1,500
1993~2,000
1994~12,220
.TE
.LP
The table should either appear as preformatted text or as a GIF
image (the latter will probably turn out a bit too small).
Finally a PIC drawing:
.PS
box "foo"
arrow
box "bar"
.PE

63
src/test.scm Normal file
View File

@ -0,0 +1,63 @@
(define old-in (input-stream))
(define out (open-output-stream '/tmp/net))
(set-output-stream! out)
(emit "Hello, ")
(set-output-stream! #f)
(define out2 (open-output-stream '[out2]))
(set-output-stream! out2)
(close-stream out)
(define in (open-input-stream '/tmp/net))
(set-input-stream! in)
(emit (read-line-expand))
(emit "world!")
(set-input-stream! #f)
(close-stream in)
(set-output-stream! #f)
(close-stream out2)
(define out2 (open-input-stream '[out2]))
(defchar #\newline "")
(set-input-stream! out2)
(emit (parse (read-line-expand)))
(emit (read-line-expand))
(set-input-stream! old-in)
(close-stream out2)
(shell-command "rm /tmp/net")
(define tt (make-table 1))
(define t (make-table 100))
(table-store! tt "table" t)
(define t (table-lookup tt 'table))
(table-store! t 'bye '())
(table-remove! t 'bye)
(table-store! t 'greet "Hello,")
(table-store! t (table-lookup t 'greet) " world!\n")
(if (and (table? t) (not (table-lookup t 'bye)))
(begin
(display (table-lookup t 'greet))
(display (table-lookup t (table-lookup t 'greet)))))
(define-option-type 'hello-type
(lambda (x) (member (string-ref x 0) '(#\H #\w)))
"oops1!!"
(lambda (old new) (string->list new))
(lambda (x) (member (car x) '(#\H #\w)))
"oops2!!")
(define-option 'hello 'hello-type "oops3!!")
((eventdef 'option 0) (string-compose "helxlxo" "-x") "Hello, ")
(display (list->string (option 'hello)))
(set-option! 'hello (string->list (string-compose "wo" "+rld")))
(display (list->string (option 'hello)))
(display
(string-prune-left (concat (repeat-string 2 "!") #\newline) "!" "oops"))

231
src/unroff.c Normal file
View File

@ -0,0 +1,231 @@
/* $Revision: 1.21 $
*/
#include "unroff.h"
extern int getopt(int ac, char * const *av, const char *optstring);
extern char *optarg;
extern int optind, opterr;
char *macros = "";
char *format;
char *directory;
int compatible;
static int got_filename;
static int did_ev_start;
static int tflag;
static void usage(void) {
fprintf(stderr, "Usage: %s [options] [file...]\n\n", get_progname());
fprintf(stderr, " -mname name of troff macro package (e.g. -ms)\n");
fprintf(stderr, " -fformat output format (e.h. -fhtml2)\n");
fprintf(stderr, " -C enable nroff/troff compatibility\n");
fprintf(stderr, " -hheapsize Scheme heap size in Kbytes\n");
fprintf(stderr, " -t enter interactive top-level for testing\n");
fprintf(stderr, " option=val any macro-package/format-specific option\n");
}
static char *filename_tail(char *fn) {
char *p = strrchr(fn, '/');
return p && p > fn && p[1] ? p+1 : fn;
}
/* If the file name has the suffix .scm, load it into the interpreter.
* Else, open an input stream and assign it to the current input stream.
* Call start event procedure if necessary; trigger prolog event.
* Start the parser; when finished, trigger epilog event.
*/
static void do_input_file(char *fn) {
Object name, base, old, str;
char *p;
int len;
GC_Node4;
len = strlen(fn);
if (len > 3 && strcmp(fn + len - 4, ".scm") == 0) {
name = Make_String(fn, strlen(fn));
(void)P_Load(1, &name);
return;
}
if (fn[0] == '-' && fn[1] == 0)
fn = "stdin";
name = base = old = str = Null;
GC_Link4(name, base, old, str);
name = Make_String(fn, strlen(fn));
p = filename_tail(fn);
base = Make_String(p, strlen(p));
str = p_open_input_stream(name);
old = p_set_input_stream(str);
if (!did_ev_start) {
args_clear();
events_vec_exec(EV_START);
did_ev_start = 1;
}
args_clear();
args_add(name);
args_add(base);
events_vec_exec(EV_PROLOG);
parse_input();
args_clear();
args_add(name);
args_add(base);
events_vec_exec(EV_EPILOG);
(void)p_set_input_stream(old);
(void)p_close_stream(str);
GC_Unlink;
}
/* Determine whether argument is a file name or an option; trigger
* option event in the latter case.
*/
static void do_argument(char *arg) {
char *p;
if ((p = strchr(arg, '=')) == 0) {
got_filename = 1;
if (!tflag)
do_input_file(arg);
} else {
if (p == arg)
fatal_error("empty option name");
args_clear();
args_add(Make_String(arg, p++ - arg));
args_add(Make_String(p, strlen(arg) - (p - arg)));
events_vec_exec(EV_OPTION);
}
}
/* Load scm/troff.scm. The rest is loaded from there.
*/
static void boot_code(void) {
char *fn = safe_malloc(strlen(directory) + 30);
Object arg;
sprintf(fn, "%s/scm/troff.scm", directory);
arg = Make_String(fn, strlen(fn));
(void)P_Load(1, &arg);
free(fn);
}
/* Load $(HOME)/.RC_FILE, if it is there.
*/
static void load_rc_file(void) {
FILE *f;
char *home, *fn;
Object port;
GC_Node;
if ((home = getenv("HOME")) == 0)
return;
fn = safe_malloc(strlen(home) + 30);
sprintf(fn, "%s/%s", home, RC_FILE);
if ((f = fopen(fn, "r")) != 0) {
port = Make_Port(P_INPUT, f, Make_String(fn, strlen(fn)));
GC_Link(port);
Load_Source_Port(port);
P_Close_Input_Port(port); /* does the fclose() */
GC_Unlink;
}
free(fn);
}
/* Load the interactive Scheme top-level.
*/
static void test_mode(void) {
Object arg;
char fn[] = TEST_TOPLEVEL;
arg = Make_String(fn, strlen(fn));
(void)P_Load(1, &arg);
}
int main(int ac, char **av) {
char **eav;
int eac = 1, c;
if (ac == 0) {
fprintf(stderr, "Oops--no argv[0]?\n"); return 1;
}
set_progname(av[0]);
if ((directory = getenv(DEFAULT_DIR_ENV)) == 0)
directory = DEFAULT_DIR;
if ((format = getenv(DEFAULT_FORMAT_ENV)) == 0)
format = DEFAULT_FORMAT;
eav = safe_malloc((ac+1+2) * sizeof(char *)); /* ac + -p xxx + 0-ptr */
eav[0] = av[0];
opterr = 0;
while ((c = getopt(ac, av, "h:gm:f:tC")) != EOF) {
switch (c) {
case 'g':
eav[eac++] = "-g"; break;
case 'h':
if (strcmp(optarg, "elp") == 0) {
usage();
return 1;
}
eav[eac++] = "-h"; eav[eac++] = optarg; break;
case 'm':
macros = optarg; break;
case 'f':
format = optarg; break;
case 't':
tflag = 1; break;
case 'C':
compatible = 1; break;
case '?':
usage();
return 1;
}
}
/* Set the Elk load-path to $(directory)/elk, so that a minimal,
* self-contained Elk runtime environment can be shipped with
* binary distributions. Sites with a full Elk can symlink
* $(directory)/elk to the real directory.
*/
eav[eac++] = "-p";
eav[eac] = safe_malloc(strlen(directory) * 2 + 30);
sprintf(eav[eac++], ".:%s/elk/scm:%s/elk/obj", directory, directory);
eav[eac] = 0;
Elk_Init(eac, eav, 0, 0);
init_args();
init_gcroot();
init_insert();
init_event();
init_expr();
init_parse();
init_prim();
init_scmtable();
init_stream();
#ifdef ELK_MAJOR
Set_Error_Tag("load");
#else
Error_Tag = "load";
#endif
boot_code();
load_rc_file();
if (tflag) {
while (optind < ac)
do_argument(av[optind++]);
if (got_filename)
warn("filename arguments are ignored when -t is given");
test_mode();
return 0;
}
#ifdef ELK_MAJOR
Set_Error_Tag("main-loop");
#else
Error_Tag = "main-loop";
#endif
while (optind < ac)
do_argument(av[optind++]);
if (!got_filename)
do_input_file("-");
if (did_ev_start) {
args_clear();
events_vec_exec(EV_EXIT);
}
return 0;
}

69
src/unroff.h Normal file
View File

@ -0,0 +1,69 @@
/* $Revision: 1.18 $
*/
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <math.h>
/* Include files that may be required by gcc although they shouldn't:
*/
#include <memory.h>
/* Prototypes that may be required by cc/gcc although they shouldn't:
*/
extern time_t time(time_t *);
extern size_t strftime(char *, size_t, const char *, const struct tm *);
extern long strtol(const char *s, char **endp, int base);
extern double strtod(const char *s, char **endp);
extern int pclose(FILE *);
extern int system(const char *);
#ifndef tolower
extern int tolower(int);
#endif
/* Prototypes for IEEE FP functions that may be missing:
*/
#ifndef finite
extern int finite(double);
#endif
#include "scheme.h"
/* Prototypes that were missing from "scheme.h" in some Elk releases:
*/
extern void Elk_Init(int ac, char **av, int call_inits, char *filename);
extern void Load_Source_Port(Object);
/* Used for passing a `char' to a function that takes an `int' argument,
* such as isspace(). Maybe I should have used `unsigned char' rather
* than `char' in the first place...
*/
#define UCHAR(c) ((unsigned char)(c))
#include "config.h"
#include "args.h"
#include "buffer.h"
#include "table.h"
#include "error.h"
#include "event.h"
#include "expr.h"
#include "gcroot.h"
#include "insert.h"
#include "malloc.h"
#include "parse.h"
#include "prim.h"
#include "scmtable.h"
#include "stream.h"
#include "subst.h"