This document explains how Spheres are built and their internal structure. This will allow you to customize or create your own spheres, so they could be included in SchemeSpheres releases.
SchemeSpheres is born with one premise: the project must be useful to the Scheme Community even if it eventually gets stalled. The code compiled and created in this project should be highly usable by any other Scheme implementation or similar project for Gambit. For this reason, a very important aspect of SchemeSpheres is that it aims at being non-intrusive to R5RS code. Modules in SchemeSpheres should work seamlessly in Gambit (with macro expansion and provided you supply the necessary dependencies).
SchemeSpheres, as a project, is comprised of three complementary parts:
Hygienic macros expansion, modules and conditional compilation. These features must be usually solved together, as they all transform the code at compile-time. Many compilers, as is the case with Gambit, leave hygienic macros and related syntax expansion to the user implementation.
Workflow tool-chain. This is perhaps the most distinctive feature of SchemeSpheres in comparison with package systems. Essentially, the framework provides a set of tools for creating workflows, instead of forcing the user through a generic one. The workflow is divided into three parts: project skeleton generation, task running, and dependency management. These are handled currently by three tools:
Ecosystem. The set of aspects that help SchemeSpheres build community and collaboration around the project. Such a project benefits from the contribution of any Scheme developer that may desire to, so it must be prepared to integrate modules, tests, generators and tasks. The main points that fall under this category are:
In summary, Spheres is a set of tools/scripts and libraries on top of Gambit that will take care of the most tedious work of developing software with Gambit. Some examples are:
SchemeSpheres uses a custom version of Psyntax, developed by Matt Hastie. It loads fast and expands code fast, and can be used when remotely debugging mobile devices. It supports syntax-rules and syntax-case (define-macro is defined in terms of syntax-case, thus is supported as well). This opens us to full R5RS compatibility, and much of the SRFIs and amazing community code that relies on syntax-rules.
There is no module system currently in SchemeSpheres, so namespaces can be used instead. That will be improved in the near future.
The macros ##spheres-load and ##spheres-include take care of loading and including the necessary files to make the dependencies work:
(##spheres-load sphere: module): brings modules and all its dependencies. This is the main interface for importing modules, and takes care of importing all the code and the macros to be used in the REPL.
(##spheres-include sphere: module): brings in a macro-only file. This will include only macros to the current environment. You won't generally use this, because ##spheres-load will generally do this for you. But it can be used to force re-including already included files.
Alternative syntax: for modules local to the current spheres, you can use the syntax (##spheres-load module).
Dependencies in the process of (cross-)compilation are handled by ssake, via the tasks defined in sakefile.scm. Details regarding the implementation of this file are given bellow. Basically, it works by expanding all the code before compilation, and setting module files in order, so headers, FFI, macro expansion and code are handled in order for every platform.
A sphere is Git repository, named starting with sphere-. This allows the remote fetching and installation of the spheres, along all its versions. The versioning system will eventually be based on the tags functionality given by Git.
This repository consists of the following mandatory items:
src/: This directory contains the module files that will be built. It needs to be named like this for consistency.
config.scm: This file lists the dependencies.
sakefile.scm: This file implements the default or custom procedures for building the sphere, and any other kind of automation that it requires.
Also, you will generally find the following items:
build (generated): This shouldn't be in the sphere's repository, but it is generated by the Sake scripts. It contains the temporary build files. It keeps intermediate files for exploration and debugging.
doc/ (optional): Ideally, the project will contain in-depth documentation, either generated from comments, or externally specified.
lib/ (generated): This shouldn't be in the sphere's repository, but it is generated by the Sake scripts. It contains the compiled modules, ready to use.
test/ (optional): Optional, but recommended. Testing the code brings the sphere to the next level of reliability and maturity.
LICENSE: This file contains the license applied to all files in the sphere, unless stated otherwise in the file itself or the documentation.
README: This file contains instructions or links to instructions and basic usage of the sphere.
Modules can be comprised of up to four kinds of files:
This file holds the basic information about the sphere. It includes:
It's probably better to look at the code:
;; Name of the Sphere (sphere: "my-sphere") ;; Dependencies, per module (dependencies: ;; The module in the current sphere (my-module ;; 'Macros' file from module1 (in sphere1) needed by my-module (include (sphere2: module1-macros)) ;; 'Prelude' file from module1 (in sphere1) needed by my-module (prelude (sphere1: module1-prelude)) ;; 'Base' file from module1 (in sphere1) needed by my-module (load (sphere1: module1)))
Only the files declared as dependencies in a stage (include, prelude, load) will become available. You probably will use the prelude file for including declarations concerning the FFI (necessary only at compile-time), the macros file for including hygienic macros, and the base file for the standard code (R5RS Scheme code, which can in turn use any of the functionality or structures defined in the previous two files).
You may wonder why the dependencies are defined in this file, instead of inside each module, what can lead to a long config.scm file. The main reason is to make the modules seamlessly portable among plain Gambit and SchemeSpheres. If R7RS modules get implemented in Gambit, this will change.
This file contains a series of tasks that can be invoked from the command-line via the command ssake.
Let's take a look inside an example:
(define modules '(base64 util)) ;; Compile the modules (both debug and optimized versions) (define-task compile () (for-each (lambda (m) (sake#compile-module m cond-expand-features: '(debug) version: '(debug)) (sake#compile-module m cond-expand-features: '(optimize))) modules)) ;; This will copy the built files to the /lib directory (define-task post-compile () (for-each (lambda (m) (sake#make-module-available m versions: '(() (debug)))) modules) (for-each (lambda (m) (sake#make-module-available m)) modules)) ;; This will install the files in /lib to the system, making them available to other spheres (define-task install () (sake#install-sphere-to-system)) ;; Run tests (define-task test () (sake#test-all)) ;; Remove all generated files (define-task clean () (sake#default-clean)) ;; Default task (if none is given through the command line) (define-task all (compile post-compile) 'all)
Each of these tasks can be invoked like this:
Ssake comes with predefined tasks, sub-tasks and useful functions. All ssake extension modules are installed in sake-extensions directory in Spheres' installation path.
Basically, it all boils downs to these steps:
This is probably best achieved by copying the structure from a simple Sphere. You may also want to take a look at how to document spheres, as an uniform and consistent documentation is one of the targets of this project.