litex/parse/
token_block.rs1use 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 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 let header_tokens = tokenize_line(content);
103 if header_tokens.is_empty() {
104 continue;
105 }
106
107 if ends_with_colon(content) {
108 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 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}