Skip to main content

praxis_syntax/
lib.rs

1//! Token and syntax-node definitions for the Praxis language.
2//!
3//! Per §14.1 of the design, this crate owns the token kinds and the lossless
4//! syntax tree node kinds. The tree is [`rowan`]-backed (ADR-003): this crate
5//! contributes the [`SyntaxKind`] vocabulary, the [`PraxisLanguage`] tag that
6//! binds it to rowan, and the [`SyntaxNode`]/[`SyntaxToken`]/[`SyntaxElement`]
7//! type aliases.
8//!
9//! The modules:
10//! - [`kind`] — the single `SyntaxKind` enum (tokens, trivia, tree nodes).
11//! - [`ident`] — the one identifier character class (§4.1).
12//! - [`interp`] — the one rule for where a `"…"` literal ends and where its
13//!   interpolation holes are (§8.1, ADR-147), shared by the lexer's pre-scan
14//!   and its resume path.
15//! - [`literal`] — the one text-literal decoder (§4.3).
16//! - [`numeric`] — the one digit-separator rule for numeric literals (§4.3).
17//! - [`template`] — the one rule for where a backtick template ends (§7.2,
18//!   D10), shared by the lexer and the input parser's template scanner.
19//! - [`language`] — the rowan `Language` impl and node aliases.
20//! - [`span_bridge`] — `Span` ↔ `rowan::TextRange` conversions (the only place
21//!   the two offset worlds meet; Praxis `Span` stays the diagnostic source of
22//!   truth).
23//!
24//! [`SyntaxNode`]: language::SyntaxNode
25
26pub mod ident;
27pub mod interp;
28pub mod kind;
29pub mod language;
30pub mod literal;
31pub mod numeric;
32pub mod span_bridge;
33pub mod template;
34
35pub use kind::SyntaxKind;
36pub use language::{PraxisLanguage, SyntaxElement, SyntaxNode, SyntaxToken};
37
38use praxis_source::Span;
39
40/// How deeply backtick templates may nest inside each other's captures (D10).
41///
42/// A capture body is a full parser expression, so `` `{g:choice(A: `{x:int}`)}` ``
43/// is one template containing another — which makes the lexer's template run
44/// and the input parser's `scan_template` mutually recursive with the file's
45/// own text. Both must refuse deep nesting rather than overflow the stack, and
46/// they must refuse it at the *same* depth or one of them accepts what the
47/// other cannot read. It lives here because `praxis-syntax` is the crate they
48/// both already depend on.
49///
50/// The bound is far above anything a person writes.
51pub const MAX_TEMPLATE_NESTING: usize = 32;
52
53/// How deeply `"…"` literals may nest inside each other's interpolation holes
54/// (§8.1, ADR-147).
55///
56/// A hole holds a full expression, so `"{f("{y}")}"` is one literal containing
57/// another and [`interp::text_end`] is recursive with the file's own text. The
58/// bound is what keeps adversarial input off the stack.
59///
60/// Unlike [`MAX_TEMPLATE_NESTING`], reaching this bound does not change what a
61/// delimiter *means* — it refuses to enter, and the literal is reported as
62/// unterminated. That difference is deliberate: the lexer's resume path only
63/// ever runs for a literal the pre-scan proved closes, so a bound that answered
64/// "closed, measured differently" would put the two on different rules at
65/// exactly the depth nobody writes.
66pub const MAX_INTERPOLATION_NESTING: usize = 32;
67
68/// A token the lexer emits before it is folded into the lossless tree: its kind,
69/// the source span it covers, and whether a line break sits in front of it.
70///
71/// The parser consumes these into a `rowan::GreenNode`; the spans are kept so
72/// diagnostics can point at lexer-level locations (§6).
73#[derive(Clone, Copy, PartialEq, Eq, Debug)]
74pub struct Token {
75    pub kind: SyntaxKind,
76    pub span: Span,
77    /// True iff the trivia run immediately before this token contained a `\n`
78    /// or `\r`, or was a line comment.
79    ///
80    /// A newline is trivia, so folding it into the tree loses the one fact
81    /// statement separation needs: whether the next token starts a new line
82    /// (D8, ADR-049). Recording it on the token is what lets the parser ask
83    /// without re-reading the source, and what keeps the answer available after
84    /// the trivia has already been emitted into the green tree.
85    pub preceded_by_newline: bool,
86}
87
88impl Token {
89    #[must_use]
90    pub fn new(kind: SyntaxKind, span: Span, preceded_by_newline: bool) -> Token {
91        Token {
92            kind,
93            span,
94            preceded_by_newline,
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn token_carries_kind_and_span() {
105        let t = Token::new(SyntaxKind::IntLit, Span::new(0, 2), false);
106        assert_eq!(t.kind, SyntaxKind::IntLit);
107        assert_eq!(t.span, Span::new(0, 2));
108        assert!(!t.preceded_by_newline);
109    }
110
111    #[test]
112    fn a_token_records_whether_a_line_break_precedes_it() {
113        let t = Token::new(SyntaxKind::Ident, Span::new(0, 1), true);
114        assert!(t.preceded_by_newline);
115    }
116}