| Crates.io | oxur-smap |
| lib.rs | oxur-smap |
| version | 0.2.0 |
| created_at | 2026-01-05 16:47:37.904842+00 |
| updated_at | 2026-01-17 00:17:27.337502+00 |
| description | Source mapping for Oxur language - tracks code transformations for error reporting |
| homepage | |
| repository | https://github.com/oxur-lang/oxur |
| max_upload_size | |
| id | 2024219 |
| size | 98,584 |
Source mapping for the Oxur language - Tracks code transformations through the compilation pipeline for accurate error reporting.
oxur-smap is a foundation crate that provides source mapping capabilities for the Oxur compiler. It enables rustc compiler errors to be translated back to the original Oxur source code positions by tracking transformations through multiple compilation stages.
The source map tracks three transformation stages:
┌─────────────────────────────────────────────────────────────┐
│ OXUR COMPILATION PIPELINE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Oxur Source (.ox) │
│ ↓ │
│ ┌──────────────────┐ │
│ │ Parser │ Creates Surface Form AST │
│ │ │ Records: NodeId → SourcePos │
│ └──────────────────┘ │
│ ↓ │
│ Surface Forms (Lisp-like AST) │
│ ↓ │
│ ┌──────────────────┐ │
│ │ Expander │ Macro expansion, desugaring │
│ │ │ Records: Surface NodeId → Core NodeId│
│ └──────────────────┘ │
│ ↓ │
│ Core Forms (Canonical IR) │
│ ↓ │
│ ┌──────────────────┐ │
│ │ Lowering │ Generate Rust AST │
│ │ │ Records: Core NodeId → Rust NodeId │
│ └──────────────────┘ │
│ ↓ │
│ Rust AST │
│ ↓ │
│ ┌──────────────────┐ │
│ │ rustc │ Compile to binary │
│ │ │ Errors reference Rust NodeIds │
│ └──────────────────┘ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ Error Mapper │ Lookup: Rust NodeId → SourcePos │
│ │ │ Traverses: Rust → Core → Surface │
│ └──────────────────┘ │
│ ↓ │
│ User-friendly error message with original source position │
│ │
└─────────────────────────────────────────────────────────────┘
NodeIdUnique identifier for AST nodes across all compilation stages.
pub struct NodeId(u32);
// Thread-safe global generator
pub fn new_node_id() -> NodeId;
SourcePosTracks source code positions with file path, line, column, and span length.
pub struct SourcePos {
pub file: String, // File path or "<repl>"
pub line: u32, // 1-indexed line number
pub column: u32, // 1-indexed column number
pub length: u32, // Span length for error highlighting
}
SourceMapMaps NodeIds through transformation stages and provides backward lookup.
pub struct SourceMap {
// Three-stage mapping:
surface_positions: HashMap<NodeId, SourcePos>,
surface_to_core: HashMap<NodeId, NodeId>,
core_to_rust: HashMap<NodeId, NodeId>,
}
use oxur_smap::{SourceMap, new_node_id, SourcePos};
let mut map = SourceMap::new();
// Stage 1: Parser creates surface node
let surface = new_node_id();
let pos = SourcePos::new("example.ox".to_string(), 10, 5, 15);
map.record_surface_node(surface, pos);
// Stage 2: Expander creates core node
let core = new_node_id();
map.record_expansion(surface, core);
// Stage 3: Lowering creates rust node
let rust = new_node_id();
map.record_lowering(core, rust);
// Error translation: Map Rust error back to original source
if let Some(original_pos) = map.lookup(&rust) {
println!("Error at {}:{}:{}",
original_pos.file,
original_pos.line,
original_pos.column);
}
For REPL input, use the SourcePos::repl() constructor:
let pos = SourcePos::repl(1, 1, 20); // Line 1, column 1, length 20
assert_eq!(pos.file, "<repl>");
For incremental compilation, generate cache keys based on transformation structure:
let hash = map.content_hash();
// Use hash as part of cache key for compiled artifacts
// Note: Hash is structure-based, excludes source positions
For build-once, read-many scenarios, freeze the map after construction:
// During compilation (single-threaded)
let mut map = SourceMap::new();
// ... record all transformations ...
map.freeze();
// During error reporting (can be multi-threaded)
// Safe to share &map across threads
if let Some(pos) = map.lookup(&rust_node) {
// ... report error ...
}
Frozen maps panic on modification attempts (defensive programming).
oxur-smap is designed for negligible compiler overhead with sub-microsecond operations.
Hardware: Apple Silicon M-series | Rust 1.75+ optimized build
Core Operations (nanoseconds):
| Operation | Time | Description |
|---|---|---|
new_node_id() |
7.5 ns | Thread-safe atomic counter |
record_surface_node() |
58 ns | Record source position |
record_expansion() |
84 ns | Link surface → core |
record_lowering() |
102 ns | Link core → rust |
lookup() |
120 ns | Full 3-stage chain traversal |
freeze() |
1.3 µs | One-time freeze operation (100 nodes) |
Scaling (complete 3-stage chains):
| Nodes | Total Operations | Build Time | Per-Node |
|---|---|---|---|
| 100 | 300 | 9.1 µs | 91 ns |
| 1,000 | 3,000 | 115 µs | 115 ns |
| 10,000 | 30,000 | 1.26 ms | 126 ns |
Performance characteristics:
Run benchmarks:
cargo bench --package oxur-smap
Per transformation chain (surface → core → rust):
Real-world estimates:
| Project Size | AST Nodes | Memory Overhead |
|---|---|---|
| Small (100 LOC) | ~100 | <20 KB |
| Medium (10k LOC) | ~10,000 | ~1-2 MB |
| Large (100k LOC) | ~100,000 | ~10-20 MB |
Compiler impact:
Frozen flag vs Arc<RwLock>:
We use a simple frozen: bool flag instead of Arc<RwLock<SourceMap>>:
Rationale: Compilation is sequential (no concurrent writes needed), error reporting is read-only (safe to share &SourceMap). The frozen flag provides defensive programming with zero runtime cost.
Three HashMaps vs unified structure:
We maintain three separate HashMaps instead of HashMap<NodeId, TransformChain>:
See module documentation for complete design rationale.
This implementation follows the specifications in:
See ../../design/docs/ for complete design documentation.
The crate includes comprehensive test coverage:
Run tests:
cargo test --package oxur-smap
Generate coverage report:
cargo llvm-cov --package oxur-smap --html
open target/llvm-cov/html/index.html
Generate full API documentation:
cargo doc --package oxur-smap --no-deps --open
Single Global Counter: We use a single global atomic counter for NodeId generation rather than per-stage ranges (e.g., 100-199 for surface). This simplifies implementation, avoids range exhaustion, and keeps NodeIds opaque.
Structure-Only Hashing: The content_hash() method hashes the transformation graph but NOT source positions. This allows cached artifacts to be reused when code structure is identical, even if moved to different lines. Trade-off: Very subtle semantic changes might be missed (acceptable for v1.0).
Three Separate HashMaps: We maintain three separate HashMaps for each transformation stage rather than a single unified structure. This provides clear separation of concerns and efficient lookup at each stage.
NodeId generation is thread-safe using AtomicU32 with SeqCst ordering. The global generator can be safely accessed from multiple threads simultaneously.
None - This is a zero-dependency foundation crate.
See the main Oxur project documentation:
Licensed under either of:
at your option.
Phase 0 (Complete): Foundation implementation with 98.41% test coverage
Phase 1 (Complete): Integration with oxur-lang, oxur-comp, oxur-repl
Phase 2 (Complete): Polish & Optimization
lookup_stats() for profilingNext Phase: Production use in full Oxur compilation pipeline