# UTILS module – various utility functions

Various utility functions

This module contains a variety of utility functions which are otherwise missing from the implementation.

## Standard scheme functions

‘Standard’ scheme functions:

- call-with-values
- define-values
- receive (ie, SRFI-8)

Miscellaneous utilities:

- define/trace
- en-dashify
- getopt
- make-set/eqv
- resolve-file
- showbytes/hex
- split-path
- string->hash
- string->page-range
- expand-tex-accents
- stringify
- stringify/true
- subprocess
- symbol<?

## Character wrangling

Case modifications:

- `uppercase-string/bst`, `lowercase-string/bst`, `titlecase-string/bst` : case modifications with the peculiarities defined by BibTeX.
- `ustring-upcase`, `ustring-downcase`, `ustring-titlecase`, `uchar-upcase`, `uchar-downcase`, `uchar-titlecase` : more conventional case modifications. The function names here are chosen to match the `string-upcase` (etc) versions built in to s7; `uppercase-string` might be a more attractive choice.

ctype-style character classes:

- char-alnum? (Unicode categories L or N)
- char-alpha? (L)
- char-cntrl? (C)
- char-digit? (N)
- char-lower? (Ll)
- char-punct? (P)
- char-space? (Z)
- char-upper? (Lu or Lt)

These commands take both characters and integers as arguments, and match `ctype(3)` when given arguments in the ASCII range.

They will also behave correctly when given Unicode codepoints (as integer); thus `char-alnum?` will return true for Unicode general categories L or N, and so on. As a special case `char-space?` will return true for category Z plus the ASCII whitespace characters below U+0020, for which `isspace` returns true (these are category ‘C’ for Unicode). All of these functions will return `#f` without error, if given an argument other than a character or integer. Also note that these functions are (currently) Unicode-aware only for characters in the Base Multilingual Plane (BMP), and will return `#f` for any codepoints beyond that (ie, from Linear-B upwards).

The function `uchar-alphabetic?` identifies a character as ‘alphabetic’ in the Unicode sense, which is slightly broader than `char-alnum?`. See the unicode module for more discussion.

The following functions are analogous, but have no ctype counterparts.

- char-other-letter? (Unicode L other than Lu, Lt or Ll)
- char-symbol? (S)
- char-mark? (M)

The following functions match the ctype ones, but have no Unicode-related extensions.

- char-blank?
- char-graph?
- char-print?
- char-xdigit?

The following functions are the ones defined in R5RS. They (currently) work only on characters, and are not defined on integers.

- char-alphabetic?
- char-downcase
- char-lower-case?
- char-numeric?
- char-upcase
- char-upper-case?
- char-whitespace?

## SRFIs

From SRFI-1 (a partial implementation, in some cases):

- any
- circular-list
- drop
- drop-right
- every
- filter
- fold
- last
- last-pair
- split-at
- take
- take-right
- zip

...plus `intersperse`, which feels like it ought to be in SRFI-1, but isn't.

From SRFI-13:

- string-index
- string-index-right
- string-join
- string-prefix?
- string-split
- string-suffix?
- string-trim
- string-trim-both
- string-trim-right

## Functions

Index:

- absolute-path?
- any
- build-path
- call-with-values
- char-alnum?
- char-alpha?
- char-blank?
- char-cntrl?
- char-digit?
- char-graph?
- char-lower?
- char-mark?
- char-nbsp?
- char-print?
- char-punct?
- char-space?
- char-symbol?
- char-upper?
- char-wordbreak?
- char-xdigit?
- circular-list
- current-directory
- define-values
- define/trace
- delay
- drop
- drop-right
- every
- explode-path
- filter
- flatten
- fold
- force
- getopt
- intersperse
- last
- last-pair
- make-set/eqv
- path->complete-path
- path-replace-extension
- promise?
- receive
- relative-path?
- resolve-file
- setenv
- showbytes/hex
- split-at
- split-path
- string->hash
- string-index
- string-index-right
- string-join
- string-prefix?
- string-split
- string-suffix?
- string-tokenize
- string-trim
- string-trim-both
- string-trim-right
- stringify
- stringify/true
- subprocess
- symbol<?
- take
- take-right
- uchar-alphabetic?
- uchar-downcase
- uchar-titlecase
- uchar-upcase
- uchar-word-character?
- zip

