ref: cbc4dbd38efbc9e123a2f30df8163fcdf5f22a3b
dir: /zuo-doc/lang-zuo.scrbl/
#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 from @racketmodname[zuo/cmdline], @racketmodname[zuo/build], @racketmodname[zuo/shell], @racketmodname[zuo/thread], @racketmodname[zuo/glob], or @racketmodname[zuo/config]. When using @racket[module->hash] on @racketmodname[zuo/base], @racketmodname[zuo], or a module implemented with one of those languages, the resulting hash table includes @racket['dynamic-require] mapped to the @racket[dynamic-require] function. Getting @racket[dynamic-require] that way provides a path from the primitive @seclink["module-protocol"]{Zuo kernel module protocol} to @racketmodname[zuo/base] module exports. @history[#:changed "1.2" @elem{Added the @racket['dynamic-require] key for @racketmodname[zuo] and related languages.}] @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[provide]), 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 macros 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 encounters 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 the 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} form. 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] is represented using plain pairs, and non-identifier elements 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)]{ Produces 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 (rename-out 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's 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 @racketmodname[racket].} @defproc[(eq? [v1 any/c] [v2 any/c]) boolean?]{ Analogous to @realracket[eq?] from @racketmodname[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 @racketmodname[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 (any/c . -> . any/c)] [lst list?]) void?] @defproc[(foldl [proc (any/c any/c . -> . any/c)] [init any/c] [lst list?]) any/c] @defproc[(andmap [proc (any/c . -> . any/c)] [lst list?]) any/c] @defproc[(ormap [proc (any/c . -> . any/c)] [lst list?]) any/c] @defproc[(filter [proc (any/c . -> . any/c)] [lst list?]) list?] @defproc[(sort [lst list?] [less-than? (any/c any/c . -> . any/c)]) 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-ci=? string<?] from @racketmodname[racket], or more precisely analogous to @realracket*[bytes? bytes bytes-length bytes-ref subbytes bytes=? bytes<?] (and, in principle, @racket[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-sha256 [str string?]) string?]{ Returns the SHA-256 hash of @racket[str] as a 64-digit hexadecimal string. See also @racket[file-sha256] and @racket[sha256-length].} @defform[(char str)]{ Expands to @racket[(string-ref str 0)], where @racket[str] must be a literal string of length 1.} @defproc*[([(string-split [str string?]) (listof string?)] [(string-split [str string?] [sep string?]) (listof string?)])]{ 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, unlike 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], but @racket[string->symbol] accepts only strings that do not contain the null character.} @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 tables 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 hash?] [key symbol?] [v any/c]) hash?] @defproc[(hash-remove [hash hash?] [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 are two additional differences: @itemlist[ @item{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; and} @item{the @racket[hash-keys] function returns interned keys sorted alphabetically.} ]} @section{Procedures} @deftogether[( @defproc[(procedure? [v any/c]) any/c] @defproc[(apply [proc procedure?] [lst list?]) any/c] @defproc[(call/cc [proc (any/c . -> . any/c)]) any/c] @defproc[(call/prompt [proc (-> any/c)] [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 a @tech{string} that is not empty and contains no null 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[path] 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 relative @racket[rel] paths, 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 find 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 as 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 it 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{.zuo}, 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. See also @secref["reader"].} @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 characters are printed followed by @litchar{: }. All other @racket[v]s (including the first one if it's not a string) are combined using @racket[~v], and that resulting string's characters are printed.} @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 output streams, 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[eof] and if 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[eof]. On Windows, @racket['avail] mode is not supported for console input. @history[#:changed "1.5" @elem{Report @racket[eof] when available in @racket['avail] mode.}]} @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-poll [handles (listof handle?)] [timeout-msecs (or/c integer? #f) #f]) (or/c handle? #f)]{ Given a list of open input and output file descriptor handles as @racket[handles], checks whether any is ready for reading or writing. If @racket[timeout-msecs] is @racket[#f], @racket[fd-poll] blocks until at least one is ready, and then it returns the first element of @racket[handles] that is ready. If @racket[timeout-msecs] is a number, then it specifies a number of milliseconds to wait; the result is @racket[#f] if no handle in @racket[handles] is ready before @racket[timeout-msecs] milliseconds pass. @history[#:added "1.1"]} @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-string?]) handle?] @defproc[(cleanable-cancel [cleanable handle?]) void?] )]{ The @racket[cleanable-file] function registers @racket[name] as a file 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); any still-open input or output pipe created for the process is closed before waiting for processes to exit.} @item{@racket['exact?] mapped to boolean (or any value): if not @racket[#f], a single @racket[args] 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['exec?] is not allowed on Windows or, more generally, when @racket[(hash-ref (runtime-env) 'can-exec?)] is @racket[#f].} ] See also @racket[shell]. @history[#:changed "1.1" @elem{Pipes created for a process are explicitly closed when a Zuo will terminate, and they are closed before waiting for processes to exit.}]} @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 success), 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]) (listof string?)] )]{ 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] [false-on-error? any/c #f]) (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 @racket[name] is a valid path but no such file, directory, or link exists, the result is @racket[#f]. If accessing @racket[name] encounters an error (e.g., @racket[name] uses a file as a directory or permission is denied), then @racket[#f] is reported instead of an error if @racket[false-on-error?]. 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?]) (listof path-string?)]{ Returns a list of path strings for files in @racket[dir].} @defproc[(ls* [dir path-string?]) (listof path-string?)]{ 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.} @deftogether[( @defproc[(cp [source path-string?] [destination path-string?] [options hash? (hash)]) void?] @defthing[:no-replace-mode hash?] )]{ Copies the file at @racket[source] to @racket[destination], replacing (or attempting to replace) @racket[destination] if it exists. On Unix, if @racket[destination] does not exist, it is created with the mode (i.e., permissions) specified by @racket['mode] in @racket[options], which must be an integer between 0 and 65535 inclusive; if @racket['mode] is not provided, the mode of @racket[source] is used. The creation-time mode can be modified by the process's umask, but unless @racket[options] maps @racket['replace-mode] to @racket[#f], the mode is explicitly applied again to @racket[destination]---whether @racket[destination] was just created or exists already, and ignoring the process's umask. On Windows, the attributes of @racket[source] are always copied to @racket[destination], and if @racket['mode] is provided, then the file is made read only if and only if the @scheme[bitwise-and] of the mode value and @racket[2] is @racket[0]. The @racket[:no-replace-mode] hash table maps @racket['replace-mode] to @racket[#f]. @history[#:changed "1.6" @elem{Added the @racket[options] argument and @racket[:no-replace-mode].}]} @defproc[(cp* [source path-string?] [destination path-string?] [options hash? (hash)]) 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). The @racket[options] argument is passed along to individual file-copy operations. @history[#:changed "1.6" @elem{Added the @racket[options] argument.}]} @deftogether[( @defproc[(file-exists? [name path-string?]) boolean?] @defproc[(directory-exists? [name path-string?]) boolean?] @defproc[(link-exists? [name path-string?]) boolean?] )]{ Uses @racket[stat] to check for a file, directory, or link, respectively. If @racket[stat] encounters an error (e.g., access permission), then the file, directory, or link is treated as non-existent.} @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]: command-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['toolchain-type]: @racket['unix] (@exec{cc}, etc.) or @racket['windows] (@exec{cl.exe}, etc.); on Windows, the default is determined by the compiler used to build the Zuo executable, but it can be set explicitly by defining either the @tt{ZUO_WINDOWS_TOOLCHAIN} or @tt{ZUO_UNIX_TOOLCHAIN} preprocessor symbol when compiling Zuo} @item{@racket['sys-dir] (Windows only): the path to the system directory} @item{@racket['can-exec?]: a boolean indicating whether @racket[process] supports a true value for the @racket['exec?] option} @item{@racket['version]: Zuo's major version number as an integer} @item{@racket['minor-version]: Zuo's minor version number as an integer} ] @history[#:changed "1.1" @elem{Added @racket['minor-version].}]} @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 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.}