Skip to main content

litex/parse/
token_block.rs

1use super::tokenizer::tokenize_line;
2use crate::prelude::*;
3use std::rc::Rc;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct TokenBlock {
7    pub header: Vec<String>,
8    pub body: Vec<TokenBlock>,
9    pub line_file: LineFile,
10    pub parse_index: usize,
11}
12
13fn indent_level(line: &str) -> usize {
14    let mut n = 0;
15    for c in line.chars() {
16        match c {
17            ' ' => n += 1,
18            '\t' => n += 4,
19            _ => break,
20        }
21    }
22    n
23}
24
25fn ends_with_colon(s: &str) -> bool {
26    let trimmed = s.trim_end();
27    trimmed.ends_with(COLON)
28}
29
30impl TokenBlock {
31    pub fn parse_blocks(
32        s: &str,
33        current_file_path: Rc<str>,
34    ) -> Result<Vec<TokenBlock>, RuntimeError> {
35        let stripped_source_code = strip_triple_quote_comment_blocks(s);
36        let lines: Vec<_> = stripped_source_code.lines().collect();
37        let mut i = 0;
38        parse_level(&lines, &mut i, 0, current_file_path)
39    }
40}
41
42fn strip_triple_quote_comment_blocks(source_code: &str) -> String {
43    // Treat a line that consists only of `"` characters (after trimming) as a delimiter.
44    // Between two delimiter lines, everything is replaced with empty lines so
45    // the parser will ignore those lines.
46    let mut in_comment = false;
47    let line_count_upper_bound = source_code.lines().count();
48    let mut out_lines: Vec<String> = Vec::with_capacity(line_count_upper_bound);
49
50    for line in source_code.lines() {
51        let trimmed = line.trim();
52        let only_quote_chars = !trimmed.is_empty() && trimmed.chars().all(|c| c == '"');
53        if only_quote_chars {
54            in_comment = !in_comment;
55            out_lines.push(String::new());
56            continue;
57        }
58
59        if in_comment {
60            out_lines.push(String::new());
61        } else {
62            out_lines.push(line.to_string());
63        }
64    }
65
66    out_lines.join("\n")
67}
68
69fn parse_level(
70    lines: &[&str],
71    i: &mut usize,
72    base_indent: usize,
73    current_file_path: Rc<str>,
74) -> Result<Vec<TokenBlock>, RuntimeError> {
75    let remaining_line_count_upper_bound = lines.len().saturating_sub(*i);
76    let mut items = Vec::with_capacity(remaining_line_count_upper_bound);
77    let mut body_indent = None;
78
79    while *i < lines.len() {
80        let raw = lines[*i];
81        let line_no = *i + 1;
82        let line_file = (line_no, current_file_path.clone());
83        let indent = indent_level(raw);
84        let content = raw.trim();
85
86        if content.is_empty() {
87            *i += 1;
88            continue;
89        }
90
91        if indent < base_indent {
92            break;
93        }
94
95        if indent > base_indent {
96            // Indented farther than the current block: normally a syntax error, but allow lines that
97            // are only a `# ...` comment (any leading spaces/tabs before `#`). Writers often align
98            // such comments with a surrounding block without attaching them to a sibling item.
99            let trimmed_start = raw.trim_start();
100            if trimmed_start.is_empty() || trimmed_start.starts_with('#') {
101                *i += 1;
102                continue;
103            }
104            return Err({
105                RuntimeError::from(ParseRuntimeError(
106                    RuntimeErrorStruct::new_with_msg_and_line_file(
107                        format!(
108                            "unexpected indent at line {} in {}",
109                            line_file.0,
110                            line_file.1.as_ref()
111                        ),
112                        line_file,
113                    ),
114                ))
115            });
116        }
117
118        *i += 1;
119
120        // Tokenize header; if it's empty (e.g. whole line comment),
121        // treat it like a blank line for block parsing.
122        let header_tokens = tokenize_line(content);
123        if header_tokens.is_empty() {
124            continue;
125        }
126
127        if ends_with_colon(content) {
128            // 必须有 body
129            if *i >= lines.len() {
130                return Err({
131                    let line_file = (line_no, current_file_path.clone());
132                    RuntimeError::from(ParseRuntimeError(
133                        RuntimeErrorStruct::new_with_msg_and_line_file(
134                            format!(
135                                "block header missing body at line {} in {}",
136                                line_file.0,
137                                line_file.1.as_ref()
138                            ),
139                            line_file,
140                        ),
141                    ))
142                });
143            }
144
145            let next_indent = indent_level(lines[*i]);
146            if next_indent <= indent {
147                return Err({
148                    let line_file = (*i + 1, current_file_path.clone());
149                    RuntimeError::from(ParseRuntimeError(
150                        RuntimeErrorStruct::new_with_msg_and_line_file(
151                            format!(
152                                "expected indent at line {} in {}",
153                                line_file.0,
154                                line_file.1.as_ref()
155                            ),
156                            line_file,
157                        ),
158                    ))
159                });
160            }
161
162            let body = parse_level(lines, i, next_indent, current_file_path.clone())?;
163            items.push(TokenBlock::new(
164                header_tokens,
165                body,
166                (line_no, current_file_path.clone()),
167            ));
168        } else {
169            items.push(TokenBlock::new(
170                header_tokens,
171                vec![],
172                (line_no, current_file_path.clone()),
173            ));
174        }
175
176        if let Some(expected) = body_indent {
177            if indent != expected {
178                return Err({
179                    let line_file = (line_no, current_file_path.clone());
180                    RuntimeError::from(ParseRuntimeError(
181                        RuntimeErrorStruct::new_with_msg_and_line_file(
182                            format!(
183                                "inconsistent indent at line {} in {}",
184                                line_file.0,
185                                line_file.1.as_ref()
186                            ),
187                            line_file,
188                        ),
189                    ))
190                });
191            }
192        } else {
193            body_indent = Some(indent);
194        }
195    }
196
197    Ok(items)
198}
199
200impl TokenBlock {
201    /// 返回当前 token;若已读完则返回 Error。
202    pub fn current(&self) -> Result<&str, RuntimeError> {
203        self.header
204            .get(self.parse_index)
205            .map(|s| s.as_str())
206            .ok_or_else(|| {
207                RuntimeError::from(ParseRuntimeError(
208                    RuntimeErrorStruct::new_with_msg_and_line_file(
209                        "Unexpected end of tokens".to_string(),
210                        self.line_file.clone(),
211                    ),
212                ))
213            })
214    }
215
216    pub fn skip_token(self: &mut Self, token: &str) -> Result<(), RuntimeError> {
217        if self.current()? == token {
218            self.parse_index += 1;
219            Ok(())
220        } else {
221            Err(RuntimeError::from(ParseRuntimeError(
222                RuntimeErrorStruct::new_with_msg_and_line_file(
223                    format!("Expected token: {}", token),
224                    self.line_file.clone(),
225                ),
226            )))
227        }
228    }
229
230    pub fn advance(&mut self) -> Result<String, RuntimeError> {
231        let t = self.current()?.to_string();
232        self.parse_index += 1;
233        Ok(t)
234    }
235
236    pub fn skip(&mut self) -> Result<(), RuntimeError> {
237        self.current()?;
238        self.parse_index += 1;
239        Ok(())
240    }
241
242    pub fn exceed_end_of_head(&self) -> bool {
243        return self.parse_index >= self.header.len();
244    }
245
246    pub fn skip_token_and_colon_and_exceed_end_of_head(
247        &mut self,
248        token: &str,
249    ) -> Result<(), RuntimeError> {
250        self.skip_token(token)?;
251        self.skip_token(COLON)?;
252        if !self.exceed_end_of_head() {
253            return Err(RuntimeError::from(ParseRuntimeError(
254                RuntimeErrorStruct::new_with_msg_and_line_file(
255                    "Expected token: at head".to_string(),
256                    self.line_file.clone(),
257                ),
258            )));
259        }
260        Ok(())
261    }
262
263    pub fn token_at_index(&self, index: usize) -> Result<&str, RuntimeError> {
264        self.header.get(index).map(|s| s.as_str()).ok_or_else(|| {
265            RuntimeError::from(ParseRuntimeError(
266                RuntimeErrorStruct::new_with_msg_and_line_file(
267                    format!("Expected token: at index {}", index),
268                    self.line_file.clone(),
269                ),
270            ))
271        })
272    }
273
274    pub fn current_token_empty_if_exceed_end_of_head(&self) -> &str {
275        if self.exceed_end_of_head() {
276            return "";
277        }
278        self.header
279            .get(self.parse_index)
280            .map(|s| s.as_str())
281            .unwrap_or("")
282    }
283
284    pub fn current_token_is_equal_to(&self, token: &str) -> bool {
285        self.current_token_empty_if_exceed_end_of_head() == token
286    }
287
288    pub fn token_at_end_of_head(&self) -> &str {
289        self.header
290            .get(self.header.len() - 1)
291            .map(|s| s.as_str())
292            .unwrap_or("")
293    }
294
295    pub fn token_at_add_index(&self, index: usize) -> &str {
296        self.header
297            .get(self.parse_index + index)
298            .map(|s| s.as_str())
299            .unwrap_or("")
300    }
301}
302
303impl TokenBlock {
304    pub fn new(tokens: Vec<String>, body: Vec<TokenBlock>, line_file: LineFile) -> TokenBlock {
305        TokenBlock {
306            header: tokens,
307            body,
308            line_file,
309            parse_index: 0,
310        }
311    }
312}