logo raviqqe's logbook

cond-expand in library declarations in Stak Scheme

Summary

Current behavior (verified)

Where to evaluate the clauses

Before collect-bodies runs, flatten the declarations recursively and replace each cond-expand declaration with the declarations of its first fulfilled clause.

Option B: evaluate them in include-files

Dropping unselected branches before inclusion avoids reading their files. However, no library is registered yet when include-files runs, so (library ...) requirements cannot see the prelude’s libraries or libraries defined in the program.

Option C: process all library declarations in the library stage

This is Option A plus processing the include, include-ci, and include-library-declarations declarations one at a time while registering a library, with include-files called only on the selected begin bodies. It follows R7RS most closely and never reads files in unselected branches, but it is the largest change. It matters when a branch for another implementation includes a file that does not exist.

Rejected: reusing the cond-expand macro

Library registration precedes macro expansion in both compilers, and the batch compiler has no macros at that point.

Where the feature list lives

Option a: copy the list into the compiler

This is the least work, but the two copies of the list can drift apart.

Generalize $$if-library into a special form that evaluates whole feature requirements (and, or, not, library, and feature identifiers), and let Option A’s splicing call the same evaluator. The prelude macro then shrinks to iterating over clauses.

(define-syntax cond-expand
  (syntax-rules (else)
    ((_)
      (syntax-error "unfulfilled cond-expand"))
    ((_ (else body ...))
      (relaxed-begin body ...))
    ((_ (requirement body ...) clause ...)
      ($$if-requirement
        requirement
        (relaxed-begin body ...)
        (cond-expand clause ...)))))

(features) would get the list from another small special form. Because requirements are evaluated as data rather than matched as literals, full-unicode, ieee-float, and little-endian start matching. Requirement symbols need de-renaming before comparison, as $$if-library already does for library names.

Option c: keep the list in the prelude and let the compiler read it

The runtime compiler would also need the list shipped as metadata, as $$libraries is. This has more moving parts than Option b for no benefit.

The two include forms

Moving the expression-level form changes some behavior:

Once both forms move, include-files disappears. The two moves can land separately. The library stage comes first because declaration-level cond-expand needs it, and include-files skips define-library forms in the meantime so that it does not read files in unselected declaration branches.

Carrying the originating directory

Nested includes resolve relative to the including file, and includes in a library body resolve relative to its .sld file. The macro expander has no source locations, and expand-library-bodies splices library bodies into the program, which loses where they came from. Therefore, a directory has to cross the boundary between the two stages.

Library stage

Macro expansion stage

The marker survives the library stage because symbols prefixed with $$ are built-in symbols that library renaming leaves alone. It disappears in macro expansion, so later passes never see it. Both compilers share the code, so eval supports it too.

Decisions this design forces

Rejected alternatives

Recommendation

  1. Splice cond-expand library declarations in register-library! (Option A) with a requirement evaluator in the compiler frontend, and move the feature list there (Option b), generalizing $$if-library for the expression-level macro.
  2. Process the include, include-ci, and include-library-declarations declarations in the library stage as well (Option C), with include-files skipping define-library forms. This can wait until a library hits a missing file in an unselected branch.
  3. Move the expression-level include into macro expansion with a directory field on the macro context and a $$directory marker around library bodies, which removes include-files.

Every step changes the compiler frontend. The bytecode snapshots of the programs that embed the compiler for eval (run.scm, repl.scm, compile.scm, and the eval benchmark) change, and the frontend has to stay portable R7RS for the CI job that compares bytecode compiled by Chibi Scheme, Guile, and Stak byte for byte.

Decisions only the maintainer can make

Sources