### absolute-path?

Return `#t` if `path` is an absolute path, and `#f` otherwise.

### any

`(any pred? l)` : Applies the predicate across the list, returning true if the predicate returns true on any application (from SRFI-1; simple case of a single list).

`any` applies `pred` to the first element of the list. If this application returns a true value, `any` immediately returns that value. Otherwise, it iterates, applying `pred` to the second element of the list, then the third, and so forth. The iteration stops when a true value is produced or the list runs out of values; in the latter case, `any` returns `#f`.

If `l` is a list, then the application of `pred` to the last element of the list is a tail call.

### build-path

`(build-path base sub ...)` : creates a path given a `base` path and any number of sub-path extensions. If `base` is an absolute path, the result is an absolute path, otherwise the result is a relative path.

The `base` and each `sub` must be either a relative path, the symbol `'up` (indicating the relative parent directory), or the symbol `'same` (indicating the relative current directory).

### call-with-values

The standard call-with-values procedure.

Usage: `(call-with-values (lambda () (values ...)) (lambda (a1 ...) ...))`

### char-alnum?

Return #t if the argument is character or integer, and is alphanumeric (Unicode L or N), and #f otherwise

### char-alpha?

Return #t if the argument is character or integer, and is alphabetic (Unicode L), and #f otherwise

### char-blank?

Return #t if the argument is character or integer, and is blank (in terms of ctype.h), and #f otherwise

### char-cntrl?

Return #t if the argument is character or integer, and is control (ctype iscntrl, plus Unicode Cc & Cf), and #f otherwise

### char-digit?

Return #t if the argument is character or integer, and is a digit (Unicode N), and #f otherwise

### char-graph?

Return #t if the argument is character or integer, and is graphic (in terms of ctype.h), and #f otherwise

### char-lower?

Return #t if the argument is character or integer, and is lower-case alphabetic (Unicode Ll), and #f otherwise

### char-mark?

Return #t if the argument is character or integer, and is mark (Unicode M), and #f otherwise

### char-nbsp?

Return #t if the argument is character or integer, and is true if the specified codepoint is a non-breaking space, and specifically whether it is U+00A0 NBSP, or U+2007 Figure Space or U+202F Narrow NBSP. Compare `char-wordbreak?`., and #f otherwise

### char-print?

Return #t if the argument is character or integer, and is printing (in terms of ctype.h), and #f otherwise

### char-punct?

Return #t if the argument is character or integer, and is punctuation (Unicode P), and #f otherwise

### char-space?

Return #t if the argument is character or integer, and is a space (Unicode Z, but including the ASCII whitespace characters below U+20).

There is more than one reasonable definition of whitespace. The whitespace characters below U+20, such as newline and tab, are not whitespace in Unicode terms, but we include them here in order to match POSIX/ctype isspace; this also includes non-breaking spaces as whitespace (though, eg, Java's whitespace definition doesn't)., and #f otherwise

### char-symbol?

Return #t if the argument is character or integer, and is symbol (Unicode S), and #f otherwise

### char-upper?

Return #t if the argument is character or integer, and is upper-case alphabetic (Unicode Lu or Lt), and #f otherwise

### char-wordbreak?

Return #t if the argument is character or integer, and is true if the specified code point is a whitespace character according to Java/ICU.

This is similar to `char-space?`, except that it does not include the non-breaking space codepoints (U+00A0 NBSP, or U+2007 Figure Space or U+202F Narrow NBSP). It is therefore defined to match the Java `Character.isWhitespace` function; See Unicode ICU docs uisWhitespace function for further discussion., and #f otherwise

### char-xdigit?

Return #t if the argument is character or integer, and is a hex-digit (in terms of ctype.h), and #f otherwise

### circular-list

