diff --git a/safe-io.rkt b/safe-io.rkt new file mode 100644 index 0000000..e84f34f --- /dev/null +++ b/safe-io.rkt @@ -0,0 +1,26 @@ +#lang racket/base + +(provide read-line-limited) + +;; Uses Internet (CRLF) convention. +(define (read-line-limited port limit) + (let collect-chars ((acc '()) + (remaining limit)) + (let ((ch (read-char port))) + (cond + ((eof-object? ch) (if (null? acc) + ch + (list->string (reverse acc)))) + ((eqv? ch #\return) (let ((ch (read-char port))) + (if (eqv? ch #\linefeed) + (list->string (reverse acc)) + (error 'read-line-limited + "Invalid character ~v after #\\return" + ch)))) + ((eqv? ch #\newline) + ;; Is this a good idea? + (error 'read-line-limited "Bare #\\linefeed encountered")) + ((positive? remaining) (collect-chars (cons ch acc) (- remaining 1))) + (else (error 'read-line-limited + "Line too long (more than ~v bytes before CRLF)" + limit)))))) diff --git a/test-safe-io.rkt b/test-safe-io.rkt new file mode 100644 index 0000000..ab19243 --- /dev/null +++ b/test-safe-io.rkt @@ -0,0 +1,23 @@ +#lang racket/base + +(require "safe-io.rkt") +(require rackunit) + +(define (s str) + (open-input-string str)) + +(check-equal? (read-line-limited (s "") 5) eof) +(check-equal? (read-line-limited (s "abc") 5) "abc") +(check-equal? (read-line-limited (s "abc\r\ndef") 5) "abc") +(check-equal? (read-line-limited (s "abcxy\r\ndef") 5) "abcxy") + +(check-exn #rx"read-line-limited: Invalid character # after #\\\\return" + (lambda () (read-line-limited (s "abc\r") 5))) +(check-exn #rx"read-line-limited: Invalid character #\\\\d after #\\\\return" + (lambda () (read-line-limited (s "abc\rdef") 5))) + +(check-exn #rx"Bare #\\\\linefeed encountered" + (lambda () (read-line-limited (s "abc\ndef") 5))) + +(check-exn #rx"Line too long \\(more than 5 bytes before CRLF\\)" + (lambda () (read-line-limited (s "abcxyz\r\ndef") 5)))