Development Guide
Environment Setup
Section titled “Environment Setup”Requirements
Section titled “Requirements”- Elixir 1.19+ on OTP 28+
- Git
Getting Started
Section titled “Getting Started”git clone <repo-url> skeincd skeinmix deps.getmix compileDependencies
Section titled “Dependencies”All dependencies are sourced from hex.pm:
# Fetch dependenciesmix deps.getBuilding a Standalone Binary
Section titled “Building a Standalone Binary”Skein uses Burrito to produce self-contained executables that bundle the Erlang runtime:
# Build for the current platformMIX_ENV=prod mix release skein
# Build for a specific targetBURRITO_TARGET=linux MIX_ENV=prod mix release skeinBURRITO_TARGET=macos MIX_ENV=prod mix release skeinBURRITO_TARGET=macos_arm MIX_ENV=prod mix release skeinBinaries are written to burrito_out/. The entry point is Skein.CLI.Main, which dispatches subcommands (compile, new, build, test, run, agents, mcp, lsp, trace, completions, version, help).
Project Layout
Section titled “Project Layout”Skein is an Elixir umbrella project:
skein/ mix.exs # Root umbrella (mix aliases for skein.* commands) apps/ skein_compiler/ # Lexer, parser, analyzer, code generator skein_runtime/ # Agents, HTTP, handlers, store, memory, LLM, tools, trace skein_cli/ # CLI: new, build, test, run, trace skein_lsp/ # Language Server Protocol implementation (GenLSP) editors/ vscode/ # VS Code extension (grammar, snippets, LSP client) docs/ # Specifications and documentation examples/ # Example .skein programsCommon Tasks
Section titled “Common Tasks”Running Tests
Section titled “Running Tests”# All tests (unit + property; see CI for current counts)mix test
# Verbose outputmix test --trace
# Just compiler testsmix test apps/skein_compiler/test/
# Just runtime testsmix test apps/skein_runtime/test/
# Just CLI testsmix test apps/skein_cli/test/
# Just LSP testsmix test apps/skein_lsp/test/
# Specific test filemix test apps/skein_compiler/test/skein/parser_test.exs
# Specific test by linemix test apps/skein_compiler/test/skein/lexer_test.exs:42Formatting Code
Section titled “Formatting Code”mix formatAlways run mix format before committing. The project follows standard Elixir formatting.
Interactive Development
Section titled “Interactive Development”cd apps/skein_compileriex -S mixThen in IEx:
# Compile a string{:module, mod} = Skein.Compiler.compile_string(~S"""module Test { fn hello() -> String { "world" }}""")mod.hello()
# Inspect tokens{:ok, tokens} = Skein.Lexer.tokenize(~S|module T { fn f() -> Int { 42 } }|)
# Inspect AST (the parser expects a complete top-level module or agent —# parsing a bare expression like `let x = 42` returns {:error, [...]}){:ok, ast} = Skein.Parser.parse(tokens)Coding Conventions
Section titled “Coding Conventions”Elixir Style
Section titled “Elixir Style”- Follow standard
mix format - Use
@specon all public functions - Use
@moduledocand@docon all public modules and functions - Pattern match in function heads over case/cond where possible
- Prefer pipe operator for data transformations
- No abbreviations except
ctx,opts,acc
Error Handling
Section titled “Error Handling”- Never raise exceptions for user-facing errors
- Use
{:ok, result}/{:error, errors}tuples throughout the pipeline - Every error is a
%Skein.Error{}struct that can serialize to JSON - Include
fix_hintandfix_codein errors where possible
Testing
Section titled “Testing”- TDD is mandatory — write tests before or alongside implementation
- Every public function needs happy path and error case tests
- Use
~Ssigil for Skein source strings in tests (avoids interpolation conflicts) - CodeGen tests must use
async: false(they load BEAM modules) - Property tests go alongside unit tests in separate
*_property_test.exsfiles
Git Conventions
Section titled “Git Conventions”- Branch naming:
<topic>/<short-description>(e.g.,compiler/named-args) - Commit messages:
[component] description(e.g.,[parser] accept named arguments in calls) - One roadmap item / issue per PR; reference it with
Closes #NN
Architecture Decisions
Section titled “Architecture Decisions”Key decisions that affect how you work on the compiler:
Token Format
Section titled “Token Format”Tokens are tuples, not structs:
{:keyword, {line, col}} # no value{:token_type, {line, col}, value} # with valueAST Nodes
Section titled “AST Nodes”Every AST node is a struct with a meta field:
%AST.Fn{ name: "add", params: [...], return_type: %AST.TypeRef{}, body: %AST.Block{}, meta: %{line: 1, col: 3, file: "hello.skein"}}Core Erlang Generation
Section titled “Core Erlang Generation”Use :cerl module functions, not text generation:
# Good: programmatic AST construction:cerl.c_call(:cerl.c_atom(:erlang), :cerl.c_atom(:+), [left, right])
# Bad: string templating"call 'erlang':'+'(#{left}, #{right})"Variable Naming
Section titled “Variable Naming”Skein snake_case becomes Core Erlang CamelCase:
"my_var" -> :MyVar"x" -> :XTemporary Variables
Section titled “Temporary Variables”Use Process.get/put(:skein_var_counter, ...) for unique variable generation during codegen.
Working on the Language Server
Section titled “Working on the Language Server”The LSP server lives in apps/skein_lsp/ and is built with GenLSP. It provides IDE features by calling the Skein compiler API directly.
Running the LSP
Section titled “Running the LSP”# Start the LSP server (stdio transport, used by VS Code)mix skein.lspLSP Modules
Section titled “LSP Modules”| Module | Purpose |
|---|---|
Skein.Lsp.Server | Main GenLSP server, protocol request/notification handlers |
Skein.Lsp.Diagnostics | Runs lexer→parser→analyzer, converts errors to LSP diagnostics |
Skein.Lsp.Symbols | Extracts document symbols from AST |
Skein.Lsp.HoverProvider | Hover info and go-to-definition |
Skein.Lsp.Completions | Context-aware code completion |
Skein.Lsp.CodeActions | Quickfix code actions from each error’s fix_hint/fix_code |
Skein.Lsp.SemanticTokens | Lexer-based semantic token encoding |
Adding LSP Features
Section titled “Adding LSP Features”When adding a new compiler feature, also consider:
- Diagnostics — the LSP automatically picks up new compiler errors
- Symbols — if you add a new top-level construct, add it to
Symbols.document_symbols/1 - Completions — if you add new keywords or built-in types, add them to
Completions - Hover — if you add new built-in types, add descriptions to
HoverProvider
VS Code Extension
Section titled “VS Code Extension”The VS Code extension at editors/vscode/ has two parts:
- Static: TextMate grammar (
skein.tmLanguage.json), snippets (snippets/skein.json), language config - Dynamic: TypeScript LSP client (
src/extension.ts) that launchesskein lspby default (mix skein.lspinside a compiler checkout)
To build the extension:
cd editors/vscodenpm installnpm run compileAdding a New Language Feature
Section titled “Adding a New Language Feature”General workflow for extending the compiler:
- Write the test first — what should the feature look like from the user’s perspective?
- Extend the lexer — add any new tokens (if needed)
- Extend the parser — add the grammar production, build AST nodes
- Add AST node types — if new node types are needed
- Extend the analyzer — add type checking / validation
- Extend the code generator — translate the new AST nodes to Core Erlang
- Add property tests — generators that exercise the new feature randomly
- Run all tests —
mix testmust pass with 0 failures
Useful References
Section titled “Useful References”docs/SKEIN_SPEC.md— Complete language specificationdocs/ARCHITECTURE.md— Compiler and runtime architecturedocs/ROADMAP.md— Development roadmapdocs/skein_first_principles.md— Language design philosophy- Erlang
:cerlmodule docs —https://www.erlang.org/doc/apps/compiler/cerl - Core Erlang spec —
https://www.it.uu.se/research/group/hipe/cerl/