Skip to content

ufo5260987423/scheme-langserver

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,971 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Scheme-langserver

Ask DeepWiki

You may read my paper and cite like this:

WANG, Z. (2025, May 12). Scheme-langserver: Treat Scheme Code Editing as the First-Class Concern. The 18th European Lisp Symposium (ELS`25), Zurich. https://doi.org/10.5281/zenodo.15384882

Due to occasional GitHub access restrictions from China, this repository is also mirrored on Codeberg and Gitee. I collaborate with XmacsLabs; a fork is available here.

default.mp4

VSCode is now supported! See the setup guide.

Note: Auto-generated type information is available here. It is mainly used for downstream development and debugging.

Implementing IDE features like autocomplete, goto definition, and hover documentation is a significant effort. Compared to languages like Java, Python, JavaScript, or C, language server implementations for Lisp dialects are still scarce. Existing tools such as Geiser, racket langserver, and swish-lint rely primarily on a REPL or keyword tokenization rather than static program analysis.

For example, when editing an incomplete project whose code is not yet fully executable, Geiser can only complete top-level bindings listed by environment-symbols (on Chez Scheme) or raw symbols—not true identifiers. This means local bindings and unfinished code receive no help in recognizing valid identifier scopes. The same limitation applies to goto definition and other core IDE features.

The root cause is that Scheme and other Lisp dialects present a formidable challenge for program analysis: their rich data structures, flexible control flow, and especially macros make static reasoning difficult. But this does not mean Scheme is only for geniuses and meta-programming. With a better editing environment, Scheme can be accessible and productive for everyone.

scheme-langserver is a Language Server Protocol (LSP) implementation for Scheme that provides completion, goto definition, hover, and type inference through static code analysis based on the R6RS standard. It handles incomplete code gracefully and is published via Akku, a Scheme package manager.

The server has been tested on Chez Scheme 9.4, 9.5, and 10.x.

Compilation, Installation & Configuration

See the setup guide.

Workspace Cache

scheme-langserver can persist the analyzed workspace state to a Chez FASL cache so that subsequent restarts skip the expensive init-references phase. Enable it with the --cache-path option:

./run --cache-path ~/.cache/scheme-langserver

The cache file is written to <cache-path>/workspace.fasl. It is saved when the server receives a normal LSP exit or shutdown request; if the process crashes or is killed, the previously saved cache remains usable. The cache is keyed by a manifest that includes the langserver version, Chez version, machine type, and record-layout fingerprint; any mismatch falls back to a cold start. Because Chez FASL is platform-specific, do not share the cache file across machines or Chez versions. When only a few files have changed since the cache was saved, the server performs an incremental refresh and preserves the analysis results for unchanged files.

Typical speedups:

Fixture Cold startup Cached startup Speedup
simple-lib ~31 ms ~1.3 ms ~24x
Synthetic 200-file fixture ~2484 ms ~49 ms ~50x
scheme-langserver itself (128 .sls files) ~55,790 ms ~1750 ms ~32x
scheme-langserver, one file changed ~58,846 ms ~1900 ms ~31x

See doc/analysis/workspace.md §8 and AGENTS.md §11 for implementation details.

Debugging

For troubleshooting tips, see debugging.md.

Release

2.1.6 — Bug-fix release completing shared/cyclic literal handling in binding forms and enabling workspace cache saves from compiled binaries. Active development is focused on bug fixes, performance profiling, and expanding the type inference system.

What's new in 2.1.6

  • Identifier analysis:
    • All remaining binding-form rules (let, let*, letrec, let-values, do, fluid-let, let-syntax, letrec-syntax, with-syntax, syntax-case, syntax-rules, define-syntax, define-record-type, s7/lambda*, s7/define*) now dereference shared index-nodes before accessing children, preventing crashes or infinite loops on cyclic parameter/binding lists.
  • Build:
    • Release and development builds now use compile-chez-program --full-chez, so the compiled ./run binary retains the $write-fasl-bytevectors primitive and can save the workspace FASL cache.
    • Dockerfile, Dockerfile.musl, and CI workflows updated to install the static libraries required for linking against full-chez.a.

What's new in 2.1.5

  • AST:
    • init-index-node now detects cyclic and shared reader graph notation (#n= / #n#) via an internal compound->node hashtable.
    • Reference occurrences become leaf index-nodes with a new shared-reference field pointing to the defining node, keeping the AST acyclic.
  • Bug fixes:
    • Fixes the memory leak/hang when analyzing files like swish src/swish/db.ss, which contains #0=(101 . #0#).
  • Type inference:
    • Reference nodes generate a type constraint equating their type with the referenced definition node.

What's new in 2.1.4

  • Type inference:
    • Rest/dotted parameters in define, lambda, and case-lambda now produce function signatures containing (inner:list? something? ...).
  • Identifier analysis:
    • Rest/dotted parameter bindings in define, case-lambda, and do loop variables now point to the correct per-symbol index-node.
  • AST:
    • Dotted-pair structure is now preserved in the index-node tree via synthetic annotations, preventing dot-position symbols from being dropped.

Features

  1. Completion for top-level and local identifier bindings. Top-level and local identifiers binding
  2. Goto definition. Goto definition with telescope.nvim
  3. Compatible with package manager: Akku.
  4. File-change synchronization with corresponding index updates.
  5. Hover.
  6. References and document highlights (document-scoped). Find references with telescope.nvim
  7. Document symbol. Find document symbols with telescope.nvim
  8. Workspace symbol search (workspace/symbol).
  9. Catching local identifier bindings in define-syntax, let-syntax, and other macro forms via hand-written rules.
  10. Automatic macro resolution (experimental). The generic expander for syntax-rules, syntax-case, let-syntax, and letrec-syntax—plus multi-layer macro cascade propagation—is functionally correct but not enabled in production because it is too slow for real-world projects (it triggers heavy macro expansion and cross-document reference back-propagation for every macro use site). The routing code in analysis/identifier/self-defined-rules/router.sls currently falls back to hand-written rules such as match-process for ufo-match. If you are interested in pushing this research forward—e.g. via lazy expansion, incremental caching, or selective rule generation—contributions and discussions are very welcome!
  11. Cross-platform parallel indexing.
  12. Custom source-code annotator compatible with .sps files.
  13. Peephole optimization for API requests using suspendable tasks.
  14. Type inference via a homemade DSL interpreter, now integrated into auto-completion. Parameters whose types match the expected signature are ranked higher, as shown below where length-a and length-b (both integer?) appear first because they match the parameter type required by <=. Autocompletion with type inference
  15. Supports R6RS, R7RS, and S7 by switching top environments.
send-message
2023 11 21 11 26 41 967266866
{"jsonrpc":"2.0","id":"3","result":[{"label":"length-a"},{"label":"length-b"},{"label":"lambda"},{"label":"latin-1-codec"},{"label":"lcm"},{"label":"least-fixnum"},{"label":"length"},{"label":"let"},{"label":"let*"},{"label":"let*-values"},{"label":"let-syntax"},{"label":"let-values"},{"label":"letrec"},{"label":"letrec*"},{"label":"letrec-syntax"},{"label":"lexical-violation?"},{"label":"list"},{"label":"list->string"},{"label":"list->vector"},{"label":"list-ref"},{"label":"list-sort"},{"label":"list-tail"},{"label":"list?"},{"label":"log"},{"label":"lookahead-char"},{"label":"lookahead-u8"}]}
  1. Abstract interpreter that resolves identifiers across multiple file extensions: .scm, .ss, .sps, .sls, .sld.
  2. Code diagnostics with LSP-standard source and code fields. Detects library-not-found, duplicate identifiers in binding forms (e.g. (lambda (x x) ...)), unused imports (e.g. (only (rnrs) car) where car is never referenced), and tokenizer syntax errors. Fail to find library

Roadmap

  1. Renaming support (textDocument/rename + prepareRename).
  2. Formatting (textDocument/formatting).
  3. Signature help (textDocument/signatureHelp).
  4. Code actions (textDocument/codeAction) — e.g. "Remove unused import", "Organize imports".
  5. Full R6RS compatibility.
  6. Step-by-step macro expander for self-defined macros.
  7. Code evaluation within the language server.
  8. Cross-language semantic support via AST transformers.
  9. Extract expression/statement into a procedure (refactoring).

Contributing

Pull requests are welcome! Please see AGENTS.md for project conventions, build steps, and coding style before opening a PR.

Vibe Coding with KIMI

Since mid-2025, active development on this project has been assisted by KIMI (Moonshot AI) in a vibe-coding workflow: the maintainer describes intent in natural language, KIMI explores the codebase, proposes changes, and iterates with tests. If you notice commits authored or co-authored by kimi, that is the AI agent trail. Human review and final approval always remain with the maintainer.

Testing

Almost all key procedures and APIs are covered by tests. Run the full suite with:

bash test.sh

For faster feedback during development, run a single test file:

source .akku/bin/activate
scheme --script tests/protocol/apis/test-definition.sps

Note: Tests currently focus on single-threaded execution.

Other Use Cases

Script-Fu in GIMP

Script-Fu is based on Scheme. Using this example, you can apply scheme-langserver to .scm files in GIMP.

Other Potential Targets

Possible future targets include OMN (Opusmodus Notation) and AutoLisp.

Code Count

find . -name "*.sls" ! -path "./.akku/*" |xargs wc -l

Detailed Document

Core Analysis

  1. Catching identifier bindings — how the abstract interpreter resolves define, lambda, let, define-record-type, etc.
  2. Macro auto-resolution — generic syntax-rules expansion vs hand-written rules
  3. Type system & inference — complete type-inference pipeline and DSL
  4. Workspace lifecycle — initialization, incremental updates, and refresh batches
  5. File dependency graph — topological sorting and linkage matrix

Protocol & Concurrency

  1. API request scheduling — request queue, engine time-slicing, cancellation, and document-sync protection
  2. Diagnostic publication — how diagnostics are generated, accumulated, and sent

Debugging & Development

  1. Debugging guide — enable logs, replay logs, and iterative printf debugging
  2. Development guide (中文) / English version
  3. AGENTS.md — build steps, testing conventions, coding style, and common traps for contributors

Research & Experiments

  1. Scheme-langserver paper (ELS'25) — academic paper on static analysis for Scheme
  2. Macro resolution notes — debugging notes for macro identifier capture
  3. Syntax candy DSL — pattern matcher for type-rule authoring
  4. Record type inference analysisdefine-record-type in the type system
  5. Type inference benchmark — performance measurement methodology
  6. DeepWiki

License

MIT

Star History

Star History Chart

Contributors

Contributors

About

Scheme language server

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages