(sigil io)
(sigil io) - Input/Output Operations
Provides file and stream I/O for reading and writing data. Includes file ports, character and byte I/O, string ports for building strings efficiently, and basic file system operations.
I/O operations are async-aware: when running inside with-async, reads from process pipes automatically yield to the scheduler while waiting for data, allowing other tasks to run.
(import (sigil io))
;; Read entire file
(call-with-input-file "data.txt"
(lambda (port)
(read-all port)))
;; Write to file
(call-with-output-file "output.txt"
(lambda (port)
(write-string "Hello, World!" port)))
;; Build string efficiently
(with-output-to-string
(lambda ()
(display "Result: ")
(display 42)))
; => "Result: 42"Exports
port?procedureCheck if a value is a port.
Returns #t for both input and output ports.
(port? (open-input-file "data.txt")) ; => #t
(port? (open-output-string)) ; => #t
(port? "not a port") ; => #finput-port?procedureCheck if a port is an input port.
(input-port? (open-input-file "data.txt")) ; => #t
(input-port? (open-output-file "out.txt")) ; => #foutput-port?procedureCheck if a port is an output port.
(output-port? (open-output-file "out.txt")) ; => #t
(output-port? (open-input-file "data.txt")) ; => #ftextual-port?procedureCheck if a port is a textual (character) port.
Textual ports handle character I/O (read-char, write-string, etc.).
(textual-port? (open-input-file "data.txt")) ; => #t
(textual-port? (open-binary-input-file "data.bin")) ; => #fbinary-port?procedureCheck if a port is a binary port.
Binary ports handle byte I/O (read-u8, write-bytevector, etc.).
(binary-port? (open-binary-input-file "data.bin")) ; => #t
(binary-port? (open-input-file "data.txt")) ; => #fport-open?procedureCheck if a port is still open.
Returns #t if the port has not been closed.
(let ((port (open-input-file "data.txt")))
(port-open? port) ; => #t
(close-input-port port)
(port-open? port)) ; => #fpipe-port?procedureCheck if a port is a pipe port.
Pipe ports are created for process stdin/stdout/stderr. They support async I/O when running in an async context.
(pipe-port? (process-stdout proc)) ; => #t
(pipe-port? (open-input-file "f")) ; => #fasync-port?procedureCheck if a port supports async I/O.
Async ports yield to the scheduler when waiting for data in an async context. Currently includes pipe ports and sockets.
(async-port? (process-stdout proc)) ; => #tport->fdprocedureGet the raw file descriptor from a port.
Returns the underlying OS file descriptor, or #f if not available. Used internally for async I/O.
(port->fd (process-stdout proc)) ; => 5 (or other fd number)
(port->fd (open-input-string "")) ; => #fopen-input-fileprocedureOpen a file for reading text.
Returns an input port. The port should be closed with close-input-port when done, or use call-with-input-file for automatic cleanup.
(let ((port (open-input-file "data.txt")))
(let ((line (read-line port)))
(close-input-port port)
line))open-output-fileprocedureOpen a file for writing text.
Creates the file if it doesn't exist, truncates if it does. The port should be closed with close-output-port when done.
(let ((port (open-output-file "output.txt")))
(write-string "Hello!" port)
(close-output-port port))open-binary-input-fileprocedureOpen a file for reading binary data.
Similar to open-input-file but for binary data. Use with read-u8 and read-bytevector.
open-binary-output-fileprocedureOpen a file for writing binary data.
Similar to open-output-file but for binary data. Use with write-u8 and write-bytevector.
flush-output-portprocedureFlush buffered output to the underlying file.
Forces any buffered data to be written immediately. Useful when you need to ensure data is written before continuing.
(write-string "Progress: 50%" port)
(flush-output-port port) ; ensure it's visible nowport-nameprocedureGet the name associated with a port.
For file ports, returns the filename. For string ports, returns a description like <output-string>.
(port-name (open-input-file "data.txt")) ; => "data.txt"port-positionprocedureGet the current byte position of a port.
For file ports, returns the current file offset. For string and bytevector ports, returns the buffer position.
(let ((port (open-binary-input-file "data.bin")))
(port-position port) ; => 0
(read-bytevector 10 port)
(port-position port)) ; => 10set-port-position!procedureSet the current byte position of a port.
Moves the read/write position to the given byte offset. For file ports, uses fseek. For string and bytevector ports, adjusts the buffer position.
(let ((port (open-binary-input-file "data.bin")))
(set-port-position! port 100)
(read-bytevector 10 port)) ; reads bytes 100-109readprocedureRead one S-expression from a port.
Parses and returns the next complete Scheme value. Returns the end-of-file object when the port is exhausted.
When reading from a pipe port in an async context, yields to the scheduler while waiting for data.
;; File contains: (1 2 3) "hello"
(read port) ; => (1 2 3)
(read port) ; => "hello"
(read port) ; => #<eof>
(eof-object? (read port)) ; => #tread-allprocedureRead all S-expressions from a port into a list.
Reads until end-of-file and returns all values as a list. Useful for reading configuration files or data files.
;; File contains: (define x 1) (define y 2)
(call-with-input-file "config.sgl"
(lambda (port) (read-all port)))
; => ((define x 1) (define y 2))read-charprocedureRead a single character from a port.
Returns the next character, or the end-of-file object if the port is exhausted.
(read-char port) ; => #\H
(read-char port) ; => #\epeek-charprocedureLook at the next character without consuming it.
Returns the next character that would be read, but leaves it in the input stream.
(peek-char port) ; => #\H
(peek-char port) ; => #\H (still there)
(read-char port) ; => #\H (now consumed)write-charprocedureWrite a single character to a port.
(write-char #\A port)
(write-char #\newline port)read-lineprocedureRead a line of text from a port.
Returns all characters up to and not including the newline. Returns the end-of-file object if already at end of input.
When reading from a pipe port in an async context, yields to the scheduler while waiting for data.
;; Read all lines from a file
(let loop ((lines '()))
(let ((line (read-line port)))
(if (eof-object? line)
(reverse lines)
(loop (cons line lines)))))read-stringprocedureRead a specific number of characters from a port.
Returns a string with up to n characters. May return fewer if end-of-file is reached.
(read-string 10 port) ; => "Hello, Wor"write-stringprocedureWrite a string to a port.
Outputs the string's characters. Optional start and end indices write a substring.
(write-string "Hello, World!" port)
(write-string "Hello" port 0 4) ; writes "Hell"read-u8procedureRead a single byte from a binary port.
Returns an integer 0-255, or the end-of-file object.
(read-u8 port) ; => 72 (ASCII 'H')peek-u8procedureLook at the next byte without consuming it.
Returns the byte as an integer 0-255, or the EOF object.
write-u8procedureWrite a single byte to a binary port.
The byte must be an integer 0-255.
(write-u8 72 port) ; writes ASCII 'H'read-bytevectorprocedureRead bytes from a port into a bytevector.
With one argument, reads up to n bytes. With a bytevector argument, fills it and returns the count read.
(read-bytevector 1024 port) ; => #u8(72 101 108 ...)write-bytevectorprocedureWrite a bytevector to a port.
Writes all bytes from the bytevector to the port.
(write-bytevector #u8(72 101 108 108 111) port) ; writes "Hello"call-with-input-fileprocedureCall a procedure with an input port, ensuring cleanup.
Opens the file, passes the port to proc, closes the port, and returns proc's result. The port is closed even if proc raises an error.
(call-with-input-file "data.txt"
(lambda (port)
(read-line port)))call-with-output-fileprocedureCall a procedure with an output port, ensuring cleanup.
Opens the file, passes the port to proc, closes the port, and returns proc's result.
(call-with-output-file "output.txt"
(lambda (port)
(write-string "Hello!" port)))with-input-from-fileprocedureExecute a thunk with a file as input.
Opens the file and executes the thunk. Currently a simplified version that doesn't rebind current-input-port.
with-output-to-fileprocedureExecute a thunk with output going to a file.
Opens the file and executes the thunk. Currently a simplified version that doesn't rebind current-output-port.
open-output-stringprocedureCreate an output port that writes to a string.
Use get-output-string to retrieve the accumulated output. Much more efficient than building strings with repeated string-append calls.
(let ((port (open-output-string)))
(display "Hello, " port)
(display "World!" port)
(get-output-string port))
; => "Hello, World!"open-input-stringprocedureCreate an input port that reads from a string.
Useful for parsing strings as if they were files.
(let ((port (open-input-string "(1 2 3)")))
(read port))
; => (1 2 3)get-output-stringprocedureGet the accumulated string from a string output port.
Can be called multiple times; each call returns all output written so far.
call-with-output-stringprocedureCall a procedure with a string output port.
The procedure receives the port as an argument and can write to it. Returns the accumulated string.
(call-with-output-string
(lambda (port)
(display "x = " port)
(display 42 port)))
; => "x = 42"with-output-to-stringprocedureExecute a thunk with output going to a string.
Within the thunk, display, write, newline, etc. write to the string port. Returns the accumulated string.
This is the preferred way to build strings from multiple pieces, as it avoids the O(n²) cost of repeated string-append.
(with-output-to-string
(lambda ()
(display "Items: ")
(for-each (lambda (x)
(display x)
(display " "))
'(1 2 3))))
; => "Items: 1 2 3 "call-with-input-stringprocedureCall a procedure with a string input port.
The procedure receives the port and can read from it.
(call-with-input-string "hello world"
(lambda (port)
(read-line port)))
; => "hello world"with-input-from-stringprocedureExecute a thunk with input coming from a string.
Within the thunk, read, read-char, etc. read from the string.
(with-input-from-string "(+ 1 2)"
(lambda () (read)))
; => (+ 1 2)open-input-bytevectorprocedureCreate an input port that reads from a bytevector.
(let ((port (open-input-bytevector #u8(72 101 108 108 111))))
(read-u8 port))
; => 72open-output-bytevectorprocedureCreate an output port that collects bytes into a bytevector.
Use get-output-bytevector to retrieve the accumulated bytes.
(let ((port (open-output-bytevector)))
(write-u8 72 port)
(write-u8 105 port)
(get-output-bytevector port))
; => #u8(72 105)get-output-bytevectorprocedureGet the accumulated bytevector from a bytevector output port.
Can be called multiple times; each call returns all bytes written so far.
read-bytevector!procedureRead bytes from a port directly into an existing bytevector.
Fills the bytevector (or a slice specified by start/end) with bytes from the port. Returns the number of bytes actually read.
(let ((bv (make-bytevector 10)))
(read-bytevector! bv port))
; => 10 (number of bytes read)char-ready?procedureCheck if a character is ready to be read from a port.
Returns #t if read-char would return immediately without blocking, or if the port is at end-of-file.
(char-ready? port) ; => #t if data availableu8-ready?procedureCheck if a byte is ready to be read from a binary port.
Returns #t if read-u8 would return immediately without blocking, or if the port is at end-of-file.
(u8-ready? port) ; => #t if data available