format.scm 4.4 KB

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