Atelier logo

Rust · standard library only · code intelligence

Go to definition,
in code you can read.

Atelier is a dependency-free code-intelligence engine for a small language, the semantic layer an editor sits on. Give it source text and a stream of edits and it returns tokens, a syntax tree, a scoped symbol table, live diagnostics, and the queries an IDE needs: go to definition, find references, hover, and a safe rename that refuses to change what a name resolves to. Pure Rust, standard library only, no crates. Click an identifier below and watch it resolve, live in your browser.

Open the editor View on GitHub
How to use this playground

Atelier analyzes the code live as you type. Everything below runs entirely on this page with no server and no network calls.

Go to definition

Click any identifier in the editor. The engine resolves it and paints a colored box around its declaration, then scrolls it into view.

Find references

That same click fills the References panel with every occurrence of the symbol, declaration first. Click a row to jump to it.

Hover for info

Move the pointer over an identifier and hold still. A popover shows its kind, its name, where it is defined, and for a function its parameter count.

Run the program

Press Run. The evaluator prints the value of each top-level expression statement, one per line, or reports a runtime error with its position.

Read diagnostics

Unresolved names, redefinitions, and parse errors get a red wavy underline in the editor and a line in the Diagnostics panel. Click a diagnostic to jump to it.

Switch files

Pick a file on the left. Each sample highlights a different feature, and your edits stay per file while the page is open.

Guided tour. Start on fib.at and press Run to see it print 55. Now click the fib name inside the recursive body and watch the References panel list the declaration plus both recursive calls. Hover over n to confirm it is a parameter of fib. Switch to scopes.at and click the two different base names to see how an inner parameter shadows the top-level binding. Finally open errors.at, where a duplicate count and an undeclared total light up as diagnostics without you doing anything.

Files

    fib.at

    Diagnostics

    References

    Click an identifier in the editor to resolve it.

    Output

    Press Run to evaluate this file.

    Two resolvers, one answer

    Atelier proves its headline claims against independent oracles over many random, adversarial programs, rather than asserting them. The generator emits shadowing, cross-scope references, redefinitions, hoisted forward references, nested scopes, and unicode identifiers.

    # the correctness gates, run straight from the repo
    $ cargo test --release
    
         Running tests/gates.rs
    running 6 tests
    test gate_diagnostics_correct ... ok
    test gate_incremental_boundary_cases ... ok
    test gate_navigation_matches_reference_resolver ... ok
    test gate_diagnostics_deterministic ... ok
    test gate_rename_preserves_resolution ... ok
    test gate_incremental_equals_batch ... ok
    
    test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
    
    # push the same gates harder; the fuzz count scales up on demand
    $ ATELIER_FUZZ_OPS=2000 cargo test --release
      

    Incremental work is an optimization, never a different answer. Two independent resolvers agreeing is real cross-validation, not a restatement, and the rename in this editor is the same one the tests hold to that bar.

    The same engine, from a terminal.

    Every query in the editor above is a CLI subcommand. A position is a byte offset or a one-based line:col, and the output is plain text an editor or a script can parse.

    # diagnostics: unresolved names, redefinitions, and parse errors
    $ atelier analyze errors.at
    2:5: Error: `count` is already defined in this scope [Redefinition]
    3:1: Error: cannot find `total_of` in this scope [UnresolvedName]
    
    # find references: declaration first, then every use, with byte spans
    $ atelier refs fib.at 4:5
    2 reference(s):
      def 4:5 (bytes 68..73)
      use 5:18 (bytes 97..102)
    
    # hover: kind, arity for a function, and where it is defined
    $ atelier hover fib.at 1:4
    fn fib(1 params)
    defined at 1:4 (bytes 3..6)
    
    # run: the evaluator prints the value of each top-level expression
    $ atelier run fib.at
    55

    How it differs

    Atelier is not trying to replace a production language server. It is the readable, dependency-free version of the same core ideas, small enough to audit end to end.

    rust-analyzer, clangd

    Production language servers with deep, correct analysis, and large codebases and dependency trees to match. Powerful to use, hard to read or embed as a teaching artifact.

    Language Server Protocol, tree-sitter

    The standard machinery an editor talks to and the grammars it parses with. General and battle-tested, and a lot to wire up before you see go to definition resolve a single name.

    Atelier

    The whole path from tokens to IDE queries in pure Rust with the standard library only, no crates. Go to definition, find references, hover, and a safe rename, with incremental re-analysis proven identical to a full re-analysis by tests you can run.

    The pipeline, stage by stage

    Six parts, each one you can read.

    Text becomes tokens, tokens become a tree, the tree becomes a scoped symbol table, and the table answers the queries. An incremental layer wraps the whole pipeline so an edit updates only what changed.

    tokens Lexer

    A single left-to-right pass with absolute byte spans. Unicode aware, so it never splits a multi-byte character and never panics on arbitrary text.

    syntax tree Parser

    Recursive descent with error recovery, output grouped per statement. Every path makes progress, so no input can hang it, and a malformed statement recovers to the next boundary.

    names Resolver

    A scope-stack walk that records every occurrence with the binding it resolves to. Functions hoist, let bindings are sequential, and inner scopes may shadow outer names.

    edits Incremental

    Reuses the prefix untouched, shifts the suffix, and re-lexes and reparses only the region around an edit. The result is identical to a full re-analysis, not merely close.

    IDE Queries

    Go to definition, find references, and hover all read the one recorded occurrence table, so the three answers can never disagree with each other.

    refactor Rename

    Rewrites a binding and every reference, then compares a resolution fingerprint before and after. A rename that would capture or dangle a name is refused, not applied.

    Use it

    Three surfaces over one engine: a CLI for files, a Rust library for embedding, and the correctness gates that double as a fuzzer.

    CLI

    analyze, def, refs, hover, rename, and run on any file, plus demo for a built-in tour of every feature.

    Library API

    Analysis takes source and a stream of edits and answers queries by byte offset. Workspace holds many files and runs one. Both are plain Rust, no crates to pull in.

    Correctness gates

    cargo test runs the navigation, incremental, diagnostics, and rename gates against independent oracles. ATELIER_FUZZ_OPS scales the same tests up into a fuzzer.

    # build, test, and take the built-in tour of every feature
    cargo build --release
    cargo test
    ./target/release/atelier demo
    
    # the CLI works on any file; a position is a byte offset or 1-based line:col
    atelier analyze path/to/file        # print diagnostics
    atelier def     path/to/file 3:5    # definition of the symbol at 3:5
    atelier refs    path/to/file 3:5    # every reference to that symbol
    atelier hover   path/to/file 3:5    # symbol info at that position
    atelier rename  path/to/file 3:5 n  # rename the symbol at 3:5 to n
    atelier run     path/to/file        # evaluate the program