Skip to main content

nargo_parser/template/
mod.rs

1use crate::{ParseState, TemplateParser};
2use nargo_ir::{AttributeIR, Comment, ElementIR, ExpressionIR, ForIteratorIR, ForNodeIR, IfNodeIR, TemplateNodeIR, Trivia};
3use nargo_types::{is_void_element, Error, Result, Span};
4
5pub struct VueTemplateParser;
6
7pub fn parse(source: &str) -> Result<Vec<TemplateNodeIR>> {
8    let mut state = ParseState::new(source);
9    let parser = VueTemplateParser;
10    parser.parse(&mut state, "html")
11}
12
13impl TemplateParser for VueTemplateParser {
14    fn parse(&self, state: &mut ParseState, _lang: &str) -> Result<Vec<TemplateNodeIR>> {
15        let mut parser = TemplateParserImpl { state };
16        parser.parse()
17    }
18}
19
20struct TemplateParserImpl<'a, 'b> {
21    state: &'a mut ParseState<'b>,
22}
23
24impl<'a, 'b> TemplateParserImpl<'a, 'b> {
25    fn consume_trivia(&mut self) -> Trivia {
26        let leading_whitespace = self.state.cursor.consume_whitespace();
27        let mut leading_comments = Vec::new();
28
29        while self.state.cursor.peek_str("<!--") {
30            let start_pos = self.state.cursor.position();
31            self.state.cursor.consume_n(4); // <!--
32            let start = self.state.cursor.pos;
33            while !self.state.cursor.is_eof() && !self.state.cursor.peek_str("-->") {
34                self.state.cursor.consume();
35            }
36            let content = self.state.cursor.current_str(start).to_string();
37            let _ = self.state.cursor.consume_n(3); // -->
38            let end_pos = self.state.cursor.position();
39            leading_comments.push(Comment { content, is_block: true, span: Span { start: start_pos, end: end_pos } });
40            // After a comment, there might be more whitespace
41            let _extra_ws = self.state.cursor.consume_whitespace();
42            // We can append this to the leading_whitespace or ignore it for now
43            // For simplicity, let's keep it simple.
44        }
45
46        Trivia { leading_whitespace, leading_comments, trailing_comments: Vec::new() }
47    }
48
49    pub fn parse(&mut self) -> Result<Vec<TemplateNodeIR>> {
50        let mut nodes = Vec::new();
51        while !self.state.cursor.is_eof() {
52            if self.state.cursor.peek() == '{' {
53                nodes.push(self.parse_interpolation()?);
54            }
55            else if self.state.cursor.peek() == '<' {
56                if self.state.cursor.peek_str("<!--") {
57                    nodes.push(self.parse_comment()?);
58                }
59                else if self.state.cursor.peek_str("</") {
60                    break; // Closing tag, handle in parent
61                }
62                else {
63                    nodes.push(self.parse_element()?);
64                }
65            }
66            else {
67                nodes.push(self.parse_text()?);
68            }
69        }
70        Ok(nodes)
71    }
72
73    fn parse_element(&mut self) -> Result<TemplateNodeIR> {
74        let start_pos = self.state.cursor.position();
75        self.state.cursor.expect('<')?;
76        let tag = self.state.cursor.consume_while(|c| c.is_alphanumeric() || c == '-');
77        if tag.is_empty() {
78            return Err(Error::parse_error("Expected tag name".to_string(), self.state.cursor.span_at_current()));
79        }
80        println!("Parsing element: <{}>", tag);
81        let mut attributes = Vec::new();
82
83        let mut trivia = self.consume_trivia();
84        while !self.state.cursor.is_eof() && self.state.cursor.peek() != '>' && !self.state.cursor.peek_str("/>") {
85            let attr_start = self.state.cursor.position();
86            let attr_name = self.state.cursor.consume_while(|c| !c.is_whitespace() && c != '=' && c != '>' && c != '/');
87
88            let mut attr_trivia = self.consume_trivia();
89            let attr_value = if self.state.cursor.peek() == '=' {
90                self.state.cursor.consume();
91                attr_trivia.leading_whitespace.push('=');
92                attr_trivia.leading_whitespace.push_str(&self.state.cursor.consume_whitespace());
93
94                let peek = self.state.cursor.peek();
95                if peek == '"' || peek == '\'' {
96                    Some(self.state.cursor.consume_string()?)
97                }
98                else if peek == '{' {
99                    // Dynamic attribute: attr={val}
100                    let interpolation = self.parse_interpolation()?;
101                    if let TemplateNodeIR::Interpolation(expr) = interpolation {
102                        Some(expr.code)
103                    }
104                    else {
105                        None
106                    }
107                }
108                else {
109                    Some(self.state.cursor.consume_while(|c| !c.is_whitespace() && c != '>' && c != '/'))
110                }
111            }
112            else {
113                None
114            };
115
116            // VOC syntax uses colon for directives: on:click
117            let is_directive = attr_name.contains(':');
118            let is_dynamic = attr_value.as_ref().map(|v| v.starts_with('{') || is_directive).unwrap_or(false);
119
120            let value_ast = None;
121            let attr_end = self.state.cursor.position();
122            attributes.push(AttributeIR { name: attr_name, value: attr_value, value_ast, argument: None, modifiers: Vec::new(), is_directive, is_dynamic, span: Span { start: attr_start, end: attr_end }, trivia: attr_trivia });
123            trivia = self.consume_trivia();
124        }
125
126        let is_self_closing = if self.state.cursor.peek_str("/>") {
127            self.state.cursor.consume_n(2);
128            true
129        }
130        else {
131            self.state.cursor.expect('>')?;
132            false
133        };
134
135        let mut children = Vec::new();
136        if !is_self_closing && !is_void_element(&tag) {
137            if tag == "script" || tag == "style" {
138                let start = self.state.cursor.pos;
139                let end_tag = format!("</{}>", tag);
140                while !self.state.cursor.is_eof() && !self.state.cursor.peek_str(&end_tag) {
141                    self.state.cursor.consume();
142                }
143                let content = self.state.cursor.current_str(start).to_string();
144                if !content.is_empty() {
145                    let text_span = self.state.cursor.span_from(start_pos);
146                    children.push(TemplateNodeIR::Text(content, text_span, Trivia::default()));
147                }
148                self.state.cursor.expect_str(&end_tag)?;
149            }
150            else if tag == "if" {
151                // 处理条件指令
152                children = self.parse()?;
153                self.state.cursor.expect_str("</if>")?;
154                let end_pos = self.state.cursor.position();
155
156                // 查找条件属性
157                let mut condition = ExpressionIR::default();
158                for attr in &attributes {
159                    if attr.name == "test" || attr.name == "condition" {
160                        if let Some(ref value) = attr.value {
161                            condition = ExpressionIR { code: value.clone(), ast: attr.value_ast.clone(), is_static: false, span: attr.span, trivia: attr.trivia.clone() };
162                        }
163                        break;
164                    }
165                }
166
167                return Ok(TemplateNodeIR::If(nargo_ir::IfNodeIR { condition, consequent: children, alternate: None, else_ifs: Vec::new(), span: Span { start: start_pos, end: end_pos } }));
168            }
169            else if tag == "for" {
170                // 处理循环指令
171                children = self.parse()?;
172                self.state.cursor.expect_str("</for>")?;
173                let end_pos = self.state.cursor.position();
174
175                // 查找循环属性
176                let mut iterator = nargo_ir::ForIteratorIR::default();
177                for attr in &attributes {
178                    if attr.name == "each" {
179                        if let Some(ref value) = attr.value {
180                            // 处理格式: item in items 或 (item, index) in items
181                            if let Some((left, right)) = value.split_once(" in ") {
182                                let left = left.trim();
183                                if let Some((item, index)) = left.split_once(',') {
184                                    iterator.item = item.trim().to_string();
185                                    iterator.index = Some(index.trim().to_string());
186                                }
187                                else {
188                                    iterator.item = left.to_string();
189                                }
190                                iterator.collection = ExpressionIR { code: right.trim().to_string(), ast: None, is_static: false, span: attr.span, trivia: attr.trivia.clone() };
191                            }
192                        }
193                        break;
194                    }
195                }
196
197                return Ok(TemplateNodeIR::For(nargo_ir::ForNodeIR { iterator, body: children, span: Span { start: start_pos, end: end_pos } }));
198            }
199            else {
200                children = self.parse()?;
201                println!("Element <{}> found {} children", tag, children.len());
202                self.state.cursor.expect_str(&format!("</{}>", tag))?;
203            }
204        }
205
206        let end_pos = self.state.cursor.position();
207
208        Ok(TemplateNodeIR::Element(ElementIR { tag, attributes, children, is_static: false, span: Span { start: start_pos, end: end_pos }, trivia }))
209    }
210
211    fn parse_interpolation(&mut self) -> Result<TemplateNodeIR> {
212        let start_pos = self.state.cursor.position();
213        self.state.cursor.expect('{')?;
214        let start = self.state.cursor.pos;
215        let mut depth = 1;
216        while !self.state.cursor.is_eof() && depth > 0 {
217            let c = self.state.cursor.consume();
218            if c == '{' {
219                depth += 1;
220            }
221            else if c == '}' {
222                depth -= 1;
223            }
224        }
225        let content = self.state.cursor.current_str(start);
226        let content = &content[..content.len() - 1]; // Remove trailing }
227        let content = content.trim().to_string();
228        // Remove surrounding braces if present
229        let content = if content.starts_with('{') && content.ends_with('}') { content[1..content.len() - 1].trim().to_string() } else { content };
230        let end_pos = self.state.cursor.position();
231
232        let ast = None;
233
234        Ok(TemplateNodeIR::Interpolation(ExpressionIR { code: content, ast, is_static: false, span: Span { start: start_pos, end: end_pos }, trivia: Trivia::default() }))
235    }
236
237    fn parse_text(&mut self) -> Result<TemplateNodeIR> {
238        let start_pos = self.state.cursor.position();
239        let start = self.state.cursor.pos;
240        while !self.state.cursor.is_eof() && self.state.cursor.peek() != '<' && self.state.cursor.peek() != '{' {
241            self.state.cursor.consume();
242        }
243        let end_pos = self.state.cursor.position();
244        Ok(TemplateNodeIR::Text(self.state.cursor.current_str(start).to_string(), Span { start: start_pos, end: end_pos }, Trivia::default()))
245    }
246
247    fn parse_comment(&mut self) -> Result<TemplateNodeIR> {
248        let start_pos = self.state.cursor.position();
249        self.state.cursor.expect_str("<!--")?;
250        let start = self.state.cursor.pos;
251        while !self.state.cursor.is_eof() && !self.state.cursor.peek_str("-->") {
252            self.state.cursor.consume();
253        }
254        let content = self.state.cursor.current_str(start).to_string();
255        self.state.cursor.expect_str("-->")?;
256        let end_pos = self.state.cursor.position();
257        Ok(TemplateNodeIR::Comment(content, Span { start: start_pos, end: end_pos }, Trivia::default()))
258    }
259}