home *** CD-ROM | disk | FTP | other *** search
- Subject: mc & xlisp pretty printer
- From: Mike Meyer <genrad!ucbvax!ucbjade!ucbopal:mwm>
- Newsgroups: mod.sources
- Approved: jpn@panda.UUCP
-
- Mod.sources: Volume 2, Issue 36
- Submitted by: Mike Meyer <ucbvax!ucbjade!ucbopal:mwm>
-
-
- # This is a shell archive. Remove anything before this line, then
- # unpack it by saving it in a file and typing "sh file". (Files
- # unpacked will be owned by you and have default permissions.)
- #
- # This archive contains:
- # README mc.clu mc.n pp.lsp
-
- echo x - README
- sed 's/^ //' > "README" << '//E*O*F README//'
- Since it doesn't look like I'm going to have time to work on the xlisp
- editor for a while, and mc looked finished, I decided to collect this
- up and let people have it.
-
- pp.lsp contains a simple lisp pretty printer for xlisp. It doesn't
- know about special forms, but is useable as is. Teaching it about
- special forms would be easy, but I find the thought ugly. For some
- reason, changing pp$pp to use the where argument causes all output to
- fall into the bit bucket. I think there's a problem in the xlisp I/O
- system (actually, I *know* there are problems in the xlisp I/O system,
- but I'm not sure that this is one of them), but I'm not going to chase
- it until I finish the xlisp editor. If somebody out there fixes this
- problem, please let me know.
-
- mc.clu and mc.n are the source and documentation for a simple
- multi-column printer. Mc is more flexible and faster than the pr hack
- suggested in K&P, but still a very simple filter. Of course, you do
- have to have a CLU compiler to use it. I can provide a pointer for
- said compiler if anyone is interested. I've also got an early version
- of mc written in C, but it's larger, slower, and less robust than the
- posted version. This is available for the asking.
-
- <mike
- ucbvax!mwm
-
- "Damn the tiddlywinks, full speed ahead!"
- //E*O*F README//
-
- echo x - mc.clu
- sed 's/^ //' > "mc.clu" << '//E*O*F mc.clu//'
- %
- % mc - a multi-column print routine. Turns an arbitary stream of lines
- % into a multi-column print.
- %
- % usage: mc [-g #] [-c #] [-w #] [ file ]
- % -g sets the gutter width; default is 2
- % -c sets the number of columns to print; default is as many as will fit
- % -w sets the width of the output device; default is taken from co entry
- % of the termcap for the current terminal.
- % file is input file name; default is standard input
- %
- % notes: column width is fixed at width of the longest element. No changing
- % allowed. If both -c and -w are specified, -w will be ignored if needed
- % to make everything fit on the page. Non-positive c or w is ignored.
- % Negative g is ignored. Only one file argument is allowed.
- %
-
- file = stream % file type, later to tweak to handle long pipes
- list = array[string] % list of strings
- fetch = string$fetch
- parse = int$parse
- putl = stream$putl
- gutter_default = 2
- characters_default = 79
-
- start_up = proc ()
- input: file
- stdout: stream := stream$primary_output()
- stderr: stream := stream$error_output()
- lines: list := list$create(0) % place to store input lines
- argv: sequence[string] := get_argv()
- gutter: int := gutter_default % spacing between columns
- longest: int := 0 % longest line to date
- characters: int := 0 % # of characters in print line
- columns: int := 0 % # of columns to print
- i: int
- my_name: string := _get_pname()
-
- % parse the arguments we was handed
- i := 1
- while fetch(argv[i], 1) = '-' do
- if fetch(argv[i], 2) = 'g' then
- i := i + 1
- gutter := parse(argv[i])
- elseif fetch(argv[i], 2) = 'w' then
- i := i + 1
- characters := parse(argv[i])
- elseif fetch(argv[i], 2) = 'c' then
- i := i + 1
- columns := parse(argv[i])
- else putl(stderr, my_name || ": unknown flag " || argv[i])
- end
- i := i + 1
- end except when bounds: end
-
- if i < sequence[string]$size(argv) then
- putl(stderr, my_name || ": only one input file allowed") end
-
- input := file$open(file_name$parse(argv[i]), "read")
- except when bounds: input := file$primary_input() end
- except when not_possible (s: string):
- signal failure(my_name || ": " ||
- argv[i] || ": " || s)
- end
-
- % get the list of lines to print, noting the longest line
- i := 0
- while true do
- list$addh(lines, file$getl(input))
- i := string$size(lines[list$high(lines)])
- if i > longest then longest := i end
- end except when end_of_file: end
-
- % now, figure out how to print it
- if gutter < 0 then gutter := gutter_default end
- longest := longest + gutter
- if characters < 1 then characters := get_tty_characters()
- except when not_found: characters := characters_default end
- end
- if columns < 1 then columns := characters / longest
- except when zero_divide:
- signal failure(my_name ||
- "All input lines of length 0") end
- end
- % We have to have at least one column, or things get sticky.
- % (Hm... How about no output if columns < 1?)
- if columns < 1 then columns := 1 end
-
- % now print it
- i := 0
- for j: int in list$indexes(lines) do
- if j // columns = 0 & j ~= 0 then
- stream$putc_image(stdout, '\012')
- else stream$putspace(stdout, i) end
- stream$puts(stdout, lines[j])
- i := longest - string$size(lines[j])
- end
- stream$putc_image(stdout, '\012')
- end start_up
- %
- % get the # of columns in ther terminal
- %
- get_tty_characters = proc () returns (int) signals (not_found)
- termcap: string := _get_termcap() resignal not_found
- count: int := int$parse(_termcap(termcap, ":co#", 0, 0))
- resignal not_found
-
- if string$indexs(":am", termcap) = 0 then return (count)
- else return (count - 1) end
- end get_tty_characters
- //E*O*F mc.clu//
-
- echo x - mc.n
- sed 's/^ //' > "mc.n" << '//E*O*F mc.n//'
- .TH MC 1
- .UC
- .SH NAME
- mc \- multi-columnate an input string
- .SH SYNOPSIS
- .B mc [
- .B -g # ] [
- .B -w # ] [
- .B -c # ] [
- .B file ]
- .SH DESCRIPTION
- .I Mc
- reads it's input, and copies it to standard out in a multicolumn
- format, one line of input turning into one column entry in the output.
- The first line of input goes to the top of the first column, the
- second line of input to the top of the second column, ... the line
- after the top entry of the last column goes to the seceond entry of
- the first column, etc.
-
- The
- .I -g
- flag specifies the width of the gutter of whitespace between
- columns. This defaults to 2, and negative values for
- .I g
- are ignored. A zero value is legal.
-
- The
- .I -w
- flag specifies how many characters wide the output device can be. The
- default is the width of the terminal. If
- .I w
- is unspecified, and no termcap entry can be found, a warning is
- printed on standard error, and a value of 79 is used. Non-positive
- values of
- .I w
- are ignored.
-
- The
- .I -c
- flag specifies how many columns of output there should be. The default
- is to print as many columns as will fit in the output device.
- Non-positive values of
- .I c
- are ignored.
-
- Mc accepts one
- .I file
- argument, which is used for input if specified. If not specified,
- standard input is used. If more than one file is specified, a warning
- is printed on standard error, and the first file is used and all
- others are ignored.
-
- The width of the columns is the width of the longest line in the
- input. There is
- .B no
- way to specify this width. If
- .I c
- is specified, and is larger than
- .B mc
- would have printed by default, then the specified number of
- columns is printed using extra space on the output line if needed.
- This can cause
- .I w
- flag to be ignored.
-
- The
- .B tr(1)
- command can be used to prepare input for mc. For example, to columnate
- all the words of a document, the command
- .I "cat document | tr -s ' \\\\012' '\\\\012' | mc"
- will do.
-
- The second argument to
- .B tr
- is a space followed by a tab. The
- .I \\\\012
- is the ascii representation for a newline, and the
- .I -s
- flag causes it to squeeze all runs of spaces, tabs and newlines
- into a single newline.
-
- To print the entries in a colon seperated list as in columns, the command
- .I "cat list | tr -s ':\\\\012' '\\\012' | mc"
- works. The
- .B tr
- command maps all strings of colons and newlines into a single newline.
-
- If the input file is already in a column format, the
- .B rs(1)
- command might be more useful in reformating it.
- .SH BUGS
- All lines are kept in memory. For pipes, this is probably as good as
- can be done. For input files, a seek to the start of the file should
- be used if the file won't fit in memory.
- .SH SEE ALSO
- tr(1), rs(1)
- //E*O*F mc.n//
-
- echo x - pp.lsp
- sed 's/^ //' > "pp.lsp" << '//E*O*F pp.lsp//'
- ;
- ; a pretty-printer, with hooks for the editor
- ;
-
- ; First, the terminal width and things to manipulate it
- (setq pp$terminal-width 79)
-
- (defmacro get-terminal-width nil
- pp$terminal_width)
-
- (defmacro set-terminal-width (new-width)
- (let ((old-width pp$terminal-width))
- (setq pp$terminal-width new-width)
- old-width))
- ;
- ; Now, a basic, simple pretty-printer
- ; pp$pp prints expression, indented to indent-level, assuming that things
- ; have already been indented to indent-so-far. It *NEVER* leaves the cursor
- ; on a new line after printing expression. This is to make the recursion
- ; simpler. This may change in the future, in which case pp$pp could vanish.
- ;
- (defun pp$pp (expression indent-level indent-so-far)
- ; Step one, make sure we've indented to indent-level
- (dotimes (x (- indent-level indent-so-far)) (princ " "))
- ; Step two, if it's an atom or it fits just print it
- (cond ((or (not (consp expression))
- (> (- pp$terminal-width indent-level) (flatsize expression)))
- (prin1 expression))
- ; else, print open paren, the car, then each sub expression, then close paren
- (t (princ "(")
- (pp$pp (car expression) (1+ indent-level) (1+ indent-level))
- (if (cadr expression)
- (progn
- (if (or (consp (car expression))
- (> (/ (flatsize (car expression)) 3)
- pp$terminal-width))
- (progn (terpri)
- (pp$pp (cadr expression)
- (1+ indent-level)
- 0))
- (pp$pp (cadr expression)
- (+ 2 indent-level (flatsize (car expression)))
- (+ 1 indent-level (flatsize (car expression)))))
- (dolist (current-expression (cddr expression))
- (terpri)
- (pp$pp current-expression
- (+ 2 indent-level
- (flatsize (car expression)))
- 0))))
- (princ ")")))
- nil)
- ;
- ; Now, the thing that outside users should call
- ; We have to have an interface layer to get the final terpri after pp$pp.
- ; This also allows hiding the second and third args to pp$pp. Said args
- ; being required makes the pp recursion loop run faster (don't have to map
- ; nil's to 0).
- ; The where arg to pp is ingnored, as the obvious hack to pp$pp [adding
- ; an extra arg to every call to a print routine or pp$pp] doesn't work,
- ; printing nothing when where is nil.
- ;
- (defun pp (expression &optional where)
- "Print EXPRESSION on STREAM, prettily"
- (pp$pp expression 0 0)
- (terpri))
- //E*O*F pp.lsp//
-
- echo Possible errors detected by \'wc\' [hopefully none]:
- temp=/tmp/shar$$
- trap "rm -f $temp; exit" 0 1 2 3 15
- cat > $temp <<\!!!
- 26 235 1269 README
- 110 599 3567 mc.clu
- 96 528 2663 mc.n
- 65 333 2323 pp.lsp
- 297 1695 9822 total
- !!!
- wc README mc.clu mc.n pp.lsp | sed 's=[^ ]*/==' | diff -b $temp -
- exit 0
-
-