mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-29 19:30:36 +02:00
* NEWS: * doc/ref/api-control.texi: * doc/ref/api-data.texi: * doc/ref/api-debug.texi: * doc/ref/api-deprecated.texi: * doc/ref/api-evaluation.texi: * doc/ref/api-foreign.texi: * doc/ref/api-i18n.texi: * doc/ref/api-io.texi: * doc/ref/api-languages.texi: * doc/ref/api-macros.texi: * doc/ref/api-memory.texi: * doc/ref/api-modules.texi: * doc/ref/api-options.texi: * doc/ref/api-peg.texi: * doc/ref/api-procedures.texi: * doc/ref/api-scheduling.texi: * doc/ref/api-undocumented.texi: * doc/ref/api-utility.texi: * doc/ref/expect.texi: * doc/ref/goops.texi: * doc/ref/misc-modules.texi: * doc/ref/posix.texi: * doc/ref/repl-modules.texi: * doc/ref/scheme-ideas.texi: * doc/ref/scheme-scripts.texi: * doc/ref/srfi-modules.texi: * gc-benchmarks/larceny/dynamic.sch: * gc-benchmarks/larceny/twobit-input-long.sch: * gc-benchmarks/larceny/twobit.sch: * libguile/gc.h: * libguile/ioext.c: * libguile/list.c: * libguile/options.c: * libguile/posix.c: * libguile/threads.c: * module/ice-9/boot-9.scm: * module/ice-9/optargs.scm: * module/ice-9/ports.scm: * module/ice-9/pretty-print.scm: * module/ice-9/psyntax.scm: * module/language/elisp/parser.scm: * module/language/tree-il/compile-bytecode.scm: * module/srfi/srfi-37.scm: * module/srfi/srfi-43.scm: * module/statprof.scm: * module/texinfo/reflection.scm: * test-suite/tests/eval.test: * test-suite/tests/fluids.test: Fix typos. Signed-off-by: Ludovic Courtès <ludo@gnu.org>
496 lines
17 KiB
Scheme
496 lines
17 KiB
Scheme
;;;; -*- coding: utf-8; mode: scheme -*-
|
||
;;;;
|
||
;;;; Copyright (C) 2001, 2004, 2006, 2009, 2010,
|
||
;;;; 2012, 2013, 2014, 2023 Free Software Foundation, Inc.
|
||
;;;;
|
||
;;;; This library is free software; you can redistribute it and/or
|
||
;;;; modify it under the terms of the GNU Lesser General Public
|
||
;;;; License as published by the Free Software Foundation; either
|
||
;;;; version 3 of the License, or (at your option) any later version.
|
||
;;;;
|
||
;;;; This library is distributed in the hope that it will be useful,
|
||
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
;;;; Lesser General Public License for more details.
|
||
;;;;
|
||
;;;; You should have received a copy of the GNU Lesser General Public
|
||
;;;; License along with this library; if not, write to the Free Software
|
||
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||
;;;;
|
||
(define-module (ice-9 pretty-print)
|
||
#:use-module (ice-9 match)
|
||
#:use-module (srfi srfi-1)
|
||
#:use-module (rnrs bytevectors)
|
||
#:use-module (ice-9 soft-ports)
|
||
#:use-module (ice-9 textual-ports)
|
||
#:export (pretty-print
|
||
truncated-print))
|
||
|
||
(define* (call-with-truncating-output-string proc success failure #:key
|
||
(initial-column 0)
|
||
(max-column 79)
|
||
(allow-newline? #f))
|
||
(define length 0)
|
||
(define strs '())
|
||
(define tag (make-prompt-tag))
|
||
(define (write-string str)
|
||
(set! length (+ length (string-length str)))
|
||
(set! strs (cons str strs))
|
||
(when (or (< (- max-column initial-column) length)
|
||
(and (not allow-newline?)
|
||
(not (zero? (port-line port)))))
|
||
(abort-to-prompt tag)))
|
||
(define port
|
||
(make-soft-port #:id "truncating-output-port"
|
||
#:write-string write-string))
|
||
(call-with-prompt
|
||
tag
|
||
(lambda ()
|
||
(proc port)
|
||
(close port)
|
||
(success (string-concatenate-reverse strs)))
|
||
(lambda (_)
|
||
(failure (string-concatenate-reverse strs)))))
|
||
|
||
|
||
|
||
|
||
;; Parts of pretty-print derived from "genwrite.scm", from SLIB.
|
||
;; Copyright (c) 1991, Marc Feeley
|
||
;; Author: Marc Feeley (feeley@iro.umontreal.ca)
|
||
;; Distribution restrictions: none
|
||
|
||
(define* (pretty-print obj #:optional port*
|
||
#:key
|
||
(port (or port* (current-output-port)))
|
||
(width 79)
|
||
(max-expr-width 50)
|
||
(display? #f)
|
||
(per-line-prefix ""))
|
||
"Pretty-print OBJ on PORT, which is a keyword argument defaulting to
|
||
the current output port. Formatting can be controlled by a number of
|
||
keyword arguments: Each line in the output is preceded by the string
|
||
PER-LINE-PREFIX, which is empty by default. The output lines will be
|
||
at most WIDTH characters wide; the default is 79. If DISPLAY? is
|
||
true, display rather than write representation will be used.
|
||
|
||
Instead of with a keyword argument, you can also specify the output
|
||
port directly after OBJ, like (pretty-print OBJ PORT)."
|
||
(define (wr obj port)
|
||
(define (wr-read-macro prefix x)
|
||
(put-string port prefix)
|
||
(wr x port))
|
||
(match obj
|
||
(('quote x) (wr-read-macro "'" x))
|
||
(('quasiquote x) (wr-read-macro "`" x))
|
||
(('unquote x) (wr-read-macro "," x))
|
||
(('unquote-splicing x) (wr-read-macro ",@" x))
|
||
((head . (rest ...))
|
||
;; A proper list: do our own list printing so as to catch read
|
||
;; macros that appear in the middle of the list.
|
||
(put-string port "(")
|
||
(wr head port)
|
||
(for-each (lambda (x)
|
||
(put-string port " ")
|
||
(wr x port))
|
||
rest)
|
||
(put-string port ")"))
|
||
(_
|
||
((if display? display write) obj port))))
|
||
|
||
; define formatting style (change these to suit your style)
|
||
(define indent-general 2)
|
||
(define max-call-head-width 5)
|
||
|
||
(define (spaces n)
|
||
(when (< 0 n)
|
||
(put-string port " " 0 (min 8 n))
|
||
(when (< 8 n)
|
||
(spaces (- n 8)))))
|
||
|
||
(define (indent to)
|
||
(let ((col (port-column port)))
|
||
(cond
|
||
((< to col)
|
||
(put-string port "\n")
|
||
(put-string port per-line-prefix)
|
||
(spaces (- to (string-length per-line-prefix))))
|
||
(else
|
||
(spaces (- to col))))))
|
||
|
||
(define (pr obj pp-pair)
|
||
(match obj
|
||
((? vector?)
|
||
(put-string port "#")
|
||
(pr (vector->list obj) pp-pair))
|
||
((not (? pair?))
|
||
(wr obj port))
|
||
(('quote x) (put-string port "'") (pr x pp-pair))
|
||
(('quasiquote x) (put-string port "`") (pr x pp-pair))
|
||
(('unquote x) (put-string port ",") (pr x pp-pair))
|
||
(('unquote-splicing x) (put-string port ",@") (pr x pp-pair))
|
||
(_
|
||
;; A pair (and possibly a list). May have to split on multiple
|
||
;; lines.
|
||
(call-with-truncating-output-string
|
||
(lambda (port) (wr obj port))
|
||
(lambda (full-str) (put-string port full-str))
|
||
(lambda (partial-str) (pp-pair obj))
|
||
#:initial-column (port-column port)
|
||
#:max-column (- width (string-length per-line-prefix))
|
||
#:allow-newline? #f))))
|
||
|
||
(define (pp-expr expr)
|
||
(match expr
|
||
(((or 'quote 'quasiquote 'unquote 'unquote-splicing) _)
|
||
(pp-quote expr))
|
||
(('lambda _ _ . _) (pp-lambda expr))
|
||
(('lambda* _ _ . _) (pp-lambda expr))
|
||
(('let (? symbol?) _ _ . _) (pp-named-let expr))
|
||
(('let _ _ . _) (pp-let expr))
|
||
(('let* _ _ . _) (pp-let expr))
|
||
(('letrec _ _ . _) (pp-let expr))
|
||
(('letrec* _ _ . _) (pp-let expr))
|
||
(('let-syntax _ _ . _) (pp-let expr))
|
||
(('letrec-syntax _ _ . _) (pp-let expr))
|
||
(('define _ _ . _) (pp-define expr))
|
||
(('define* _ _ . _) (pp-define expr))
|
||
(('define-public _ _ . _) (pp-define expr))
|
||
(('define-syntax _ _ . _) (pp-define expr))
|
||
(('if _ _ . (or () (_))) (pp-if expr))
|
||
(('cond . _) (pp-cond expr))
|
||
(('case _ . _) (pp-case expr))
|
||
(('begin . _) (pp-begin expr))
|
||
(('do _ _ . _) (pp-do expr))
|
||
(('syntax-rules _ . _) (pp-syntax-rules expr))
|
||
(('syntax-case _ _ . _) (pp-syntax-case expr))
|
||
(((? symbol? head) . _)
|
||
(if (< max-call-head-width (string-length (symbol->string head)))
|
||
(pp-list expr pp-expr)
|
||
(pp-call expr pp-expr)))
|
||
(_ (pp-list expr pp-expr))))
|
||
|
||
(define (pp0 head body)
|
||
(let ((body-col (+ (port-column port) indent-general)))
|
||
(put-string port "(")
|
||
(wr head port)
|
||
(pp-down body body-col pp-expr)))
|
||
|
||
(define (pp1 head param0 body pp-param0)
|
||
(let ((body-col (+ (port-column port) indent-general)))
|
||
(put-string port "(")
|
||
(wr head port)
|
||
(put-string port " ")
|
||
(pr param0 pp-param0)
|
||
(pp-down body body-col pp-expr)))
|
||
|
||
(define (pp2 head param0 param1 body pp-param0 pp-param1)
|
||
(let ((body-col (+ (port-column port) indent-general)))
|
||
(put-string port "(")
|
||
(wr head port)
|
||
(put-string port " ")
|
||
(pr param0 pp-param0)
|
||
(put-string port " ")
|
||
(pr param1 pp-param1)
|
||
(pp-down body body-col pp-expr)))
|
||
|
||
(define (pp-quote expr)
|
||
(match obj
|
||
((head x)
|
||
(put-string port
|
||
(match x
|
||
('quote "'")
|
||
('quasiquote "`")
|
||
('unquote ",")
|
||
('unquote-splicing ",@")))
|
||
(pr x pp-expr))))
|
||
|
||
(define (pp-lambda expr)
|
||
(match expr
|
||
((head args . body)
|
||
(pp1 head args body pp-expr-list))))
|
||
|
||
(define (pp-let expr)
|
||
(match expr
|
||
((head bindings . body)
|
||
(pp1 head bindings body pp-expr-list))))
|
||
|
||
(define (pp-named-let expr)
|
||
(match expr
|
||
((head name bindings . body)
|
||
(pp2 head name bindings body pp-expr pp-expr-list))))
|
||
|
||
(define (pp-define expr)
|
||
(match expr
|
||
((head args . body)
|
||
(pp1 head args body pp-expr-list))))
|
||
|
||
(define (pp-if expr)
|
||
(match expr
|
||
((head test . body)
|
||
;; "if" indent is 4.
|
||
(put-string port "(")
|
||
(wr head port)
|
||
(put-string port " ")
|
||
(let ((body-col (port-column port)))
|
||
(pr test pp-expr)
|
||
(pp-down body body-col pp-expr)))))
|
||
|
||
(define (pp-cond expr)
|
||
(match expr
|
||
((head . clauses)
|
||
(pp0 head clauses))))
|
||
|
||
(define (pp-case expr)
|
||
(match expr
|
||
((head x . clauses)
|
||
(pp1 head x clauses pp-expr))))
|
||
|
||
(define (pp-begin expr)
|
||
(match expr
|
||
((head . body) (pp0 head body))))
|
||
|
||
(define (pp-do expr)
|
||
(match expr
|
||
((head bindings exit . body)
|
||
(pp2 head bindings exit body pp-expr-list pp-expr-list))))
|
||
|
||
(define (pp-syntax-rules expr)
|
||
(match expr
|
||
((head literals . clauses)
|
||
(pp1 head literals clauses pp-expr-list))))
|
||
|
||
(define (pp-syntax-case expr)
|
||
(match expr
|
||
((head stx literals . clauses)
|
||
(pp2 head stx literals clauses pp-expr pp-expr-list))))
|
||
|
||
; (head item1
|
||
; item2
|
||
; item3)
|
||
(define (pp-call expr pp-item)
|
||
(match expr
|
||
((head . tail)
|
||
(put-string port "(")
|
||
(wr head port)
|
||
(pp-down tail (+ (port-column port) 1) pp-item))))
|
||
|
||
; (item1
|
||
; item2
|
||
; item3)
|
||
(define (pp-list l pp-item)
|
||
(put-string port "(")
|
||
(pp-down l (port-column port) pp-item))
|
||
|
||
(define (pp-down l item-indent pp-item)
|
||
(let loop ((l l))
|
||
(match l
|
||
(() (put-string port ")"))
|
||
((head . tail)
|
||
(indent item-indent)
|
||
(pr head pp-item)
|
||
(loop tail))
|
||
(improper-tail
|
||
(indent item-indent)
|
||
(put-string port ".")
|
||
(indent item-indent)
|
||
(pr improper-tail pp-item)
|
||
(put-string port ")")))))
|
||
|
||
(define (pp-expr-list l)
|
||
(pp-list l pp-expr))
|
||
|
||
(put-string port per-line-prefix)
|
||
(pr obj pp-expr)
|
||
(newline port)
|
||
;; Return `unspecified'
|
||
(if #f #f))
|
||
|
||
|
||
|
||
|
||
(define* (truncated-print x #:optional port*
|
||
#:key
|
||
(port (or port* (current-output-port)))
|
||
(width 79)
|
||
(display? #f)
|
||
(breadth-first? #f))
|
||
"Print @var{x}, truncating the output, if necessary, to make it fit
|
||
into @var{width} characters. By default, @var{x} will be printed using
|
||
@code{write}, though that behavior can be overriden via the
|
||
@var{display?} keyword argument.
|
||
|
||
The default behavior is to print depth-first, meaning that the entire
|
||
remaining width will be available to each sub-expression of @var{x} --
|
||
e.g., if @var{x} is a vector, each member of @var{x}. One can attempt to
|
||
\"ration\" the available width, trying to allocate it equally to each
|
||
sub-expression, via the @var{breadth-first?} keyword argument."
|
||
|
||
(define ellipsis
|
||
;; Choose between `HORIZONTAL ELLIPSIS' (U+2026) and three dots, depending
|
||
;; on the encoding of PORT.
|
||
(let ((e "…"))
|
||
(catch 'encoding-error
|
||
(lambda ()
|
||
(with-fluids ((%default-port-conversion-strategy 'error))
|
||
(call-with-output-string
|
||
(lambda (p)
|
||
(set-port-encoding! p (port-encoding port))
|
||
(display e p)))))
|
||
(lambda (key . args)
|
||
"..."))))
|
||
|
||
(let ((ellipsis-width (string-length ellipsis)))
|
||
|
||
(define* (print-sequence x width len ref next #:key inner?)
|
||
(let lp ((x x)
|
||
(width width)
|
||
(i 0))
|
||
(if (> i 0)
|
||
(display #\space))
|
||
(cond
|
||
((= i len)) ; catches 0-length case
|
||
((and (= i (1- len)) (or (zero? i) (> width 1)))
|
||
(print (ref x i) (if (zero? i) width (1- width)) #:inner? inner?))
|
||
((<= width (+ 1 ellipsis-width))
|
||
(display ellipsis))
|
||
(else
|
||
(let ((str (with-output-to-string
|
||
(lambda ()
|
||
(print (ref x i)
|
||
(if breadth-first?
|
||
(max 1
|
||
(1- (floor (/ width (- len i)))))
|
||
(- width (+ 1 ellipsis-width)))
|
||
#:inner? inner?)))))
|
||
(display str)
|
||
(lp (next x) (- width 1 (string-length str)) (1+ i)))))))
|
||
|
||
(define (print-tree x width)
|
||
;; width is >= the width of # . #, which is 5
|
||
(let lp ((x x)
|
||
(width width))
|
||
(cond
|
||
((or (not (pair? x)) (<= width 4))
|
||
(display ". ")
|
||
(print x (- width 2)))
|
||
(else
|
||
;; width >= 5
|
||
(let ((str (with-output-to-string
|
||
(lambda ()
|
||
(print (car x)
|
||
(if breadth-first?
|
||
(floor (/ (- width 3) 2))
|
||
(- width 4)))))))
|
||
(display str)
|
||
(display " ")
|
||
(lp (cdr x) (- width 1 (string-length str))))))))
|
||
|
||
(define (truncate-string str width)
|
||
(unless (< width (string-length str))
|
||
(error "precondition failed"))
|
||
(or (or-map (match-lambda
|
||
((prefix . suffix)
|
||
(and (string-prefix? prefix str)
|
||
(<= (+ (string-length prefix)
|
||
(string-length suffix)
|
||
ellipsis-width)
|
||
width)
|
||
(format #f "~a~a~a"
|
||
(substring str 0
|
||
(- width (string-length suffix)
|
||
ellipsis-width))
|
||
ellipsis
|
||
suffix))))
|
||
'(("#<" . ">")
|
||
("#(" . ")")
|
||
("(" . ")")
|
||
("\"" . "\"")))
|
||
"#"))
|
||
|
||
(define* (print x width #:key inner?)
|
||
(cond
|
||
((<= width 0)
|
||
(error "expected a positive width" width))
|
||
((list? x)
|
||
(cond
|
||
((>= width (+ 2 ellipsis-width))
|
||
(display "(")
|
||
(print-sequence x (- width 2) (length x)
|
||
(lambda (x i) (car x)) cdr)
|
||
(display ")"))
|
||
(else
|
||
(display "#"))))
|
||
((vector? x)
|
||
(cond
|
||
((>= width (+ 3 ellipsis-width))
|
||
(display "#(")
|
||
(print-sequence x (- width 3) (vector-length x)
|
||
vector-ref identity)
|
||
(display ")"))
|
||
(else
|
||
(display "#"))))
|
||
((bytevector? x)
|
||
(cond
|
||
((>= width 9)
|
||
(format #t "#~a(" (array-type x))
|
||
(print-sequence x (- width 6) (array-length x)
|
||
array-ref identity)
|
||
(display ")"))
|
||
(else
|
||
(display "#"))))
|
||
((bitvector? x)
|
||
(cond
|
||
((>= width (+ 2 (array-length x)))
|
||
(format #t "~a" x))
|
||
;; the truncated bitvector would print as #1b(...), so we print by hand.
|
||
((>= width (+ 2 ellipsis-width))
|
||
(format #t "#*")
|
||
(array-for-each (lambda (xi) (display (if xi "1" "0")))
|
||
(make-shared-array x list (- width 2 ellipsis-width)))
|
||
(display ellipsis))
|
||
(else
|
||
(display "#"))))
|
||
((and (array? x) (not (string? x)))
|
||
(let* ((type (array-type x))
|
||
(prefix
|
||
(if inner?
|
||
""
|
||
(call-with-output-string
|
||
(lambda (s) ((@@ (ice-9 arrays) array-print-prefix) x s)))))
|
||
(width-prefix (string-length prefix)))
|
||
(cond
|
||
((>= width (+ 2 width-prefix ellipsis-width))
|
||
(format #t "~a(" prefix)
|
||
(if (zero? (array-rank x))
|
||
(print (array-ref x) (- width width-prefix 2))
|
||
(print-sequence x (- width width-prefix 2) (array-length x)
|
||
(let ((base (caar (array-shape x))))
|
||
(lambda (x i) (array-cell-ref x (+ base i))))
|
||
identity
|
||
#:inner? (< 1 (array-rank x))))
|
||
(display ")"))
|
||
(else
|
||
(display "#")))))
|
||
((pair? x)
|
||
(cond
|
||
((>= width (+ 4 ellipsis-width))
|
||
(display "(")
|
||
(print-tree x (- width 2))
|
||
(display ")"))
|
||
(else
|
||
(display "#"))))
|
||
(else
|
||
(call-with-truncating-output-string
|
||
(lambda (port)
|
||
(if display? (display x port) (write x port)))
|
||
(lambda (full-str)
|
||
(display full-str))
|
||
(lambda (partial-str)
|
||
(display (truncate-string partial-str width)))
|
||
#:max-column width
|
||
#:allow-newline? #f))))
|
||
|
||
(with-output-to-port port
|
||
(lambda ()
|
||
(print x width)))))
|