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(':') || trimmed.ends_with(':')
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 indent = indent_level(raw);
83        let content = raw.trim();
84
85        if content.is_empty() {
86            *i += 1;
87            continue;
88        }
89
90        if indent < base_indent {
91            break;
92        }
93
94        if indent > base_indent {
95            return Err(
96                {
97            let line_file = (
98                    line_no,
99                    current_file_path.clone(),
100                );
101            RuntimeError::from(ParseRuntimeError(RuntimeErrorStruct::new(
102                None,
103                format!(
104                    "unexpected indent at line {} in {}",
105                    line_file.0,
106                    line_file.1.as_ref()
107                ),
108                line_file,
109                None,
110                vec![],
111            )))
112        },
113            );
114        }
115
116        *i += 1;
117
118        // Tokenize header; if it's empty (e.g. whole line comment),
119        // treat it like a blank line for block parsing.
120        let header_tokens = tokenize_line(content);
121        if header_tokens.is_empty() {
122            continue;
123        }
124
125        if ends_with_colon(content) {
126            // 必须有 body
127            if *i >= lines.len() {
128                return Err(
129                    {
130            let line_file = (
131                        line_no,
132                        current_file_path.clone(),
133                    );
134            RuntimeError::from(ParseRuntimeError(RuntimeErrorStruct::new(
135                None,
136                format!(
137                    "block header missing body at line {} in {}",
138                    line_file.0,
139                    line_file.1.as_ref()
140                ),
141                line_file,
142                None,
143                vec![],
144            )))
145        },
146                );
147            }
148
149            let next_indent = indent_level(lines[*i]);
150            if next_indent <= indent {
151                return Err(
152                    {
153            let line_file = (
154                        *i + 1,
155                        current_file_path.clone(),
156                    );
157            RuntimeError::from(ParseRuntimeError(RuntimeErrorStruct::new(
158                None,
159                format!(
160                    "expected indent at line {} in {}",
161                    line_file.0,
162                    line_file.1.as_ref()
163                ),
164                line_file,
165                None,
166                vec![],
167            )))
168        },
169                );
170            }
171
172            let body = parse_level(lines, i, next_indent, current_file_path.clone())?;
173            items.push(TokenBlock::new(
174                header_tokens,
175                body,
176                (line_no, current_file_path.clone()),
177            ));
178        } else {
179            items.push(TokenBlock::new(
180                header_tokens,
181                vec![],
182                (line_no, current_file_path.clone()),
183            ));
184        }
185
186        if let Some(expected) = body_indent {
187            if indent != expected {
188                return Err(
189                    {
190            let line_file = (
191                        line_no,
192                        current_file_path.clone(),
193                    );
194            RuntimeError::from(ParseRuntimeError(RuntimeErrorStruct::new(
195                None,
196                format!(
197                    "inconsistent indent at line {} in {}",
198                    line_file.0,
199                    line_file.1.as_ref()
200                ),
201                line_file,
202                None,
203                vec![],
204            )))
205        },
206                );
207            }
208        } else {
209            body_indent = Some(indent);
210        }
211    }
212
213    Ok(items)
214}
215
216impl TokenBlock {
217    /// 返回当前 token;若已读完则返回 Error。
218    pub fn current(&self) -> Result<&str, RuntimeError> {
219        self.header
220            .get(self.parse_index)
221            .map(|s| s.as_str())
222            .ok_or_else(|| {
223                RuntimeError::from(ParseRuntimeError(RuntimeErrorStruct::new(
224 None,
225                "Unexpected end of tokens".to_string(),
226                self.line_file.clone(),
227                None,
228                vec![],
229            )))
230            })
231    }
232
233    pub fn skip_token(self: &mut Self, token: &str) -> Result<(), RuntimeError> {
234        if self.current()? == token {
235            self.parse_index += 1;
236            Ok(())
237        } else {
238            Err(
239                RuntimeError::from(ParseRuntimeError(RuntimeErrorStruct::new(
240 None,
241                format!("Expected token: {}", token),
242                self.line_file.clone(),
243                None,
244                vec![],
245            ))))
246        }
247    }
248
249    pub fn advance(&mut self) -> Result<String, RuntimeError> {
250        let t = self.current()?.to_string();
251        self.parse_index += 1;
252        Ok(t)
253    }
254
255    pub fn skip(&mut self) -> Result<(), RuntimeError> {
256        self.current()?;
257        self.parse_index += 1;
258        Ok(())
259    }
260
261    pub fn exceed_end_of_head(&self) -> bool {
262        return self.parse_index >= self.header.len();
263    }
264
265    pub fn skip_token_and_colon_and_exceed_end_of_head(
266        &mut self,
267        token: &str,
268    ) -> Result<(), RuntimeError> {
269        self.skip_token(token)?;
270        self.skip_token(COLON)?;
271        if !self.exceed_end_of_head() {
272            return Err(
273                RuntimeError::from(ParseRuntimeError(RuntimeErrorStruct::new(
274 None,
275                "Expected token: at head".to_string(),
276                self.line_file.clone(),
277                None,
278                vec![],
279            ))));
280        }
281        Ok(())
282    }
283
284    pub fn token_at_index(&self, index: usize) -> Result<&str, RuntimeError> {
285        self.header.get(index).map(|s| s.as_str()).ok_or_else(|| {
286            RuntimeError::from(ParseRuntimeError(RuntimeErrorStruct::new(
287 None,
288                format!("Expected token: at index {}", index),
289                self.line_file.clone(),
290                None,
291                vec![],
292            )))
293        })
294    }
295
296    pub fn current_token_empty_if_exceed_end_of_head(&self) -> &str {
297        if self.exceed_end_of_head() {
298            return "";
299        }
300        self.header
301            .get(self.parse_index)
302            .map(|s| s.as_str())
303            .unwrap_or("")
304    }
305
306    pub fn current_token_is_equal_to(&self, token: &str) -> bool {
307        self.current_token_empty_if_exceed_end_of_head() == token
308    }
309
310    pub fn token_at_end_of_head(&self) -> &str {
311        self.header
312            .get(self.header.len() - 1)
313            .map(|s| s.as_str())
314            .unwrap_or("")
315    }
316}
317
318impl TokenBlock {
319    pub fn new(tokens: Vec<String>, body: Vec<TokenBlock>, line_file: LineFile) -> TokenBlock {
320        TokenBlock {
321            header: tokens,
322            body,
323            line_file,
324            parse_index: 0,
325        }
326    }
327}