orbit/parser/
mod.rs

1//! Main parser module for .orbit files
2
3mod ast;
4mod template;
5mod tokenizer;
6
7pub use ast::{OrbitAst, TemplateNode};
8
9use std::fs;
10use std::path::Path;
11
12/// Main parser for .orbit files
13#[derive(Default)]
14pub struct OrbitParser;
15
16impl OrbitParser {
17    /// Parse an .orbit file into an AST
18    pub fn parse(content: &str) -> Result<OrbitAst, String> {
19        // Split into sections first
20        let sections = Self::split_sections(content)?;
21
22        // Parse each section
23        let template_node = template::TemplateParser::new(&sections.template).parse()?;
24
25        // TODO: Implement style parser
26        let style_node = ast::StyleNode {
27            rules: Vec::new(),
28            scoped: false,
29        };
30
31        // TODO: Implement script parser
32        let script_node = ast::ScriptNode {
33            imports: Vec::new(),
34            component_name: String::new(),
35            props: Vec::new(),
36            state: Vec::new(),
37            methods: Vec::new(),
38            lifecycle: Vec::new(),
39        };
40
41        Ok(OrbitAst::new(template_node, style_node, script_node))
42    }
43
44    /// Parse an .orbit file from a file path
45    pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<OrbitAst, String> {
46        let content = fs::read_to_string(path).map_err(|e| format!("Failed to read file: {e}"))?;
47        Self::parse(&content)
48    }
49
50    /// Split an .orbit file into its constituent sections
51    fn split_sections(content: &str) -> Result<Sections, String> {
52        let mut template = String::new();
53        let mut style = String::new();
54        let mut script = String::new();
55
56        let mut current_section = None;
57        let mut current_content = String::new();
58
59        for line in content.lines() {
60            match line.trim() {
61                "<template>" => {
62                    current_section = Some(Section::Template);
63                    continue;
64                }
65                "</template>" => {
66                    template = current_content.clone();
67                    current_content.clear();
68                    current_section = None;
69                    continue;
70                }
71                "<style>" => {
72                    current_section = Some(Section::Style);
73                    continue;
74                }
75                "</style>" => {
76                    style = current_content.clone();
77                    current_content.clear();
78                    current_section = None;
79                    continue;
80                }
81                "<script>" | "<code>" | "<code lang=\"rust\">" => {
82                    current_section = Some(Section::Script);
83                    continue;
84                }
85                "</script>" | "</code>" => {
86                    script = current_content.clone();
87                    current_content.clear();
88                    current_section = None;
89                    continue;
90                }
91                _ => {}
92            }
93
94            if let Some(_section) = current_section {
95                current_content.push_str(line);
96                current_content.push('\n');
97            }
98        }
99
100        if template.is_empty() {
101            return Err("Missing <template> section".to_string());
102        }
103
104        Ok(Sections {
105            template,
106            style,
107            script,
108        })
109    }
110}
111
112#[derive(Debug, Clone, Copy)]
113enum Section {
114    Template,
115    Style,
116    Script,
117}
118
119#[derive(Debug)]
120#[allow(dead_code)]
121struct Sections {
122    template: String,
123    style: String,
124    script: String,
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_parse_simple_component() {
133        let content = r#"
134<template>
135    <div class="greeting">
136        <h1>Hello, {{ name }}!</h1>
137        <button @click="increment">Count: {{ count }}</button>
138    </div>
139</template>
140
141<style>
142.greeting {
143    font-family: Arial, sans-serif;
144    padding: 20px;
145}
146</style>
147
148<script>
149use orbit::prelude::*;
150
151pub struct Greeting {
152    name: String,
153    count: i32,
154}
155
156impl Component for Greeting {
157    fn new() -> Self {
158        Self {
159            name: String::from("World"),
160            count: 0,
161        }
162    }
163}
164</script>
165"#;
166
167        let ast = OrbitParser::parse(content).unwrap();
168
169        // Verify template structure
170        match ast.template {
171            TemplateNode::Element {
172                tag,
173                attributes,
174                events: _,
175                children,
176            } => {
177                assert_eq!(tag, "div");
178                assert!(attributes.contains_key("class"));
179                assert_eq!(children.len(), 2);
180            }
181            _ => panic!("Expected element node"),
182        }
183    }
184}