home: hub: zuo

ref: 95ad5c77a146a99179e4f553662a4203d070adfa
dir: /zuo-doc/lang-zuo.scrbl/

View raw version
#lang scribble/manual
@(require (for-label zuo-doc/fake-zuo)
          "real-racket.rkt")

@title[#:tag "zuo-base"]{Zuo Base Language}

@defmodule[#:multi (zuo zuo/base) #:no-declare #:lang #:packages ()]
@declare-exporting[zuo zuo/base #:packages () #:use-sources (zuo-doc/fake-zuo)]

The @racketmodname[zuo] language is Zuo's default language. It's meant
to be familiar to Racket programmers, and the description here leans
heavily on comparisons and the Racket documentation, for now. Zuo
forms and functions tend use traditional Racket names, even when a
different choice might be made in a fresh design, and even when the
Zuo construct is not exactly the same. Filesystem operations, however,
tend to use the names of Unix programs, which are much shorter than
Racket's long names.

The @racketmodname[zuo/base] language includes most of the bindings
from @racketmodname[zuo], but not the ones that are from
@racketmodname[zuo/cmdline], @racketmodname[zuo/build],
@racketmodname[zuo/shell], @racketmodname[zuo/thread],
@racketmodname[zuo/glob], or @racketmodname[zuo/config].

@section{Syntax and Evaluation Model}

A @racketmodname[zuo] module consists of a sequence of definitions
(e.g., @racket[define]), macro definitions (e.g.,
@racket[define-syntax]), imports (e.g., @racket[require]), exports
(e.g., @racket[provides]), and expressions (e.g., @racket[5]). Loading
the module first @deftech{expands} it, and then @deftech{evaluates}
it. A module is loaded only once, so if a module is demanded more than
once, the result of the first load is used.

The expansion process expands macro uses, loads imported modules, and
evaluates macro definitions as such forms are encountered for the
module body. Expansion creates a binding for each definition as
encountered, but does not expand or evaluate the definition, yet.
Expansion of definitions and expressions is deferred until all forms
in the module body have been processed. Some expression forms have
local definition contexts, which can include further imports and macro
definitions, so expansion at those points nests the same two-step
process as used for the module body.

Evaluation of a module evaluates its definitions and expressions (some
of which may have been introduced by macro expansion) in order.
Definitions bind mutually recursively within the enclosing module or
definition context, and referencing a defined variable before its
evaluation is an error. The value of each expression in a module body
is printed using @racket[alert] compiled with @racket[~v].

A module's provided variables and syntax are made available to other
modules that import it. Variables and macros that are not provided are
completely inaccessible outside of the module.

There are no @defterm{phases} in the sense of Racket. When
@racketmodname[zuo] macro expansion encountered an import, it makes
all of the imported module's exports immediately available for use in
macro implementations, both variables and macros. For example, an
imported macro might be used both to implement a macro body and in
nearby run-time code or even run-time code generated by the macro's
expansion. The absence of a phase separation is related to way that
each module is evaluated only once, and it's made workable in part by
the absence of mutable data structures in Zuo, and in part because
there is no support for compiling a @racketmodname[zuo] module and
saving it separate from it's instantiation in a Zuo process or saved
image.

Zuo macros consume a representation of syntax that uses plain pairs,
numbers, strings, etc., but with an identifier @tech{syntax object}
potentially in place of a symbol. Even for symbols, using a syntax
object is optional; by using @racket[quote-syntax] to create a syntax
object, a macro can generate a term with identifiers bound at the
macro's definition site, instead of a use site's, but the macro
expander does not impose or automate that binding. See
@racket[quote-syntax] for more information.

@; ----------------------------------------

@section{Binding and Control Forms}

A @racketmodname[zuo] syntactic form is either a @deftech{definition}
form or an @deftech{expression forms}. Expressions can appear in
definition contexts, but not vice versa. In descriptions of syntactic
forms @racket[_body ...+] refers to a context that allows definition
forms, but the last form in the expansion of the definition context
must be an expression form.

@subsection{Expression Forms}

@defform[(lambda formals body ...+)
         #:grammar ([formals (id ... [id expr] ...)
                             id
                             (id ... [id expr] ... . id)])]{

Analogous to @realracket[lambda] in @racketmodname[racket], but
without keyword arguments.}


@defform[#:link-target? #f #:id not-expr (expr expr ...)]{

A function call, where the initial @racket[expr] is not an identifier
bound to a macro.}


@deftogether[(
@defform*[[(let ([id val-expr] ...) body ...+)
           (let proc-id ([id init-expr] ...) body ...+)]]
@defform[(let* ([id val-expr] ...) body ...+)]
@defform[(letrec ([id val-expr] ...) body ...+)]
)]{

Just like @realracket*[let let* letrec] in @racketmodname[racket].}


@deftogether[(
@defform[(if test-expr then-expr else-expr)]
@defform[(and expr ...)]
@defform[(or expr ...)]
@defform[(when test-expr body ...+)]
@defform[(unless test-expr body ...+)]
@defform[#:literals (else)
         (cond cond-clause ...)
         #:grammar ([cond-clause [test-expr then-body ...+]
                                 [else then-body ...+]])]
@defform[#:id else else]
@defform[(begin expr ...+)]
)]{

Just like @realracket*[if and or when unless cond else begin] in
@racketmodname[racket], except that @racket[cond] is more limited.}


