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