updating (chibi show) with srfi changes

This commit is contained in:
Alex Shinn 2017-11-08 22:56:40 +09:00
parent bc3fa73ec4
commit 80c69291ba
8 changed files with 596 additions and 218 deletions

View file

@ -1,7 +1,9 @@
(define-library (chibi show-test) (define-library (chibi show-test)
(export run-tests) (export run-tests)
(import (scheme base) (scheme read) (chibi test) (import (scheme base) (scheme char) (scheme read)
(chibi show) (chibi show base) (chibi show pretty)) (chibi test)
(chibi show) (chibi show base)
(chibi show column) (chibi show pretty))
(begin (begin
(define-syntax test-pretty (define-syntax test-pretty
(syntax-rules () (syntax-rules ()
@ -144,6 +146,29 @@
(test "3.14+2.00i" (test "3.14+2.00i"
(show #f (with ((precision 2)) (string->number "3.14159+2i")))))) (show #f (with ((precision 2)) (string->number "3.14159+2i"))))))
(test "608" (show #f (numeric/si 608)))
(test "3.9Ki" (show #f (numeric/si 3986)))
(test "4kB" (show #f (numeric/si 3986 1000) "B"))
(test "1.2Mm" (show #f (numeric/si 1.23e6 1000) "m"))
(test "123km" (show #f (numeric/si 1.23e5 1000) "m"))
(test "12.3km" (show #f (numeric/si 1.23e4 1000) "m"))
(test "1.2km" (show #f (numeric/si 1.23e3 1000) "m"))
(test "123m" (show #f (numeric/si 1.23e2 1000) "m"))
(test "12.3m" (show #f (numeric/si 1.23e1 1000) "m"))
(test "1.2m" (show #f (numeric/si 1.23 1000) "m"))
(test "123mm" (show #f (numeric/si 0.123 1000) "m"))
(test "12.3mm" (show #f (numeric/si 1.23e-2 1000) "m")) ;?
(test "1.2mm" (show #f (numeric/si 1.23e-3 1000) "m"))
(test "123µm" (show #f (numeric/si 1.23e-4 1000) "m")) ;?
(test "12.3µm" (show #f (numeric/si 1.23e-5 1000) "m")) ;?
(test "1.2µm" (show #f (numeric/si 1.23e-6 1000) "m"))
(test "1,234,567" (show #f (numeric/comma 1234567)))
(test "1.23" (show #f (numeric/fitted 4 1.2345 10 2)))
(test "1.00" (show #f (numeric/fitted 4 1 10 2)))
(test "#.##" (show #f (numeric/fitted 4 12.345 10 2)))
;; padding/trimming ;; padding/trimming
(test "abc " (show #f (padded 5 "abc"))) (test "abc " (show #f (padded 5 "abc")))
@ -253,6 +278,14 @@
'(lions tigers . bears) '(lions tigers . bears)
", "))) ", ")))
;; escaping
(test "hi, bob!" (show #f (escaped "hi, bob!")))
(test "hi, \\\"bob!\\\"" (show #f (escaped "hi, \"bob!\"")))
(test "bob" (show #f (maybe-escaped "bob" char-whitespace?)))
(test "\"hi, bob!\""
(show #f (maybe-escaped "hi, bob!" char-whitespace?)))
;; shared structures ;; shared structures
(test "#0=(1 . #0#)" (test "#0=(1 . #0#)"
@ -389,4 +422,163 @@
(ones ',ones)) (ones ',ones))
(append zeros ones)))))) (append zeros ones))))))
;; columns
(test "abc\ndef\n"
(show #f (show-columns (list displayed "abc\ndef\n"))))
(test "abc123\ndef456\n"
(show #f (show-columns (list displayed "abc\ndef\n")
(list displayed "123\n456\n"))))
(test "abc123\ndef456\n"
(show #f (show-columns (list displayed "abc\ndef\n")
(list displayed "123\n456"))))
(test "abc123\ndef456\n"
(show #f (show-columns (list displayed "abc\ndef")
(list displayed "123\n456\n"))))
(test "abc123\ndef456\nghi789\n"
(show #f (show-columns (list displayed "abc\ndef\nghi\n")
(list displayed "123\n456\n789\n"))))
(test "abc123wuv\ndef456xyz\n"
(show #f (show-columns (list displayed "abc\ndef\n")
(list displayed "123\n456\n")
(list displayed "wuv\nxyz\n"))))
(test "abc 123\ndef 456\n"
(show #f (show-columns (list (lambda (x) (padded/right 5 x))
"abc\ndef\n")
(list displayed "123\n456\n"))))
(test "ABC 123\nDEF 456\n"
(show #f (show-columns (list (lambda (x) (upcased (padded/right 5 x)))
"abc\ndef\n")
(list displayed "123\n456\n"))))
(test "ABC 123\nDEF 456\n"
(show #f (show-columns (list (lambda (x) (padded/right 5 (upcased x)))
"abc\ndef\n")
(list displayed "123\n456\n"))))
(test "hello\nworld\n"
(show #f (with ((width 8)) (wrapped "hello world"))))
(test "\n" (show #f (wrapped " ")))
(test
"The quick
brown fox
jumped
over the
lazy dog
"
(show #f
(with ((width 10))
(justified "The quick brown fox jumped over the lazy dog"))))
(test
"The fundamental list iterator.
Applies KONS to each element of
LS and the result of the previous
application, beginning with KNIL.
With KONS as CONS and KNIL as '(),
equivalent to REVERSE.
"
(show #f
(with ((width 36))
(wrapped "The fundamental list iterator. Applies KONS to each element of LS and the result of the previous application, beginning with KNIL. With KONS as CONS and KNIL as '(), equivalent to REVERSE."))))
(test
"(define (fold kons knil ls)
(let lp ((ls ls) (acc knil))
(if (null? ls)
acc
(lp (cdr ls)
(kons (car ls) acc)))))
"
(show #f
(with ((width 36))
(pretty '(define (fold kons knil ls)
(let lp ((ls ls) (acc knil))
(if (null? ls)
acc
(lp (cdr ls)
(kons (car ls) acc)))))))))
'(test
"(define (fold kons knil ls) ; The fundamental list iterator.
(let lp ((ls ls) (acc knil)) ; Applies KONS to each element of
(if (null? ls) ; LS and the result of the previous
acc ; application, beginning with KNIL.
(lp (cdr ls) ; With KONS as CONS and KNIL as '(),
(kons (car ls) acc))))) ; equivalent to REVERSE.
"
(show #f
(show-columns
(list
(lambda (x) (padded/right 36 x))
(with ((width 36))
(pretty '(define (fold kons knil ls)
(let lp ((ls ls) (acc knil))
(if (null? ls)
acc
(lp (cdr ls)
(kons (car ls) acc))))))))
(list
(lambda (x) (each " ; " x))
(with ((width 36))
(wrapped "The fundamental list iterator. Applies KONS to each element of LS and the result of the previous application, beginning with KNIL. With KONS as CONS and KNIL as '(), equivalent to REVERSE."))))))
'(test
"(define (fold kons knil ls) ; The fundamental list iterator.
(let lp ((ls ls) (acc knil)) ; Applies KONS to each element of
(if (null? ls) ; LS and the result of the previous
acc ; application, beginning with KNIL.
(lp (cdr ls) ; With KONS as CONS and KNIL as '(),
(kons (car ls) acc))))) ; equivalent to REVERSE.
"
(show #f (with ((width 76))
(columnar
(pretty '(define (fold kons knil ls)
(let lp ((ls ls) (acc knil))
(if (null? ls)
acc
(lp (cdr ls)
(kons (car ls) acc))))))
" ; "
(wrapped "The fundamental list iterator. Applies KONS to each element of LS and the result of the previous application, beginning with KNIL. With KONS as CONS and KNIL as '(), equivalent to REVERSE.")))))
(test
"- Item 1: The text here is
indented according
to the space \"Item
1\" takes, and one
does not known what
goes here.
"
(show #f (columnar 9 (each "- Item 1:") " " (with ((width 20)) (wrapped "The text here is indented according to the space \"Item 1\" takes, and one does not known what goes here.")))))
(test
"- Item 1: The text here is
indented according
to the space \"Item
1\" takes, and one
does not known what
goes here.
"
(show #f (columnar 9 (each "- Item 1:\n") " " (with ((width 20)) (wrapped "The text here is indented according to the space \"Item 1\" takes, and one does not known what goes here.")))))
(test
"- Item 1: The-text-here-is----------------------------------------------------
--------- indented-according--------------------------------------------------
--------- to-the-space-\"Item--------------------------------------------------
--------- 1\"-takes,-and-one---------------------------------------------------
--------- does-not-known-what-------------------------------------------------
--------- goes-here.----------------------------------------------------------
"
(show #f (with ((pad-char #\-)) (columnar 9 (each "- Item 1:\n") " " (with ((width 20)) (wrapped "The text here is indented according to the space \"Item 1\" takes, and one does not known what goes here."))))))
(test
"a | 123
bc | 45
def | 6
"
(show #f (with ((width 20))
(tabular (each "a\nbc\ndef\n") " | "
(each "123\n45\n6\n")))))
(test-end)))) (test-end))))

View file

@ -2,8 +2,9 @@
(define-library (chibi show) (define-library (chibi show)
(export (export
show fn fn-fork with update! each each-in-list call-with-output show fn fn-fork with update! each each-in-list call-with-output
displayed written written-shared written-simply numeric nothing displayed written written-shared written-simply
nl fl space-to tab-to numeric numeric/comma numeric/si numeric/fitted
nothing nl fl space-to tab-to escaped maybe-escaped
padded padded/left padded/right padded/both padded padded/left padded/right padded/both
trimmed trimmed/left trimmed/right trimmed/both trimmed/lazy trimmed trimmed/left trimmed/right trimmed/both trimmed/lazy
fitted fitted/left fitted/right fitted/both fitted fitted/left fitted/right fitted/both

View file

@ -3,10 +3,29 @@
(export (export
show fn fn-fork with update! each each-in-list call-with-output show fn fn-fork with update! each each-in-list call-with-output
displayed written written-shared written-simply numeric nothing displayed written written-shared written-simply numeric nothing
escaped maybe-escaped numeric/si numeric/fitted numeric/comma
;; internal ;; internal
output-default extract-shared-objects write-to-string write-with-shares output-default extract-shared-objects write-to-string write-with-shares
call-with-shared-ref call-with-shared-ref/cdr) call-with-shared-ref call-with-shared-ref/cdr)
(import (scheme base) (scheme write) (scheme complex) (scheme inexact) (import (scheme base) (scheme write) (scheme complex) (scheme inexact)
(srfi 1) (srfi 69) (chibi string) (chibi monad environment)) (srfi 1) (srfi 69) (chibi string) (chibi monad environment))
(cond-expand
(chibi
(import (only (chibi) let-optionals*)))
(else
(begin
(define-syntax let-optionals*
(syntax-rules ()
((let-optionals* opt-ls () . body)
(begin . body))
((let-optionals* (op . args) vars . body)
(let ((tmp (op . args)))
(let-optionals* tmp vars . body)))
((let-optionals* tmp ((var default) . rest) . body)
(let ((var (if (pair? tmp) (car tmp) default))
(tmp2 (if (pair? tmp) (cdr tmp) '())))
(let-optionals* tmp2 rest . body)))
((let-optionals* tmp tail . body)
(let ((tail tmp)) . body)))))))
(include "base.scm") (include "base.scm")
(include "write.scm")) (include "write.scm"))

View file

@ -2,6 +2,20 @@
;; Copyright (c) 2006-2017 Alex Shinn. All rights reserved. ;; Copyright (c) 2006-2017 Alex Shinn. All rights reserved.
;; BSD-style license: http://synthcode.com/license.txt ;; BSD-style license: http://synthcode.com/license.txt
(define (string-split-words str separator?)
(let ((start (string-cursor-start str))
(end (string-cursor-end str)))
(let lp ((sc start) (res '()))
(cond
((string-cursor>=? sc end)
(reverse res))
(else
(let ((sc2 (string-index str separator? sc)))
(lp (string-cursor-next str sc2)
(if (string-cursor=? sc sc2)
res
(cons (substring/cursors str sc sc2) res)))))))))
(define (call-with-output-generator producer consumer) (define (call-with-output-generator producer consumer)
(fn () (fn ()
(let ((out (open-output-string)) (let ((out (open-output-string))
@ -12,10 +26,18 @@
(define (output* str) (define (output* str)
(fn (row col string-width) (fn (row col string-width)
(list-queue-add-back! queue str) (list-queue-add-back! queue str)
(each
(let ((nl-index
(string-index-right str (lambda (ch) (eqv? ch #\newline)))))
(if (string-cursor>? nl-index (string-cursor-start str))
(update!
(row (+ row (string-count str (lambda (ch) (eqv? ch #\newline)))))
(col (string-width str (string-cursor->index str nl-index))))
(update! (col (+ col (string-width str))))))
(call-with-current-continuation (call-with-current-continuation
(lambda (cc) (lambda (cc)
(set! resume cc) (set! resume cc)
(return nothing))) (return nothing))))
nothing)) nothing))
(define (generate) (define (generate)
(if (and resume (list-queue-empty? queue)) (if (and resume (list-queue-empty? queue))
@ -320,6 +342,10 @@
(fn () (lp (string-cursor-next str nli)))))))))))) (fn () (lp (string-cursor-next str nli))))))))))))
(each-in-list ls)))) (each-in-list ls))))
;; `seq' is a list or vector of pre-tokenized words. `line' is called
;; on each wrapped line and the accumulator, starting with `knil'.
;; The optional `last-line' is used instead on the last line of the
;; paragraph.
(define (wrap-fold-words seq knil max-width get-width line . o) (define (wrap-fold-words seq knil max-width get-width line . o)
(let* ((last-line (if (pair? o) (car o) line)) (let* ((last-line (if (pair? o) (car o) line))
(vec (if (list? seq) (list->vector seq) seq)) (vec (if (list? seq) (list->vector seq) seq))
@ -380,21 +406,21 @@
(last-line (sub-list i len-1) acc) (last-line (sub-list i len-1) acc)
(lp (+ break 1) (line (sub-list i break) acc))))))))) (lp (+ break 1) (line (sub-list i break) acc)))))))))
;; XXXX don't split, traverse the string manually and keep track of (define (wrapped/list ls)
;; sentence endings so we can insert two spaces (fn (width string-width pad-char)
(define (wrap-fold str . o) (joined/suffix
(apply wrap-fold-words (string-split str " ") o)) (lambda (ls) (joined displayed ls pad-char))
(reverse
(wrap-fold-words ls '() width (or string-width string-length) cons))
"\n")))
(define (wrapped . ls) (define (wrapped . ls)
(call-with-output (call-with-output
(each-in-list ls) (each-in-list ls)
(lambda (str) (lambda (str)
(fn (width string-width pad-char) (fn (word-separator?)
(joined/suffix (wrapped/list
(lambda (ls) (joined displayed ls pad-char)) (string-split-words str (or word-separator? char-whitespace?)))))))
(reverse
(wrap-fold str '() width (or string-width string-length) cons))
"\n")))))
(define (justified . ls) (define (justified . ls)
(fn (output width string-width) (fn (output width string-width)
@ -427,11 +453,16 @@
(call-with-output (call-with-output
(each-in-list ls) (each-in-list ls)
(lambda (str) (lambda (str)
(fn (word-separator?)
(joined/last (joined/last
justify-line justify-line
justify-last justify-last
(reverse (wrap-fold str '() width string-width cons)) (reverse
"\n"))))) (wrap-fold-words
(string-split-words str (or word-separator? char-whitespace?))
'() width (or string-width string-length)
cons))
"\n"))))))
(define (from-file path) (define (from-file path)
(fn () (fn ()

View file

@ -6,5 +6,6 @@
(export (export
call-with-output-generator call-with-output-generators call-with-output-generator call-with-output-generators
string->line-generator string->line-generator
columnar tabular wrapped wrapped/char justified line-numbers from-file) tabular columnar show-columns wrapped wrapped/list wrapped/char
justified line-numbers from-file)
(include "column.scm")) (include "column.scm"))

View file

@ -42,7 +42,7 @@
(string-find str pred (string-index->cursor str i)))) (string-find str pred (string-index->cursor str i))))
(define (try-fitted2 proc fail) (define (try-fitted2 proc fail)
(fn (width string-width output) (fn (width output)
(let ((out (open-output-string))) (let ((out (open-output-string)))
(call-with-current-continuation (call-with-current-continuation
(lambda (abort) (lambda (abort)
@ -53,7 +53,7 @@
(fn (col) (fn (col)
(let lp ((i 0) (col col)) (let lp ((i 0) (col col))
(let ((nli (string-find/index str #\newline i)) (let ((nli (string-find/index str #\newline i))
(len (string-width str))) (len (string-length str)))
(if (< nli len) (if (< nli len)
(if (> (+ (- nli i) col) width) (if (> (+ (- nli i) col) width)
(abort fail) (abort fail)

View file

@ -1,5 +1,5 @@
;; show.scm -- additional combinator formatters ;; show.scm -- additional combinator formatters
;; Copyright (c) 2013 Alex Shinn. All rights reserved. ;; Copyright (c) 2013-2017 Alex Shinn. All rights reserved.
;; BSD-style license: http://synthcode.com/license.txt ;; BSD-style license: http://synthcode.com/license.txt
;;> A library of procedures for formatting Scheme objects to text in ;;> A library of procedures for formatting Scheme objects to text in

View file

@ -30,6 +30,64 @@
(if (pair? rule) (cdr rule) rule) (if (pair? rule) (cdr rule) rule)
(cons sep (cons (substring str i2 i) res))))))) (cons sep (cons (substring str i2 i) res)))))))
;;> Outputs the string str, escaping any quote or escape characters.
;;> If esc-ch, which defaults to #\\, is #f, escapes only the
;;> quote-ch, which defaults to #\", by doubling it, as in SQL strings
;;> and CSV values. If renamer is provided, it should be a procedure
;;> of one character which maps that character to its escape value,
;;> e.g. #\newline => #\n, or #f if there is no escape value.
(define (escaped fmt . o)
(let-optionals* o ((quot #\")
(esc #\\)
(rename (lambda (x) #f)))
(let ((quot-str (if (char? quot) (string quot) quot))
(esc-str (if (char? esc) (string esc) esc)))
(fn (output)
(define (output* str)
(let ((start (string-cursor-start str))
(end (string-cursor-end str)))
(let lp ((i start) (j start))
(define (collect)
(if (eq? i j) "" (substring-cursor str i j)))
(if (string-cursor>=? j end)
(output (collect))
(let ((c (string-cursor-ref str j))
(j2 (string-cursor-next str j)))
(cond
((or (eqv? c quot) (eqv? c esc))
(each (output (collect))
(output esc-str)
(fn () (lp j j2))))
((rename c)
=> (lambda (c2)
(each (output (collect))
(output esc-str)
(output (if (char? c2) (string c2) c2))
(fn () (lp j2 j2)))))
(else
(lp i j2))))))))
(with ((output output*))
fmt)))))
;;> Only escape if there are special characters, in which case also
;;> wrap in quotes. For writing symbols in |...| escapes, or CSV
;;> fields, etc. The predicate indicates which characters cause
;;> slashification - this is in addition to automatic slashifying when
;;> either the quote or escape char is present.
(define (maybe-escaped fmt pred . o)
(let-optionals* o ((quot #\")
(esc #\\)
(rename (lambda (x) #f)))
(define (esc? c) (or (eqv? c quot) (eqv? c esc) (rename c) (pred c)))
(call-with-output
fmt
(lambda (str)
(if (string-cursor<? (string-find str esc?) (string-cursor-end str))
(each quot (escaped str quot esc rename) quot)
(displayed str))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; numeric formatting ;; numeric formatting
@ -47,9 +105,24 @@
;; special cases, so the below is a simplification which tries to rely ;; special cases, so the below is a simplification which tries to rely
;; on number->string for common cases. ;; on number->string for common cases.
(define (numeric n) (define unspec (list 'unspecified))
(fn (radix precision decimal-sep decimal-align comma-rule comma-sep sign-rule)
(let ((dec-sep (or decimal-sep (if (eqv? comma-sep #\.) #\, #\.)))) (define-syntax default
(syntax-rules ()
((default var dflt) (if (eq? var unspec) dflt var))))
(define (numeric n . o)
(let-optionals* o ((rad unspec) (prec unspec) (sgn unspec)
(comma unspec) (commasep unspec) (decsep unspec))
(fn (radix precision sign-rule
comma-rule comma-sep decimal-sep decimal-align)
(let ((radix (default rad radix))
(precision (default prec precision))
(sign-rule (default sgn sign-rule))
(comma-rule (default comma comma-rule))
(comma-sep (default comma-sep commasep))
(dec-sep (default decsep
(or decimal-sep (if (eqv? comma-sep #\.) #\, #\.)))))
;; General formatting utilities. ;; General formatting utilities.
(define (get-scale q) (define (get-scale q)
(expt radix (- (integer-log q radix) 1))) (expt radix (- (integer-log q radix) 1)))
@ -151,7 +224,7 @@
(string-cursor>? last (string-cursor-start s)) (string-cursor>? last (string-cursor-start s))
(odd? (digit-value (odd? (digit-value
(string-cursor-ref (string-cursor-ref
s (string-cursor-prev last 1)))))))) s (string-cursor-prev s last))))))))
(list->string (list->string
(reverse (reverse
(map char-digit (map char-digit
@ -229,7 +302,68 @@
(each (format (real-part n) sign-rule) (each (format (real-part n) sign-rule)
(format (imag-part n) #t) (format (imag-part n) #t)
"i")))) "i"))))
(write-complex n)))) (write-complex n)))))
(define numeric/si
(let* ((names10 '#("" "k" "M" "G" "T" "E" "P" "Z" "Y"))
(names-10 '#("" "m" "µ" "n" "p" "f" "a" "z" "y"))
(names2 (list->vector
(cons ""
(cons "Ki" (map (lambda (s) (string-append s "i"))
(cddr (vector->list names10)))))))
(names-2 (list->vector
(cons ""
(map (lambda (s) (string-append s "i"))
(cdr (vector->list names-10)))))))
(define (round-to n k)
(/ (round (* n k)) k))
(lambda (n . o)
(let-optionals* o ((base 1024)
(separator ""))
(let* ((log-n (log n))
(names (if (negative? log-n)
(if (= base 1024) names-2 names-10)
(if (= base 1024) names2 names10)))
(k (min (exact ((if (negative? log-n) ceiling floor)
(/ (abs log-n) (log base))))
(vector-length names)))
(n2 (round-to (/ n (expt base (if (negative? log-n) (- k) k)))
10)))
(each (if (integer? n2)
(number->string (exact n2))
(inexact n2))
(if (zero? k) "" separator)
(vector-ref names k)))))))
;; Force a number into a fixed width, print as #'s if doesn't fit.
;; Needs to be wrapped in PADDED if you want to expand to the width.
(define (numeric/fitted width n . args)
(call-with-output
(apply numeric n args)
(lambda (str)
(if (> (string-length str) width)
(fn (precision decimal-sep comma-sep)
(let ((prec (if (and (pair? args) (pair? (cdr args)))
(cadr args)
precision)))
(if prec
(let* ((dec-sep
(or decimal-sep
(if (eqv? #\. comma-sep) #\, #\.)))
(diff (- width (+ prec
(if (char? dec-sep)
1
(string-length dec-sep))))))
(each (if (positive? diff) (make-string diff #\#) "")
dec-sep (make-string prec #\#)))
(displayed (make-string width #\#)))))
(displayed str)))))
(define (numeric/comma n . o)
(fn (comma-rule)
(with ((comma-rule (or comma-rule 3)))
(apply numeric n o))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; shared structure utilities ;;; shared structure utilities