@deftogether[(
@defform[(quote datum)]
@defform[(quasiquote datum)]
@defform[#:id unquote unquote]
@defform[#:id unquote-splicing unquote-splicing]
)]{

Just like @realracket*[quote quasiquote unquote unquote-splicing] from
@racketmodname[racket].}


@defform[(quote-syntax datum)]{

Analogous to @realracket[quote-syntax] from @racketmodname[racket],
but only identifiers have a specialized syntax-object representation
in place of symbols. Tree structure in @racket[datum] represented
using plain pairs, and non-identifier elements of @racket[datums] are
represented with plain numbers, strings, etc.

A Zuo module's representation starts with plain pairs and symbols, a
macro procedure can receive terms containing plain symbols, and it can
return a term with plain symbols. A symbol non-hygienically acquires a
@tech{scope} at the point where its binding is resolved or where it
creates a binding.

A @deftech{scope} corresponds to a particular binding context. It can
be a module context, an internal definition context, or a binding site
for an expression form like the formals of a @racket[lambda] or the
right-hand side of a @racket[letrec].

An identifier @tech{syntax object} created by @racket[quote-syntax] closes
over a binding at the point where it is created, closing over the
enclosing module scope if the identifier is not (yet) bound. The
closure does not change if the identifier is nested in a later
@racket[quote-syntax] form. Identifiers that are introduced by macros
are not automatically given a scope or otherwise distinguished from
identifiers that appeared as input to a macro, and a plain symbol is
implicitly coerced to a syntax object only at the point where it binds
or where its binding is resolved as a reference.

There is no @realracket[quasisyntax], @realracket[unsyntax], or
@realracket[unsyntax-splicing] analog, since @racket[quasiquote],
@racket[unquote], and @racket[unquote-splicing] are already convenient
enough for most purposes. To generate a fresh symbol for the output of
a macro expansion, use @racket[string->uninterned-symbol].}

@defform[(quote-module-path)]{

Returns the module path of the enclosing module.}


@subsection{Definition Forms}

@defform*[[(define id expr)
           (define (id . formals) body ...++)]]{

Like @realracket*[define] from @racketmodname[racket], but without
keyword arguments, optional arguments, or header nesting for curried
functions.}

@defform*[[(define-syntax id expr)
           (define-syntax (id . formals) body ...++)]]{

Analogous to @realracket*[define-syntax] from @racketmodname[racket],
binds @racket[id] as a macro. The value of @racket[expr] must be
either a procedure (of one argument) or a value constructed by
@racket[context-consumer].

If @racket[expr] produces a @racket[context-consumer] wrapper, then
when @racket[id] is used for a macro invocation, the wrapped procedure
receives three arguments: the macro use as syntax, a function that
acts like @realracket[free-identifier=?], and either @racket[#f] or an
inferred-name string. (In @racketmodname[racket],
@realracket[free-identifier=?] and @realracket[syntax-local-name] are
implicitly parameterized over the context of a macro invocation.
Explicitly providing a comparison procedure and name string to a macro
implementation, instead, avoids the implicit parameterization.)

See @racket[quote-syntax] for more information about the
representation of syntax that a macro function consumes and produces.}

@defform[(struct id (field-id ...))]{

Analogous to @realracket*[struct] from @racketmodname[racket], but
defining only @racket[id] as a constructor,
@racket[id]@racketidfont{?} as a predicate,
@racket[id]@racketidfont{-}@racket[field-id] as an accessor for each
@racket[field-id], and
@racket[id]@racketidfont{-set-}@racket[field-id] as a
functional-update operation (along the lines of
@realracket[struct-copy]) for each @racket[field-id].}

@defform[(include module-path)]{

Splices the content of the module identified by @racket[module-path],
assuming that @racket[module-path] is implemented in a language like
@racketmodname[zuo/datum].}

@deftogether[(
@defform[#:literals (only-in rename-in)
         (require spec ...)
         #:grammar ([spec module-path
                          (only-in module-path
                                   maybe-renamed-id ...)
                          (rename-in module-path
                                     renamed-id ...)]
                    [maybe-renamed-id id
                                      renamed-id]
                    [renamed-id [provided-id id]])]
@defform[#:literals (all-from-out)
         (provide spec ...)
         #:grammar ([spec id
                          (rename-out renamed-id ...)
                          (all-from-out module-path)]
                    [maybe-renamed-id id
                                      renamed-id]
                    [renamed-id [id provided-id]])]
)]{

Like @realracket*[require provide] from @racketmodname[racket], but a
@racket[require] can appear in any definition context, while
@racket[provide] is not allowed in @tech{submodules}.}

@defform[(module+ id defn-or-expr ...)]{

Declares a kind of @deftech{submodule}, roughly analogous to
@realracket[module+] from @racketmodname[racket], but without allowing
submodules nested in submodules.

A submodule becomes a procedure of zero arguments that is a mapped
from the symbol form of @racket[id] in the encloding module's
representation as a hash table (see @secref["module-protocol"]).
Calling the procedure evaluates the @racket[defn-or-expr] content of
the submodule, where expression results are printed and the procedure
result is @racket[(void)].

When Zuo loads a starting module (see @secref["running"]), it checks
for a @racketidfont{main} submodule and runs it if one is found.}

@; ----------------------------------------

@section{Booleans}

