format.scm 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. ; Copyright (c) 1993-2008 by Richard Kelsey and Jonathan Rees. See file COPYING.
  2. ; Quicky FORMAT
  3. ;
  4. ; (FORMAT port string . args)
  5. ;
  6. ; PORT is one of:
  7. ; an output port, in which case FORMAT prints to the port;
  8. ; #T, FORMAT prints to the current output port;
  9. ; #F, FORMAT returns a string.
  10. ;
  11. ; The following format directives have been implemented:
  12. ; ~~ -prints a single ~
  13. ; ~A -prints the next argument using DISPLAY
  14. ; ~D -prints the next argument using NUMBER->STRING (`D'ecimal)
  15. ; ~S -prints the next argument using WRITE
  16. ; ~% -prints a NEWLINE character
  17. ; ~& -prints a NEWLINE character if the previous printed character was not one
  18. ; (this is implemented using FRESH-LINE)
  19. ; ~? -performs a recursive call to FORMAT using the next two arguments as the
  20. ; string and the list of arguments
  21. ;
  22. ; FORMAT is case-insensitive with respect to letter directives (~a and ~A have
  23. ; the same effect).
  24. ; The entry point. Gets the port and writes the output.
  25. ; Get the appropriate writer for the port specification.
  26. (define (format port string . args)
  27. (cond ((not port)
  28. (call-with-string-output-port
  29. (lambda (port)
  30. (real-format port string args))))
  31. ((eq? port #t)
  32. (real-format (current-output-port) string args))
  33. ((output-port? port)
  34. (real-format port string args))
  35. (else
  36. (error "invalid port argument to FORMAT" port))))
  37. ; Loop down the format string printing characters and dispatching on directives
  38. ; as required. Procedures for the directives are in a vector indexed by
  39. ; character codes. Each procedure takes four arguments: the format string,
  40. ; the index of the next unused character in the format string, the list of
  41. ; remaining arguments, and the writer. Each should return a list of the unused
  42. ; arguments.
  43. (define (real-format out string all-args)
  44. (let loop ((i 0) (args all-args))
  45. (cond ((>= i (string-length string))
  46. (if (null? args)
  47. #f
  48. (error "too many arguments to FORMAT" string all-args)))
  49. ((char=? #\~ (string-ref string i))
  50. (if (= (+ i 1) (string-length string))
  51. (error "invalid format string" string i)
  52. (loop (+ i 2)
  53. ((vector-ref format-dispatch-vector
  54. (char->ascii (string-ref string (+ i 1))))
  55. string
  56. (+ i 2)
  57. args
  58. out))))
  59. (else
  60. (write-char (string-ref string i) out)
  61. (loop (+ i 1) args)))))
  62. ; One more than the highest integer that CHAR->ASCII may return.
  63. (define number-of-char-codes ascii-limit)
  64. ; The vector of procedures implementing format directives.
  65. (define format-dispatch-vector
  66. (make-vector number-of-char-codes
  67. (lambda (string i args out)
  68. (error "illegal format command"
  69. string
  70. (string-ref string (- i 1))))))
  71. ; This implements FORMAT's case-insensitivity.
  72. (define (define-format-command char proc)
  73. (vector-set! format-dispatch-vector (char->ascii char) proc)
  74. (if (char-alphabetic? char)
  75. (vector-set! format-dispatch-vector
  76. (char->ascii (if (char-lower-case? char)
  77. (char-upcase char)
  78. (char-downcase char)))
  79. proc)))
  80. ; Write a single ~ character.
  81. (define-format-command #\~
  82. (lambda (string i args out)
  83. (write-char #\~ out)
  84. args))
  85. ; Newline
  86. (define-format-command #\%
  87. (lambda (string i args out)
  88. (newline out)
  89. args))
  90. ; Fresh-Line
  91. (define-format-command #\&
  92. (lambda (string i args out)
  93. (fresh-line out)
  94. args))
  95. ; Display (`A' is for ASCII)
  96. (define-format-command #\a
  97. (lambda (string i args out)
  98. (check-for-format-arg args)
  99. (display (car args) out)
  100. (cdr args)))
  101. ; Decimals
  102. (define-format-command #\d
  103. (lambda (string i args out)
  104. (check-for-format-arg args)
  105. (if (not (number? (car args)))
  106. (error "invalid number argument to ~D in FORMAT" string (car args)))
  107. (display (number->string (car args) 10) out)
  108. (cdr args)))
  109. ; Write (`S' is for S-expression)
  110. (define-format-command #\s
  111. (lambda (string i args out)
  112. (check-for-format-arg args)
  113. (write (car args) out)
  114. (cdr args)))
  115. ; Recursion
  116. (define-format-command #\?
  117. (lambda (string i args out)
  118. (check-for-format-arg args)
  119. (check-for-format-arg (cdr args))
  120. (real-format out (car args) (cadr args))
  121. (cddr args)))
  122. ; Signal an error if ARGS is empty.
  123. (define (check-for-format-arg args)
  124. (if (null? args)
  125. (error "insufficient number of arguments to FORMAT")))