A Scheme R7RS interpreter with parallel implementations in C# (.NET 10) and
Java 11. The two implementations share a common library.scm and a common
set of .sld library files, kept in sync from a single source of truth.
- R7RS-small core
- Bytecode VM (tokenizer → compiler → VM) mirrored in C# and Java
- A JIT that compiles hot procedures to JVM bytecode or .NET IL
- Module system with
scheme(R7RS) andscm(extensions) libraries - 22 SRFI implementations (1, 2, 8, 9, 13, 14, 18, 19, 26, 28, 39, 64, 69, 95, 98, 111, 125, 128, 132, 133, 151, 158)
- Sets-of-scopes hygienic macro expander (Flatt, POPL 2016)
- ~4500 test assertions across R7RS, SRFI, and feature suites
scm-lib/— source of truth forlibrary.scmand.sldlibrary filesscm-tests/— source of truth for tests (core/,tests/,failures/)scm-csharp/— C# implementation (scheme/,repl/,tests/)scm-java/— Java implementationdocumentation/— generated API documentationexamples/— example scriptsscm-bootstrap.jar— bootstrap interpreter used by the build scripts
The build system is Scheme-based: builder.sh (or builder.bat) runs
java -jar scm-bootstrap.jar against a .scm build script.
# Sync library and test files from source-of-truth to both implementations
./builder.sh update-scheme-files.scm
# C#: build / test / build+test
./builder.sh build-scm-csharp.scm
./builder.sh test-scm-csharp.scm
./builder.sh build-and-test-scm-csharp.scm
# Java: build / test / build+test
./builder.sh build-scm-java.scm
./builder.sh test-scm-java.scm
./builder.sh build-and-test-scm-java.scm
# Update documentation (needs an up-to-date scm-java/scm.jar)
./builder.sh update-documentation.scm
# Full rebuild: sync, build+test both, regenerate documentation
./builder.sh rebuild-all.scmDo not run the C# and Java test suites in parallel — they share files and directories in the temp folder and will collide.
Requirements:
- .NET 10 SDK (
dotnet) - JDK 11 or later (
java,javac,jar)
After building, the C# executable is scm and the Java jar is scm.jar.
scm -e 'CODE' # eval CODE with minimal imports
scm -b 'CODE' # eval CODE with no imports (useful for debugging base)
scm -f 'CODE' # eval CODE with full REPL imports
scm script.scm # run a script (in `user program`, no auto-imports)
scm # start a REPL (in `user main`, REPL auto-imports)Both implementations compile a procedure to the platform's own code (JVM bytecode, .NET IL) once the interpreter has called it 100 times; the JVM or .NET JIT then compiles it like any other code. Long-running programs (servers, large batch runs) gain the most, short runs hardly at all. It is controlled by system properties (Java) or environment variables (C#):
| Java | C# | |
|---|---|---|
-Dscm.jit=off |
SCM_JIT=off |
interpreter only |
-Dscm.jit.threshold=0 |
SCM_JIT_THRESHOLD=0 |
compile on the first call |
-Dscm.jit.stats=true |
SCM_JIT_STATS=true |
print counters at exit |
-Dscm.jit.debug=true |
SCM_JIT_DEBUG=true |
report compiled/rejected code |
java -Dscm.jit=off -jar scm.jar script.scm
SCM_JIT=off scm script.scmThe generated code is verified Java 11 class files, or IL in a dynamic assembly, loaded by the platform's standard mechanisms. No native code is generated by dabscm itself and no runtime flags are needed.
MIT — see LICENSE. Third-party code and academic influences are documented in ACKNOWLEDGMENTS.md.