token_lang/lib.rs
1//! # token_lang
2//!
3//! The token type that sits between a lexer and a parser.
4//!
5//! token-lang defines one thing: a [`Token<K>`] — a language-specific *kind*
6//! paired with the [`Span`] of source it covers — and the [`TokenKind`] trait that
7//! lets generic, language-agnostic code reason about a token stream. A lexer
8//! produces `Token<K>`; a parser consumes it; neither depends on the other,
9//! because both agree only on this seam. It owns the token type and nothing else:
10//! no scanning, no grammar, no diagnostics.
11//!
12//! ## The model
13//!
14//! A token is a *classified span*. The kind `K` says **what** was lexed — it is
15//! the language's own `enum` of keywords, punctuation, literals, and an
16//! end-of-input marker — and the [`Span`] says **where**. token-lang stays
17//! reusable across languages by leaving `K` to the language and owning only the
18//! pairing and the small set of questions every parser asks of any token:
19//!
20//! - [`is_trivia`](TokenKind::is_trivia) — skip whitespace and comments;
21//! - [`is_eof`](TokenKind::is_eof) — detect the end of input;
22//! - [`symbol`](TokenKind::symbol) — read the interned lexeme an identifier,
23//! keyword, or literal carries, as a cheap [`Symbol`] handle.
24//!
25//! Every trait method has a default, so a kind that has no trivia and carries no
26//! interned text implements [`TokenKind`] with an empty `impl` block.
27//!
28//! ## Quickstart
29//!
30//! ```
31//! use token_lang::{Span, Token, TokenKind};
32//!
33//! // A language defines its own kinds; token-lang carries them.
34//! #[derive(Clone, Copy, Debug, PartialEq, Eq)]
35//! enum Kind {
36//! Ident,
37//! Plus,
38//! Whitespace,
39//! Eof,
40//! }
41//!
42//! impl TokenKind for Kind {
43//! fn is_trivia(&self) -> bool {
44//! matches!(self, Kind::Whitespace)
45//! }
46//! fn is_eof(&self) -> bool {
47//! matches!(self, Kind::Eof)
48//! }
49//! }
50//!
51//! // `a + b`, lexed (including the whitespace) and terminated.
52//! let tokens = [
53//! Token::new(Kind::Ident, Span::new(0, 1)),
54//! Token::new(Kind::Whitespace, Span::new(1, 2)),
55//! Token::new(Kind::Plus, Span::new(2, 3)),
56//! Token::new(Kind::Whitespace, Span::new(3, 4)),
57//! Token::new(Kind::Ident, Span::new(4, 5)),
58//! Token::new(Kind::Eof, Span::empty(5)),
59//! ];
60//!
61//! // A parser keeps the significant tokens — without knowing the language.
62//! let significant: usize = tokens
63//! .iter()
64//! .filter(|t| !t.is_trivia() && !t.is_eof())
65//! .count();
66//! assert_eq!(significant, 3); // Ident, Plus, Ident
67//! assert!(tokens.last().unwrap().is_eof());
68//! ```
69//!
70//! ## `no_std`
71//!
72//! The crate is `no_std`-compatible and allocation-free: the token type and the
73//! trait need neither the standard library nor `alloc`. The default `std` feature
74//! is additive and only forwards to the standard-library builds of the coordinate
75//! and interning crates. Disable default features to build for a `no_std` target.
76
77#![cfg_attr(not(feature = "std"), no_std)]
78#![cfg_attr(docsrs, feature(doc_cfg))]
79#![forbid(unsafe_code)]
80#![deny(missing_docs)]
81#![deny(unused_must_use)]
82#![deny(unused_results)]
83#![deny(clippy::unwrap_used)]
84#![deny(clippy::expect_used)]
85#![deny(clippy::todo)]
86#![deny(clippy::unimplemented)]
87#![deny(clippy::print_stdout)]
88#![deny(clippy::print_stderr)]
89#![deny(clippy::dbg_macro)]
90#![deny(clippy::unreachable)]
91
92mod kind;
93mod token;
94
95pub use kind::TokenKind;
96pub use token::Token;
97
98/// A four-byte `Copy` handle to an interned string, re-exported from
99/// [`intern_lang`] so consumers can name the type [`TokenKind::symbol`] returns
100/// without depending on `intern-lang` directly.
101pub use intern_lang::Symbol;
102/// The half-open byte range a token covers, re-exported from [`span_lang`] so
103/// consumers can name the type [`Token`] is built from without depending on
104/// `span-lang` directly.
105pub use span_lang::Span;
106/// A value paired with its [`Span`], re-exported from [`span_lang`]. [`Token`]
107/// converts to and from `Spanned` through its [`From`] impls.
108pub use span_lang::Spanned;