1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-11 00:00:49 +02:00
guile/module/slib/scanf.scm
2001-04-14 11:24:45 +00:00

350 lines
11 KiB
Scheme

;;;;"scanf.scm" implemenation of formated input
;Copyright (C) 1996, 1997 Aubrey Jaffer
;
;Permission to copy this software, to redistribute it, and to use it
;for any purpose is granted, subject to the following restrictions and
;understandings.
;
;1. Any copy made of this software must include this copyright notice
;in full.
;
;2. I have made no warrantee or representation that the operation of
;this software will be error-free, and I am under no obligation to
;provide any services, by way of maintenance, update, or otherwise.
;
;3. In conjunction with products arising from the use of this
;material, there shall be no use of my name in any advertising,
;promotional, or sales literature without prior written consent in
;each case.
;;; Originally jjb@isye.gatech.edu (John Bartholdi) wrote some public
;;; domain code for a subset of scanf, but it was too difficult to
;;; extend to POSIX pattern compliance. Jan 96, I rewrote the scanf
;;; functions starting from the POSIX man pages.
(require 'string-port)
(define (stdio:scan-and-set format-string input-port . args)
(define setters args)
(if (equal? '(#f) args) (set! args #f))
(cond
((not (equal? "" format-string))
(call-with-input-string
format-string
(lambda (format-port)
(define items '())
(define chars-scanned 0)
(define assigned-count 0)
(define (char-non-numeric? c) (not (char-numeric? c)))
(define (flush-whitespace port)
(do ((c (peek-char port) (peek-char port))
(i 0 (+ 1 i)))
((or (eof-object? c) (not (char-whitespace? c))) i)
(read-char port)))
(define (flush-whitespace-input)
(set! chars-scanned (+ (flush-whitespace input-port) chars-scanned)))
(define (read-input-char)
(set! chars-scanned (+ 1 chars-scanned))
(read-char input-port))
(define (add-item report-field? next-item)
(cond (args
(cond ((and report-field? (null? setters))
(slib:error 'scanf "not enough variables for format"
format-string))
((not next-item) (return))
((not report-field?) (loop1))
(else
(let ((suc ((car setters) next-item)))
(cond ((not (boolean? suc))
(slib:warn 'scanf "setter returned non-boolean"
suc)))
(set! setters (cdr setters))
(cond ((not suc) (return))
((eqv? -1 report-field?) (loop1))
(else
(set! assigned-count (+ 1 assigned-count))
(loop1)))))))
((not next-item) (return))
(report-field? (set! items (cons next-item items))
(loop1))
(else (loop1))))
(define (return)
(cond ((and (zero? chars-scanned)
(eof-object? (peek-char input-port)))
(peek-char input-port))
(args assigned-count)
(else (reverse items))))
(define (read-string width separator?)
(cond (width
(let ((str (make-string width)))
(do ((i 0 (+ 1 i)))
((>= i width)
str)
(let ((c (peek-char input-port)))
(cond ((eof-object? c)
(set! str (substring str 0 i))
(set! i width))
((separator? c)
(set! str (if (zero? i) "" (substring str 0 i)))
(set! i width))
(else
(string-set! str i (read-input-char))))))))
(else
(do ((c (peek-char input-port) (peek-char input-port))
(l '() (cons c l)))
((or (eof-object? c) (separator? c))
(list->string (reverse l)))
(read-input-char)))))
(define (read-word width separator?)
(let ((l (read-string width separator?)))
(if (zero? (string-length l)) #f l)))
(define (loop1)
(define fc (read-char format-port))
(cond
((eof-object? fc)
(return))
((char-whitespace? fc)
(flush-whitespace format-port)
(flush-whitespace-input)
(loop1))
((eqv? #\% fc) ; interpret next format
(set! fc (read-char format-port))
(let ((report-field? (not (eqv? #\* fc)))
(width #f))
(define (width--) (if width (set! width (+ -1 width))))
(define (read-u)
(string->number (read-string width char-non-numeric?)))
(define (read-o)
(string->number
(read-string
width
(lambda (c) (not (memv c '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7)))))
8))
(define (read-x)
(string->number
(read-string
width
(lambda (c) (not (memv (char-downcase c)
'(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8
#\9 #\a #\b #\c #\d #\e #\f)))))
16))
(define (read-radixed-unsigned)
(let ((c (peek-char input-port)))
(case c
((#\0) (read-input-char)
(width--)
(set! c (peek-char input-port))
(case c
((#\x #\X) (read-input-char)
(width--)
(read-x))
(else (read-o))))
(else (read-u)))))
(define (read-ui)
(let* ((dot? #f)
(mantissa (read-word
width
(lambda (c)
(not (or (char-numeric? c)
(cond (dot? #f)
((eqv? #\. c)
(set! dot? #t)
#t)
(else #f)))))))
(exponent (cond
((not mantissa) #f)
((and (or (not width) (> width 1))
(memv (peek-char input-port) '(#\E #\e)))
(read-input-char)
(width--)
(let* ((expsign
(case (peek-char input-port)
((#\-) (read-input-char)
(width--)
"-")
((#\+) (read-input-char)
(width--)
"+")
(else "")))
(expint
(and
(or (not width) (positive? width))
(read-word width char-non-numeric?))))
(and expint (string-append
"e" expsign expint))))
(else #f))))
(and mantissa
(string->number
(string-append
"#i" (or mantissa "") (or exponent ""))))))
(define (read-signed proc)
(case (peek-char input-port)
((#\-) (read-input-char)
(width--)
(let ((ret (proc)))
(and ret (- ret))))
((#\+) (read-input-char)
(width--)
(proc))
(else (proc))))
;;(trace read-word read-signed read-ui read-radixed-unsigned read-x read-o read-u)
(cond ((not report-field?) (set! fc (read-char format-port))))
(if (char-numeric? fc) (set! width 0))
(do () ((or (eof-object? fc) (char-non-numeric? fc)))
(set! width (+ (* 10 width) (string->number (string fc))))
(set! fc (read-char format-port)))
(case fc ;ignore h,l,L modifiers.
((#\h #\l #\L) (set! fc (read-char format-port))))
(case fc
((#\n) (if (not report-field?)
(slib:error 'scanf "not saving %n??"))
(add-item -1 chars-scanned)) ;-1 is special flag.
((#\c #\C)
(if (not width) (set! width 1))
(let ((str (make-string width)))
(do ((i 0 (+ 1 i))
(c (peek-char input-port) (peek-char input-port)))
((or (>= i width)
(eof-object? c))
(add-item report-field? (substring str 0 i)))
(string-set! str i (read-input-char)))))
((#\s #\S)
;;(flush-whitespace-input)
(add-item report-field? (read-word width char-whitespace?)))
((#\[)
(set! fc (read-char format-port))
(let ((allbut #f))
(case fc
((#\^) (set! allbut #t)
(set! fc (read-char format-port))))
(let scanloop ((scanset (list fc)))
(set! fc (read-char format-port))
(case fc
((#\-)
(set! fc (peek-char format-port))
(cond
((and (char<? (car scanset) fc)
(not (eqv? #\] fc)))
(set! fc (char->integer fc))
(do ((i (char->integer (car scanset)) (+ 1 i)))
((> i fc) (scanloop scanset))
(set! scanset (cons (integer->char i) scanset))))
(else (scanloop (cons #\- scanset)))))
((#\])
(add-item report-field?
(read-word
width
(if allbut (lambda (c) (memv c scanset))
(lambda (c) (not (memv c scanset)))))))
(else (cond
((eof-object? fc)
(slib:error 'scanf "unmatched [ in format"))
(else (scanloop (cons fc scanset)))))))))
((#\o #\O)
;;(flush-whitespace-input)
(add-item report-field? (read-o)))
((#\u #\U)
;;(flush-whitespace-input)
(add-item report-field? (read-u)))
((#\d #\D)
;;(flush-whitespace-input)
(add-item report-field? (read-signed read-u)))
((#\x #\X)
;;(flush-whitespace-input)
(add-item report-field? (read-x)))
((#\e #\E #\f #\F #\g #\G)
;;(flush-whitespace-input)
(add-item report-field? (read-signed read-ui)))
((#\i)
;;(flush-whitespace-input)
(add-item report-field? (read-signed read-radixed-unsigned)))
((#\%)
(cond ((or width (not report-field?))
(slib:error 'SCANF "%% has modifiers?"))
((eqv? #\% (read-input-char))
(loop1))
(else (return))))
(else (slib:error 'SCANF
"Unknown format directive:" fc)))))
((eqv? (peek-char input-port) fc)
(read-input-char)
(loop1))
(else (return))))
;;(trace flush-whitespace-input flush-whitespace add-item return read-string read-word loop1)
(loop1))))
(args 0)
(else '())))
;;;This implements a Scheme-oriented version of SCANF: returns a list of
;;;objects read (rather than set!-ing values).
(define (scanf-read-list format-string . optarg)
(define input-port
(cond ((null? optarg) (current-input-port))
((not (null? (cdr optarg)))
(slib:error 'scanf-read-list 'wrong-number-of-args optarg))
(else (car optarg))))
(cond ((input-port? input-port)
(stdio:scan-and-set format-string input-port #f))
((string? input-port)
(call-with-input-string
input-port (lambda (input-port)
(stdio:scan-and-set format-string input-port #f))))
(else (slib:error 'scanf-read-list "argument 2 not a port"
input-port))))
(define (stdio:setter-procedure sexp)
(let ((v (gentemp)))
(cond ((symbol? sexp) `(lambda (,v) (set! ,sexp ,v) #t))
((not (and (pair? sexp) (list? sexp)))
(slib:error 'scanf "setter expression not understood" sexp))
(else
(case (car sexp)
((vector-ref) `(lambda (,v) (vector-set! ,@(cdr sexp) ,v) #t))
((substring)
(require 'rev2-procedures)
`(lambda (,v) (substring-move-left!
,v 0 (min (string-length ,v)
(- ,(cadddr sexp) ,(caddr sexp)))
,(cadr sexp) ,(caddr sexp))
#t))
((list-ref)
(require 'rev4-optional-procedures)
`(lambda (,v) (set-car! (list-tail ,@(cdr sexp)) ,v) #t))
((car) `(lambda (,v) (set-car! ,@(cdr sexp) ,v) #t))
((cdr) `(lambda (,v) (set-cdr! ,@(cdr sexp) ,v) #t))
(else (slib:error 'scanf "setter not known" sexp)))))))
(defmacro scanf (format-string . args)
`(stdio:scan-and-set ,format-string (current-input-port)
,@(map stdio:setter-procedure args)))
(defmacro sscanf (str format-string . args)
`(call-with-input-string
,str (lambda (input-port)
(stdio:scan-and-set ,format-string input-port
,@(map stdio:setter-procedure args)))))
(defmacro fscanf (input-port format-string . args)
`(stdio:scan-and-set ,format-string ,input-port
,@(map stdio:setter-procedure args)))