diff --git a/eval.c b/eval.c index e6b6ea10..5bb1e465 100644 --- a/eval.c +++ b/eval.c @@ -1058,7 +1058,17 @@ sexp sexp_open_binary_output_file (sexp ctx, sexp self, sexp_sint_t n, sexp path } sexp sexp_close_port_op (sexp ctx, sexp self, sexp_sint_t n, sexp port) { + sexp res = SEXP_VOID; sexp_assert_type(ctx, sexp_portp, SEXP_OPORT, port); + /* we can't run arbitrary scheme code in the finalizer, so we need */ + /* to flush and run the closer here */ + if (sexp_port_customp(port)) { + if (sexp_oportp(port)) res = sexp_flush_output(ctx, port); + if (sexp_exceptionp(res)) return res; + if (sexp_applicablep(sexp_port_closer(port))) + res = sexp_apply1(ctx, sexp_port_closer(port), port); + if (sexp_exceptionp(res)) return res; + } return sexp_finalize_port(ctx, self, n, port); } diff --git a/include/chibi/features.h b/include/chibi/features.h index 7349a370..6e4b5fe4 100644 --- a/include/chibi/features.h +++ b/include/chibi/features.h @@ -179,10 +179,10 @@ /* Making them immutable allows for packed UTF-8 strings. */ /* #define SEXP_USE_MUTABLE_STRINGS 0 */ -/* uncomment this to disable string ports */ -/* If disabled some basic functionality such as number->string */ -/* will not be available by default. */ -/* #define SEXP_USE_STRING_STREAMS 0 */ +/* uncomment this to base string ports on C streams */ +/* This historic option enables string and custom ports backed */ +/* by FILE* objects using memstreams and funopen/fopencookie. */ +/* #define SEXP_USE_STRING_STREAMS 1 */ /* uncomment this to disable automatic closing of ports */ /* If enabled, the underlying FILE* for file ports will be */ @@ -525,11 +525,7 @@ #endif #ifndef SEXP_USE_STRING_STREAMS -#ifdef _WIN32 #define SEXP_USE_STRING_STREAMS 0 -#else -#define SEXP_USE_STRING_STREAMS ! SEXP_USE_NO_FEATURES -#endif #endif #ifndef SEXP_USE_AUTOCLOSE_PORTS @@ -544,6 +540,10 @@ #define SEXP_USE_BIDIRECTIONAL_PORTS ! SEXP_USE_NO_FEATURES #endif +#ifndef SEXP_PORT_BUFFER_SIZE +#define SEXP_PORT_BUFFER_SIZE 4096 +#endif + #ifndef SEXP_USE_2010_EPOCH #define SEXP_USE_2010_EPOCH ! SEXP_USE_NO_FEATURES #endif diff --git a/include/chibi/sexp.h b/include/chibi/sexp.h index f3d9f6b5..929bab38 100755 --- a/include/chibi/sexp.h +++ b/include/chibi/sexp.h @@ -681,6 +681,15 @@ sexp sexp_make_flonum(sexp ctx, double f); #define sexp_stream_portp(x) (sexp_port_stream(x) != NULL) #endif +#define sexp_port_customp(x) (sexp_vectorp(sexp_port_cookie(x)) && sexp_vector_length(sexp_port_cookie(x)) == 6) + +/* only valid on custom ports */ +#define sexp_port_buffer(x) (sexp_vector_ref(sexp_port_cookie(x), SEXP_ONE)) +#define sexp_port_reader(x) (sexp_vector_ref(sexp_port_cookie(x), SEXP_TWO)) +#define sexp_port_writer(x) (sexp_vector_ref(sexp_port_cookie(x), SEXP_THREE)) +#define sexp_port_seeker(x) (sexp_vector_ref(sexp_port_cookie(x), SEXP_FOUR)) +#define sexp_port_closer(x) (sexp_vector_ref(sexp_port_cookie(x), SEXP_FIVE)) + /***************************** constructors ****************************/ #define sexp_make_boolean(x) ((x) ? SEXP_TRUE : SEXP_FALSE) diff --git a/lib/chibi/io.sld b/lib/chibi/io.sld index ab53e3c2..80d0bd48 100644 --- a/lib/chibi/io.sld +++ b/lib/chibi/io.sld @@ -5,7 +5,8 @@ port->list port->string-list port->sexp-list port->string file-position set-file-position! seek/set seek/cur seek/end make-custom-input-port make-custom-output-port - make-null-output-port make-broadcast-port make-concatenated-port + make-null-output-port make-null-input-port + make-broadcast-port make-concatenated-port make-generated-input-port make-filtered-output-port make-filtered-input-port string-count open-input-bytevector open-output-bytevector get-output-bytevector diff --git a/lib/chibi/io/io.scm b/lib/chibi/io/io.scm index 775d6180..12f84a7f 100644 --- a/lib/chibi/io/io.scm +++ b/lib/chibi/io/io.scm @@ -1,5 +1,5 @@ ;; io.scm -- various input/output utilities -;; Copyright (c) 2010-2011 Alex Shinn. All rights reserved. +;; Copyright (c) 2010-2012 Alex Shinn. All rights reserved. ;; BSD-style license: http://synthcode.com/license.txt ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -37,7 +37,7 @@ ((not string-streams) (define (write-string str n . o) (let ((out (if (pair? o) (car o) (current-output-port)))) - (display (substring str 0 n out)))))) + (display (substring str 0 n) out))))) ;;> @subsubsubsection{(read-line [in [n]])} @@ -90,7 +90,7 @@ (let ((out (open-output-string))) (do ((i 0 (+ i 1)) (ch (read-char in) (read-char in))) - ((or (= i n) (eof-object? ch)) (get-output-string out)) + ((or (= i n) (eof-object? ch)) (list i (get-output-string out))) (write-char ch out)))))) (define (read-string n . o) @@ -183,76 +183,84 @@ (%make-custom-output-port write seek close))) (define (make-null-output-port) - (make-custom-output-port (lambda (str n) 0))) + (make-custom-output-port (lambda (str start end) 0))) (define (make-broadcast-port . ports) (make-custom-output-port - (lambda (str n) - (for-each (lambda (p) (write-string str n p)) ports) - n))) + (lambda (str start end) + (let ((str (if (zero? start) str (substring str start))) + (n (- end start))) + (for-each (lambda (p) (write-string str n p)) ports) + n)))) (define (make-filtered-output-port filter out) (make-custom-output-port - (lambda (str n) + (lambda (str start end) (let* ((len (string-length str)) - (s1 (if (= n len) str (substring str 0 n))) + (s1 (if (and (zero? start) (= end len)) str (substring str start end))) (s2 (filter s1))) (if (string? s2) (write-string s2 (string-length s2) out)))))) (define (make-concatenated-port . ports) (make-custom-input-port - (lambda (str n) + (lambda (str start end) (if (null? ports) 0 - (let lp ((i (read-string! str n (car ports)))) - (cond - ((>= i n) - i) - (else - (set! ports (cdr ports)) + (let ((str (if (zero? start) str (substring str start))) + (n (- end start))) + (let lp ((i (read-string! str n (car ports)))) (cond - ((null? ports) + ((>= i n) i) (else - (let* ((s (read-string (- n i) (car ports))) - (len (if (string? s) (string-length s) 0))) - (if (and (string? str) (> len 0)) - (string-copy! str i s 0 len)) - (lp (+ i len)))))))))))) + (set! ports (cdr ports)) + (cond + ((null? ports) + i) + (else + (let* ((s (read-string (- n i) (car ports))) + (len (if (string? s) (string-length s) 0))) + (if (and (string? str) (> len 0)) + (string-copy! str i s 0 len)) + (lp (+ i len))))))))))))) + +(define (make-null-input-port) + (make-concatenated-port)) (define (make-generated-input-port generator) (let ((buf "") (len 0) (offset 0)) (make-custom-input-port - (lambda (str n) - (cond - ((>= (- len offset) n) - (string-copy! str 0 buf offset (+ offset n)) - (set! offset (+ offset n)) - n) - (else - (string-copy! str 0 buf offset len) - (let lp ((i (- len offset))) - (set! buf (generator)) - (cond - ((not (string? buf)) - (set! buf "") - (set! len 0) - (set! offset 0) - (- n i)) - (else - (set! len (string-length buf)) - (set! offset 0) + (lambda (str start end) + (let ((n (- end start))) + (cond + ((>= (- len offset) n) + (string-copy! str start buf offset (+ offset n)) + (set! offset (+ offset n)) + n) + (else + (string-copy! str start buf offset len) + (let lp ((i (+ start (- len offset)))) + (set! buf (generator)) (cond - ((>= (- len offset) (- n i)) - (string-copy! str i buf offset (+ offset (- n i))) - (set! offset (+ offset (- n i))) - n) + ((not (string? buf)) + (set! buf "") + (set! len 0) + (set! offset 0) + (- i start)) (else - (string-copy! str i buf offset len) - (lp (+ i (- len offset)))))))))))))) + (set! len (string-length buf)) + (set! offset 0) + (cond + ((>= (- len offset) (- n i)) + (string-copy! str i buf offset (+ offset (- n i))) + (set! offset (+ offset (- n i))) + n) + (else + (string-copy! str i buf offset len) + (lp (+ i (- len offset))))))))))))))) (define (make-filtered-input-port filter in) (make-generated-input-port diff --git a/lib/chibi/io/io.stub b/lib/chibi/io/io.stub index f26040d5..c1b962e3 100644 --- a/lib/chibi/io/io.stub +++ b/lib/chibi/io/io.stub @@ -9,7 +9,6 @@ (define-c size_t (%read-string! "fread") (string (value 1 size_t) size_t (default (current-input-port) input-port))) - )) (define-c-const int (seek/set "SEEK_SET")) diff --git a/lib/chibi/io/port.c b/lib/chibi/io/port.c index 981f633b..049f1ec6 100644 --- a/lib/chibi/io/port.c +++ b/lib/chibi/io/port.c @@ -2,7 +2,6 @@ #include #include -#define SEXP_PORT_BUFFER_SIZE 1024 #define SEXP_LAST_CONTEXT_CHECK_LIMIT 256 #define sexp_cookie_ctx(vec) sexp_vector_ref((sexp)vec, SEXP_ZERO) @@ -62,7 +61,8 @@ static ssize_t sexp_cookie_reader (void *cookie, char *buffer, size_t size) sexp_gc_preserve2(ctx, ctx2, args); if (size > sexp_string_length(sexp_cookie_buffer(vec))) sexp_cookie_buffer_set(vec, sexp_make_string(ctx, sexp_make_fixnum(size), SEXP_VOID)); - args = sexp_list2(ctx, sexp_cookie_buffer(vec), sexp_make_fixnum(size)); + args = sexp_list2(ctx, SEXP_ZERO, sexp_make_fixnum(size)); + args = sexp_cons(ctx, sexp_cookie_buffer(vec), args); res = sexp_apply(ctx, sexp_cookie_read(vec), args); sexp_gc_release2(ctx); if (sexp_fixnump(res)) { @@ -88,7 +88,8 @@ static ssize_t sexp_cookie_writer (void *cookie, const char *buffer, size_t size if (size > sexp_string_length(sexp_cookie_buffer(vec))) sexp_cookie_buffer_set(vec, sexp_make_string(ctx, sexp_make_fixnum(size), SEXP_VOID)); memcpy(sexp_string_data(sexp_cookie_buffer(vec)), buffer, size); - args = sexp_list2(ctx, sexp_cookie_buffer(vec), sexp_make_fixnum(size)); + args = sexp_list2(ctx, SEXP_ZERO, sexp_make_fixnum(size)); + args = sexp_cons(ctx, sexp_cookie_buffer(vec), args); res = sexp_apply(ctx, sexp_cookie_write(vec), args); sexp_gc_release2(ctx); return (sexp_fixnump(res) ? sexp_unbox_fixnum(res) : -1); @@ -189,21 +190,47 @@ static sexp sexp_make_custom_port (sexp ctx, sexp self, char *mode, static sexp sexp_make_custom_port (sexp ctx, sexp self, char *mode, sexp read, sexp write, sexp seek, sexp close) { - return sexp_user_exception(ctx, self, "custom ports not supported in this configuration", SEXP_NULL); + sexp vec; + sexp_gc_var2(res, str); + sexp_gc_preserve2(ctx, res, str); + str = sexp_make_string(ctx, sexp_make_fixnum(SEXP_PORT_BUFFER_SIZE), SEXP_VOID); + if (sexp_exceptionp(str)) return str; + res = sexp_make_input_string_port(ctx, str); + if (sexp_exceptionp(res)) return res; + if (mode && mode[0] == 'w') { + sexp_pointer_tag(res) = SEXP_OPORT; + sexp_port_cookie(res) = str; + } else { + sexp_port_offset(res) = 0; + sexp_port_size(res) = 0; + } + vec = sexp_make_vector(ctx, SEXP_SIX, SEXP_VOID); + if (sexp_exceptionp(vec)) return vec; + sexp_vector_set(vec, SEXP_ZERO, SEXP_FALSE); + sexp_vector_set(vec, SEXP_ONE, sexp_port_cookie(res)); + sexp_vector_set(vec, SEXP_TWO, read); + sexp_vector_set(vec, SEXP_THREE, write); + sexp_vector_set(vec, SEXP_FOUR, seek); + sexp_vector_set(vec, SEXP_FIVE, close); + sexp_port_cookie(res) = vec; + sexp_gc_release2(ctx); + return res; } #endif -static sexp sexp_make_custom_input_port (sexp ctx, sexp self, - sexp read, sexp seek, sexp close) { +sexp sexp_make_custom_input_port (sexp ctx, sexp self, + sexp read, sexp seek, sexp close) { return sexp_make_custom_port(ctx, self, "r", read, SEXP_FALSE, seek, close); } -static sexp sexp_make_custom_output_port (sexp ctx, sexp self, - sexp write, sexp seek, sexp close) { +sexp sexp_make_custom_output_port (sexp ctx, sexp self, + sexp write, sexp seek, sexp close) { sexp res = sexp_make_custom_port(ctx, self, "w", SEXP_FALSE, write, seek, close); +#if SEXP_USE_STRING_STREAMS if (!sexp_exceptionp(res)) sexp_pointer_tag(res) = SEXP_OPORT; +#endif return res; } diff --git a/sexp.c b/sexp.c index 7958edd0..823eb105 100644 --- a/sexp.c +++ b/sexp.c @@ -124,7 +124,11 @@ sexp sexp_finalize_port (sexp ctx, sexp self, sexp_sint_t n, sexp port) { #if !SEXP_USE_STRING_STREAMS || (sexp_iportp(port) && sexp_truep(sexp_port_cookie(port))) #endif - )) + ) +#if SEXP_USE_STRING_STREAMS + && !sexp_port_customp(port) +#endif + ) free(sexp_port_buf(port)); } #ifndef PLAN9 @@ -1354,9 +1358,8 @@ sexp sexp_open_output_file_descriptor (sexp ctx, sexp self, sexp_sint_t n, sexp #else /* ! SEXP_USE_STRING_STREAMS */ -#define SEXP_PORT_BUFFER_SIZE 4096 - int sexp_buffered_read_char (sexp ctx, sexp p) { + sexp_gc_var1(tmp); int res = 0; if (sexp_port_offset(p) < sexp_port_size(p)) { return sexp_port_buf(p)[sexp_port_offset(p)++]; @@ -1376,6 +1379,21 @@ int sexp_buffered_read_char (sexp ctx, sexp p) { res = ((sexp_port_offset(p) < sexp_port_size(p)) ? sexp_port_buf(p)[sexp_port_offset(p)++] : EOF); } + } else if (sexp_port_customp(p)) { + sexp_gc_preserve1(ctx, tmp); + tmp = sexp_list2(ctx, SEXP_ZERO, sexp_make_fixnum(SEXP_PORT_BUFFER_SIZE)); + tmp = sexp_cons(ctx, sexp_port_buffer(p), tmp); + tmp = sexp_apply(ctx, sexp_port_reader(p), tmp); + if (sexp_fixnump(tmp) && sexp_unbox_fixnum(tmp) > 0) { + sexp_port_offset(p) = 0; + sexp_port_size(p) = sexp_unbox_fixnum(tmp); + res = ((sexp_port_offset(p) < sexp_port_size(p)) + ? sexp_port_buf(p)[sexp_port_offset(p)++] : EOF); + } else { + res = EOF; + sexp_port_size(p) = 0; + } + sexp_gc_release1(ctx); } else { res = EOF; } @@ -1434,15 +1452,22 @@ int sexp_buffered_flush (sexp ctx, sexp p) { sexp_port_offset(p) = 0; res = 0; } - } else if (sexp_port_offset(p) > 0) { /* string port */ + } else if (sexp_port_offset(p) > 0) { sexp_gc_preserve1(ctx, tmp); - tmp = sexp_c_string(ctx, sexp_port_buf(p), off); - if (tmp && sexp_stringp(tmp)) { - sexp_push(ctx, sexp_port_cookie(p), tmp); - sexp_port_offset(p) = 0; - res = 0; - } else { - res = -1; + if (sexp_port_customp(p)) { /* custom port */ + tmp = sexp_list2(ctx, SEXP_ZERO, sexp_make_fixnum(sexp_port_offset(p))); + tmp = sexp_cons(ctx, sexp_port_buffer(p), tmp); + tmp = sexp_apply(ctx, sexp_port_writer(p), tmp); + res = (sexp_fixnump(tmp) && sexp_unbox_fixnum(tmp) > 0) ? 0 : -1; + } else { /* string port */ + tmp = sexp_c_string(ctx, sexp_port_buf(p), off); + if (tmp && sexp_stringp(tmp)) { + sexp_push(ctx, sexp_port_cookie(p), tmp); + sexp_port_offset(p) = 0; + res = 0; + } else { + res = -1; + } } sexp_gc_release1(ctx); } diff --git a/tests/io-tests.scm b/tests/io-tests.scm index 1fd653d7..134fac16 100644 --- a/tests/io-tests.scm +++ b/tests/io-tests.scm @@ -23,40 +23,34 @@ (call-with-input-string "abc\ndef" (lambda (in) (let ((line (read-line in))) (list line (read-line in)))))) -;; Custom ports are only supported with string streams (i.e. either -;; GNU fopencookie or BSD funopen). +(test "null-output-port" #t + (let ((out (make-null-output-port))) + (write 1 out) + (close-output-port out) + #t)) -(cond-expand - (string-streams +(test "null-input-port" #t + (let ((in (make-null-input-port))) + (let ((res (eof-object? (read-char in)))) + (close-input-port in) + res))) - (test "null-output-port" #t - (let ((out (make-null-output-port))) - (write 1 out) - (close-output-port out) - #t)) +(define (string-upcase str) + (list->string (map char-upcase (string->list str)))) - (test "null-input-port" #t - (let ((in (make-concatenated-port))) - (let ((res (eof-object? (read-char in)))) - (close-input-port in) - res))) +(test "upcase-input-port" "ABC" + (call-with-input-string "abc" + (lambda (in) + (let ((in (make-filtered-input-port string-upcase in))) + (let ((res (read-line in))) + (close-input-port in) + res))))) - (define (string-upcase str) - (list->string (map char-upcase (string->list str)))) - - (test "upcase-input-port" "ABC" - (call-with-input-string "abc" - (lambda (in) - (let ((in (make-filtered-input-port string-upcase in))) - (let ((res (read-line in))) - (close-input-port in) - res))))) - - (test "upcase-output-port" "ABC" - (call-with-output-string - (lambda (out) - (let ((out (make-filtered-output-port string-upcase out))) - (display "abc" out) - (close-output-port out))))))) +(test "upcase-output-port" "ABC" + (call-with-output-string + (lambda (out) + (let ((out (make-filtered-output-port string-upcase out))) + (display "abc" out) + (close-output-port out))))) (test-end)