cortexkit_rich_ui_syntax/
lib.rs1mod expression;
6mod markdown;
7mod parser;
8#[cfg(feature = "test-vectors")]
9pub mod vectors;
10
11pub use parser::{parse, parse_bytes, StreamParser};
12
13pub const DIALECT: &str = "rich-ui/1";
15pub const PROGRAM_BYTES: usize = 64 * 1024;
16pub const REPLY_BYTES: usize = 256 * 1024;
17pub const AST_NODES: usize = 8192;
18pub const BLOCKS: usize = 2048;
19pub const OUTPUT_SCALARS: usize = 65_536;
20pub const REFERENCES: usize = 64;
21pub const HANDLES: usize = 64;
22pub const DECLARATIONS: usize = 256;
23pub const EXPRESSION_DEPTH: usize = 64;
24pub const TAG_DEPTH: usize = 32;
25pub const MARKDOWN_DEPTH: usize = 16;
26pub const EMPHASIS_DEPTH: usize = 64;
27pub const DIAGNOSTICS: usize = 50;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum Mode {
31 Reply,
32 Program,
33}
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub struct Span {
38 pub start: usize,
39 pub end: usize,
40}
41impl Span {
42 pub fn new(start: usize, end: usize) -> Self {
43 Self { start, end }
44 }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct Diagnostic {
49 pub code: &'static str,
50 pub span: Span,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct Node {
55 pub span: Span,
56 pub kind: Kind,
57 pub children: Vec<Node>,
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
61pub enum Kind {
62 Markdown(Markdown),
63 Text(String),
64 Literal(String),
66 Reference {
67 n: String,
68 },
69 Handle {
70 kind: String,
71 path: String,
72 },
73 Tag {
74 name: String,
75 block: bool,
76 props: Vec<Prop>,
77 },
78 Interpolation {
79 block: bool,
80 },
81 Declaration {
82 name: String,
83 state: bool,
84 },
85 Expression(Expression),
86}
87
88#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct Prop {
90 pub name: String,
91 pub span: Span,
92 pub value: Node,
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
96pub enum Expression {
97 Number(String),
98 String(String),
99 Boolean(bool),
100 Null,
101 Name(String),
102 State(String),
103 Group,
104 List,
105 Record(Vec<String>),
106 Call(String),
107 Unary(String),
108 Binary(String),
109 Conditional,
110 Member(String),
111 Index,
112}
113
114#[derive(Clone, Debug, PartialEq, Eq)]
115pub enum Markdown {
116 Paragraph,
117 Heading(u8),
118 ThematicBreak,
119 CodeBlock {
120 language: String,
121 text: String,
122 },
123 Code(String),
124 Quote,
125 List {
126 start: Option<u64>,
127 },
128 Item,
129 Task(bool),
130 Table,
131 TableHead,
132 TableRow,
133 TableCell,
134 Emphasis,
135 Strong,
136 Strikethrough,
137 Link {
139 destination: String,
140 title: String,
141 image: bool,
142 },
143 SoftBreak,
144 HardBreak,
145}
146
147#[derive(Clone, Debug, PartialEq, Eq)]
148pub struct Document {
149 pub mode: Mode,
150 pub nodes: Vec<Node>,
151 pub diagnostics: Vec<Diagnostic>,
152 pub suppressed_diagnostics: usize,
153 pub parsed_bytes: usize,
154 pub shortened: bool,
155 pub fallback: bool,
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
160pub struct Reference {
161 pub n: String,
163 pub span: Span,
164 pub excess: bool,
168}
169
170#[derive(Clone, Debug, PartialEq, Eq)]
171pub struct ScanResult {
172 pub references: Vec<Reference>,
173 pub diagnostics: Vec<Diagnostic>,
174 pub suppressed_diagnostics: usize,
175 pub parsed_bytes: usize,
176 pub shortened: bool,
177 pub fallback: bool,
178}
179
180impl Document {
181 pub fn references(&self) -> Vec<Reference> {
183 fn visit(node: &Node, refs: &mut Vec<Reference>) {
184 if let Kind::Reference { n } = &node.kind {
185 refs.push(Reference {
186 n: n.clone(),
187 span: node.span,
188 excess: false,
189 });
190 }
191 if let Kind::Tag { props, .. } = &node.kind {
192 for prop in props {
193 visit(&prop.value, refs);
194 }
195 }
196 for child in &node.children {
197 visit(child, refs);
198 }
199 }
200 let mut refs = Vec::new();
201 for node in &self.nodes {
202 visit(node, &mut refs);
203 }
204 refs.sort_by_key(|r| r.span.start);
205 for (i, reference) in refs.iter_mut().enumerate() {
206 reference.excess = i >= REFERENCES;
207 }
208 refs
209 }
210}
211
212pub fn scan_references(source: &str, mode: Mode) -> ScanResult {
213 let doc = parse(source, mode);
214 ScanResult {
215 references: doc.references(),
216 diagnostics: doc.diagnostics,
217 suppressed_diagnostics: doc.suppressed_diagnostics,
218 parsed_bytes: doc.parsed_bytes,
219 shortened: doc.shortened,
220 fallback: doc.fallback,
221 }
222}
223
224pub fn check_token_slot(value: &str) -> Option<Reference> {
226 if value.len() > REPLY_BYTES {
227 return None;
228 }
229 let end = reference_end(value, 0)?;
230 (end == value.len()).then(|| Reference {
231 n: value[5..end - 1].to_owned(),
232 span: Span::new(0, end),
233 excess: false,
234 })
235}
236
237pub(crate) fn reference_end(source: &str, start: usize) -> Option<usize> {
238 let tail = source.get(start..)?;
239 let digits = tail.strip_prefix("$$tc(")?;
240 let count = digits.bytes().take_while(u8::is_ascii_digit).count();
241 if count == 0
242 || (count > 1 && digits.starts_with('0'))
243 || digits.as_bytes().get(count) != Some(&b')')
244 {
245 return None;
246 }
247 Some(start + 6 + count)
248}