Skip to main content

neutron_engine/
lib.rs

1//! # Neutron Engine
2//!
3//! A lightweight markup parser for the Neutron styling language (.nt).
4//! Provides fast, zero-dependency parsing of XML-like tags with inline style declarations.
5//!
6//! ## Example
7//!
8//! ```rust
9//! use neutron_engine::{parse_tag, parse_attributes, parse_style, normalize_decl, Attribute, StyleDecl};
10//!
11//! let tag = r#"<Container style="b:#000; w:fill" id="Main">"#;
12//! if let Some((name, attrs_text)) = parse_tag(tag) {
13//!     println!("Tag: {}", name);
14//!
15//!     let mut attrs = [Attribute { name: "", value: "" }; 12];
16//!     let attr_count = parse_attributes(attrs_text, &mut attrs);
17//!
18//!     for attr in &attrs[..attr_count] {
19//!         if attr.name == "style" {
20//!             let mut styles = [StyleDecl { name: "", value: "" }; 16];
21//!             let style_count = parse_style(attr.value, &mut styles);
22//!
23//!             for style in &styles[..style_count] {
24//!                 let prop = normalize_decl(*style);
25//!                 println!("Style: {:?}", prop);
26//!             }
27//!         }
28//!     }
29//! }
30//! ```
31
32#[path = "iris/mod.rs"]
33pub mod iris;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49use std::fs;
50
51/// Represents a style declaration with name and value
52#[derive(Debug, Clone, Copy)]
53pub struct StyleDecl<'a> {
54    pub name: &'a str,
55    pub value: &'a str,
56}
57
58/// Represents an attribute with name and value
59#[derive(Debug, Clone, Copy)]
60pub struct Attribute<'a> {
61    pub name: &'a str,
62    pub value: &'a str,
63}
64
65/// Normalized style property enum
66#[derive(Debug)]
67pub enum StyleProp<'a> {
68    Background(&'a str),
69    Color(&'a str),
70    Width(&'a str),
71    Height(&'a str),
72    FontSize(&'a str),
73    Raw(&'a str, &'a str),
74}
75
76/// Parse a tag string into tag name and attributes text
77///
78/// # Examples
79///
80/// ```
81/// use neutron_engine::parse_tag;
82///
83/// let result = parse_tag(r#"<Container style="b:#000">"#);
84/// assert_eq!(result, Some(("Container", r#"style="b:#000""#)));
85/// ```
86pub fn parse_tag<'a>(input: &'a str) -> Option<(&'a str, &'a str)> {
87    let input = input.trim();
88    if !input.starts_with('<') || !input.ends_with('>') {
89        return None;
90    }
91    let inner = &input[1..input.len() - 1];
92    let mut parts = inner.splitn(2, char::is_whitespace);
93    let tag_name = parts.next()?.trim();
94    let attrs = parts.next().unwrap_or("").trim();
95    Some((tag_name, attrs))
96}
97
98/// Parse attributes from a string into an array of Attribute structs
99///
100/// # Examples
101///
102/// ```
103/// use neutron_engine::{parse_attributes, Attribute};
104///
105/// let mut attrs = [Attribute { name: "", value: "" }; 12];
106/// let count = parse_attributes(r#"style="b:#000" id="Main""#, &mut attrs);
107/// assert_eq!(count, 2);
108/// assert_eq!(attrs[0].name, "style");
109/// assert_eq!(attrs[0].value, "b:#000");
110/// ```
111pub fn parse_attributes<'a>(input: &'a str, out: &mut [Attribute<'a>]) -> usize {
112    let mut count = 0;
113    let mut i = 0;
114    let bytes = input.as_bytes();
115
116    while i < bytes.len() && count < out.len() {
117        while i < bytes.len() && bytes[i].is_ascii_whitespace() {
118            i += 1;
119        }
120        if i >= bytes.len() {
121            break;
122        }
123
124        let start = i;
125        while i < bytes.len() && bytes[i] != b'=' && !bytes[i].is_ascii_whitespace() {
126            i += 1;
127        }
128        let name = &input[start..i].trim();
129
130        while i < bytes.len() && bytes[i].is_ascii_whitespace() {
131            i += 1;
132        }
133        if i >= bytes.len() || bytes[i] != b'=' {
134            break;
135        }
136        i += 1;
137
138        while i < bytes.len() && bytes[i].is_ascii_whitespace() {
139            i += 1;
140        }
141        if i >= bytes.len() || bytes[i] != b'"' {
142            break;
143        }
144        i += 1;
145        let value_start = i;
146
147        while i < bytes.len() && bytes[i] != b'"' {
148            i += 1;
149        }
150        if i > bytes.len() {
151            break;
152        }
153        let value = &input[value_start..i];
154        i += 1;
155
156        out[count] = Attribute { name, value };
157        count += 1;
158    }
159
160    count
161}
162
163/// Parse style declarations from a string
164///
165/// # Examples
166///
167/// ```
168/// use neutron_engine::{parse_style, StyleDecl};
169///
170/// let mut styles = [StyleDecl { name: "", value: "" }; 16];
171/// let count = parse_style("b:#000; c:#fff", &mut styles);
172/// assert_eq!(count, 2);
173/// assert_eq!(styles[0].name, "b");
174/// assert_eq!(styles[0].value, "#000");
175/// ```
176pub fn parse_style<'a>(input: &'a str, out: &mut [StyleDecl<'a>]) -> usize {
177    let mut count = 0;
178    let mut rest = input;
179
180    while !rest.is_empty() && count < out.len() {
181        let semicolon = rest.find(';').unwrap_or(rest.len());
182        let decl = rest[..semicolon].trim();
183
184        if !decl.is_empty() {
185            if let Some((name, value)) = decl.split_once(':') {
186                let name = name.trim();
187                let value = value.trim();
188                if !name.is_empty() && !value.is_empty() {
189                    out[count] = StyleDecl { name, value };
190                    count += 1;
191                }
192            }
193        }
194
195        rest = if semicolon < rest.len() {
196            &rest[semicolon + 1..]
197        } else {
198            ""
199        };
200    }
201
202    count
203}
204
205/// Normalize a style declaration into a typed StyleProp
206///
207/// # Examples
208///
209/// ```
210/// use neutron_engine::{normalize_decl, StyleDecl, StyleProp};
211///
212/// let decl = StyleDecl { name: "b", value: "#000" };
213/// let prop = normalize_decl(decl);
214///
215/// match prop {
216///     StyleProp::Background(color) => assert_eq!(color, "#000"),
217///     _ => panic!("Expected Background"),
218/// }
219/// ```
220pub fn normalize_decl<'a>(decl: StyleDecl<'a>) -> StyleProp<'a> {
221    match decl.name {
222        "b" => StyleProp::Background(decl.value),
223        "c" => StyleProp::Color(decl.value),
224        "w" => StyleProp::Width(decl.value),
225        "h" => StyleProp::Height(decl.value),
226        "f-sz" => StyleProp::FontSize(decl.value),
227        _ => StyleProp::Raw(decl.name, decl.value),
228    }
229}
230
231/// Parse a complete Neutron document and print the parsed structure
232pub fn parse_neutron(input: &str) {
233    let mut attrs = [Attribute { name: "", value: "" }; 12];
234    let mut styles = [StyleDecl { name: "", value: "" }; 16];
235
236    let mut i = 0;
237    while let Some(start) = input[i..].find('<') {
238        let start = i + start;
239        if let Some(end) = input[start..].find('>') {
240            let end = start + end + 1;
241            let tag_content = &input[start..end];
242
243            if let Some((tag_name, attrs_text)) = parse_tag(tag_content) {
244                println!("[Tag] {}", tag_name);
245
246                let attr_count = parse_attributes(attrs_text, &mut attrs);
247                for attr in &attrs[..attr_count] {
248                    println!("  [Attr] {} = \"{}\"", attr.name, attr.value);
249
250                    if attr.name == "style" {
251                        let style_count = parse_style(attr.value, &mut styles);
252                        for style in &styles[..style_count] {
253                            let prop = normalize_decl(*style);
254                            println!("    [Style] {:?}", prop);
255                        }
256                    }
257                }
258            }
259
260            i = end;
261        } else {
262            break;
263        }
264    }
265}
266
267/// Read a Neutron file and parse it
268pub fn parse_neutron_file(path: &str) {
269    let neutron_code = match fs::read_to_string(path) {
270        Ok(content) => content,
271        Err(_) => r#"
272        <Neutron>
273            <Body>
274                <Container style="b:#000; w:fill; h:fill" id="Main">
275                    <Text style="c:#fff; f-sz:20pt">Iris Neo di Laptop</Text>
276                    <Text style="c:#0f0; f-sz:20pt">Neutron Demo</Text>
277                </Container>
278            </Body>
279        </Neutron>
280    "#
281        .to_string(),
282    };
283
284    println!("--- Neutron Engine Booting ---");
285    parse_neutron(&neutron_code);
286}
287