Constructs a circular list of the elements (from SRFI-1).

    (circular-list 'z 'q) => (z q z q z q ...)`

### current-directory

`(current-directory)` : return the current working directory, as a string.

### define-values

Standard form: `(define-values (a b) (values 1 2))` defines `a` and `b` to have the given values.

### define/trace

Like (define ...). but tracing calls to the function (for debugging)

### delay

`(delay body ...)` : Creates a ‘promise’ that, when `force`d, evaluates the `body`s to produce the value of the last expression in the body. The result is then cached, so further uses of `force` produce the cached value immediately.

### drop

`(drop l i)` : return all but the first `i` elements of list `l` (from SRFI-1)

### drop-right

`(drop-right l i)` : returns all but the last `i` elements of `l` (from SRFI-1)

### every

`(every pred? l)` : Applies the predicate across the list or iterator, returning true if the predicate returns true on every application (from SRFI-1; simple case of a single list).

`every` applies `pred` to the first element of the list parameter. If this application returns false, `every` immediately returns false. Otherwise, it iterates, applying `pred` to the second element of the list, then the third, and so forth. The iteration stops when a false value is produced or the list runs out of values. In the latter case, `every` returns the true value produced by its final application of `pred`.

When `l` is a list, the application of `pred` to the last element of the list is a tail call.; when `l` is an iterator, the function still returns the last application of `pred?`, but it is not a tail-call.

If the list or iterator has no elements, every simply returns #t.

### explode-path

`(explode-path path)` : split the path into a list of path components.

If the path is absolute, then the first component in the list will be `"/"`. Path elements `"."` and `".."` will be replaced by symbols `same` and `up` respectively.

### filter

`(filter pred? list)` : Return all the elements of list that satisfy predicate `pred?`. The list is not disordered -- elements that appear in the result list occur in the same order as they occur in the argument list.

The input ‘list’ may also be an iterator, or something which has an implicit iterator.

From SRFI-1.

### flatten

`(flatten v)` : Flattens an arbitrary S-expression structure of proper lists into a single list. More precisely, `v` is treated as a binary tree where pairs are interior nodes, and the resulting list contains all of the non-null leaves of the tree in the same order as an in-order traversal.

Example:

    > (flatten '((a) b (c (d) e) ()))
    '(a b c d e)
    > (flatten 'a)
    '(a)

[specification taken from Racket, but with the added requirement that the input have no improper lists].

### fold

`(fold kons knil l)` : The fundamental list iterator.

Given a list `(e1 e2 ... en)`, this evaluates to

    (kons en ... (kons e2 (kons e1 knil)) ... )

The input ‘list’ may also be an iterator, or something which has an implicit iterator.

From SRFI-1.

### force

`(force v)` : If v is a promise, then the promise is forced to obtain a value. If the promise has not been forced before, then the result is recorded in the promise so that future forces on the promise produce the same value.

If v is not a promise, then it is returned as the result.

### getopt

`(getopt spec [:command-line '("command" "arg" ...)` : Given a command-line (which defaults to `*command-line*` if the second argument is not present), parse it according to the option spec in the first argument.

The `spec` is a list of lists with at least three elements: either `(#\c "docstring" expr ...)` or `(#\c arg "docstring" expr ...)`. This will detect an option `-c` (or `-c arg` in the second case), and evaluate the `expr ...`, for side-effects.

Example:

    (getopt '((#\a "set 'a' true" (set! a-flag #t))))

will take a `*command-line*` of `'("name" "-a" "one" "two")`, set `a-flag` to true, and evaluate to `'("one" "two")`.

If there is no `-h` option, then this synthesises one from the docstrings.

Returns the command name (the first item in `*command-line*`), an alist of detected options plus the return from their handler, and the remainder of the argument list, as multiple values.

### intersperse

`(intersperse inter l)` : return a new list, with object `inter` between each element of the list `l`.

Thus `(intersperse 'x '(a b c))` evaluates to `(a x b x c)`.

### last

`(last l)` : return the last element of the non-empty, finite list L (from SRFI-1)

### last-pair

`(last-pair l)` : return the last pair in the non-empty, finite list L (from SRFI-1)

### make-set/eqv

`(make-set/eqv l)` : lookup, (listof symbol?) -> (symbol? -> boolean?). Given a list of symbols, return a function f s.t. `(f 'x)` is true if `'x` was in the list. Also

- `(f 'add '(sym...))` adds `'sym` to the set;
- `(f 'contains 'sym)` is true if `'sym` is in the set (ie, same as `(f 'sym)`);
- `(f 'n 'sym)` returns the number of times `'sym` bas been added;
- `(f 'list proc)` returns `proc` applied to each of `(key . n)`, as a list.

### path->complete-path

`(path->complete-path path [base])` : turn a path into a complete path. If path is already complete, then it is returned; if not, it is appended to base. It is an error if base is not a complete path.

### path-replace-extension

`(path-replace-extension path ext)` : Returns a path that is the same as `path`, except that the extension for the last element of the path (including the extension separator) is changed to `ext`. If the last element of `path` has no extension, then `ext` is added to the path.

An extension is defined as a `.` that is not at the start of the path element followed by any number of non-`.` characters/bytes at the end of the path element, as long as the path element is not a directory indicator like "..".

### promise?

`(promise? p)` : true if p is a promise, created by `(delay)`

### receive

`(receive (v ...) expr body...`) : receive multiple values [from SRFI-8].

Example:

    (receive (v1 v2)
        (values 1 2)
      (printf "v1=~s~%" v1)
      (list v2 v1))

prints `v1=1` and evaluates to `(2 1)`.

### relative-path?

Return `#t` if `path` is an relative path, and `#f` otherwise.

### resolve-file

`(resolve-file/kpse fn ext [:error-if-not-found? #t])` : Resolve a file to a full path, using `kpsewhich`. Given a file `fn`, try to resolve this to a full path, using the kpathsea mechanism, and trying both without and with the `ext` appended.

If keyword `:error-if-not-found?` is true (the default), then throw a beastie-error if no file is found; if this is false return `#f` if no file can be found.

Since it uses `kpsewhich`, it respects the environment variables `$BIBINPUTS`, etc.

### setenv

`(setenv envvar (or/c string? #f))` : set the environment variable `envvar` to the given value. If the value is `#f`, the environment variable is deleted.

### showbytes/hex

Debugging: show the bytes in the given string.

### split-at

`(split-at l idx)` : splits the list L at index I, returning a list of the first I elements, and the remaining tail, as multiple values. It is equivalent to

    (values (take x i) (drop x i))

(from SRFI-1)

### split-path

`(split-path path)` : splits a filesystem path into components

    (split-path string?) -> (or/c string? 'relative #f)
                            (or/c string? 'up 'same)
                            boolean?

Deconstructs path into a smaller path and an immediate directory or file name. Three values are returned:

`base` is either

- a path,
- `'relative` if path is an immediate relative directory or filename, or
- `#f` if path is the root directory.

`name` is either

- a directory-name path,
- a filename,
- `'up` if the last part of path specifies the parent directory of the preceding path (e.g., .. on Unix), or
- `'same` if the last part of path specifies the same directory as the preceding path (e.g., . on Unix).

`must-be-dir?` is `#t` if `path` explicitly specifies a directory (e.g., with a trailing separator), `#f` otherwise.

This doesn't do any processing of redundant separators.

### string->hash

`string->hash : string? -> integer?` : Given a string, return an integer hash for it. This is an unsophisticated hash function, merely using the K&R/Java hash function

### string-index

`(string-index s c [:start 0] [:end #f])` : Return the index of the first character in `s` which is `c`, where `c` is a procedure, character, or string; in the last case, the procedure returns the first index containing any character in the string `c` [From SRFI-13, but with 'char-class' represented as a string].

The `:start` and `:end` keyword arguments delimit the scan, and default to the start and end of the string; these arguments are indexes into the string, with `end` indicating the index one past the last character to be considered; `end` may be `#f` to indicate the end of the string.

Returns `#f` if the character is not present.

### string-index-right

`(string-index-right s c [:start 0] [:end #f])` : As `string-index`, but searching leftwards from the end.

### string-join

`(string-join l [delim [grammar]])` : join the list of strings `l`, separated by string `delim` (default `" "`).

The `grammar` argument is a symbol that determines how the delimiter is used, and defaults to `'infix`.

- `'infix` means an infix or separator grammar: insert the delimiter between list elements. An empty list will produce an empty string.
- `'strict-infix` means the same as `'infix`, but will raise an error if given an empty list.
- `'suffix` means a suffix or terminator grammar: insert the delimiter after every list element.
- `'prefix` means a prefix grammar: insert the delimiter before every list element.

### string-prefix?

`(string-prefix? s1 s2)` : Is `s1` a prefix of `s2`? (SRFI-13 without optional arguments)

### string-split

`(string-split s c)` : Split a string at a given character. Argument `s` must be a `string?`. Argument `c` can be a character, a predicate, or a string containing split characters. The empty string is split to an empty list (This isn't in SRFI-13, which instead has `string-tokenize`, which splits based on the char-set of the items to be included).

Repeated separator characters result in empty fields. That is `(string-split "a:b::c" #\:)` produces `'("a" "b" "" "c")`.

### string-suffix?

`(string-suffix? s1 s2)` : Is `s1` a suffix of `s2`? (SRFI-13 without optional arguments)

### string-tokenize

`(string-tokenize s)` : split the string `s` into a list of substrings, where each substring is a maximal non-empty contiguous sequence of characters separated by whitespace.

This is a (still) cut-down version of the function from SRFI-13, restricted to whitespace, and without the start and end parameters.

### string-trim

`(string-trim s c)` : Trim whitespace from the start of string `s`. Argument `c` can be a character, a predicate, or a string containing characters to trim. (from SRFI-13, but currently without the start/end arguments)

### string-trim-both

As with `string-trim`, but trimming from both sides

### string-trim-right

As with `string-trim`, but trimming from the right

### stringify

`(stringify x)` : Turn X into a string, one way or another.

### stringify/true

`(stringify/true x)` : like `(stringify x)`, unless `x` is `#f, when it evaluates to` #f`. Special-casing` #f`means this is useful alongside functions such as` maybe-sprintf`.

### subprocess

`subprocess : string? ... -> (or string? #f)`(subprocess "program" ...) : Call the first argument, as a command, passing the others as arguments. Returns stdout as a string, or #f if the command returns non-zero, or raise 'subprocess if the command can't be run.

If the PROGRAM doesn't start with a slash, '/', it is looked up in the path.

### symbol<?

`symbol<? : symbol? ... -> boolean?` : true if the symbols `s1`... are strictly ordered. The comparison is equivalent to that which would arise from using `symbol->string`.

### take

`(take l i)` :return the first `i` elements of list `l` (from SRFI-1)

### take-right

`(take-right l i)` : returns the last `i` elements of list `l` (from SRFI-1)

### uchar-alphabetic?

Returns `#t` if the character has the Unicode ‘Alphabetic’ property. That is, it is lowercase, uppercase, of classes Lt, Lm, Lo, or Nl, or is ‘other alphabetic’. See the Unicode Character Database for details.

The induced set of characters is a superset of that induced by `char-letter?`. This function is also distinct from the s7 `char-alphabetic?` function, which is defined only for ASCII characters (but it overlaps with that function in that range).

### uchar-downcase

Change the given character to lowercase. Analogous to `uchar-upcase`, qv.

### uchar-titlecase

Change the given character to titlecase. Analogous to `uchar-upcase`, qv.

### uchar-upcase

`(uchar-upcase c)` : change the given character to uppercase.

This matches `char-upcase` for ASCII characters, but it also works correctly for other Unicode characters. The function accepts either a character or an integer argument, and returns an integer. If given an argument of another type, including string types, it returns the argument unchanged.

### uchar-word-character?

Returns `#t` if the character argument should be regarded as a part of a word. The induced character set includes `uchar-alphabetic?` (and thus `char-alpha?`), but includes (the Unicode categories) diacritics, extender characters and join-control characters.

There isn't a formal definition of this set within Unicode, but this set has been described as ‘a decent approximation of what is (naively) expected to fall within an “alphabetic” string for most scripts’.

### zip

`(zip l1 l2 ...) -> list` : If zip is passed n lists, it returns a list as long as the shortest of these lists, each element of which is an n-element list comprised of the corresponding elements from the parameter lists.

    (zip '(one two three)
         '(1 2 3)
         '(odd even odd even odd even odd even))
        => ((one 1 odd) (two 2 even) (three 3 odd))

    (zip '(1 2 3)) => ((1) (2) (3))

At least one of the argument lists must be finite:

    (zip '(3 1 4 1) (circular-list #f #t))
        => ((3 #f) (1 #t) (4 #f) (1 #t))

(from SRFI-1)

Norman
2026 August 02