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(s: &str, current_file_path: Rc<str>) -> Result<Vec<TokenBlock>, RuntimeError> {
32        let stripped_source_code = strip_triple_quote_comment_blocks(s);
33        let lines: Vec<_> = stripped_source_code.lines().collect();
34        let mut i = 0;
35        parse_level(&lines, &mut i, 0, current_file_path)
36    }
37}
38
39fn strip_triple_quote_comment_blocks(source_code: &str) -> String {
40    // Treat a line that consists only of `"` characters (after trimming) as a delimiter.
41    // Between two delimiter lines, everything is replaced with empty lines so
42    // the parser will ignore those lines.
43    let mut in_comment = false;
44    let line_count_upper_bound = source_code.lines().count();
45    let mut out_lines: Vec<String> = Vec::with_capacity(line_count_upper_bound);
46
47    for line in source_code.lines() {
48        let trimmed = line.trim();
49        let only_quote_chars = !trimmed.is_empty() && trimmed.chars().all(|c| c == '"');
50        if only_quote_chars {
51            in_comment = !in_comment;
52            out_lines.push(String::new());
53            continue;
54        }
55
56        if in_comment {
57            out_lines.push(String::new());
58        } else {
59            out_lines.push(line.to_string());
60        }
61    }
62
63    out_lines.join("\n")
64}
65
66fn parse_level(
67    lines: &[&str],
68    i: &mut usize,
69    base_indent: usize,
70    current_file_path: Rc<str>,
71) -> Result<Vec<TokenBlock>, RuntimeError> {
72    let remaining_line_count_upper_bound = lines.len().saturating_sub(*i);
73    let mut items = Vec::with_capacity(remaining_line_count_upper_bound);
74    let mut body_indent = None;
75
76    while *i < lines.len() {
77        let raw = lines[*i];
78        let line_no = *i + 1;
79        let indent = indent_level(raw);
80        let content = raw.trim();
81
82        if content.is_empty() {
83            *i += 1;
84            continue;
85        }
86
87        if indent < base_indent {
88            break;
89        }
90
91        if indent > base_indent {
92            return Err(RuntimeError::new_parse_error_for_block_unexpected_indent_at_line_file((
93                line_no,
94                current_file_path.clone(),
95            )));
96        }
97
98        *i += 1;
99
100        // Tokenize header; if it's empty (e.g. whole line comment),
101        // treat it like a blank line for block parsing.
102        let header_tokens = tokenize_line(content);
103        if header_tokens.is_empty() {
104            continue;
105        }
106
107        if ends_with_colon(content) {
108            // 必须有 body
109            if *i >= lines.len() {
110                return Err(RuntimeError::new_parse_error_for_block_missing_body_at_line_file((
111                    line_no,
112                    current_file_path.clone(),
113                )));
114            }
115
116            let next_indent = indent_level(lines[*i]);
117            if next_indent <= indent {
118                return Err(RuntimeError::new_parse_error_for_block_expected_indent_at_line_file((
119                    *i + 1,
120                    current_file_path.clone(),
121                )));
122            }
123
124            let body = parse_level(lines, i, next_indent, current_file_path.clone())?;
125            items.push(TokenBlock::new(
126                header_tokens,
127                body,
128                (line_no, current_file_path.clone()),
129            ));
130        } else {
131            items.push(TokenBlock::new(
132                header_tokens,
133                vec![],
134                (line_no, current_file_path.clone()),
135            ));
136        }
137
138        if let Some(expected) = body_indent {
139            if indent != expected {
140                return Err(RuntimeError::new_parse_error_for_block_inconsistent_indent_at_line_file((
141                    line_no,
142                    current_file_path.clone(),
143                )));
144            }
145        } else {
146            body_indent = Some(indent);
147        }
148    }
149
150    Ok(items)
151}
152
153impl TokenBlock {
154    /// 返回当前 token;若已读完则返回 Error。
155    pub fn current(&self) -> Result<&str, RuntimeError> {
156        self.header
157            .get(self.parse_index)
158            .map(|s| s.as_str())
159            .ok_or_else(|| {
160                RuntimeError::new_parse_error_with_msg_position_previous_error("Unexpected end of tokens".to_string(), self.line_file.clone(), None)
161            })
162    }
163
164    pub fn skip_token(self: &mut Self, token: &str) -> Result<(), RuntimeError> {
165        if self.current()? == token {
166            self.parse_index += 1;
167            Ok(())
168        } else {
169            Err(RuntimeError::new_parse_error_with_msg_position_previous_error(
170                format!("Expected token: {}", token),
171                self.line_file.clone(),
172                None,
173            ))
174        }
175    }
176
177    pub fn advance(&mut self) -> Result<String, RuntimeError> {
178        let t = self.current()?.to_string();
179        self.parse_index += 1;
180        Ok(t)
181    }
182
183    pub fn skip(&mut self) -> Result<(), RuntimeError> {
184        self.current()?;
185        self.parse_index += 1;
186        Ok(())
187    }
188
189    pub fn exceed_end_of_head(&self) -> bool {
190        return self.parse_index >= self.header.len();
191    }
192
193    pub fn skip_token_and_colon_and_exceed_end_of_head(
194        &mut self,
195        token: &str,
196    ) -> Result<(), RuntimeError> {
197        self.skip_token(token)?;
198        self.skip_token(COLON)?;
199        if !self.exceed_end_of_head() {
200            return Err(RuntimeError::new_parse_error_with_msg_position_previous_error(
201                "Expected token: at head".to_string(),
202                self.line_file.clone(),
203                None,
204            ));
205        }
206        Ok(())
207    }
208
209    pub fn token_at_index(&self, index: usize) -> Result<&str, RuntimeError> {
210        self.header.get(index).map(|s| s.as_str()).ok_or_else(|| {
211            RuntimeError::new_parse_error_with_msg_position_previous_error(
212                format!("Expected token: at index {}", index),
213                self.line_file.clone(),
214                None,
215            )
216        })
217    }
218
219    pub fn current_token_empty_if_exceed_end_of_head(&self) -> &str {
220        if self.exceed_end_of_head() {
221            return "";
222        }
223        self.header
224            .get(self.parse_index)
225            .map(|s| s.as_str())
226            .unwrap_or("")
227    }
228
229    pub fn current_token_is_equal_to(&self, token: &str) -> bool {
230        self.current_token_empty_if_exceed_end_of_head() == token
231    }
232
233    pub fn token_at_end_of_head(&self) -> &str {
234        self.header
235            .get(self.header.len() - 1)
236            .map(|s| s.as_str())
237            .unwrap_or("")
238    }
239}
240
241impl TokenBlock {
242    pub fn new(tokens: Vec<String>, body: Vec<TokenBlock>, line_file: LineFile) -> TokenBlock {
243        TokenBlock {
244            header: tokens,
245            body,
246            line_file,
247            parse_index: 0,
248        }
249    }
250}