t3x.org / sketchy / sk04.html
SketchyLISP
Reference
  Copyright (C) 2007
Nils M Holm

4 Primitive Procedures

4.1 Introduction

This chapter summarizes the primitive procedures of SketchyLISP. Another large part of SketchyLISP consists of user-level procedures, which are described in a later chapter.

Primitive procedures are procedures that are not implemented using lambda expressions for a variety of reasons. The decision whether a procedure is primitive or not is eventually part of the implementation strategy. Other implementations may chose other sets of primitives.

SketchyLISP primitives perform simple task (like composing or decomposing pairs) as well as potentially computation-intensive tasks (such as adding big numbers or appending strings).

At the beginning of each procedure description a generalized sample application is given. This application lists the types that the procedure expects by giving examples:

(car '(x . y)) => x

Unless such an example uses a variable as argument, it is an error to apply the procedure to a type other than that specified in the example:

(car 'non-pair) => bottom

When a variable is used as an argument, the procedure accepts any type:

(pair? x) => {#t,#f}

The symbol bottom is used to denote an undefined value. A value is undefined, if it does not have a unique normal form. This does not imply that a reduction that results in bottom terminates with an error. It rather says that the result of the reduction should be considered invalid.

As an example, consider the expression

(eq? 1 1)

which may reduce to #t or #f, since the identity of numbers is arguable. Because the result is not predictable, it must be considered undefined:

(eq? 1 1) => bottom

4.2 Bindings and Definitions

4.2.1 bottom

(bottom ...) => bottom

Applications of bottom evaluate to an undefined result. Any expression that contains a subexpression evaluating to (bottom) evaluates itself to (bottom). The bottom procedure may have any number of arguments.

(bottom)           => bottom
(bottom 'x 'y 'z)  => bottom
(eq? (bottom) ())  => bottom

The bottom procedure is a SketchyLISP Extension.

4.2.2 gensm

(gensym [symbol]) => symbol

Gensym returns a unique symbol each time it is applied. The symbol is guaranteed not to have occurred anywhere in the calling program. From this follows that gensym returns different symbols when it is called multiple times:

(gensym) => gensym1
(gensym) => gensym2
(gensym) => gensym3

When a symbol is passed to gensym, this symbol will be a prefix of the new symbol:

(gensym 'foo) => foo4
(gensym 'x)   => x5

When no prefix is given, gensym is used.

The gensym procedure is a SketchyLISP Extension.

4.2.3 load

(load "file") => #<void>

Loads loads a file by reading its definitions and reducing them to their normal forms. Normal forms are not echoed.

If the file name passed to load begins with a dot (.) or a slash (/) or a tilde (~), load simply attempts to open that file. Otherwise the following locations are tried:

The SKETCHYSRC environment variable is a colon-separated list of paths. Load tries the given file name (with and without the .scm suffix) in all directories specified in this variable before giving up.

To see which file load actually loads, use the :load meta command.

If a file that is being loaded by load loads other files, the files loaded by nested applications will be searched in the directory of the original file first. Here is an example:

(load "lib/foo.scm")

will load the file foo.scm from the directory lib, so all files loaded by foo.scm will be searched inside of lib first. Hence the command

(load "bar.scm")

inside of lib/foo.scm will load lib/bar.scm if that file exists. Only if lib/bar.scm does not exist, other locations will be tried.

Load resolves path names beginning with a ~/ prefix by replacing the ~ of that prefix with the value of the HOME environment variable.

The load procedure conforms to R5RS with one extension:

4.2.4 package

(package ['symbol]) => symbol

The package procedure is used to protect global symbols from re-definition. The symbols to be protected (which are typically created using define) are enclosed in two applications of package. The first application creates a new package which is to contain the protected symbols. The second one closes the package. An opening application names the package using its argument. Closing applications have no arguments.

When multiple packages define equal symbols, the currently open package is always searched first. The order of searching the other packages is unspecified. When no user-defined package is open, an initial package called the default package is open.

The following program demonstrates the use of package:

(define foo #t)         ; protect package
(package 'foo)          ; create and/or open package foo
(define bar 'baz)
(define (f) bar)
(package)               ; close package foo

(f) => baz
(define bar 'goo)       ; re-define bar
bar => goo              ; bar is re-defined as expected
(f) => baz              ; f of foo still returns bar of foo

Caveats

Packages break the identity of symbols:

(package 'p)
(define (f) 'unique-name)
(package)
(define unique-name #t)
(f) => 'unique-name
(eq? 'unique-name (f)) => #f

There is normally no need to use package in user-level code. It was added to protect some internal procedures of the SketchyLISP implementation from re-definition.

The package procedure is a SketchyLISP Extension.

4.2.5 recursive-bind

(recursive-bind env) => env

The recursive-bind procedure is used by the letrec syntax to fix references to recursive procedures in local environments.

The environment passed to recursive-bind is an association list (a list of pairs) with procedure names as keys (car parts) and procedures as values (cdr parts):

((f . #<procedure () (f) ((f . #<void>))>))

The above association list associates a recursive zero-argument procedure with the symbol f. Such an association is typically created by the let syntax. Because let binds the procedure to its name after closing over the name, the procedure cannot recurse:

(f) => bottom

Any application of f in f results in an application of a non-procedure.

To make the recursive procedure f work, the binding of f in the lexical environment of f must be bound to the value of f in the outer environment - the environment passed to recursive-bind. In other words: the local definition of f must bind to f itself. This is exactly what recursive-bind achieves:

(recursive-bind '((f . #<procedure () (f) ((f . #<void>))>)))
=> ((f . #<procedure () (f)
           ((f . #<procedure () (f)
                   ((f . ...))>))>))

Recursive-bind resolves mutually recursive defintions as well.

Notes

The structure created by recursive-bind is potentially self-referential and hence infinite. Do not try to print it.

The creation of recursive bindings is normally done using letrec. Recursive-bind is a conceptual procedure that serves no other purpose than facilitating the implementation of metacircular interpreters.

The recursive-bind procedure is a SketchyLISP Extension.

4.2.6 require

(require "file") => {#t,#f}

Require loads a package (using load) that is required by the program or package following in the input stream. It reads definitions from the given file and reduces them to their normal forms. Normal forms are not echoed.

Require reads the package only if it has not been required before. It returns #t after actually reading a package and #f when the package already was present.

To determine whether a package already has been required, require tests whether the basename of the given file is a symbol that is bound to a value. The basename is the name of the given file with its directory and suffix removed:

(require "src/unlet.scm") ; "unlet" is the basename

The require procedure is a SketchyLISP Extension.

4.2.7 void

(void) => #<void>

The void procedure evaluates to an unspecific value. By binding a symbol to (void), the binding of that symbols becomes unspecific. That is, the symbol becomes unbound:

(define x 'foo)
x => foo
(define x (void))
x => bottom

The void procedure is a SketchyLISP Extension.

4.3 Control

4.3.1 apply

(apply fun a1 ... an list) => eval[(fun a1 ... an . list)]

Apply applies the procedure fun to the arguments contained in the list list. The additional arguments a1...an are optional. All arguments are passed to apply call-by-value, but fun is applied to the arguments in list using call-by-name. The number of arguments of fun (plus n, if additional arguments are given) must match the number of members of list. For example,

(apply cons '(a b))          => (a . b)
(apply (lambda () 'foo) '()) => foo
(apply cons '(a))            => bottom

If any additional arguments a1...an are specified, these are consed to the argument list list before applying fun to it:

(apply cons 1 '(2))       => (1 . 2)
(apply list 1 2 3 '(4 5)) => (1 2 3 4 5)

The apply procedure conforms to R5RS.

4.4 Composition and Decomposition

4.4.1 car

(car '(x . y)) => x

(Car x) evaluates to the car part of x. X must be a pair. The following rules apply:

(car '(x . y))  => x
(car '(x y))    => x
(car '(x))      => x
(car ())        => bottom
(car 'non-pair) => bottom

The car procedure conforms to R5RS.

4.4.2 cdr

(cdr '(x . y)) => y

(Cdr x) evaluates to the cdr part of x. X must be a pair. The following rules apply:

(cdr '(x . y))  => y
(cdr '(x y))    => (y)
(cdr '(x))      => ()
(cdr ())        => bottom
(cdr 'non-pair) => bottom

The cdr procedure conforms to R5RS.

4.4.3 cons

(cons 'x 'y) => (x . y)

Cons creates a new pair from two given forms. Its first argument x forms the car part of the new pair and its second argument y forms its cdr part. The following rules apply:

(cons 'x 'y)        => (x . y)
(cons 'x ())        => (x)
(cons 'x '(y . ())) => (x y)
(cons 'x '(y))      => (x y)
(cons 'x '(y . z))  => (x y . z)

The cons procedure conforms to R5RS.

4.5 Predicates

4.5.1 char?

(char? x) => {#t,#f}

(Char? x) evaluates to #t if x is a char and otherwise to #f. The following rules apply:

(char? #\x)       => #t
(char? #\space)   => #t
(char? #\\)       => #t
(char? 'non-char) => #f

The char? procedure conforms to R5RS.

4.5.2 eq?

(eq? x y) => {#t,#f}

(Eq? x y) evaluates to #t if x and y are identical and otherwise to #f. Two forms are identical, if, and only if they are the same symbol or they are both the same boolean or they are both empty lists. All other forms may be different, even if they look equal. Forms bound to the same symbol are always identical, so

(eq? a a) => #t ; unless A is not bound

for any value of a. The following rules apply:

(eq? x x)           => #t
(eq? 'x 'x)         => #t
(eq? 'x 'y)         => #f
(eq? () ())         => #t
(eq? 'x '(x . y))   => #f
(eq? #f #f)         => #t
(eq? '(x y) '(x y)) => bottom
(eq? 123 123)       => bottom
(eq? #\x #\x)       => bottom
(eq? "foo" "foo")   => bottom

Note especially that:

(eq? (cons x y) (cons x y)) => #f

for any values of x and y.

The eq? procedure conforms to R5RS.

4.5.3 null?

(null? x) => {#t,#f}

(Null? x) evaluates to #t if x is equal to () and otherwise to #f.

The null? procedure conforms to R5RS.

4.5.4 number?

(number? x) => {#t,#f}

(Number? x) evaluates to #t if x is a number and otherwise to #f. The following rules apply:

(number? 123)          => #t
(number? -123)         => #t
(number? +123)         => #t
(number? 'non-integer) => #f

The number? procedure conforms to R5RS.

4.5.5 pair?

(pair? x) => {#t,#f}

Applications of pair? evaluate to #t if x is a pair and otherwise to #f. The following rules apply:

(pair? '(x . y))  => #t
(pair? '(x y))    => #t
(pair? ())        => #f
(pair? 'non-pair) => #f

The pair? procedure conforms to R5RS.

4.5.6 procedure?

(procedure? x) => {#t,#f}

(Procedure? x) evaluates to #t if x is a procedure and otherwise to #f. The following objects are procedures:

The following rules apply:

(procedure? cons)       => #t
(procedure? procedure?) => #t
(procedure? lambda)     => #f
(procedure? (lambda (x) x))           => #t
(procedure? 'non-procedure)           => #f

The procedure? procedure conforms to R5RS.

4.5.7 string?

(string? x) => {#t,#f}

(String? x) evaluates to #t if x is a string and otherwise to #f. The following rules apply:

(string? "some text") => #t
(string? "")          => #t
(string? 'non-string) => #f

The string? procedure conforms to R5RS.

4.5.8 symbol?

(symbol? x) => {#t,#f}

(Symbol? x) evaluates to #t if x is a symbol and otherwise to #f. The following rules apply:

(symbol? 'foo)         => #t
(symbol? 'symbol?)     => #t
(symbol? symbol?)      => #f
(symbol? "non-symbol") => #f

The symbol? procedure conforms to R5RS.

4.6 Type Conversion

4.6.1 char->integer

(char->integer #\c) => integer

The char->integer procedure evaluates to an integer whose value is equal to the ASCII code of its argument. The following rules apply:

(char->integer #\a)       => 97
(char->integer #\A)       => 65
(char->integer #\space)   => 32
(char->integer #\\)       => 92
(char->integer 'non-char) => bottom

The char->integer procedure conforms to R5RS.

4.6.2 integer->char

(integer->char 123) => char

The integer->char procedure evaluates to a char whose ASCII code is equal to its argument. The argument must be in the range 0..127. The following rules apply:

(integer->char 97)           => #\a
(integer->char 65)           => #\A
(integer->char 32)           => #\space
(integer->char 92)           => #\\
(integer->char 'non-integer) => bottom
(integer->char -1)           => bottom
(integer->char 128)          => bottom

The integer->char procedure conforms to R5RS.

4.6.3 integer->list

(integer->list 123) => list

The integer->list procedure evaluates to a list containing the digits and, if one exists, the prefix of the given integer. Digits are represented by the symbols 0d through 9d. The following rules apply:

(integer->list 0)            => (0d)
(integer->list 123)          => (1d 2d 3d)
(integer->list -5)           => (- 5d)
(integer->list +7)           => (+ 7d)
(integer->list 'non-integer) => bottom

The integer->list procedure is a SketchyLISP Extension.

4.6.4 list->integer

(list->integer list) => integer

The list->integer procedure evaluates to an integer composed of the digits and the prefix (if any) contained in the given list. The list must contain a positive number of digits, and it may contain a sign of the form + or - at its first position. Digits are represented by the symbols 0d through 9d. The following rules apply:

(list->integer '(0d))        => 0
(list->integer '(1d 2d 3d))  => 123
(list->integer '(- 7d))      => -7
(list->integer '(+ 5d))      => +5
(list->integer '(non-digit)) => bottom
(list->integer '())          => bottom
(list->integer '(-))         => bottom
(list->integer '(+ + 1d))    => bottom
(list->integer 'non-list)    => bottom

The list->integer procedure is a SketchyLISP Extension.

4.6.5 list->string

(list->string list) => string

The list->string procedure evaluates to a string that is composed of the characters contained in the list passed to it. The list must contain objects of the type char exclusively. The following rules apply:

(list->string '(#\X))             => "X"
(list->string '(#\t #\e #\x #\t)) => "text"
(list->string '())                => ""
(list->string '(#\"))             => "\""
(list->string '(non-char))        => bottom
(list->string 'non-list)          => bottom

The list->string procedure conforms to R5RS.

4.6.6 string->list

(string->list "xyz") => list

The string->list procedure evaluates to a list containing the same characters as the string passed to it. Each character of the string will be represented by an individual char in the resulting list. The following rules apply:

(string->list "X")         => (#\X)
(string->list "text")      => (#\t #\e #\x #\t)
(string->list "")          => ()
(string->list "\"")        => (#\")
(string->list 'non-string) => bottom

The string->list procedure conforms to R5RS.

4.6.7 string->symbol

(string->symbol "xyz") => xyz

The string->symbol procedure evaluates to a symbol that is composed of the characters of the given string argument. The following rules apply:

(string->symbol "foo")       => foo
(string->symbol "FOO")       => FOO
(string->symbol " ")         => #<unreadable symbol>
(string->symbol "")          => bottom
(string->symbol 'non-string) => bottom

Notes

(1) String->symbol may be used to create symbols that cannot be accessed by programs, such as symbols containing white space, symbols containing upper case letters, and symbols containing special characters like parentheses, dots, semicolons, etc.

(2) Symbols containing spaces lead to an ambiguity, as demonstrated below. Therefore future implementation of SketchyLISP may refuse to create such symbols.

(string->symbol "foo bar")                  => foo bar
(symbol->string (string->symbol "foo bar")) => "foo bar"
(symbol->string 'foo bar)                   => bottom

The string->symbol procedure conforms to R5RS with one restriction:

4.6.8 symbol->string

(symbol->string 'xyz) => "xyz"

The symbol->string procedure evaluates to a string that is composed of the characters of the symbol passed to it. The following rules apply:

(symbol->string 'foo)         => "foo"
(symbol->string 'FOO)         => "foo"
(symbol->string "non-symbol") => bottom

The symbol->string procedure conforms to R5RS.

4.7 Char Procedures

4.7.1 char-ci<?
4.7.2 char-ci<=?
4.7.3 char-ci=?
4.7.4 char-ci>?
4.7.5 char-ci>=?

(char-ci<? #\a #\B ...)  => {#t,#f}
(char-ci<=? #\c #\C ...) => {#t,#f}
(char-ci=? #\c #\C ...)  => {#t,#f}
(char-ci>? #\B #\a ...)  => {#t,#f}
(char-ci>=? #\C #\c ...) => {#t,#f}

The char-ci...? procedures test whether a given monotonic ordering applies to their arguments. They return #t, if the ordering applies and otherwise #f.

The char-ci...? procedures are case-insensitive.

The following rules apply:

(char-ci<? #\a #\b)       => #t
(char-ci<=? #\a #\A)      => #t
(char-ci=? #\A #\a)       => #t
(char-ci>? #\B #\a)       => #t
(char-ci>=? #\B #\b)      => #t
(char-ci<? #\a #\b #\c)   => #t
(char-ci<? #\b #\a)       => #f
(char-ci=? 'non-char #\x) => bottom
(char-ci=? #\x 'non-char) => bottom

The char-ci<?, char-ci<=?, char-ci=?, char-ci>?, and char-ci>=? procedures conform to R5RS.

4.7.6 char<?
4.7.7 char<=?
4.7.8 char=?
4.7.9 char>?
4.7.10 char>=?

(char<? #\a #\b ...)  => {#t,#f}
(char<=? #\c #\c ...) => {#t,#f}
(char=? #\c #\c ...)  => {#t,#f}
(char>? #\b #\a ...)  => {#t,#f}
(char>=? #\c #\c ...) => {#t,#f}

The char...? procedures test whether a given monotonic ordering applies to their arguments. They return #t, if the ordering applies and otherwise #f.

The following rules apply:

(char<? #\a #\b)       => #t
(char<=? #\a #\a)      => #t
(char=? #\a #\a)       => #t
(char>? #\b #\a)       => #t
(char>=? #\b #\b)      => #t
(char<? #\a #\b #\c)   => #t
(char<? #\b #\a)       => #f
(char=? 'non-char #\x) => bottom
(char=? #\x 'non-char) => bottom

The char<?, char<=?, char=?, char>?, and char>=? procedures conform to R5RS.

4.8 Numeric Procedures

4.8.1 n+

(n+ num1 num2) => num3

The n+ procedure adds two natural numbers num1 and num2 (no signs are allowed!) and returns their sum num3. Precision is arbitrary, no overflow can occur.

N+ is used to implement the more general bignum arithmetic procedure like +, *, etc. The following rules apply:

(n+ 12 9)            => 21
(n+ 12 0)            => 12
(n+ 12 +1)           => bottom
(n+ -12 1)           => bottom
(n+ 'non-number 123) => bottom
(n+ 123 'non-number) => bottom

The n+ procedure is a SketchyLISP Extension.

4.8.2 n-

(n- num1 num2) => num3

The n- procedure subtracts the number num2 from num1, returning their difference num3. Both of its arguments must be natural numbers without any signs. N- cannot create negative results, so num2 must be less than or equal to num1. This procedure may subtract number of any size.

N- is used to implement the more general bignum arithmetic procedure like +, *, etc. The following rules apply:

(n- 12 9)            => 3
(n- 12 0)            => 12
(n- 9 12)            => bottom
(n- 12 +1)           => bottom
(n- -12 1)           => bottom
(n- 'non-number 123) => bottom
(n- 123 'non-number) => bottom

The n- procedure is a SketchyLISP Extension.

4.8.3 n<

(n< num1 num2) => {#t,#f}

The n< procedure compares the two natural numbers num1 and num2, returning #t if num1 is less than num2. In case num1 is greater than or equal to num2, it returns #f. N< may compare number of any size.

The n< procedure is used to implement the more general bignum comparison procedure like <, >=, etc. The following rules apply:

(n< 9 12)            => #t
(n< 12 9)            => #f
(n< 12 +9)           => bottom
(n< -12 9)           => bottom
(n< 'non-number 123) => bottom
(n< 123 'non-number) => bottom

The n< procedure is a SketchyLISP Extension.

4.9 String Procedures

4.9.1 read-from-string

(read-from-string string) => {(form), #f}

Read-from-string reads the external representation of a form from the given string, converts it to internal representation, and returns a singleton list containing that form. Read is capable of reading any unambiguous external representation that was written by write-to-string.

Read-from-string expects that the input string terminates after the form read. In case there are trailing characters after the expression, it returns #f.

In case the input string does not contain a well-formed datum, read-from-string returns #f.

Read-from-string skips over all sorts of comments found in its input.

Here are some sample applications of read:

(read-from-string "foo")       => (foo)
(read-from-string "   foo")    => (foo)
(read-from-string "'foo")      => ('foo)
(read-from-string "\"hello\"") => ("hello")
(read-from-string "#\\x")      => (#\x)
(read-from-string "#\\space")  => (#\space)
(read-from-string "#\\;")      => (#\;)
(read-from-string "#f")        => (#f)
(read-from-string "(x . y)")   => ((x . y))
(read-from-string "#|...|# x") => (x)
(read-from-string "foo bar")   => #f
(read-from-string "(x")        => #f
(read-from-string "")          => #f
(read-from-string "#<foo>")    => #f

The read-from-string procedure is a SketchyLISP extension.

4.9.2 string-append

(string-append str1 ...) => str

String-append returns a new string that contains the concatenation of the strings passed to it as arguments. The following rules apply:

(string-append)             => ""
(string-append "xyz")       => "xyz"
(string-append "x" "y" "z") => "xyz"
(string-append 'non-string) => bottom

The string-append procedure conforms to R5RS.

4.9.3 string-length

(string-length str) => num

The string-length procedure returns the number of characters contained in the string str. The following rules apply:

(string-length "")          => 0
(string-length "xyz")       => 3
(string-length 'non-string) => bottom

The string-length procedure conforms to R5RS.

4.9.4 string-ref

(string-ref str num) => char

String-ref extracts the character at position num from the string str. The position of the first character of a string is zero. Num may not be negative and it must be less than the length of str. String-ref returns the extracted character. The following rules apply:

(string-ref "x" 0)    => #\x
(string-ref "xyz" 0)  => #\x
(string-ref "xyz" 2)  => #\z
(string-ref "xyz" 3)  => bottom
(string-ref "xyz" -1) => bottom
(string-ref 'non-string 0)     => bottom
(string-ref "xyz" 'non-number) => bottom

The string-ref procedure conforms to R5RS.

4.9.5 substring

(substring str1 num1 num2) => str2

The substring primitive returns a new string containing a substring of str1. The substring to be extracted is described by the arguments num1 and num2 as follows: Num1 is the position of the first character to be extracted, and num2 is the position of the first character not to be extracted. Positions start at zero. The following assertion must hold:

(<= num1 num2 (string-length str1))

The following rules apply:

(substring "" 0 0)    => ""
(substring "xyz" 0 0) => ""
(substring "xyz" 0 3) => "xyz"
(substring "xyz" 0 1) => "x"
(substring "xyz" 1 3) => "yz"
(substring 'non-string 0 0)     => bottom
(substring "xyz" 'non-number 0) => bottom
(substring "xyz" 0 'non-number) => bottom

The substring procedure conforms to R5RS.

4.9.6 write-to-string

(write-to-string form) => string

The write-to-string procedure writes the external representation of form to a string and then returns that string.

Unambiguous external representation written by write-to-string may be read back using the read-from-string procedure. The original form a and the car part of the re-read form b are guaranteed to be equal in the sense of (equal? a (car b)). They will not be identical, though (see eq?).

The following rules apply:

(write-to-string 'foo)     => "foo"
(write-to-string #\a)      => "#\\a"
(write-to-string 123)      => "123"
(write-to-string "hello")  => "\"hello\""
(write-to-string "\"hi!\"") => "\"\\\"hi!\\\"\""
(write-to-string '(x . y)) => "(x . y)"
(write-to-string #\space)  => "#\\space"
(write-to-string #f)       => "#f"
(write-to-string (lambda (x) x))  => "#<procedure (x)>"
(write-to-string car)      => "#<primitive car>"
(write-to-string cond)     => "#<syntax cond>"

The write-to-string procedure is a SketchyLISP extension.

4.10 Input/Output

4.10.1 delete-file

(delete-file str) => {#t,#f}

(delete-file str) deletes the file named in str. It returns #t, if the given file could be deleted and otherwise #f. Possible reasons why a file could not be deleted include

The following rules apply:

(delete-file "foo")       => #t     ; file foo deleted
(delete-file "foo")       => #f     ; file foo not deleted
(delete-file 'non-string) => bottom

The delete-file procedure is a SketchyLISP Extension.

4.10.2 display

(display form) => #<void>

The display procedure writes a nicely formatted copy of form to the output stream. Formatting includes the removal of quotation marks of strings and the #\ prefix of chars. The #\space and #\newline objects print as a blank character and a newline character.

Display does not print the external representation of a form. Use write to print unambiguous representations of forms.

Output is formatted as follows:

(display 'foo)      writes  foo
(display #\a)       writes  a
(display 123)       writes  123
(display "hello")   writes  hello
(display "\"hi\"")  writes  "hi"
(display '(x . y))  writes  (x . y)
(display #\space)   writes
(display #f)        writes  #f
(display (lambda (x) x))  writes  #<procedure (x)>
(display car)       writes  #<primitive car>
(display cond)      writes  #<syntax cond>

The display procedure conforms to R5RS.

4.10.3 eof-object?

(eof-object? form) => {#t,#f}

(Eof-object? x) evaluates to #t, if x is the EOF object and otherwise to #f. The EOF object is returned by the read and read-char procedures when attempting to read beyond the end of the input stream.

Because the EOF object has no unambiguous external representation, the following examples use the one defined here:

(define eof
  (begin
    (with-output-to-file "empty-file" (lambda () ()))
    (with-input-from-file "empty-file" read)))

Given above definition, the following rules apply:

(eof-object? eof)               => #t
(eof-object? 'any-other-object) => #f

The eof-object? procedure conforms to R5RS.

4.10.4 peek-char

(peek-char) => char

Peek-char reads a single character from the input stream and returns a matching char object. Like read-char it reads raw characters. Unlike read-char, it does not advance to the next character in the input stream. When no characters are available from the input stream, the EOF object is returned.

The peek-char procedure conforms to R5RS.

4.10.5 read

(read) => datum

Read reads the external representation of a form from the input stream, converts it to internal representation, and returns it. After reading a single form, read returns. Any text following the form in the input stream will be left there. Read is capable of reading any unambiguous external representation that was written by write.

Read skips over all sorts of comments found in its input.

When read is applied to an input stream that does not hold any input, it returns the EOF object which can be checked using the eof-object? procedure.

The top level loop of the SketchyLISP interpreter uses read to parse programs.

Here are some sample applications of read:

(read)foo        => foo
(read) foo       => foo
(read) 'foo      => 'foo
(read) foo bar   => foo     ; bar is left in the input stream
(read) "hello"   => "hello"
(read) #\x       => #\x
(read) #\space   => #\space
(read) #\;       => #\;
(read) #t        => #t
(read) (x . y)   => (x . y)
(read) #|...|# x => x
(read) #<foo>    => bottom   ; unreadable object

The read procedure conforms to R5RS.

4.10.6 read-char

(read-char) => char

Read-char reads a single character from the input stream and returns a matching char object. Unlike read it does not parse its input, but reads raw characters. If multiple characters are available from the input stream, read-char returns only the first one and leaves the remaining input in the stream. When no characters are available from the input stream, the EOF object is returned.

Here are some sample applications of read-char:

(read-char)x  => #\x
(read-char) x => #\space   ; x is left in the input stream
(read-char)(  => #\(
(read-char);  => ;
(read-char)
              => #\newline

The read-char procedure conforms to R5RS.

4.10.7 with-input-from-file

(with-input-from-file str fun) => datum

The with-input-from-file procedure opens the file named in the string str and connects the input stream to that file. In this context, it applies the procedure fun, which must be a procedure of zero arguments. As soon as the procedure returns, the file is closed and the input stream is connected to the source that was in effect before.

All applications of read, read-char, or peek-char inside of fun will read from the given file. Applications of with-input-from-file evaluate to the value of (fun).

In case the specified file does not exist, applications of with-input-from-file evaluate to bottom.

A sample application of this procedure follows. The file foo is assumed to contain the text (foo "hello" 5).

(with-input-from-file "foo" read) => (foo "hello" 5)

The with-input-from-file procedure conforms to R5RS.

4.10.8 with-output-to-file

(with-output-to-file str fun) => datum

The with-output-to-file procedure opens the file named in the string str and connects the output stream to that file. In this context, it applies the procedure fun, which must be a procedure of zero arguments. As soon as the procedure returns, the file is closed and the output stream is connected to the file or device that was in effect before.

All applications of write, write-char, or display inside of fun will write to the given file. Applications of with-output-to-file evaluate to the value of (fun).

In case the file specified in str already exists, its content will be overwritten.

The following sample application of this procedure writes the form (foo "hello" 5) to the file foo.

(with-output-to-file "foo"
  (lambda () (write '(foo "hello" 5))))
=> #<void>

The with-output-to-file procedure conforms to R5RS.

4.10.9 write

(write form) => #<void>

The write procedure writes the external representation of form to the output stream.

Unambiguous external representation written by write may be read back using the read procedure. The original form a and the re-read form b are guaranteed to be equal in the sense of (equal? a b). They will not be identical, though (see eq?).

Output is formatted as follows:

(write 'foo)       writes  foo
(write #\a)        writes  #\a
(write 123)        writes  123
(write "hello")    writes  "hello"
(write "\"hi\"")   writes  "\"hi\""
(write '(x . y))   writes  (x . y)
(write #\space)    writes  #\space
(write #f)         writes  #f
(write (lambda (x) x))  writes  #<procedure (x)>
(write car)        writes  #<primitive car>
(write cond)       writes  #<syntax cond>

The write procedure conforms to R5RS.

4.10.10 write-char

(write-char #\c) => #<void>

The write-char procedure writes the given character to the output stream.

Output is formatted as follows:

(write-char '#x)  writes  x
(write-char '#X)  writes  X
(write-char '#;)  writes  ;
(write-char '#()  writes  (

The write-char procedure conforms to R5RS.