1use crate::quotes::{DelimitedReferences, QUOTES};
4use crate::reference_literal::reference_literal_end;
5use crate::{
6 LiNo, ParseError, ParserConfig, SyntaxReference, parse_lino_to_links_with_config,
7 parse_lino_to_syntax_links_with_config,
8};
9use std::collections::VecDeque;
10use std::fmt;
11
12const DEFAULT_MAX_BUFFER_SIZE: usize = 10 * 1024 * 1024;
13const SEPARATORS: &[u8] = b" \t\n\r(:)";
15const BEFORE_COMMENT: &[u8] = b" \t\n\r";
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct StreamPosition {
20 pub line: usize,
22 pub column: usize,
24 pub offset: usize,
26 pub buffered: usize,
28}
29
30pub type ErrorLocation = StreamPosition;
32
33impl fmt::Display for StreamPosition {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 write!(
36 formatter,
37 "line {}, column {} (offset {})",
38 self.line, self.column, self.offset
39 )
40 }
41}
42
43#[derive(Debug)]
45pub struct StreamParseError {
46 pub message: String,
48 pub location: Option<StreamPosition>,
50 pub parse_error: Option<Box<ParseError>>,
52}
53
54impl fmt::Display for StreamParseError {
55 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match &self.location {
57 Some(location) => write!(
58 formatter,
59 "stream parse error at {location}: {}",
60 self.message
61 ),
62 None => write!(formatter, "stream parse error: {}", self.message),
63 }
64 }
65}
66
67impl std::error::Error for StreamParseError {
68 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
69 self.parse_error
70 .as_deref()
71 .map(|error| error as &(dyn std::error::Error + 'static))
72 }
73}
74
75impl From<ParseError> for StreamParseError {
76 fn from(error: ParseError) -> Self {
77 Self {
78 message: error.to_string(),
79 location: None,
80 parse_error: Some(Box::new(error)),
81 }
82 }
83}
84
85pub type LinkCallback<T = String> = Box<dyn FnMut(&LiNo<T>) + Send>;
87
88pub type ErrorCallback = Box<dyn FnMut(&StreamParseError) + Send>;
90
91mod sealed {
92 pub trait Sealed {}
93 impl Sealed for String {}
94 impl Sealed for crate::SyntaxReference {}
95}
96
97pub trait StreamReference: sealed::Sealed + Clone + Send + 'static {
101 #[doc(hidden)]
103 fn parse_record(document: &str, config: &ParserConfig) -> Result<Vec<LiNo<Self>>, ParseError>;
104}
105
106impl StreamReference for String {
107 fn parse_record(document: &str, config: &ParserConfig) -> Result<Vec<LiNo<Self>>, ParseError> {
108 parse_lino_to_links_with_config(document, config)
109 }
110}
111
112impl StreamReference for SyntaxReference {
113 fn parse_record(document: &str, config: &ParserConfig) -> Result<Vec<LiNo<Self>>, ParseError> {
114 parse_lino_to_syntax_links_with_config(document, config)
117 }
118}
119
120pub struct StreamParser<T: StreamReference = String> {
145 config: ParserConfig,
146 buffer: String,
147 current_line: String,
148 base_indentation: Option<usize>,
149 line_classified: bool,
150 link_callback: Option<LinkCallback<T>>,
151 error_callback: Option<ErrorCallback>,
152 collect: bool,
153 max_buffer_size: usize,
154 links: Vec<LiNo<T>>,
155 offset: usize,
156 line: usize,
157 column: usize,
158 segment_offset: usize,
159 segment_line: usize,
160 ended: bool,
161}
162
163impl Default for StreamParser {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169impl StreamParser {
170 pub fn new() -> Self {
172 Self::with_config(ParserConfig::default())
173 }
174
175 pub fn with_config(config: ParserConfig) -> Self {
177 Self::create(config)
178 }
179
180 pub fn parse_chunks<I, S>(chunks: I) -> StreamIterator<I::IntoIter>
182 where
183 I: IntoIterator<Item = S>,
184 S: AsRef<str>,
185 {
186 StreamIterator::new(chunks.into_iter(), Self::new())
187 }
188}
189
190impl StreamParser<SyntaxReference> {
191 pub fn syntax() -> Self {
193 Self::syntax_with_config(ParserConfig::default())
194 }
195
196 pub fn syntax_with_config(config: ParserConfig) -> Self {
199 Self::create(config)
200 }
201
202 pub fn parse_syntax_chunks<I, S>(chunks: I) -> StreamIterator<I::IntoIter, SyntaxReference>
205 where
206 I: IntoIterator<Item = S>,
207 S: AsRef<str>,
208 {
209 StreamIterator::new(chunks.into_iter(), Self::syntax())
210 }
211}
212
213impl<T: StreamReference> StreamParser<T> {
214 fn create(config: ParserConfig) -> Self {
215 Self {
216 config,
217 buffer: String::new(),
218 current_line: String::new(),
219 base_indentation: None,
220 line_classified: false,
221 link_callback: None,
222 error_callback: None,
223 collect: true,
224 max_buffer_size: DEFAULT_MAX_BUFFER_SIZE,
225 links: Vec::new(),
226 offset: 0,
227 line: 1,
228 column: 1,
229 segment_offset: 0,
230 segment_line: 1,
231 ended: false,
232 }
233 }
234
235 pub fn on_link<F>(&mut self, callback: F) -> &mut Self
237 where
238 F: FnMut(&LiNo<T>) + Send + 'static,
239 {
240 self.link_callback = Some(Box::new(callback));
241 self
242 }
243
244 pub fn on_error<F>(&mut self, callback: F) -> &mut Self
246 where
247 F: FnMut(&StreamParseError) + Send + 'static,
248 {
249 self.error_callback = Some(Box::new(callback));
250 self
251 }
252
253 pub fn set_collect(&mut self, collect: bool) -> &mut Self {
255 self.collect = collect;
256 self
257 }
258
259 pub fn set_max_buffer_size(
261 &mut self,
262 max_buffer_size: usize,
263 ) -> Result<&mut Self, StreamParseError> {
264 if max_buffer_size == 0 {
265 return Err(StreamParseError {
266 message: "maximum buffer size must be positive".to_string(),
267 location: Some(self.position()),
268 parse_error: None,
269 });
270 }
271 self.max_buffer_size = max_buffer_size;
272 Ok(self)
273 }
274
275 pub fn write(&mut self, chunk: &str) -> Result<Vec<LiNo<T>>, StreamParseError> {
277 if self.ended {
278 return self.fail(StreamParseError {
279 message: "cannot write after finish()".to_string(),
280 location: Some(self.position()),
281 parse_error: None,
282 });
283 }
284
285 let mut emitted = Vec::new();
286 for character in chunk.chars() {
287 self.current_line.push(character);
288 self.offset += character.len_utf8();
289
290 if character == '\n' {
291 self.buffer.push_str(&self.current_line);
292 self.current_line.clear();
293 self.line_classified = false;
294 self.line += 1;
295 self.column = 1;
296 } else {
297 if !self.line_classified && !matches!(character, ' ' | '\t' | '\r') {
298 self.line_classified = true;
299 if !(self.config.comments && character == '#') {
300 let indentation = leading_spaces(&self.current_line);
301 self.start_content_line(indentation, &mut emitted);
302 }
303 }
304 self.column += 1;
305 }
306
307 if self.buffer.len() + self.current_line.len() > self.max_buffer_size {
308 return self.fail(StreamParseError {
309 message: format!(
310 "buffered record exceeds maximum size of {} bytes",
311 self.max_buffer_size
312 ),
313 location: Some(self.position()),
314 parse_error: None,
315 });
316 }
317 }
318 Ok(emitted)
319 }
320
321 pub fn finish(&mut self) -> Result<Vec<LiNo<T>>, StreamParseError> {
323 if self.ended {
324 return Ok(if self.collect {
325 self.links.clone()
326 } else {
327 Vec::new()
328 });
329 }
330
331 let mut emitted = Vec::new();
332 let document = format!("{}{}", self.buffer, self.current_line);
333 if !document.is_empty() {
334 let parsed = match T::parse_record(&document, &self.config) {
335 Ok(links) => links,
336 Err(error) => {
337 let stream_error = self.stream_error(error);
338 return self.fail(stream_error);
339 }
340 };
341 self.publish(parsed, &mut emitted);
342 self.advance_segment(&document);
343 }
344
345 self.buffer.clear();
346 self.current_line.clear();
347 self.base_indentation = None;
348 self.ended = true;
349 Ok(if self.collect {
350 self.links.clone()
351 } else {
352 emitted
353 })
354 }
355
356 pub fn drain(&mut self) -> Vec<LiNo<T>> {
358 std::mem::take(&mut self.links)
359 }
360
361 pub fn reset(&mut self) -> &mut Self {
363 self.buffer.clear();
364 self.current_line.clear();
365 self.base_indentation = None;
366 self.line_classified = false;
367 self.links.clear();
368 self.offset = 0;
369 self.line = 1;
370 self.column = 1;
371 self.segment_offset = 0;
372 self.segment_line = 1;
373 self.ended = false;
374 self
375 }
376
377 pub fn position(&self) -> StreamPosition {
379 StreamPosition {
380 line: self.line,
381 column: self.column,
382 offset: self.offset,
383 buffered: self.buffer.len() + self.current_line.len(),
384 }
385 }
386
387 fn start_content_line(&mut self, indentation: usize, emitted: &mut Vec<LiNo<T>>) {
388 if !self.buffer.is_empty()
389 && self
390 .base_indentation
391 .is_some_and(|base| indentation <= base)
392 && structurally_complete(&self.buffer, self.config.comments)
393 {
394 if let Ok(parsed) = T::parse_record(&self.buffer, &self.config) {
395 let document = std::mem::take(&mut self.buffer);
396 self.publish(parsed, emitted);
397 self.advance_segment(&document);
398 self.base_indentation = None;
399 }
400 }
401 self.base_indentation.get_or_insert(indentation);
402 }
403
404 fn publish(&mut self, parsed: Vec<LiNo<T>>, emitted: &mut Vec<LiNo<T>>) {
405 for link in parsed {
406 emitted.push(link.clone());
407 if self.collect {
408 self.links.push(link.clone());
409 }
410 if let Some(callback) = &mut self.link_callback {
411 callback(&link);
412 }
413 }
414 }
415
416 fn advance_segment(&mut self, document: &str) {
417 self.segment_offset += document.len();
418 self.segment_line += document.matches('\n').count();
419 }
420
421 fn stream_error(&self, error: ParseError) -> StreamParseError {
422 let location = match &error {
423 ParseError::SyntaxError(syntax) => StreamPosition {
424 line: self.segment_line + syntax.line - 1,
425 column: syntax.column,
426 offset: self.segment_offset + syntax.offset,
427 buffered: self.buffer.len() + self.current_line.len(),
428 },
429 ParseError::NestingTooDeep(nesting) => StreamPosition {
430 line: self.segment_line + nesting.line - 1,
431 column: nesting.column,
432 offset: self.segment_offset + nesting.offset,
433 buffered: self.buffer.len() + self.current_line.len(),
434 },
435 _ => StreamPosition {
436 line: self.segment_line,
437 column: 1,
438 offset: self.segment_offset,
439 buffered: self.buffer.len() + self.current_line.len(),
440 },
441 };
442 StreamParseError {
443 message: error.to_string(),
444 location: Some(location),
445 parse_error: Some(Box::new(error)),
446 }
447 }
448
449 fn fail<R>(&mut self, error: StreamParseError) -> Result<R, StreamParseError> {
450 if let Some(callback) = &mut self.error_callback {
451 callback(&error);
452 }
453 Err(error)
454 }
455}
456
457pub struct StreamIterator<I, T: StreamReference = String> {
460 chunks: I,
461 parser: StreamParser<T>,
462 ready: VecDeque<LiNo<T>>,
463 finished: bool,
464}
465
466impl<I, T: StreamReference> StreamIterator<I, T> {
467 fn new(chunks: I, mut parser: StreamParser<T>) -> Self {
468 parser.set_collect(false);
469 Self {
470 chunks,
471 parser,
472 ready: VecDeque::new(),
473 finished: false,
474 }
475 }
476}
477
478impl<I, S, T> Iterator for StreamIterator<I, T>
479where
480 I: Iterator<Item = S>,
481 S: AsRef<str>,
482 T: StreamReference,
483{
484 type Item = Result<LiNo<T>, StreamParseError>;
485
486 fn next(&mut self) -> Option<Self::Item> {
487 loop {
488 if let Some(link) = self.ready.pop_front() {
489 return Some(Ok(link));
490 }
491 if self.finished {
492 return None;
493 }
494 match self.chunks.next() {
495 Some(chunk) => match self.parser.write(chunk.as_ref()) {
496 Ok(links) => self.ready.extend(links),
497 Err(error) => {
498 self.finished = true;
499 return Some(Err(error));
500 }
501 },
502 None => {
503 self.finished = true;
504 match self.parser.finish() {
505 Ok(links) => self.ready.extend(links),
506 Err(error) => return Some(Err(error)),
507 }
508 }
509 }
510 }
511 }
512}
513
514fn leading_spaces(line: &str) -> usize {
515 line.bytes().take_while(|byte| *byte == b' ').count()
516}
517
518fn follows(document: &[u8], position: usize, allowed: &[u8]) -> bool {
519 position == 0 || allowed.contains(&document[position - 1])
520}
521
522fn structurally_complete(document: &str, comments: bool) -> bool {
523 let bytes = document.as_bytes();
527 let references = DelimitedReferences::new(document);
528 let mut position = 0;
529 let mut depth = 0_isize;
530 let mut token_start = true;
531
532 while position < bytes.len() {
533 let byte = bytes[position];
534 if comments && byte == b'#' && follows(bytes, position, BEFORE_COMMENT) {
535 match document[position..].find('\n') {
536 Some(newline) => position += newline,
537 None => break,
538 }
539 continue;
540 }
541 if SEPARATORS.contains(&byte) {
542 match byte {
543 b'(' => depth += 1,
544 b')' => depth -= 1,
545 _ => {}
546 }
547 token_start = true;
548 position += 1;
549 continue;
550 }
551 if token_start && QUOTES.contains(&byte) {
552 match references.read(&document[position..]) {
556 Some(reading) if reading.closed => position += reading.length,
557 _ => return false,
558 }
559 continue;
560 }
561 if token_start {
562 if let Some(end) = reference_literal_end(document, position) {
563 position = end;
564 continue;
565 }
566 }
567 token_start = false;
568 position += 1;
569 }
570 depth == 0
571}