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 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 let header_tokens = tokenize_line(content);
121 if header_tokens.is_empty() {
122 continue;
123 }
124
125 if ends_with_colon(content) {
126 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 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}