Zuo booleans are written @racket[#t] or @racket[#true] and @racket[#f]
or @racket[#false]. Any value other than @racket[#f] counts as true
for conditionals.

@deftogether[(
@defproc[(boolean? [v any/c]) boolean?]
@defproc[(not [v any/c]) boolean?]
)]{

Just like @realracket*[boolean? not] from @racket[racket].}

@defproc[(eq? [v1 any/c] [v2 any/c]) boolean?]{

Analogous to @realracket[eq?] from @racket[racket], but even small Zuo
numbers are not necessarily @racket[eq?] when they are @racket[=].}

@defproc[(equal? [v1 any/c] [v2 any/c]) boolean?]{

Analogous to @realracket[equal?] from @racket[racket].}


@section{Numbers}

A Zuo number corresponds to a 64-bit two's complement representation
with modular arithmetic (i.e., wraparound on overflow). It is always
written in decimal form with a leading @litchar{-} for negative
numbers.

@defproc[(integer? [v any/c]) boolean?]{

Returns @racket[#t] if @racket[v] is an integer, @racket[#f] otherwise.}

@deftogether[(
@defproc[(+ [z integer?] ...) integer?]
@defproc*[([(- [z integer?]) integer?]
           [(- [z integer?] [w integer?] ...+) integer?])]
@defproc[(* [z integer?] ...) integer?]
@defproc[(quotient [n integer?] [m integer?]) integer?]
@defproc[(modulo [n integer?] [m integer?]) integer?]
@defproc[(= [z integer?] [w integer?]) boolean?]
@defproc[(< [x integer?] [y integer?]) boolean?]
@defproc[(<= [x integer?] [y integer?]) boolean?]
@defproc[(> [x integer?] [y integer?]) boolean?]
@defproc[(>= [x integer?] [y integer?] ...) boolean?]
@defproc[(bitwise-ior [n integer?] [m integer?]) integer?]
@defproc[(bitwise-and [n integer?] [m integer?]) integer?]
@defproc[(bitwise-xor [n integer?] [m integer?]) integer?]
@defproc[(bitwise-not [n integer?])  integer?]
)]{

Analogous to @realracket*[+ - * quotient modulo = < <= > >=
bitwise-ior bitwise-and bitwise-xor bitwise-not] from
@racketmodname[racket], but on Zuo integers and sometimes constrained
to two arguments.}


@section{Pairs and Lists}

Zuo pairs and lists work the same as in Racket with the same textual
representation.

@deftogether[(
@defproc[(pair? [v any/c])
         boolean?]
@defproc[(null? [v any/c])
         boolean?]
@defproc[(list? [v any/c])
         boolean?]
@defproc[(cons [a any/c] [d any/c])
         pair?]
@defproc[(car [p pair?])
         any/c]
@defproc[(cdr [p pair?])
         any/c]
@defproc[(list [v any/c] ...)
         list?]
@defproc[(list* [v any/c] ... [tail any/c])
         any/c]
@defproc*[([(append [lst list?] ...) list?]
           [(append [lst list?] ... [v any/c]) any/c])]
@defproc[(reverse [lst list?]) list?]
@defproc[(length [lst list?]) integer?]
@defproc[(list-ref [lst pair?] [pos integer?]) any/c]
@defproc[(list-set [lst pair?] [pos integer?] [v any/c]) any/c]
@defproc[(list-tail [lst any/c] [pos integer?]) any/c]
)]{

Just like @realracket*[pair? null? cons car cdr list? list* append
reverse list-ref list-set list-tail] from @racketmodname[racket], except that
@racket[list?] takes time proportional to the length of the list.}

@deftogether[(
@defproc[(caar [p pair?]) any/c]
@defproc[(cadr [p pair?]) any/c]
@defproc[(cdar [p pair?]) any/c]
@defproc[(cddr [p pair?]) any/c]
)]{

Just like @realracket*[caar cadr cdar cddr] from @racketmodname[racket].}


@deftogether[(
@defproc[(map [proc procedure?] [lst list?] ...+)
         list?]
@defproc[(for-each [proc procedure?] [lst list?])
         void?]
@defproc[(foldl [proc procedure?] [init any/c] [lst list?])
         any/c]
@defproc[(andmap [proc procedure?] [lst list?])
          any/c]
@defproc[(ormap [proc procedure?] [lst list?])
         any/c]
@defproc[(filter [proc procedure?] [lst list?])
         list?]
@defproc[(sort [lst list?] [less-than? procedure?])
         list?]
)]{

Like @realracket*[map for-each foldl andmap ormap filter] from
@racketmodname[racket], but mostly restricted to a single list.}

@deftogether[(
@defproc[(member [v any/c] [lst list?])
         (or/c pair? #f)]
@defproc[(assoc [v any/c] [lst list?])
         (or/c pair? #f)]
@defproc[(remove [v any/c] [lst list?])
         (or/c pair? #f)]
)]{

Like @realracket*[member assoc remove] from @racketmodname[racket].}


@section{Strings}

Zuo @deftech{strings} are sequences of bytes.

@deftogether[(
@defproc[(string? [v any/c]) boolean?]
@defproc[(string [char integer?] ...) string?]
@defproc[(string-length [str string?]) integer?]
@defproc[(string-ref [str string?] [k integer?]) integer?]
@defproc[(substring [str string?]
                    [start integer?]
                    [end integer? (string-length str)]) string?]
@defproc[(string=? [str1 string?] [str2 string?]) boolean?]
@defproc[(string-ci=? [str1 string?] [str2 string?]) boolean?]
@defproc[(string<? [str1 string?] [str2 string?]) boolean?]
)]{

Analogous to @realracket*[string? string string-length string-ref substring
string=? string<?] from @racketmodname[racket], or more precisely analogous to
@realracket*[bytes? bytes-length bytes-ref subbytes bytes=? bytes-ci=?] from
@racketmodname[racket].}

@defproc[(string-u32-ref [str string?] [k integer?]) integer?]{

Returns the two's complement interpretation of four bytes in
@racket[str] starting at index @racket[k] using the host machine's
endianness.}

@defproc[(string->integer [str string?]) (or/c integer? #f)]{

Tries to parse @racket[str] as an integer returning @racket[#f] if
that fails.}

@defproc[(string-sha1 [str string?]) string?]{

Returns the SHA-1 hash of @racket[str] as a 40-digit hexadecimal string.}

@defform[(char str)]{

Expands to @racket[(string-ref str 0)], where @racket[str] must be a
string of length 1.}

@defproc*[([(string-split [str string?]) list?]
           [(string-split [str string?] [sep string?]) list?])]{

Breaks @racket[str] into a sequence of substrings that have a
non-empty separator string in between. When @racket[sep] is not
provided, @racket[" "] is used as the separator, and empty strings are
filtered from the result list. When @racket[sep] is provided, empty
strings are @emph{not} filtered from the result list.}

@defproc[(string-join [strs list?] [sep string? " "]) string?]{

Concatenates the strings in @racket[strs] with @racket[sep] between
each pair of strings.}

@defproc[(string-trim [str string?] [edge-str string? " "]) string?]{

Removes any number of repetitions of @racket[edge-str] from the start
and end of @racket[str].}


@defproc[(string-tree? [v any/c]) boolean?]{

Returns @racket[#t] if @racket[v] is a string or if it is a list where
@racket[string-tree?] returns @racket[#t] for each element,
@racket[#f] otherwise. The flattened form of a string tree is a list
of its strings in order. See also @racket[process] and
@racket[build-shell].}


@section{Symbols}

Zuo symbols are @deftech{interned} by the reader, where two interned
symbols are @racket[eq?] when they have the same string content. An
@deftech{uninterned} symbol is @racket[eq?] only to itself. Zuo
symbols are the only kind of value that can be used as a key for a Zuo
@tech{hash table}.

The textual representation of symbols does not include escapes for
special character, analogous to the way @litchar{|} works in Racket.
Symbols with those characters will print in a way that cannot be read
back into Zuo.

@deftogether[(
@defproc[(symbol? [v any/c]) boolean?]
@defproc[(symbol->string [sym symbol?]) string?]
@defproc[(string->symbol [str string?]) symbol?]
@defproc[(string->uninterned-symbol [str string?]) symbol?]
)]{

Analogous to @realracket*[symbol? symbol->string string->symbol
string->uninterned-symbol] from @racketmodname[racket].}


@section{Hash Tables (Persistent Maps)}

Zuo @tech{hash tables} do not actually have anything to do with
hashing, but they're called that for similarly to Racket. A hash table
maps symbols to other values, and updating a hash table produces a new
hash table (which, internally, may share with the original).

Hash table print in a way analogous to Racket, but there is no reader
support to convert the textual form back into a hash table value.

@deftogether[(
@defproc[(hash? [v any/c]) boolean?]
@defproc[(hash [key symbol?] [val any/c] ... ...) hash?]
@defproc*[([(hash-ref [hash hash?]
                      [key symbol?])
            any/c]
           [(hash-ref [hash hash?]
                      [key symbol?]
                      [failure-value any/c])
            any/c])]
@defproc[(hash-set [hash (and/c hash? immutable?)]
                   [key symbol?]
                   [v any/c])
         hash?]
@defproc[(hash-remove [hash (and/c hash? immutable?)]
                      [key symbol?])
         hash?]
@defproc[(hash-keys [hash hash?]) (listof symbol?)]
@defproc[(hash-count [hash hash?]) integer?]
@defproc[(hash-keys-subset? [hash1 hash?] [hash2 hash?])
         boolean?]
)]{

Analogous to @realracket*[hash? hash hash-ref hash-set hash-remove
hash-keys hash-count hash-keys-subset?] from @racketmodname[racket].
Besides being constrained to symbol keys, there is one additional
difference: the third argument to @racket[hash-ref], when supplied,
is always used as a value to return if a key is missing, as
opposed to a failure thunk.}


@section{Procedures}

@deftogether[(
@defproc[(procedure? [v any/c]) any/c]
@defproc[(apply [proc procedure?] [lst list?]) any/c]
@defproc[(call/cc [proc procedure?]) any/c]
@defproc[(call/prompt [proc procedure?] [tag symbol?]) any/c]
@defproc[(continuation-prompt-available? [tag symbol?]) boolean?]
)]{

Like @realracket*[procedure? apply call/cc
call-with-continuation-prompt continuation-prompt-available?] from
@racketmodname[racket], but @racket[apply] accepts only two arguments,
@racket[call/cc] has no prompt-tag argument and captures up to the
nearest enclosing prompt of any tag, @racket[call/prompt] expects a
symbol for a prompt tag, and @racket[continuation-prompt-available?]
checks only whether the immediately enclosing prompt has the given tag.}


@section{Paths}

A @deftech{path string} is is a @tech{string} that is not non-empty
and to contains no nul bytes.

@defproc[(path-string? [v any/c]) boolean?]{

Returns @racket[#t] if @racket[v] is a path string, @racket[#f] otherwise.}

@defproc[(relative-path? [path path-string?]) boolean?]{

Returns @racket[#t] if @racket[v] is a relative path, @racket[#f] otherwise.}

@defproc[(build-raw-path [base path-string?] [rel path-string?] ...) path-string?]{

Combines @racket[base] path (absolute or relative) with the relative
paths @racket[rel], adding path separators as needed.}

@defproc[(build-path [base path-string?] [rel path-string?] ...) path-string?]{

Similar to @racket[build-raw-path], but any @filepath{.} or
@filepath{..} element in a @racket[rel] is syntactically eliminated,
and separators in @racket[rel] are normalized. Removing @filepath{..}
elements may involve syntactically resolving elements at the end of
@racket[base]. Furthermore, if base is at some point reduced to
@racket["."], it will not be prefixed on the result.}

@defproc[(split-path [path path-string?]) pair?]{

Splits @racket[path] into its directory (if any) and a final element
components. If @racket[path] has only a single element, the
@racket[car] of the result is @racket[#f], and the @racket[cdr] is
@racket[path] unchanged; otherwise, the final element is returned
without trailing separators.}

@defproc[(explode-path [path path-string?]) (listof path-string?)]{

Split @racket[path] into a list of individual path elements by
repeatedly applying @racket[split-path].}

@defproc[(simple-form-path [path path-string?]) path-string?]{

Syntactically normalizes @racket[path] by eliminating @filepath{.} and
@filepath{..} elements (except for @filepath{..} at the start that
cannot be eliminated), removing redundant path separators, and making
all path separators the platform default (on Windows).}

@defproc[(find-relative-path [base path-string?] [path path-string?]) path-string?]{

Attempts to finds a path relative to @racket[base] that accesses the
same file or directory as @racket[path]. Both @racket[base] and
@racket[path] must be normalized in the sense of
@racket[simple-form-path], otherwise @filepath{.} and @filepath{..}
elements are treated normal path elements. Assuming that @racket[base]
and @racket[path] are normalized, the result is always normalized.

The result path depends on whether @racket[base] and @racket[path] are
relative or absolute:

@itemlist[

 @item{If both are relative, the result is always a relative path. If
       @racket[base] starts with @filepath{..} elements that are not
       matched by @racket[path], then elements are drawn from
       @racket[(hash-ref (runtime-env) 'dir)].}

 @item{If both are absolute, the result is absolute if @racket[base]
       and @racket[path] do not share a root element, otherwise the
       result is relative.}

 @item{If @racket[path] is absolute and @racket[base] is relative,
       @racket[path] is returned as-is. The intent of this mode is to
       preserve the ``absoluteness'' of @racket[path] in a setting
       that otherwise works in terms of relative paths.}

 @item{If @racket[base] is absolute and @racket[path] is relative,
       @racket[path] is converted to absolute via
       @racket[path->complete-path], and the result is as when both
       are absolute (so, the result may still be absolute).}

]}

@defproc[(path-only [path path-string?]) path-string?]{

Returns @racket[path] without its final path element in the case that
@racket[path] is not syntactically a directory. If @racket[path] has
only a single, non-directory path element, @racket["."] is returned.
If @racket[path] is syntactically a directory, then @racket[path] is
returned unchanged.}

@defproc[(file-name-from-path [path path-string?]) (or/c path-string? #f)]{

Returns the last element of @racket[path] in the case that
@racket[path] is not syntactically a directory, @racket[#f]
otherwise.}

@defproc[(path->complete-path [path path-string?]) path-string?]{

Returns @racket[path] if it is absolute, otherwise returns
@racket[(build-path (hash-ref (runtime-env) 'dir) path)].}

@defproc[(path-replace-extension [path path-string?] [suffix string?]) path-string?]{

Removes any @litchar{.} suffix from the last element of @racket[path],
and then appends @racket[suffix] to the end of the path. A @litchar{.}
at the start of a path element does not count as a file suffix.}

@defidform[at-source]{

Expands to a function that acts like @racket[build-path] starting from
the enclosing module's directory. That is, expands to a function
roughly equivalent to

@racketblock[(lambda args
               (apply build-path (cons (path-only (quote-module-path))
                                       args)))]

If the argument to the function is an absolute path, however, the
enclosing module's directory is ignored, and the function acts simply
like @racket[build-path].}


@section{Opaque Records}

@defproc[(opaque [key any/c] [val any/c]) any/c]{

Returns an opaque record that encapsulates @racket[val] with access
allowed via @racket[key].}

@defproc[(opaque-ref [key any/c] [v any/c] [failure-val any/c]) any/c]{

Returns the value encapsulated in @racket[v] if its is an opaque
object with access allowed via @racket[key], @racket[failure-val] otherwise.}


@section{Variables}

A @tech{variable} is a value with a name that contains an another
value. The contained value is initially undefined, and attempting to
access the contained value before it's set results in an error where
the variable's name is used in the error message. A variable's
contained value can be set only once.

@defproc[(variable? [v any/c]) boolean?]{

Returns @racket[#t] if @racket[v] is a variable, @racket[#f] otherwise.}


@defproc[(variable [name symbol?]) variable?]{

Creates a variable named by @racket[name] and without a value until
one is installed with @racket[variable-set!].}

@defproc[(variable-set! [var variable?] [val any/c]) void?]{

Sets the value contained by @racket[var] to @racket[val] or errors if
@racket[var] already has a contained value.}

@defproc[(variable-ref [var variable?]) any/c]{

Returns the value contained by @racket[var] or errors if @racket[var]
does not yet have a contained value.}


@section{Modules and Evaluation}

A @deftech{module path} is a path string or a symbol, where a symbol
must contain only the letters @litchar{A}-@litchar{Z},
@litchar{a}-@litchar{z}, @litchar{A}-@litchar{Z},
@litchar{0}-@litchar{9}, @litchar{-}, @litchar{+}, @litchar{+}, or
@litchar{/}. Furthermore, @litchar{/} in a symbol module path cannot
be at the start, end, or adjacent to another @litchar{/}.

@defproc[(module-path? [v any/c]) boolean?]{

Returns @racket[#t] if @racket[v] is a @tech{module path}, @racket[#f]
otherwise.}

@defproc[(build-module-path [base module-path?] [rel-path path-string?]) module-path?]{

Analogous to @racket[build-path], but for @tech{module paths}. The
@racket[rel-path] string must end with @litchar{.zou}, and the
characters of @racket[rel-path] must be allowable in a symbol module
paths, except for a @litchar{.} in @filepath{.} and @filepath{..}
elements or a @litchar{.zuo} suffix.}

@defproc[(module->hash [mod-path module-path?]) hash?]{

Loads @racket[mod-path] if it has not been loaded already, and returns
the @tech{hash table} representation of the loaded module. See also
Secref["module-protocol"]}

@defproc[(dynamic-require [mod-path module-path?] [export symbol?]) any/c]{

Like @racket[module->hash], but extracts an exported value. The module
referenced by @racket[mod-path] must be implemented in
@racketmodname[zuo], @racketmodname[zuo/hygienic], or a derived
compatible language.}

@defproc[(kernel-eval [s-exp any/c]) any/c]{

Evaluates a term as if it appeared in a @racketmodname[zuo/kernel] module
(but the result does not have to be a @tech{hash table}).}

@defproc[(kernel-env) hash?]{

Returns a @tech{hash table} that maps each primitive and constant name
available in the body of a @racketmodname[zuo/kernel] module to its value.}


@section{void}

@defproc[(void? [v any/c]) boolean?]{

Returns @racket[#t] if @racket[v] is the unique @deftech{void} value,
@racket[#f] otherwise.}

@defproc[(void [v any/c] ...) void?]{

Accepts any number of arguments and ignored them, returning the void
value.}


@section{Reading and Writing Objects}

@defproc[(string-read [str string?] [start integer? 0] [where any/c #f]) list?]{

Reads all S-expressions in @racket[str], starting at index
@racket[start] and returning a list of the S-expressions (in order as
they appeared in the string). The @racket[where] argument, if not
@racket[#f], is used to report the source of errors.}


@deftogether[(
@defproc[(~v [v any/c] ...) string?]
@defproc[(~a [v any/c] ...) string?]
@defproc[(~s [v any/c] ...) string?]
)]{

Like @realracket*[~v ~a ~s], but with no formatting options. These
three format options corresponds to @realracket[print] style,
@realracket[display] style, and @realracket[write] style,
respectively.

Unlike uninterned symbols in @racketmodname[racket], Zuo uninterned
symbols format in @realracket[print] and @realracket[write] styles with
@litchar{#<symbol:}...@litchar{>}. @tech{Opaque objects},
@tech{handles}, and @tech{variables} print with
@litchar{#<:}...@litchar{>} notation in all styles.}

@deftogether[(
@defproc[(display [v any/c]) void?]
@defproc[(displayln [v any/c]) void?]
)]{

Convenience output functions that are analogous to @realracket*[display
displayln] from @racketmodname[racket]. They use @racket[~a] and
@racket[(fd-open-output 'stdout)].}


@defproc[(error [v any/c] ...) void?]{

Errors (and exits) after printing the @racket[v]s to standard error,
using an error color if standard error is a terminal.

If the first @racket[v] is a string, its character are printed output
@realracket[display]-style, and then @litchar{: } is printed. All
other @racket[v]s (including the first one if it's not a string) are
combined using @racket[~v], and that resulting string is written
@realracket[display]-style.}


@defproc[(alert [v any/c] ...) void?]{

Prints to standard output using the same formatting rules as
@racket[error], but in an alert color for terminals. This function is
useful for simple logging and debugging tasks.}


@defproc[(arity-error [name (or/c string? #f)] [args list?]) void?]{

Errors (and exits) after printing an error about @racket[name]
receiving the wrong number of arguments, where @racket[args] are the
arguments that were supplied.}

@defproc[(arg-error [who symbol?] [what string?] [v any/c]) void?]{

Errors (and exits) after printing an error from @racket[who] about a
@racket[what] expected in place of @racket[v].}


@section{Syntax Objects}

A @deftech{syntax object} combines a symbol with a binding scope,
where the two are used to determine a binding when the identifier is
used in a macro expansion.

@deftogether[(
@defproc[(identifier? [v any/c]) boolean?]
@defproc[(syntax-e [v identifier?]) symbol?]
@defproc[(syntax->datum [v any/c]) any/c]
@defproc[(datum->syntax [ctx identifier?] [v any/c]) any/c]
@defproc[(bound-identifier=? [id1 identifier?]
                             [id2 identifier?]) boolean?]
)]{

Analogous to @realracket*[identifier? syntax-e syntax->datum
datum->syntax bound-identifier=?] from @racketmodname[racket]. Plain
symbols count as an identifier, however, and for
@racket[bound-identifier=?], a symbol is equal only to itself. The
@racket[datum->syntax] function always just returns its second
argument.}

@defproc[(syntax-error [message string?] [stx any/c]) void?]{

Exits as an error after printing @racket[message], @litchar{: }, and
@racket[(~s (syntax->datum stx))].}

@deftogether[(
@defproc[(bad-syntax [stx any/c]) void?]
@defproc[(misplaced-syntax [stx any/c]) void?]
@defproc[(duplicate-identifier [stx any/c]) void?]
)]{

Calls @racket[syntax-error] with a suitable error message and @racket[stx].}

@deftogether[(
@defproc[(context-consumer [proc procedure]) context-consumer?]
@defproc[(context-consumer? [v any/c]) boolean?]
)]{

The @racket[context-consumer] constructor wraps a procedure to
indicate that it expects three arguments as a macro transformer; see
@racket[define-syntax] for more information. The
@racket[context-consumer?] predicate recognizes values produced by
@racket[context-consumer].}


@section{Files, Streams, and Processes}

Files, input and out streams more generally, and processes are all
represented as @tech{handles}.

@defproc[(handle? [v any/c]) boolean?]{

Returns @racket[#t] if @racket[v] is a @tech{handle}, @racket[#f]
otherwise.}

@defproc[(fd-open-input [filename (or/c path-string? 'stdin integer?)]
                        [options hash? (hash)])
         handle?]{

Opens a file for reading, obtains a reference to standard input when
@racket[filename] is @racket['stdin], or (on Unix) obtains a
reference to an existing file descriptor when @racket[filename]
is an integer. The result handle can be used
with @racket[fd-read] and closed with @racket[fd-close].

No keys are currently recognized for @racket[options], so it must be
an empty hash table.}


@deftogether[(
@defproc[(fd-open-output [filename (or/c path-string? 'stdout 'stderr integer?)]
                         [options hash? (hash)]) handle?]
@defthing[:error hash?]
@defthing[:truncate hash?]
@defthing[:must-truncate hash?]
@defthing[:append hash?]
@defthing[:update hash?]
@defthing[:can-update hash?]
)]{

The @racket[fd-open-output] procedure opens a file for writing,
obtains a reference to standard output when @racket[filename] is
@racket['stdout], obtains a reference to standard error when
@racket[filename] is @racket['stderr], or (on Unix) obtains a
reference to an existing file descriptor when @racket[filename]
is an integer. When opening a file,
@racket[options] specifies options as described below, but
@racket[options] must be empty for @racket['stdout] or
@racket['stderr]. The result handle can be used with @racket[fd-write]
and closed with @racket[fd-close].

In @racket[options], a single key is currently recognized:
@racket['exists]. The mapping for @racket['exists] must be one of the
symbols accepted for @racket[#:exists] by
@realracket[open-output-file] from @racketmodname[racket], but not
@racket['replace] or @racket['truncate/replace], and the default
mapping is @racket['error]. Any other key in @racket[options] is an
error.

The @racket[:error], @racket[:truncate], @racket[:must-truncate],
@racket[:append], @racket[:update], and @racket[:can-update] hash
tables each map @racket['exists] to the corresponding mode.}

@defproc[(fd-close [handle handle?]) void?]{

Closes the file or stream associated with @racket[handle], if it
refers to an open input or output stream. Any other kind of
@racket[handle] triggers an error.}

@defproc[(fd-read [handle handle?] [amount (or/c integer? eof 'avail)]) (or/c string? eof)]{

Reads from the input file or input stream associated with
@racket[handle], erroring for any other kind of @racket[handle]. The
@racket[amount] argument can be a non-negative integer to read up to
that many bytes, @racket[eof] to read all content up to an
end-of-file, or @racket['avail] where supported (on Unix) to read as
many bytes as available in non-blocking mode. The result is
@racket[eof] if @racket[amount] is not @racket[0] or @racket['avail]
and no bytes are available before an end-of-file; otherwise, it is a
string containing the read bytes.

The number of bytes in the returned string can be less than
@racket[amount] if the number of currently available bytes is less
than @racket[amount] but at least one byte. The result can be an empty
string only if @racket[amount] is @racket[0] or @racket['avail].}

@defproc[(fd-write [handle handle?] [str string?]) void?]{

Writes the bytes of @racket[str] to the output file or output stream
associated with @racket[handle], erroring for any other kind of
@racket[handle].}

@defproc[(fd-terminal? [handle handle?] [check-ansi? any/c #f]) boolean?]{

Returns @racket[#t] if the open input or output stream associated with
@racket[handle] is a terminal, @racket[#f] otherwise. If
@racket[check-ansi?] is true, the result is @racket[#t] only if the
terminal is likely to support ANSI escape codes.

When using ANSI escapes that change the character style, consider
bracketing a change and restore with @racket[suspend-signal] and
@racket[resume-signal] to avoid leaving a terminal in a mangled state
after Ctl-C.}

@defproc[(fd-valid? [handle handle?]) boolean?]{

Reports whether a file descriptor opened by appears to be a valid file
descriptor, which is potentially useful after supplying an integer to
@racket[fd-open-input] or @racket[fd-open-output] }

@deftogether[(
@defproc[(file->string [name path-string]) string?]
@defproc[(display-to-file [str string?] [name path-string] [options hash? (hash)]) void?]
)]{

Convenience function to open @racket[name] and read its content into a
string or to write @racket[str] as its new content.}

@defthing[eof any/c]{

A constant representing an end-of-file.}

@deftogether[(
@defproc[(cleanable-file [name path?]) handle?]
@defproc[(cleanable-cancel [cleanable handle?]) void?]
)]{

The @racket[cleanable-file] function register @racket[name] as a file
name to delete on any exit, including errors or termination signals,
unless @racket[cleanable-cancel] is called on the handle to cancel the
clean-up action.}


@defproc[(process [executable path-string?] [args string-tree?] ... [options hash? (hash)]) hash?]{

Creates a new process to run @racket[executable] with arguments as the
flattened sequence @racket[args]. The result is a @tech{hash table} that at least
contains the key @racket['process] mapped to a handle representing the
new process. The process handle can be used with @racket[process-wait]
and @racket[process-status].

If @racket[options] is supplied, it controls the process creation and
may cause additional keys to be mapped in the result. The recognized
keys are as follows, and supplying an unrecognized key in
@racket[options] is an error:

@itemlist[

@item{@racket['dir] mapped to a path string: the working directory of
      the new porcess; if @racket[executable] is a relative path, it
      is relative to this directory}

@item{@racket['env] mapped to a list of pairs of strings: environment
      variables for the new process, where the @racket[car] or each
      pair is an environment variable name and the @racket[cdr] is its
      value}

@item{@racket['stdin] mapped to @racket['pipe]: creates a new output
      stream connected to the new process's standard input; the result
      hash table contains @racket['stdin] mapped to the new stream handle}

@item{@racket['stdin] mapped to an input stream: supplies (a copy of)
      the input stream as the new process's standard input}

@item{@racket['stdout] mapped to @racket['pipe]: creates a new input
      stream connected to the new process's standard output; the
      result hash table contains @racket['stdout] mapped to the new
      stream handle}

@item{@racket['stdout] mapped to an output stream: supplies (a copy
      of) the output stream as the new process's standard input}

@item{@racket['stderr] mapped to @racket['pipe]: creates a new input
      stream connected to the new process's standard error; the result
      hash table contains @racket['stderr] mapped to the new stream
      handle}

@item{@racket['stderr] mapped to an output stream: supplies (a copy
      of) the output stream as the new process's standard error}

@item{@racket['cleanable?] mapped to boolean (or any value): if
      @racket[#f], the Zuo process can exit without waiting for the
      created process to terminate; otherwise, and by default, the Zuo
      process waits for every processes created with @racket[process]
      to terminate before exiting itself, whether exiting normally, by
      an error, or by a received termination signal (such as Ctl-C).}

@item{@racket['exact?] mapped to boolean (or any value): if not
      @racket[#f], a single @racket[arg] must be provided, and it is
      provided as-is for the created process's command line on
      Windows. A non-@racket[#f] value for @racket['exact?] is not
      allowed on Unix.}

@item{@racket['exec?] mapped to boolean (or any value): if not
      @racket[#f], the target executable is run in the current
      process, after waiting for any other subprocesses and deleting
      cleanables. A non-@racket[#f] value for @racket['exact?] is not
      allowed on Windows or, more generally, when @racket[(hash-ref
      (runtime-env) 'can-exec?)] produced @racket[#f].}

]

See also @racket[shell].}

@defproc[(process-wait [process handle?] ...) handle?]{

Waits until the process represented by a @racket[process] has
terminated, erroring if a @racket[process] is any other kind of
handle. The result is the handle for a terminated process that's one
of the argument @racket[process] handles; waiting again on the same
handle will produce a result immediately.}

@defproc[(process-status [process handle?]) (or/c 'running integer?)]{

Returns @racket['running] if the process represented by
@racket[process] is still running, the exit value if the process has
exited (@racket[0] normally means succes), erroring for any other kind
of handle.}


@defproc[(find-executable-path [name path-string?]) (or/c path-string? #f)]{

Returns an absolute path to a file @racket[name], potentially by
consulting the @envvar{PATH} environment variable. If @racket[name] is
an absolute path already, and if a file @racket[name] exists, the
@racket[name] is returned as-is---except that @filepath{.exe} is added
on Windows if it produces an existent path name while @racket[name] by
itself does not exist. Otherwise, if a file exists relative to path in
@envvar{PATH} (with @filepath{.exe} potentially added on Windows),
that file's path is returned.

The @envvar{PATH} environment variable is treated as a list of
@litchar{:}-separated paths on Unix and @litchar{;}-separated paths on
Windows. On Windows, the current directory is automatically added to
the start of @envvar{PATH}.}

@deftogether[(
@defproc[(string->shell [str string?]) string?]
@defproc[(shell->strings [str string?] [starts-exe? any/c #f]) list?]
)]{

The @racket[string->shell] function converts a string to a
command-line fragment that encodes the same string. The
@racket[shell->strings] function takes a command-line fragment and
parses it into a list of strings in the same way the shell would. On
Windows, the shell parses an executable name differently than
arguments in a command, so provide a true value as
@racket[starts-exe?] if the command-line fragment @racket[str] starts
with an executable name.}


@section{Filesystem}

@defproc[(stat [name path-string?] [follow-links? any/c #t]) (or/c hash? #f)]{

Returns information about the file, directory, or link referenced by
@racket[name]. If @racket[follow-links?] is @racket[#f], then when
@racket[name] refers to a link, information is reported about the
link; otherwise, information is reported about the target of a link.

If no such file, directory, or link exists, the result is @racket[#f].
Otherwise, the hash table has similar keys and values as
@realracket[file-or-directory-stat] from @racketmodname[racket], but
with only certain keys per platform:

@itemlist[

 @item{Unix: @racket['device-id], @indexed-racket['inode],
       @racket['mode], @racket['type] (abbreviated),
       @racket['hardlink-count], @racket['user-id],
       @racket['group-id], @racket['device-id-for-special-file],
       @racket['size], @racket['block-size], @racket['block-count],
       @racket['access-time-seconds], @racket['modify-time-seconds],
       @racket['change-time-seconds],
       @racket['access-time-nanoseconds],
       @racket['modify-time-nanoseconds], and
       @racket['change-time-nanoseconds]}

 @item{Windows: @racket['device-id], @indexed-racket['inode],
       @racket['mode] (read and write bits only), @racket['type]
       (abbreviated), @racket['hardlink-count], @racket['size],
       @racket['access-time-seconds], @racket['modify-time-seconds],
       @racket['creation-time-seconds],
       @racket['access-time-nanoseconds],
       @racket['modify-time-nanoseconds], and
       @racket['creation-time-nanoseconds]}

]

The abbreviated @racket['type] field contains @racket['file],
@racket['dir], or @racket['link], with @racket['link] only on Unix and
only when @racket[follow-links?] is @racket[#f].}

@defproc[(ls [dir path-string?]) list?]{

Returns a list of path strings for files in @racket[dir].}

@defproc[(ls* [dir path-string?]) list?]{

Like @racket[ls], but builds a path using @racket[dir] for each
element of the result list.}

@defproc[(rm [name path-string?]) void?]{

Deletes the file or link @racket[name].}

@defproc[(rm* [name path-string?]) void?]{

Deletes the file, directory, or link @racket[name], including the
directory content if @racket[name] refers to a directory (and not to a
link to a directory). Unlike @racket[rm], it's not an error if
@racket[name] does not refer to an existing file, directory, or link.}

@defproc[(mv [name path-string?] [new-name path-string?]) void?]{

Renames the file, directory, or link @racket[name] to @racket[new-name].}

@defproc[(mkdir [dir path-string?]) void?]{

Creates a directory @racket[dir].}

@defproc[(mkdir-p [dir path-string?]) void?]{

Creates a directory @racket[dir] if it does not already exist, along
with its ancector directories.}

@defproc[(rmdir [dir path-string?]) void?]{

Deletes a directory @racket[dir].}

@defproc[(symlink [target path-string?] [name path-string?]) void?]{

Creates a symbolic link @racket[name] with the content @racket[target]. This
function is not supported on Windows.}

@defproc[(readlink [name path-string?]) void?]{

Gets the content of a link @racket[name]. This function is not
supported on Windows.}

@defproc[(cp [source path-string?] [destination path-string?]) void?]{

Copies the file at @racket[source] to @racket[destination],
preserving permissions and replacing (or attempting to replace)
@racket[destination] if it exists.}

@defproc[(cp* [source path-string?] [destination path-string?]) void?]{

Copies the file, directory, or link @racket[source] to a corresponding
new file, directory, or link @racket[destination], including the
directory content if @racket[source] refers to a directory (and not to
a link to a directory),.}

@deftogether[(
@defproc[(file-exists? [name path-string?]) booelan?]
@defproc[(directory-exists? [name path-string?]) booelan?]
@defproc[(link-exists? [name path-string?]) booelan?]
)]{

Uses @racket[stat] to check for a file, directory, or link,
respectively.}


@section{Run Time Configuration}

@defproc[(runtime-env) hash?]{

Returns a @tech{hash table} containing information about the current
Zuo process. The hash table includes the following keys:

@itemlist[

@item{@racket['args]: comment-line arguments provided when the
      process was started, not counting Zuo configuration arguments or
      the name of a script to run}

@item{@racket['dir]: the current directory}

@item{@racket['env]: a list of pairs of strings for environment variables}

@item{@racket['script]: the script provided to Zuo to run, which might
      be @racket[""] to indicate a script read from standard input}

@item{@racket['exe]: an absolute path for the running Zuo executable}

@item{@racket['system-type]: @racket['unix] or @racket['windows]}

@item{@racket['sys-dir] (Windows only): the path to the system directory}

@item{@racket['can-exec?]: a boolean whether @racket[process] supports
      a true value for the @racket['exec?] option}

@item{@racket['version]: Zuo's version number as an integer}

]}

@defproc[(system-type) symbol?]{

Returns @racket[(hash-ref (runtime-env) 'system-type)].}

@defproc[(current-time) pair?]{

Reports the current wall-clock time as a pair: seconds since January
1, 1970 and additional nanoseconds.}

@defproc[(dump-image-and-exit [output handle?]) void?]{

Writes an image of the current Zuo process to @racket[output], which
must be an open output file or stream, and then exits.

This function is intended to be used after some set of modules has
been loaded, so that the loaded modules are included in the image. The
dump fails if if any @tech{handle} is encountered as reachable from
loaded modules, however.}

@defproc[(exit [status integer? 0]) void?]{

Exits the Zuo process with the given @racket[status], where @racket[0]
normally means ``success.''}


@deftogether[(
@defproc[(suspend-signal) void?]
@defproc[(resume-signal) void?]
)]{

Suspends or resumes recognition of termination signals, such as Ctl-C.
Calls can be nested, and each call to @racket[suspend-signal] must be
balanced by a call to @racket[resume-signal] to re-enable signal
handling.}