Part of #9. Phase 1 — level 1.
Learning goals
tree-sitter's Rust bindings (an FFI-backed crate — feel where the C boundary is), cursor
traversal, byte offsets vs &str UTF-8 slicing (why source[start..end] can panic and what to
do about it), pattern matching on node kinds (TRPL Patterns and Matching).
Task
The per-file builder, smallest slice first: parse one file with tree_sitter_rust, walk the tree,
and emit a RustModule containing only free functions — name, signature_of(), params
(name/type/default), return type, is_async/is_const/is_unsafe/extern_abi, doc comments,
spans, raw code, locals, cyclomatic complexity, and unresolved call sites
(callee_signature: None, receiver expr/args/position recorded). Stamp cache metadata
(sha256/mtime/size). Key the module by relative path. Wire it into core behind a
SymbolTableBuilder struct — a cohesive unit whose methods are named per node kind
(build_callable, build_params, collect_call_sites, ...), state on self, NOT a pile of
free functions (references/symbol-table-construction.md).
Teacher's notes
- Fixture-first: create
testdata/fixture/ as a tiny real crate now — an async fn, an unsafe fn,
a ?-using fn, a call chain. Every later issue extends this fixture. - tree-sitter gives byte offsets; keep
&source[node.byte_range()] behind one checked helper. - Cyclomatic complexity = 1 + branches (
if, match arms beyond first, loops, &&/||) — a
pure tree-walk, good iterator practice.
Gate (symbol-table gate, subset)
- Output validates against the schema and round-trips;
symbol_table keyed by relative paths
(no key starts with / or .. — assert both). - Known fixture fn has: exact signature string,
is_async == true asserted by value, recorded
call sites all callee_signature == None.
Part of #9. Phase 1 — level 1.
Learning goals
tree-sitter's Rust bindings (an FFI-backed crate — feel where the C boundary is), cursor
traversal, byte offsets vs
&strUTF-8 slicing (whysource[start..end]can panic and what todo about it), pattern matching on node kinds (TRPL Patterns and Matching).
Task
The per-file builder, smallest slice first: parse one file with
tree_sitter_rust, walk the tree,and emit a
RustModulecontaining only free functions — name,signature_of(), params(name/type/default), return type,
is_async/is_const/is_unsafe/extern_abi, doc comments,spans, raw
code, locals, cyclomatic complexity, and unresolved call sites(
callee_signature: None, receiver expr/args/position recorded). Stamp cache metadata(sha256/mtime/size). Key the module by relative path. Wire it into
corebehind aSymbolTableBuilderstruct — a cohesive unit whose methods are named per node kind(
build_callable,build_params,collect_call_sites, ...), state onself, NOT a pile offree functions (
references/symbol-table-construction.md).Teacher's notes
testdata/fixture/as a tiny real crate now — an async fn, an unsafe fn,a
?-using fn, a call chain. Every later issue extends this fixture.&source[node.byte_range()]behind one checked helper.if,matcharms beyond first, loops,&&/||) — apure tree-walk, good iterator practice.
Gate (symbol-table gate, subset)
symbol_tablekeyed by relative paths(no key starts with
/or..— assert both).is_async == trueasserted by value, recordedcall sites all
callee_signature == None.