1use crate::{ParseState, ParserRegistry};
2use nargo_ir::{CustomBlockIR, IRModule, StyleIR, TemplateIR};
3use nargo_types::{Cursor, Result, Span};
4use std::{collections::HashMap, sync::Arc};
5
6pub struct VocShell<'a> {
7 name: String,
8 state: ParseState<'a>,
9 registry: Arc<ParserRegistry>,
10}
11
12impl<'a> VocShell<'a> {
13 pub fn new(name: String, source: &'a str, registry: Arc<ParserRegistry>) -> Self {
14 Self { name, state: ParseState::new(source), registry }
15 }
16
17 pub fn parse_all(&mut self) -> Result<IRModule> {
18 self.parse_module()
19 }
20
21 pub fn parse_module(&mut self) -> Result<IRModule> {
22 let mut template_nodes = Vec::new();
23 let mut scripts = Vec::new();
24 let mut scripts_server = Vec::new();
25 let mut scripts_client = Vec::new();
26 let mut ir_styles = Vec::new();
27 let mut metadata = HashMap::new();
28 let mut custom_blocks = Vec::new();
29
30 while !self.state.cursor.is_eof() {
31 self.state.cursor.skip_whitespace();
32 if self.state.cursor.is_eof() {
33 break;
34 }
35
36 if self.state.cursor.peek_str("<template") {
37 self.parse_template_block(&mut template_nodes)?;
38 }
39 else if self.state.cursor.peek_str("<script") {
40 self.parse_script_block(&mut scripts, &mut scripts_server, &mut scripts_client)?;
41 }
42 else if self.state.cursor.peek_str("<style") {
43 self.parse_style_block(&mut ir_styles)?;
44 }
45 else if self.state.cursor.peek_str("<metadata") {
46 self.parse_metadata_block(&mut metadata)?;
47 }
48 else if self.state.cursor.peek_str("<") {
49 self.parse_custom_block(&mut custom_blocks)?;
50 }
51 else {
52 self.state.cursor.consume();
53 }
54 }
55
56 let script = self.merge_scripts(scripts);
57 let script_server = self.merge_scripts(scripts_server);
58 let script_client = self.merge_scripts(scripts_client);
59
60 let mut script_meta = None;
62 if let Some(s) = &script {
63 let analyzer = nargo_script_analyzer::ScriptAnalyzer::new();
64 if let Ok(meta) = analyzer.analyze(s) {
65 script_meta = Some(meta.to_nargo_value());
66 }
67 }
68
69 Ok(IRModule { name: self.name.clone(), metadata, script, script_server, script_client, script_meta, template: if template_nodes.is_empty() { None } else { Some(TemplateIR { nodes: template_nodes, span: Span::default() }) }, hoisted_nodes: HashMap::new(), styles: ir_styles, i18n: None, wasm: Vec::new(), custom_blocks, tests: Vec::new(), span: Span::default() })
70 }
71
72 fn parse_template_block(&mut self, nodes: &mut Vec<nargo_ir::TemplateNodeIR>) -> Result<()> {
73 let (content, attrs, start_pos) = self.parse_special_tag("template")?;
74 let lang = attrs.get("lang").cloned().unwrap_or_else(|| "nargo".to_string());
75 if let Some(template_parser) = self.registry.get_template_parser(&lang) {
76 let mut sub_state = ParseState::with_cursor(Cursor::with_sliced_source(content, start_pos));
77 nodes.extend(template_parser.parse(&mut sub_state, &lang)?);
78 }
79 Ok(())
80 }
81
82 fn parse_script_block(&mut self, scripts: &mut Vec<nargo_ir::JsProgram>, server: &mut Vec<nargo_ir::JsProgram>, client: &mut Vec<nargo_ir::JsProgram>) -> Result<()> {
83 let (content, attrs, start_pos) = self.parse_special_tag("script")?;
84 let lang = attrs.get("lang").cloned().unwrap_or_else(|| "ts".to_string());
85 let is_server = attrs.contains_key("server");
86 let is_client = attrs.contains_key("client");
87
88 if let Some(script_parser) = self.registry.get_script_parser(&lang) {
89 let mut sub_state = ParseState::with_cursor(Cursor::with_sliced_source(content, start_pos));
90 let program = script_parser.parse(&mut sub_state, &lang)?;
91
92 if is_server {
93 server.push(program);
94 }
95 else if is_client {
96 client.push(program);
97 }
98 else {
99 scripts.push(program);
100 }
101 }
102 Ok(())
103 }
104
105 fn parse_style_block(&mut self, styles: &mut Vec<StyleIR>) -> Result<()> {
106 let (content, attrs, start_pos) = self.parse_special_tag("style")?;
107 let lang = attrs.get("lang").cloned().unwrap_or_else(|| "css".to_string());
108 let scoped = attrs.contains_key("scoped");
109 let span = self.state.cursor.span_from(start_pos);
110
111 if let Some(style_parser) = self.registry.get_style_parser(&lang) {
112 let mut sub_state = ParseState::with_cursor(Cursor::with_sliced_source(content, start_pos));
113 if let Ok((code, trivia)) = style_parser.parse(&mut sub_state, &lang) {
114 styles.push(StyleIR { code, lang, scoped, span, trivia });
115 }
116 }
117 Ok(())
118 }
119
120 fn parse_metadata_block(&mut self, metadata: &mut HashMap<String, nargo_types::NargoValue>) -> Result<()> {
121 let (content, attrs, start_pos) = self.parse_special_tag("metadata")?;
122 let lang = attrs.get("lang").cloned().unwrap_or_else(|| "json".to_string());
123 if let Some(metadata_parser) = self.registry.get_metadata_parser(&lang) {
124 let mut sub_state = ParseState::with_cursor(Cursor::with_sliced_source(content, start_pos));
125 if let Ok((nargo_types::NargoValue::Object(map), _trivia)) = metadata_parser.parse(&mut sub_state, &lang) {
126 metadata.extend(map);
127 }
128 }
129 Ok(())
130 }
131
132 fn parse_custom_block(&mut self, blocks: &mut Vec<CustomBlockIR>) -> Result<()> {
133 self.state.cursor.consume(); let block_name = self.state.cursor.consume_while(|c| c.is_alphanumeric() || c == '-');
135 let attrs = self.state.parse_tag_attributes();
136 self.state.cursor.expect('>')?;
137
138 let start_pos = self.state.cursor.position();
139 let start_offset = self.state.cursor.pos;
140 let end_tag = format!("</{}>", block_name);
141
142 let remaining_source = &self.state.cursor.source[start_offset..];
144 if let Some(end_offset) = remaining_source.find(&end_tag) {
145 self.state.cursor.pos = start_offset + end_offset;
146 }
147 else {
148 while !self.state.cursor.is_eof() {
150 self.state.cursor.consume();
151 }
152 }
153
154 let content = self.state.cursor.current_str(start_offset).to_string();
155 let span = self.state.cursor.span_from(start_pos);
156 self.state.cursor.consume_str(&end_tag);
157
158 blocks.push(CustomBlockIR { name: block_name, content, attributes: attrs, span, trivia: nargo_ir::Trivia::default() });
159 Ok(())
160 }
161
162 fn parse_special_tag(&mut self, tag: &str) -> Result<(&'a str, HashMap<String, String>, nargo_types::Position)> {
163 let start_tag = format!("<{}", tag);
164 let end_tag = format!("</{}>", tag);
165
166 self.state.cursor.consume_str(&start_tag);
167 let attrs = self.state.parse_tag_attributes();
168 self.state.cursor.expect('>')?;
169
170 let start_pos = self.state.cursor.position();
171 let start_offset = self.state.cursor.pos;
172
173 let remaining_source = &self.state.cursor.source[start_offset..];
175 if let Some(end_offset) = remaining_source.find(&end_tag) {
176 self.state.cursor.pos = start_offset + end_offset;
177 }
178 else {
179 while !self.state.cursor.is_eof() {
181 self.state.cursor.consume();
182 }
183 }
184
185 let content = &self.state.cursor.source[start_offset..self.state.cursor.pos];
186 self.state.cursor.consume_str(&end_tag);
187
188 Ok((content, attrs, start_pos))
189 }
190
191 fn merge_scripts(&self, mut scripts: Vec<nargo_ir::JsProgram>) -> Option<nargo_ir::JsProgram> {
192 if scripts.is_empty() {
193 return None;
194 }
195 let mut first = scripts.remove(0);
196 for mut script in scripts {
197 first.body.append(&mut script.body);
198 }
199 Some(first)
200 }
201}