sim_codec_python/types.rs
1//! Public, runtime-independent syntax data.
2
3use std::fmt;
4
5/// Half-open UTF-8 byte range in the original source.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub struct Span {
8 /// First byte.
9 pub start: usize,
10 /// Byte after the range.
11 pub end: usize,
12}
13
14/// Kind of a lossless lexical token.
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub enum TokenKind {
17 /// Identifier, including contextual soft-keyword spellings.
18 Name,
19 /// Reserved keyword.
20 Keyword,
21 /// Numeric literal with its original spelling retained.
22 Number,
23 /// Ordinary string or bytes literal.
24 String,
25 /// Formatted string literal.
26 FString,
27 /// Python 3.14 template string literal.
28 TemplateString,
29 /// Operator or delimiter.
30 Operator,
31 /// Physical line ending.
32 Newline,
33 /// Significant indentation increase.
34 Indent,
35 /// Significant indentation decrease (zero-width).
36 Dedent,
37 /// Spaces, tabs, form feeds, comments, or escaped newlines.
38 Trivia,
39 /// End marker (zero-width).
40 End,
41}
42
43/// A token borrowing no source storage; text is recovered through [`Token::span`].
44#[derive(Clone, Debug, Eq, PartialEq)]
45pub struct Token {
46 /// Token category.
47 pub kind: TokenKind,
48 /// Original byte range.
49 pub span: Span,
50 /// One-based line.
51 pub line: usize,
52 /// Zero-based Unicode-scalar column.
53 pub column: usize,
54}
55
56/// Concrete source-tree node category.
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub enum NodeKind {
59 /// Complete file input.
60 Module,
61 /// Logical statement.
62 Statement,
63 /// Indented suite.
64 Suite,
65 /// Delimited expression/grouping region.
66 Group,
67 /// Expression region whose precedence was admitted through the Pratt table.
68 Expression,
69}
70
71/// A concrete node covering a contiguous token range.
72#[derive(Clone, Debug, Eq, PartialEq)]
73pub struct Node {
74 /// Node category.
75 pub kind: NodeKind,
76 /// Half-open token-index range.
77 pub tokens: std::ops::Range<usize>,
78 /// Nested structural nodes.
79 pub children: Vec<Node>,
80}
81
82/// Complete lossless Python source tree.
83#[derive(Clone, Debug, Eq, PartialEq)]
84pub struct SyntaxTree {
85 source: String,
86 /// Lossless token stream, including trivia and layout markers.
87 pub tokens: Vec<Token>,
88 /// Structural root.
89 pub root: Node,
90}
91
92impl SyntaxTree {
93 /// Returns the exact input bytes as UTF-8 text.
94 #[must_use]
95 pub fn source(&self) -> &str {
96 &self.source
97 }
98
99 /// Re-emits source byte-for-byte. Zero-width layout markers contribute no bytes.
100 #[must_use]
101 pub fn preserve_source(&self) -> String {
102 self.source.clone()
103 }
104
105 pub(crate) fn new(source: &str, tokens: Vec<Token>, root: Node) -> Self {
106 Self {
107 source: source.to_owned(),
108 tokens,
109 root,
110 }
111 }
112}
113
114/// Resource limits shared by lexing and parsing.
115#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub struct Limits {
117 /// Maximum source bytes.
118 pub max_bytes: usize,
119 /// Maximum emitted tokens (including trivia/layout).
120 pub max_tokens: usize,
121 /// Maximum bracket, f-string, and indentation nesting.
122 pub max_nesting: usize,
123 /// Maximum physical lines.
124 pub max_lines: usize,
125}
126
127impl Default for Limits {
128 fn default() -> Self {
129 Self {
130 max_bytes: 4 * 1024 * 1024,
131 max_tokens: 1_000_000,
132 max_nesting: 256,
133 max_lines: 250_000,
134 }
135 }
136}
137
138/// Stable diagnostic category.
139#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140pub enum DiagnosticCode {
141 /// A configured resource bound was crossed.
142 ResourceLimit,
143 /// Indentation did not match an earlier level.
144 InvalidIndentation,
145 /// A tab/space combination has an ambiguous visual column.
146 AmbiguousIndentation,
147 /// A literal was not terminated.
148 UnterminatedLiteral,
149 /// A character cannot begin a Python token.
150 InvalidCharacter,
151 /// Delimiters are unmatched or crossed.
152 UnmatchedDelimiter,
153 /// Statement structure is invalid.
154 InvalidSyntax,
155}
156
157/// Deterministic, located syntax failure.
158#[derive(Clone, Debug, Eq, PartialEq)]
159pub struct Diagnostic {
160 /// Stable category.
161 pub code: DiagnosticCode,
162 /// Offending source range.
163 pub span: Span,
164 /// One-based source line.
165 pub line: usize,
166 /// Zero-based Unicode-scalar column.
167 pub column: usize,
168 /// Stable human-readable detail.
169 pub message: String,
170}
171
172impl fmt::Display for Diagnostic {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 write!(f, "{}:{}: {}", self.line, self.column, self.message)
175 }
176}
177
178impl std::error::Error for Diagnostic {}