{"id":51514730,"url":"https://github.com/jamesgober/span-lang","last_synced_at":"2026-07-08T10:01:44.025Z","repository":{"id":365811048,"uuid":"1273890034","full_name":"jamesgober/span-lang","owner":"jamesgober","description":"Source positions, spans, and byte-to-line/column mapping for language tooling.","archived":false,"fork":false,"pushed_at":"2026-06-19T02:44:35.000Z","size":34,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-19T03:05:45.702Z","etag":null,"topics":["compiler","language","parser","rust","spans"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["jamesgober"]}},"created_at":"2026-06-19T01:19:34.000Z","updated_at":"2026-06-19T02:46:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesgober/span-lang","commit_stats":null,"previous_names":["jamesgober/span-lang"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/span-lang","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fspan-lang","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fspan-lang/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fspan-lang/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fspan-lang/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/span-lang/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fspan-lang/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35260671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","language","parser","rust","spans"],"created_at":"2026-07-08T10:01:42.946Z","updated_at":"2026-07-08T10:01:44.018Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":["https://github.com/sponsors/jamesgober"],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cimg width=\"99\" alt=\"Rust logo\" src=\"https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg\"\u003e\n    \u003cbr\u003e\n    \u003cb\u003espan-lang\u003c/b\u003e\n    \u003cbr\u003e\n    \u003csub\u003e\u003csup\u003eSOURCE SPANS \u0026 POSITION MAPPING\u003c/sup\u003e\u003c/sub\u003e\n\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003ca href=\"https://crates.io/crates/span-lang\"\u003e\u003cimg alt=\"Crates.io\" src=\"https://img.shields.io/crates/v/span-lang\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/span-lang\"\u003e\u003cimg alt=\"Downloads\" src=\"https://img.shields.io/crates/d/span-lang?color=%230099ff\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/span-lang\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/docsrs/span-lang\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/jamesgober/span-lang/actions\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/jamesgober/span-lang/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/rust-lang/rfcs/blob/master/text/2495-min-rust-version.md\"\u003e\u003cimg alt=\"MSRV\" src=\"https://img.shields.io/badge/MSRV-1.85%2B-blue\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"left\"\u003e\n    \u003cp\u003e\n        span-lang provides the source-position substrate for language tooling: compact byte-offset spans, line/column resolution, and multi-file coordinate mapping. It is the foundational crate every later stage of a lexer, parser, or compiler references when reporting where something lives in source.\n    \u003c/p\u003e\n    \u003cbr\u003e\n    \u003chr\u003e\n    \u003cp\u003e\n        \u003cstrong\u003eMSRV is 1.85+\u003c/strong\u003e (Rust 2024 edition).\n    \u003c/p\u003e\n    \u003cblockquote\u003e\n        \u003cstrong\u003eStatus: stable (\u003ccode\u003ev1.0.0\u003c/code\u003e).\u003c/strong\u003e The full position, span, resolution, and \u003ccode\u003eSpanned\u0026lt;T\u0026gt;\u003c/code\u003e surface is implemented and property-tested, with the \u003ccode\u003eO(log lines)\u003c/code\u003e lookup verified by benchmark scaling. The public API is frozen under the SemVer promise: no item is removed or changed incompatibly before \u003ccode\u003e2.0.0\u003c/code\u003e. See \u003ca href=\"./docs/API.md#stability\"\u003e\u003ccode\u003eAPI.md\u003c/code\u003e\u003c/a\u003e and \u003ca href=\"./CHANGELOG.md\"\u003e\u003ccode\u003eCHANGELOG.md\u003c/code\u003e\u003c/a\u003e.\n    \u003c/blockquote\u003e\n\u003c/div\u003e\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Installation\n\n```toml\n[dependencies]\nspan-lang = \"1\"\n```\n\n`no_std` targets disable the default `std` feature; the crate then relies only on `core` and `alloc`:\n\n```toml\nspan-lang = { version = \"1\", default-features = false }\n```\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## Performance\n\nA `Span` is a `Copy` value (two packed 32-bit offsets, eight bytes), and line/column resolution is a binary search over line starts — `O(log lines)`, never a re-scan of the source. Latest local Criterion means (`cargo bench`, Windows x86_64, Rust stable):\n\n- **`Span::merge`** — ~0.6 ns/op\n- **`LineIndex::offset`** (line/col \u0026rarr; byte) — ~2.5 ns/op\n- **`LineIndex::line_col`** (byte \u0026rarr; line/col) — ~8.7 ns/op\n- **`LineIndex::new`** — ~8.4 µs to index 1 000 lines (the only `O(n)` operation; lookups allocate nothing)\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## Features\n\n- **`BytePos`** — a 4-byte `Copy` byte offset; the atom every span is built from.\n- **`Span`** — a half-open `start..end` byte range with `len`, `is_empty`, `contains`, ordering, and an associative, commutative `merge`. The `start \u003c= end` invariant is enforced at construction.\n- **`LineCol`** — a resolved 1-based line/column, where the column counts Unicode scalar values (never bytes, never inside a multi-byte sequence).\n- **`LineIndex`** — built once per source; maps `BytePos` \u0026harr; `LineCol` in `O(log lines)`, handling `\\n` and `\\r\\n` uniformly, with no allocation on the lookup path. Also yields a line's text span for rendering.\n- **`Spanned\u003cT\u003e`** — a value paired with the span it came from, so a token or AST node carries its location without the value type knowing about positions.\n\nAn optional `serde` feature round-trips every position type; `Span` deserialises through its constructor, so a value from an untrusted source cannot violate the `start \u003c= end` invariant. Correctness is held to the [project invariants](./docs/API.md#invariants) by property tests cross-checked against a naive reference resolver over UTF-8 input including multi-byte characters and CRLF.\n\n\u003cbr\u003e\n\n## Usage\n\n```rust\nuse span_lang::{LineIndex, Span};\n\nlet src = \"fn main() {\\n    work();\\n}\\n\";\n\n// Spans are half-open byte ranges; merge covers both inputs.\nlet call = Span::new(16, 22);\nassert_eq!(call.len(), 6);\n\n// Resolve a byte offset to a human (line, column) coordinate.\nlet index = LineIndex::new(src);\nlet lc = index.line_col(call.start());\nassert_eq!((lc.line, lc.col), (2, 5));\n\n// The mapping is reversible.\nassert_eq!(index.offset(lc), Some(call.start()));\n```\n\n\u003cbr\u003e\n\n## API Overview\n\nFor a complete reference with examples, see [`docs/API.md`](./docs/API.md).\n\n- [`BytePos`](./docs/API.md#bytepos) — a byte offset into one source.\n- [`Span`](./docs/API.md#span) — a half-open byte range with `merge`, `contains`, and ordering.\n- [`LineCol`](./docs/API.md#linecol) — a resolved 1-based line/column coordinate.\n- [`LineIndex`](./docs/API.md#lineindex) — byte \u0026harr; line/column resolution in `O(log lines)`, plus per-line text spans.\n- [`Spanned\u003cT\u003e`](./docs/API.md#spanned) — a value paired with its `Span`.\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Contributing\n\nSee \u003ca href=\"./dev/DIRECTIVES.md\"\u003e\u003ccode\u003edev/DIRECTIVES.md\u003c/code\u003e\u003c/a\u003e for engineering standards and the definition of done. Before a PR: `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all-features` must be clean.\n\n\u003cbr\u003e\n\n\u003cdiv id=\"license\"\u003e\n    \u003ch2\u003eLicense\u003c/h2\u003e\n    \u003cp\u003eLicensed under either of\u003c/p\u003e\n    \u003cul\u003e\n        \u003cli\u003e\u003cb\u003eApache License, Version 2.0\u003c/b\u003e \u0026mdash; \u003ca href=\"./LICENSE-APACHE\"\u003eLICENSE-APACHE\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003cb\u003eMIT License\u003c/b\u003e \u0026mdash; \u003ca href=\"./LICENSE-MIT\"\u003eLICENSE-MIT\u003c/a\u003e\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp\u003eat your option.\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch2\u003e\u003c/h2\u003e\n  \u003csup\u003eCOPYRIGHT \u003csmall\u003e\u0026copy;\u003c/small\u003e 2026 \u003cstrong\u003eJames Gober \u003cme@jamesgober.com\u003e.\u003c/strong\u003e\u003c/sup\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Fspan-lang","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Fspan-lang","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Fspan-lang/lists"}