sigildocs

Chapter 7: I/O

This chapter covers reading from and writing to files and the console.

Console I/O

Output

For most output, use println with format strings:

(println "Hello, ~a!" name)       ; Format and print with newline
(println "Got ~a items" (length items))
(print "Enter name: ")            ; Print without newline (for prompts)

The low-level primitives display, write, and newline are also available:

(display "Hello")        ; Print without quotes or newline
(write "Hello")          ; Print with quotes: "Hello" (for data)
(write '(1 2 3))         ; Print as data: (1 2 3)
(newline)                ; Print a newline

display is for human-readable output. write produces output that can be read back by read.

Input

(define input (read))    ; Read a Scheme expression
(define line (read-line)) ; Read a line as string
(define char (read-char)) ; Read a single character

Example interactive program:

(print "What is your name? ")
(define name (read-line))
(println "Hello, ~a!" name)

Detecting End of Input

(let ((input (read)))
  (if (eof-object? input)
      (println "End of input")
      (println "You entered: ~s" input)))  ; ~s uses write format

File I/O

Import the I/O library:

(import (sigil io))

Opening Files

;; Text files
(define port (open-input-file "data.txt"))
(define out (open-output-file "output.txt"))

;; Binary files
(define bin-in (open-binary-input-file "image.png"))
(define bin-out (open-binary-output-file "copy.png"))

Reading Files

;; Read one line
(define line (read-line port))

;; Read all lines
(let loop ()
  (let ((line (read-line port)))
    (unless (eof-object? line)
      (process-line line)
      (loop))))

;; Read Scheme expressions
(define expr (read port))

;; Read all expressions
(define exprs (read-all port))

Writing Files

(display "Hello" port)
(write '(data to save) port)
(newline port)
(flush-output-port port)  ; Ensure data is written

Closing Files

(close-input-port port)
(close-output-port out)

Safe File Handling

Use call-with-input-file to ensure files are closed:

(call-with-input-file "data.txt"
  (lambda (port)
    (let loop ((lines '()))
      (let ((line (read-line port)))
        (if (eof-object? line)
            (reverse lines)
            (loop (cons line lines)))))))

Similarly for output:

(call-with-output-file "output.txt"
  (lambda (port)
    (display "Line 1" port)
    (newline port)
    (display "Line 2" port)
    (newline port)))

Practical Example: Config File

Read a simple key=value config file:

(import (sigil io)
        (sigil string))

(define (read-config filename)
  (call-with-input-file filename
    (lambda (port)
      (let loop ((config '()))
        (let ((line (read-line port)))
          (if (eof-object? line)
              config
              (let ((parts (string-split line "=")))
                (if (= (length parts) 2)
                    (loop (cons (cons (string-trim (car parts))
                                      (string-trim (cadr parts)))
                                config))
                    (loop config)))))))))

;; Usage:
;; (define config (read-config "app.conf"))
;; (assoc "username" config)

Filesystem Operations

Import the filesystem library:

(import (sigil fs))

Checking Files

(file-exists? "data.txt")   ; => #t or #f
(file? "data.txt")          ; Is it a regular file?
(directory? "mydir")        ; Is it a directory?

Directory Operations

(make-directory "newdir")
(directory-list ".")     ; List files in directory
(current-directory)         ; Get current directory
(set-current-directory! "/tmp")

File Information

(file-size "data.txt")      ; Size in bytes
(file-mtime "data.txt")     ; Modification time

Walking Directories

(directory-walk "src"
  (lambda (path type)
    (println "~a (~a)" path type)
    #t))  ; Return #t to descend into directories

Path Manipulation

Import the path library:

(import (sigil path))

Path Components

> (import (sigil path))
> (path-dirname "/home/user/file.txt")
"/home/user"
> (path-basename "/home/user/file.txt")
"file.txt"
> (path-extname "/home/user/file.txt")
".txt"

Building Paths

> (path-join "home" "user" "file.txt")
"home/user/file.txt"
> (path-normalize "foo/bar/../baz")
"foo/baz"

Path Predicates

> (path-absolute? "/home/user")
#t
> (path-absolute? "relative")
#f

Complete Example: File Processor

A program that processes all .txt files in a directory:

(import (sigil io)
        (sigil fs)
        (sigil path)
        (sigil string))

(define (process-text-file path)
  (println "Processing: ~a" path)
  (let ((lines (call-with-input-file path
                 (lambda (port)
                   (let loop ((lines '()))
                     (let ((line (read-line port)))
                       (if (eof-object? line)
                           (reverse lines)
                           (loop (cons line lines)))))))))
    (println "  Lines: ~a" (length lines))))

(define (process-directory dir)
  (directory-walk dir
    (lambda (path type)
      (when (and (eq? type 'file)
                 (string=? (path-extname path) ".txt"))
        (process-text-file path))
      #t)))

(process-directory ".")

Practice Exercises

  1. Write a program that counts words in a text file.
  2. Create a simple file backup utility.
  3. Write a program that finds all files larger than a given size.

What's Next

Let's learn about macros — code that writes code.

Next: Macros →