Skip to main content

earl_lang_syntax/
lib.rs

1//! A tokenizer and parser for the language Earl.  The syntax of Earl resembles
2//! the syntax of Lisp or S-expressions.  The main difference is that Earl does
3//! not define ordered pairs, i.e. `(x . y)`, but only lists, e.g. the empty
4//! list `(x y z)`.  The design of multiline strings and nesting multiline
5//! comments is possibly unorthodox.
6
7use std::error::Error;
8use std::fmt;
9
10#[derive(Debug)]
11/// Any kind of error that can pop up while tokenizing and parsing.
12pub enum SyntaxError {
13    /// Error while tokenizing or parsing.
14    ParsingError(ParsingError),
15    /// Error due to faulty assumptions in the code.
16    AssumptionError(String),
17}
18
19impl From<ParsingError> for SyntaxError {
20    fn from(error: ParsingError) -> Self {
21        SyntaxError::ParsingError(error)
22    }
23}
24
25impl fmt::Display for SyntaxError {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        write!(f, "{}", self)
28    }
29}
30
31impl std::error::Error for SyntaxError {}
32
33/// Error codes we might receive when tokenizing and parsing.
34// TODO: change in the future.
35#[derive(PartialEq, Debug)]
36pub enum ErrorCode {
37    UnterminatedNormalString,
38    UnterminatedExtendedString,
39    UnterminatedMultiLineComment,
40    UnexpectedClosingDelimiter,
41    DelimiterMismatch,
42    PotentialErroneousClosingDelimiterFollowedByComment,
43    UnclosedDelimiter,
44    BadAssumption,
45}
46
47impl fmt::Display for ErrorCode {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        write!(f, "{}", self)
50    }
51}
52
53/// Error information returned to the user if there's a problem tokenizing or
54/// parsing the input.
55// NOTE: maybe find a better name?  This is both for tokenizing and parsing.
56#[derive(Debug)]
57pub struct ParsingError {
58    pub error_code: ErrorCode,
59    pub error_message: String,
60}
61
62impl fmt::Display for ParsingError {
63    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64        write!(f, "({}): {}", self.error_code, self.error_message)
65    }
66}
67
68impl Error for ParsingError {}
69
70impl ParsingError {
71    /// Creates a new parsing error.
72    // TODO: maybe need differnt ones for parsing since we have access to different kind of
73    // information.
74    // TODO: in future add hints or support for hints.
75    #[inline]
76    pub fn new(
77        code: &[u8],
78        start_index: usize,
79        end_index: usize,
80        error_code: ErrorCode,
81        error_message: &str,
82    ) -> ParsingError {
83        // &[u8] -> ... -> String
84        // TODO: in future remove these attributes
85        #![allow(unused_variables, unused_assignments)]
86
87        // TODO: add better error messages in the future
88
89        let code = String::from_utf8_lossy(code);
90
91        let error_message = String::from(error_message);
92
93        let si = start_index;
94
95        let mut sl = 0;
96
97        let mut sc = 0;
98
99        let ei = end_index;
100        let mut el = 0;
101        let mut ec = 0;
102
103        // Find line and column for start and end index
104
105        {
106            let mut code_index = 0;
107            let mut line_index = 0;
108            let mut column_index = 0;
109
110            let mut count = 2;
111
112            for c in code.chars() {
113                if count == 0 {
114                    break;
115                }
116
117                if si == code_index {
118                    // TODO: in future remove this attribute
119
120                    sl = line_index;
121                    sc = column_index;
122
123                    count -= 1;
124                }
125
126                if ei == code_index {
127                    el = line_index;
128                    ec = column_index;
129
130                    count -= 1;
131                }
132
133                // End
134
135                code_index += 1;
136
137                if c == '\n' {
138                    line_index += 1;
139                    column_index = 0;
140                } else {
141                    column_index += 1;
142                }
143            }
144        }
145
146        let lines: Vec<&str> = code.split("\n").collect();
147
148        let mut msg: Vec<String> = Vec::new();
149
150        let end_line_number_string = format!("{}", el + 1);
151
152        let mut gutter_size = 0;
153        gutter_size = std::cmp::max(gutter_size, end_line_number_string.len());
154
155        let e_line = lines.get(el).unwrap();
156
157        msg.push(String::from(""));
158        msg.push(format!(" {} | {}", end_line_number_string, e_line));
159        msg.push(format!(
160            " {} | {}^",
161            " ".repeat(gutter_size),
162            " ".repeat(ec)
163        ));
164
165        msg.push(String::from(""));
166        msg.push(format!("{}", error_message));
167
168        let error_message = msg.join("\n");
169
170        ParsingError {
171            error_code,
172            error_message,
173        }
174    }
175}
176
177// TODO: write a function that creates an error message based off of the error code and etc.
178
179/// Token IDs or token types.
180#[derive(Debug, PartialEq, Clone, Copy)]
181pub enum TokenId {
182    Whitespace = 2,
183
184    Word = 7,
185
186    RoundBracketOpen = 10,
187    RoundBracketClose = 11,
188    CurlyBracketOpen = 12,
189    CurlyBracketClose = 14,
190    SquareBracketOpen = 15,
191    SquareBracketClose = 16,
192
193    StringNormal = 21,
194    StringExtended = 22,
195
196    CommentSingleLine = 31,
197
198    CommentMultiLine = 32,
199}
200
201/// When tokenizing the input we do it in different states and transition between states
202/// based on what we see in the input.
203#[derive(Debug, PartialEq)]
204enum State {
205    Undetermined,
206    Whitespace,
207
208    CommentAmbiguous,
209    CommentSingleLine,
210    CommentMultiLine,
211
212    StringAmbiguous,
213    StringNormal,
214    StringExtendedConfig, // config section
215    StringExtendedBody,   // body section
216    StringExtendedEnd,    // triggered by . or ,
217
218    Word,
219}
220
221/// A token in Earl contains where it appears in the code and its type or ID.
222///
223/// * The field `id` is what type of token this is.
224/// * The field `index` is the index of the byte where this token begins.
225/// * The field `length` is how many bytes this token spans.
226#[derive(Clone, Debug)]
227pub struct Token {
228    /// The ID or type of token.
229    pub id: TokenId,
230    /// The index of where the token appears in the code.
231    pub index: usize,
232    /// The length of the token in the code.
233    pub length: usize,
234}
235
236impl Token {
237    /// Creates a new token.
238    #[inline]
239    pub fn new(id: TokenId, index: usize, length: usize) -> Token {
240        Token { id, index, length }
241    }
242
243    /// Returns the lexeme of this code.
244    #[inline]
245    pub fn get_lexeme<'a>(self: &Self, code: &'a str) -> &'a str {
246        let i = self.index;
247        let j = self.index + self.length;
248
249        let x = &code[i..j];
250
251        x
252    }
253}
254
255// Toggle this on and off to debug the tokenizer.
256//
257// TODO: figure out a way to use conditional compilation instead of this.
258const DEBUG: bool = false;
259
260// Temporary const to remind me that I'm using a hack to circumvent problems that could occur with
261// issue 132.
262//
263// TODO: once issue 132 has been resolved remove this const.
264#[allow(dead_code)]
265const HAS_ISSUE_132: bool = true;
266
267/// Returns a tokenization result.
268///
269/// The input `code` is a byte slice.  It's expected its a byte slice of a UTF-8 code.
270pub fn tokenize(
271    code: &str,
272    include_comments: bool,
273    include_whitespace: bool,
274) -> Result<Vec<Token>, SyntaxError> {
275    let code = code.as_bytes();
276
277    if DEBUG {
278        println!("{}", "-".repeat(80));
279
280        let code = std::str::from_utf8(code).unwrap();
281        println!("Code: [{}]", code);
282    }
283
284    let mut tokens: Vec<Token> = Vec::new();
285
286    let mut token_id = TokenId::Whitespace;
287    let mut token_index = 0;
288    let mut token_length = 0;
289
290    let mut state: State = State::Undetermined;
291
292    let mut attempting_to_close_comment = false;
293    let mut attempting_to_open_comment = false;
294
295    let mut start_semicolon_count = 0;
296    let mut end_semicolon_count = 0;
297
298    // We use this count to determine whether we're a normal string or an
299    // extended string.
300    //
301    // We also use this number for extended strings to keep track of how
302    // many consecutive double quotation marks we must match before the string
303    // ends.
304    let mut start_double_quote_count = 0;
305    let mut end_double_quote_count = 0;
306
307    let mut multi_line_comment_stack = Vec::new();
308
309    // Previous character
310    let mut pc = b'a';
311
312    // NOTE: I don't reset the `token_id` after adding the token to the tokens.
313
314    // We walk over the bytes in the code in the code loop.
315    //
316    // I need to be able to look arbitrary characters ahead. :'(
317    'code_loop: for (code_index, c) in code.iter().enumerate() {
318        let c = *c;
319
320        let mut no_progress_iter = 0;
321
322        // pc.len_utf8()
323
324        if DEBUG {
325            if code_index > 0 {
326                // Resolution state
327                println!("> State = {:?} (end)", state);
328            }
329
330            println!("{}", "-".repeat(80));
331            println!("i = {}, c = [{}]", code_index, c);
332        }
333
334        // In the decision loop we decide what state we enter based on the byte we see and then
335        // at that state how we process that byte.
336        'decision_loop: loop {
337            if DEBUG {
338                if no_progress_iter == 0 {
339                    println!("> State = {:?} (start)", state);
340                } else {
341                    println!("> State = {:?}", state);
342                }
343            }
344
345            {
346                // NOTE: Safety iteration block.  Remove this block in the future once this code
347                // appears to be robust.
348                no_progress_iter += 1;
349
350                if no_progress_iter == 3 {
351                    return Err(SyntaxError::AssumptionError(
352                        "No progress while tokenizing.".to_string(),
353                    ));
354                }
355            }
356
357            match &state {
358                State::Undetermined => {
359                    // We enter this state to determine what state we want to enter based off of
360                    // the character under consideration.
361
362                    if c.is_ascii_whitespace() {
363                        pc = c;
364
365                        token_id = TokenId::Whitespace;
366                        token_index = code_index;
367                        token_length = 1;
368
369                        state = State::Whitespace;
370
371                        continue 'code_loop;
372                    } else if c == b'(' {
373                        pc = c;
374
375                        token_id = TokenId::RoundBracketOpen;
376                        token_index = code_index;
377                        token_length = 1;
378
379                        tokens.push(Token::new(token_id, token_index, token_length));
380
381                        continue 'code_loop;
382                    } else if c == b')' {
383                        pc = c;
384
385                        token_id = TokenId::RoundBracketClose;
386                        token_index = code_index;
387                        token_length = 1;
388
389                        // NOTE: this is a temporary solution for Issue[132].
390
391                        tokens.push(Token::new(token_id, token_index, token_length));
392
393                        continue 'code_loop;
394                    } else if c == b'[' {
395                        pc = c;
396
397                        token_id = TokenId::SquareBracketOpen;
398                        token_index = code_index;
399                        token_length = 1;
400
401                        tokens.push(Token::new(token_id, token_index, token_length));
402
403                        continue 'code_loop;
404                    } else if c == b']' {
405                        pc = c;
406
407                        token_id = TokenId::SquareBracketClose;
408                        token_index = code_index;
409                        token_length = 1;
410
411                        tokens.push(Token::new(token_id, token_index, token_length));
412
413                        continue 'code_loop;
414                    } else if c == b'{' {
415                        pc = c;
416
417                        token_id = TokenId::CurlyBracketOpen;
418                        token_index = code_index;
419                        token_length = 1;
420
421                        tokens.push(Token::new(token_id, token_index, token_length));
422
423                        continue 'code_loop;
424                    } else if c == b'}' {
425                        pc = c;
426
427                        token_id = TokenId::CurlyBracketClose;
428                        token_index = code_index;
429                        token_length = 1;
430
431                        tokens.push(Token::new(token_id, token_index, token_length));
432
433                        continue 'code_loop;
434                    } else if c == b'"' {
435                        pc = c;
436
437                        start_double_quote_count = 1;
438
439                        token_index = code_index;
440                        token_length = 1;
441
442                        state = State::StringAmbiguous;
443
444                        continue 'code_loop;
445                    } else if c == b';' {
446                        // Issue[132]: temporary strategy
447                        if pc == b')' {
448                            return Err(SyntaxError::ParsingError(ParsingError::new(
449                                code,
450                                code_index,
451                                code_index,
452                                ErrorCode::PotentialErroneousClosingDelimiterFollowedByComment,
453                                "Unterminated potential erroneous closing delimiter followed by comment",
454                            )));
455                        }
456
457                        pc = c;
458
459                        token_index = code_index;
460                        token_length = 1;
461
462                        start_semicolon_count = 1;
463
464                        state = State::CommentAmbiguous;
465
466                        continue 'code_loop;
467                    } else {
468                        pc = c;
469
470                        token_id = TokenId::Word;
471                        token_index = code_index;
472                        token_length = 1;
473
474                        state = State::Word;
475
476                        continue 'code_loop;
477                    }
478                }
479                State::Whitespace => {
480                    if c.is_ascii_whitespace() {
481                        token_length += 1;
482
483                        continue 'code_loop;
484                    } else {
485                        // Token done
486                        if include_whitespace {
487                            tokens.push(Token::new(token_id, token_index, token_length));
488                        }
489
490                        state = State::Undetermined;
491
492                        continue 'decision_loop;
493                    }
494                }
495                State::CommentAmbiguous => {
496                    // We arrive at this state for the first time when we encounted ; comming from
497                    // State::Undetermined.
498                    //
499                    // It has yet to be determined whether this state transitions to a single-line
500                    // comment state or a multi-line comment state.
501
502                    if c == b';' {
503                        // We might be a multi-line comment or a single-line
504                        // comment.  We just don't know... yet.
505                        token_length += 1;
506                        start_semicolon_count += 1;
507
508                        continue 'code_loop;
509                    } else if c == b'(' {
510                        // Transition to a multi-line comment state.
511                        multi_line_comment_stack.push(start_semicolon_count);
512
513                        token_length += 1;
514
515                        token_id = TokenId::CommentMultiLine;
516                        state = State::CommentMultiLine;
517
518                        attempting_to_open_comment = false;
519                        attempting_to_close_comment = false;
520
521                        continue 'code_loop;
522                    } else if c == b'\n' {
523                        // Transition to single-line comment state.
524
525                        token_id = TokenId::CommentSingleLine;
526
527                        if include_comments {
528                            tokens.push(Token::new(token_id, token_index, token_length));
529                        }
530
531                        state = State::Undetermined;
532
533                        continue 'decision_loop;
534                    } else {
535                        // Transition to single-line comment state.
536
537                        token_length += 1;
538
539                        token_id = TokenId::CommentSingleLine;
540
541                        state = State::CommentSingleLine;
542
543                        continue 'code_loop;
544                    }
545                }
546                State::CommentSingleLine => {
547                    // We arrive here the first time from State::CommentAmbiguous.
548                    //
549                    // The first time we reach this state is when we're in a text like,
550                    //
551                    //   ;;;..;;?
552                    //          ^
553                    // where ? is not ; and ? is not (.
554
555                    // #[cfg(debug_block_comment_tokenization)]
556
557                    if c == b'\n' {
558                        // We're done
559                        if include_comments {
560                            tokens.push(Token::new(token_id, token_index, token_length));
561                        }
562
563                        state = State::Undetermined;
564
565                        continue 'decision_loop;
566                    } else {
567                        token_length += 1;
568
569                        continue 'code_loop;
570                    }
571                }
572                State::CommentMultiLine => {
573                    // We arrive here the first time from State::CommentAmbiguous.  When we enter
574                    // this state for the first time then we're in a text like,
575                    //
576                    //   ;;;...;;(?
577                    //            ^
578                    //
579                    // where we walked over a sequence of ; and just walked passed (, and now we're
580                    // considering the character pointed by ^.
581
582                    // TODO: we actually only need one variable for start_semicolon_count and
583                    // end_semicolon_count
584
585                    if DEBUG {
586                        let s: Vec<String> = (&multi_line_comment_stack)
587                            .into_iter()
588                            .map(|n| format!("{}{{", ";".repeat(*n)))
589                            .collect();
590
591                        println!("    Stack: {:?}", s);
592
593                        if attempting_to_open_comment {
594                            println!("      Opening?: {}", ";".repeat(start_semicolon_count));
595                        } else if attempting_to_close_comment {
596                            println!("      Closing?: ){}", ";".repeat(end_semicolon_count));
597                        }
598                    }
599
600                    // - * - * - * - * - * - * - * - * - * - * - * - * - * - * -
601
602                    if attempting_to_open_comment {
603                        if c == b';' {
604                            // Keep on GOING
605                            start_semicolon_count += 1;
606                            token_length += 1;
607
608                            continue 'code_loop;
609                        } else if c == b'(' {
610                            attempting_to_open_comment = false;
611                            // Nest some more
612                            token_length += 1;
613
614                            multi_line_comment_stack.push(start_semicolon_count);
615
616                            continue 'code_loop;
617                        } else {
618                            // Cancel, but don't know how to handle symbol
619                            attempting_to_open_comment = false;
620
621                            continue 'decision_loop;
622                        }
623                    } else if attempting_to_close_comment {
624                        if c == b';' {
625                            end_semicolon_count += 1;
626
627                            token_length += 1;
628
629                            continue 'code_loop;
630                        } else {
631                            if end_semicolon_count == 0 {
632                                // Here we're in this text,
633                                //
634                                //   ?)?
635                                //     ^.__ not ;
636                                //
637                                //
638                                attempting_to_close_comment = false;
639
640                                continue 'decision_loop;
641                            } else {
642                                // Here we're in this text,
643                                //
644                                //   ?);..;?
645                                //         ^.__ not ;
646                                //
647
648                                // Now here we try to match our  );;..;  against something so we're
649                                // no longer "attempting to close the comment".
650                                attempting_to_close_comment = false;
651
652                                // Try to match against something in the stack
653
654                                let mut pop_off_stack = false;
655                                let mut match_index = 0;
656
657                                // Search the stack for a suitable opening mark to match against.
658                                'stack_loop: for (i, stack_semicolon_count) in
659                                    multi_line_comment_stack.iter().enumerate().rev()
660                                {
661                                    if end_semicolon_count < *stack_semicolon_count {
662                                        // We're in this situtation,
663                                        //
664                                        //                .-- we're here and ? is not ;
665                                        //               v
666                                        //  ;;;;( ... );;?
667                                        //  aaaaa     bbb
668                                        //
669                                        // Where aaaaa corresponds to the stack semicolon count in
670                                        // the stack, and we can't look past it since it has more
671                                        // semicolons.
672
673                                        // Guarded, no we're done, we don't push anything
674                                        // on top.
675                                        attempting_to_close_comment = false;
676
677                                        if DEBUG {
678                                            println!("        Dropped!");
679                                        }
680
681                                        continue 'decision_loop;
682                                    } else if end_semicolon_count == *stack_semicolon_count {
683                                        // We found a match!
684                                        //
685                                        // We're in this sitation,
686                                        //
687                                        //  ;;;( ... );;;
688                                        //  aaaa     bbbb
689                                        //
690                                        // where we found an exact match.
691
692                                        pop_off_stack = true;
693                                        match_index = i;
694
695                                        if DEBUG {
696                                            println!("        Matched!");
697                                        }
698
699                                        break 'stack_loop;
700                                    }
701                                }
702
703                                if pop_off_stack {
704                                    // Our stack could like this at this point,
705                                    //
706                                    // [  ;;;;(  ;;;(  ;(  ;;(  ]
707                                    //
708                                    // and we have  );;;   which matches this, -.
709                                    //                                          |
710                                    //              .___________________________'
711                                    //              v
712                                    // [  ;;;;(  ;;;(  ;(  ;;(  ]
713                                    //                  ^    ^
714                                    //                  |    |
715                                    //          .-------`----'
716                                    //          |
717                                    // and these need to be popped off the stack.
718                                    //
719                                    //
720                                    //       v            <- pop this and everything to the right
721                                    // 0 1 2 3 4 5 6 7    <- idx
722                                    // 1 2 3              <- len
723
724                                    while multi_line_comment_stack.len() > match_index {
725                                        multi_line_comment_stack.pop();
726
727                                        if DEBUG {
728                                            println!("        Pop!");
729                                        }
730                                    }
731
732                                    // If we empty the stack then that means we've completed this
733                                    // token.
734
735                                    if multi_line_comment_stack.len() == 0 {
736                                        // We have completed this token.
737
738                                        if include_comments {
739                                            tokens.push(Token::new(
740                                                token_id,
741                                                token_index,
742                                                token_length,
743                                            ));
744                                        }
745
746                                        state = State::Undetermined;
747
748                                        continue 'decision_loop;
749                                    }
750                                }
751
752                                attempting_to_close_comment = false;
753
754                                continue 'decision_loop;
755                            }
756                        }
757                    } else {
758                        if c == b';' {
759                            token_length += 1;
760
761                            attempting_to_open_comment = true;
762                            start_semicolon_count = 1;
763
764                            continue 'code_loop;
765                        } else if c == b')' {
766                            token_length += 1;
767
768                            attempting_to_close_comment = true;
769                            end_semicolon_count = 0;
770
771                            continue 'code_loop;
772                        } else {
773                            token_length += 1;
774
775                            continue 'code_loop;
776                        }
777                    }
778
779                    // - * - * - * - * - * - * - * - * - * - * - * - * - * - * -
780                }
781                State::StringAmbiguous => {
782                    // At this point all we know is that we're a string.  We
783                    // don't yet know whether or not we're a "normal" string or
784                    // an "extended" string.
785
786                    if c == b'"' {
787                        token_length += 1;
788                        start_double_quote_count += 1;
789
790                        continue 'code_loop;
791                    } else {
792                        if start_double_quote_count == 1 {
793                            // We're a normal string.
794                            token_length += 1;
795
796                            pc = c;
797
798                            token_id = TokenId::StringNormal;
799
800                            state = State::StringNormal;
801
802                            continue 'code_loop;
803                        } else if start_double_quote_count == 2 {
804                            // This was an empty normal string
805
806                            token_id = TokenId::StringNormal;
807
808                            tokens.push(Token::new(token_id, token_index, token_length));
809
810                            state = State::Undetermined;
811
812                            continue 'decision_loop;
813                        } else {
814                            // start_double_quote_count >= 3
815                            token_id = TokenId::StringExtended;
816
817                            state = State::StringExtendedConfig;
818
819                            // We're an "extended" string
820
821                            continue 'decision_loop;
822                        }
823                    }
824                }
825                State::StringNormal => {
826                    token_length += 1;
827
828                    if c == b'"' && pc != b'\\' {
829                        // We're done
830                        tokens.push(Token::new(token_id, token_index, token_length));
831
832                        state = State::Undetermined;
833                    } else {
834                        pc = c;
835                    }
836
837                    continue 'code_loop;
838                }
839                State::StringExtendedConfig => {
840                    token_length += 1;
841
842                    if c == b'.' || c == b',' {
843                        // Continue on to body
844
845                        state = State::StringExtendedBody;
846                    }
847
848                    continue 'code_loop;
849                }
850                State::StringExtendedBody => {
851                    token_length += 1;
852
853                    if c == b'.' || c == b',' {
854                        // Go to end
855                        end_double_quote_count = 0;
856
857                        state = State::StringExtendedEnd;
858                    }
859
860                    continue 'code_loop;
861                }
862                State::StringExtendedEnd => {
863                    if c == b'"' {
864                        token_length += 1;
865                        end_double_quote_count += 1;
866
867                        if end_double_quote_count == start_double_quote_count {
868                            // We're done !
869                            tokens.push(Token::new(token_id, token_index, token_length));
870
871                            state = State::Undetermined
872                        }
873                    } else if c == b'.' || c == b',' {
874                        token_length += 1;
875                        end_double_quote_count = 0;
876                    } else {
877                        token_length += 1;
878                        state = State::StringExtendedBody;
879                    }
880
881                    continue 'code_loop;
882                }
883                State::Word => {
884                    // NOTE: I don't like this code... at all.
885                    //
886                    // The problem with doing this is we've done all the
887                    // checking here and need to redo it in State::Undetermined.
888                    if c.is_ascii_whitespace()
889                        || c == b'('
890                        || c == b')'
891                        || c == b'['
892                        || c == b']'
893                        || c == b'{'
894                        || c == b'}'
895                        || c == b'"'
896                        || c == b';'
897                    {
898                        // Done
899                        tokens.push(Token::new(token_id, token_index, token_length));
900
901                        state = State::Undetermined;
902
903                        continue 'decision_loop;
904                    } else {
905                        token_length += 1;
906                        continue 'code_loop;
907                    }
908                }
909            }
910        }
911    }
912
913    if DEBUG {
914        // Resolution state
915        println!("> State = {:?} (end)", state);
916        println!("{}", "-".repeat(80));
917    }
918
919    match &state {
920        State::Undetermined => { /* NOTE: I think it should impossible to reach this state. */ }
921        State::Whitespace => {
922            if include_whitespace {
923                tokens.push(Token::new(token_id, token_index, token_length));
924            }
925        }
926        State::CommentAmbiguous => {
927            // NOTE: This'll be treated as a single-line comment
928            token_id = TokenId::CommentSingleLine;
929
930            if include_comments {
931                tokens.push(Token::new(token_id, token_index, token_length));
932            }
933        }
934        State::CommentSingleLine => {
935            if include_comments {
936                tokens.push(Token::new(token_id, token_index, token_length));
937            }
938        }
939        State::CommentMultiLine => {
940            let mut ok = false;
941
942            // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
943            // <  copy code here >
944
945            if attempting_to_close_comment {
946                if end_semicolon_count > 0 {
947                    let mut pop_off_stack = false;
948                    let mut match_index = 0;
949
950                    'leftover_stack_loop: for (i, stack_semicolon_count) in
951                        multi_line_comment_stack.iter().enumerate().rev()
952                    {
953                        if end_semicolon_count < *stack_semicolon_count {
954                            break 'leftover_stack_loop;
955                        } else if end_semicolon_count == *stack_semicolon_count {
956                            pop_off_stack = true;
957                            match_index = i;
958
959                            break 'leftover_stack_loop;
960                        }
961                    }
962
963                    if pop_off_stack {
964                        while multi_line_comment_stack.len() > match_index {
965                            multi_line_comment_stack.pop();
966                        }
967
968                        if multi_line_comment_stack.len() == 0 {
969                            ok = true;
970
971                            if include_comments {
972                                tokens.push(Token::new(token_id, token_index, token_length));
973                            }
974                        }
975                    }
976                }
977            }
978
979            // </ copy code here >
980            // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
981
982            if !ok {
983                // Unterminated multi-line comment
984
985                return Err(SyntaxError::ParsingError(ParsingError::new(
986                    code,
987                    token_index,
988                    code.len(),
989                    ErrorCode::UnterminatedMultiLineComment,
990                    "Unterminated multiline comment",
991                )));
992            }
993        }
994        State::StringAmbiguous => {
995            if start_double_quote_count == 1 {
996                // Unterminated normal string
997                return Err(SyntaxError::ParsingError(ParsingError::new(
998                    code,
999                    token_index,
1000                    code.len(),
1001                    ErrorCode::UnterminatedNormalString,
1002                    "Unterminated normal string",
1003                )));
1004            } else if start_double_quote_count == 2 {
1005                token_id = TokenId::StringNormal;
1006                // Empty string
1007                tokens.push(Token::new(token_id, token_index, token_length));
1008            } else {
1009                // Unterminated extended string
1010                return Err(SyntaxError::ParsingError(ParsingError::new(
1011                    code,
1012                    token_index,
1013                    code.len(),
1014                    ErrorCode::UnterminatedExtendedString,
1015                    "Unterminated extended string",
1016                )));
1017            }
1018        }
1019        State::StringNormal => {
1020            // Untermianted normal string
1021            return Err(SyntaxError::ParsingError(ParsingError::new(
1022                code,
1023                token_index,
1024                code.len(),
1025                ErrorCode::UnterminatedNormalString,
1026                "Unterminated normal string",
1027            )));
1028        }
1029        State::StringExtendedConfig | State::StringExtendedBody | State::StringExtendedEnd => {
1030            // Untermianted extended string
1031            return Err(SyntaxError::ParsingError(ParsingError::new(
1032                code,
1033                token_index,
1034                code.len(),
1035                ErrorCode::UnterminatedExtendedString,
1036                "Unterminated extended string",
1037            )));
1038        }
1039        State::Word => {
1040            tokens.push(Token::new(token_id, token_index, token_length));
1041        }
1042    }
1043
1044    Ok(tokens)
1045}
1046
1047// Unit tests
1048
1049/// A parse node is either a token or a list of parse nodes.
1050#[derive(Debug)]
1051pub enum ParseNode {
1052    Token(Token),
1053    List(ParseNodeList),
1054}
1055
1056/// A parse node list is a list of parse nodes.
1057#[derive(Debug)]
1058pub struct ParseNodeList {
1059    /// Index of the parse node list starts in the code.
1060    pub index: usize,
1061    /// The length of the code this parse node list spans over.
1062    pub length: usize,
1063    /// List of parse nodes
1064    pub children: Vec<ParseNode>,
1065}
1066
1067impl ParseNodeList {
1068    /// Creates a new parse node list.
1069    #[inline]
1070    fn new(index: usize) -> ParseNodeList {
1071        ParseNodeList {
1072            index,
1073            length: 0,
1074            children: Vec::new(),
1075        }
1076    }
1077
1078    fn add_child_length(&mut self, child: ParseNode) -> () {
1079        match &child {
1080            ParseNode::Token(token) => self.length += token.length,
1081            ParseNode::List(parse_node_list) => self.length += parse_node_list.length,
1082        }
1083    }
1084
1085    fn add_child(&mut self, child: ParseNode) -> () {
1086        match &child {
1087            ParseNode::Token(token) => self.length += token.length,
1088            ParseNode::List(parse_node_list) => self.length += parse_node_list.length,
1089        }
1090        self.children.push(child);
1091    }
1092}
1093
1094/// Parses the code and returns a result of parse trees or a parsing error.
1095pub fn parse(
1096    code: &str,
1097    include_comments: bool,
1098    include_whitespace: bool,
1099    include_list_delimiters: bool,
1100) -> Result<Vec<ParseNode>, SyntaxError> {
1101    let tokens: Vec<Token>;
1102
1103    match tokenize(code, include_comments, include_whitespace) {
1104        Err(parsing_error) => {
1105            return Err(parsing_error);
1106        }
1107        Ok(result_tokens) => {
1108            tokens = result_tokens;
1109        }
1110    }
1111
1112    let mut parse_node_list_stack = Vec::new();
1113
1114    parse_node_list_stack.push(ParseNodeList::new(0));
1115
1116    let mut open_bracket_token_stack = Vec::new();
1117
1118    fn brackets_match(open_bracket: TokenId, close_bracket: TokenId) -> Result<bool, SyntaxError> {
1119        match open_bracket {
1120            TokenId::RoundBracketOpen => match close_bracket {
1121                TokenId::RoundBracketClose => return Ok(true),
1122                TokenId::CurlyBracketClose => return Ok(false),
1123                TokenId::SquareBracketClose => return Ok(false),
1124                _ => {
1125                    return Err(SyntaxError::AssumptionError(
1126                        "Logic error in brackets_match (1).".to_string(),
1127                    ))
1128                }
1129            },
1130            TokenId::CurlyBracketOpen => match close_bracket {
1131                TokenId::RoundBracketClose => return Ok(false),
1132                TokenId::CurlyBracketClose => return Ok(true),
1133                TokenId::SquareBracketClose => return Ok(false),
1134                _ => {
1135                    return Err(SyntaxError::AssumptionError(
1136                        "Logic error in brackets_match (2).".to_string(),
1137                    ))
1138                }
1139            },
1140            TokenId::SquareBracketOpen => match close_bracket {
1141                TokenId::RoundBracketClose => return Ok(false),
1142                TokenId::CurlyBracketClose => return Ok(false),
1143                TokenId::SquareBracketClose => return Ok(true),
1144                _ => {
1145                    return Err(SyntaxError::AssumptionError(
1146                        "Logic error in brackets_match (3).".to_string(),
1147                    ))
1148                }
1149            },
1150            _ => {
1151                return Err(SyntaxError::AssumptionError(
1152                    "Logic error in brackets_match (4).".to_string(),
1153                ))
1154            }
1155        }
1156    }
1157
1158    for token in tokens {
1159        match token.id {
1160            TokenId::Whitespace => {
1161                let parse_node_list = parse_node_list_stack.last_mut().unwrap();
1162
1163                if include_comments {
1164                    parse_node_list.add_child(ParseNode::Token(token));
1165                } else {
1166                    parse_node_list.add_child_length(ParseNode::Token(token));
1167                }
1168            }
1169            TokenId::CommentSingleLine | TokenId::CommentMultiLine => {
1170                let parse_node_list = parse_node_list_stack.last_mut().unwrap();
1171
1172                if include_comments {
1173                    parse_node_list.add_child(ParseNode::Token(token));
1174                } else {
1175                    parse_node_list.add_child_length(ParseNode::Token(token));
1176                }
1177            }
1178            TokenId::Word | TokenId::StringNormal | TokenId::StringExtended => {
1179                parse_node_list_stack
1180                    .last_mut()
1181                    .unwrap()
1182                    .add_child(ParseNode::Token(token));
1183            }
1184            TokenId::RoundBracketOpen | TokenId::SquareBracketOpen | TokenId::CurlyBracketOpen => {
1185                // Increase nesting
1186                parse_node_list_stack.push(ParseNodeList::new(token.index));
1187
1188                if include_list_delimiters {
1189                    parse_node_list_stack
1190                        .last_mut()
1191                        .unwrap()
1192                        .add_child(ParseNode::Token(token.clone()));
1193                }
1194
1195                open_bracket_token_stack.push(token);
1196            }
1197            TokenId::RoundBracketClose
1198            | TokenId::SquareBracketClose
1199            | TokenId::CurlyBracketClose => {
1200                // Decrease nesting
1201                match open_bracket_token_stack.pop() {
1202                    None => {
1203                        // TODO: add better start / end index
1204
1205                        let parsing_error = ParsingError::new(
1206                            code.as_bytes(),
1207                            0,
1208                            0,
1209                            ErrorCode::UnexpectedClosingDelimiter,
1210                            "Unexpected closing delimiter",
1211                        );
1212
1213                        return Err(SyntaxError::ParsingError(parsing_error));
1214                    }
1215                    Some(open_bracket) => {
1216                        if brackets_match(open_bracket.id, token.id)? {
1217                            if include_list_delimiters {
1218                                parse_node_list_stack
1219                                    .last_mut()
1220                                    .unwrap()
1221                                    .add_child(ParseNode::Token(token));
1222                            }
1223
1224                            let parse_node_level = parse_node_list_stack.pop().unwrap();
1225
1226                            parse_node_list_stack
1227                                .last_mut()
1228                                .unwrap()
1229                                .add_child(ParseNode::List(parse_node_level));
1230                        } else {
1231                            // TODO: add better start / end index
1232
1233                            let parsing_error = ParsingError::new(
1234                                code.as_bytes(),
1235                                0,
1236                                0,
1237                                ErrorCode::DelimiterMismatch,
1238                                "Delimiter mismatch",
1239                            );
1240
1241                            return Err(SyntaxError::ParsingError(parsing_error));
1242                        }
1243                    }
1244                }
1245            }
1246        }
1247    }
1248
1249    if parse_node_list_stack.len() != 1 {
1250        // TODO: add better start / end index
1251
1252        let parsing_error = ParsingError::new(
1253            code.as_bytes(),
1254            0,
1255            0,
1256            ErrorCode::UnclosedDelimiter,
1257            "Unclosed delimiter mismatch",
1258        );
1259
1260        return Err(SyntaxError::ParsingError(parsing_error));
1261    }
1262
1263    let parse_trees = parse_node_list_stack.pop().unwrap().children;
1264
1265    return Ok(parse_trees);
1266}
1267
1268#[cfg(test)]
1269mod test_tokenizer {
1270    use super::tokenize as original_tokenize;
1271    use super::*;
1272
1273    #[inline]
1274    fn tokenize(code: &str) -> Result<Vec<Token>, SyntaxError> {
1275        return original_tokenize(code, true, true);
1276    }
1277
1278    /// Returns `true` if equal, otherwise `false`.
1279    #[inline]
1280    fn assert_eq_code_and_tokens_length(code: &str, tokens: &Vec<Token>) {
1281        let m = code.len();
1282
1283        let mut n = 0;
1284
1285        for token in tokens {
1286            n += token.length;
1287        }
1288
1289        assert_eq!(m, n);
1290    }
1291
1292    fn assert_code_is_ok(code: &str) {
1293        let tokenization_result = tokenize(code);
1294        match tokenization_result {
1295            Ok(_) => (),
1296            Err(syntax_error) => match syntax_error {
1297                SyntaxError::ParsingError(parsing_error) => panic!(parsing_error.error_message),
1298                SyntaxError::AssumptionError(error_message) => panic!(error_message),
1299            },
1300        }
1301    }
1302
1303    fn assert_code_is_not_ok(code: &str) {
1304        let tokenization_result = tokenize(code);
1305        match tokenization_result {
1306            Ok(_) => {
1307                panic!();
1308            }
1309            Err(_) => (),
1310        }
1311    }
1312
1313    // -------------------------------------------------------------------------
1314
1315    #[test]
1316    fn test_misc() {
1317        assert_code_is_ok("");
1318        assert_code_is_ok(" ");
1319
1320        if HAS_ISSUE_132 {
1321            assert_code_is_not_ok(");");
1322            assert_code_is_not_ok(";(););");
1323
1324            assert_code_is_ok(";();");
1325            assert_code_is_ok(";(;(););");
1326            assert_code_is_ok(";(;(;();););");
1327
1328            assert_code_is_ok(";;();;");
1329
1330            //
1331            assert_code_is_ok(";;(;;();;);;");
1332
1333            assert_code_is_not_ok(";(;;();;););");
1334        }
1335    }
1336
1337    #[test]
1338    fn test_icelandic_name() {
1339        assert_code_is_ok(r#"(var name "Davíð")"#)
1340    }
1341
1342    #[test]
1343    fn test_whitespace() {
1344        {
1345            let code = r#" "#;
1346
1347            let tokenization_result = tokenize(code);
1348            let tokens = tokenization_result.unwrap();
1349
1350            assert_eq_code_and_tokens_length(code, &tokens);
1351
1352            assert!(tokens.len() == 1);
1353
1354            let token = &tokens[0];
1355
1356            assert!(token.id == TokenId::Whitespace);
1357            assert!(token.index == 0);
1358            assert!(token.length == 1);
1359        }
1360    }
1361
1362    #[test]
1363    fn test_word() {
1364        {
1365            let code = r#"hallo"#;
1366
1367            let tokenization_result = tokenize(code);
1368            let tokens = tokenization_result.unwrap();
1369
1370            assert_eq_code_and_tokens_length(code, &tokens);
1371
1372            assert!(tokens.len() == 1);
1373
1374            let token = &tokens[0];
1375
1376            assert!(token.id == TokenId::Word);
1377            assert!(token.index == 0);
1378            assert!(token.length == 5);
1379        }
1380    }
1381
1382    #[test]
1383    fn test_round_bracket_open() {
1384        {
1385            let code = r#"()"#;
1386
1387            let tokenization_result = tokenize(code);
1388            let tokens = tokenization_result.unwrap();
1389
1390            assert_eq_code_and_tokens_length(code, &tokens);
1391
1392            assert!(tokens.len() == 2);
1393
1394            let token = &tokens[0];
1395
1396            assert!(token.id == TokenId::RoundBracketOpen);
1397            assert!(token.index == 0);
1398            assert!(token.length == 1);
1399        }
1400    }
1401
1402    #[test]
1403    fn test_round_bracket_close() {
1404        {
1405            let code = r#"()"#;
1406
1407            let tokenization_result = tokenize(code);
1408            let tokens = tokenization_result.unwrap();
1409
1410            assert_eq_code_and_tokens_length(code, &tokens);
1411
1412            assert!(tokens.len() == 2);
1413
1414            let token = &tokens[1];
1415
1416            assert!(token.id == TokenId::RoundBracketClose);
1417            assert!(token.index == 1);
1418            assert!(token.length == 1);
1419        }
1420    }
1421
1422    #[test]
1423    fn test_curly_bracket_open() {
1424        {
1425            let code = r#"{}"#;
1426
1427            let tokenization_result = tokenize(code);
1428            let tokens = tokenization_result.unwrap();
1429
1430            assert_eq_code_and_tokens_length(code, &tokens);
1431
1432            assert!(tokens.len() == 2);
1433
1434            let token = &tokens[0];
1435
1436            assert!(token.id == TokenId::CurlyBracketOpen);
1437            assert!(token.index == 0);
1438            assert!(token.length == 1);
1439        }
1440    }
1441
1442    #[test]
1443    fn test_curly_bracket_close() {
1444        {
1445            let code = r#"{}"#;
1446
1447            let tokenization_result = tokenize(code);
1448            let tokens = tokenization_result.unwrap();
1449
1450            assert_eq_code_and_tokens_length(code, &tokens);
1451
1452            assert!(tokens.len() == 2);
1453
1454            let token = &tokens[1];
1455
1456            assert!(token.id == TokenId::CurlyBracketClose);
1457            assert!(token.index == 1);
1458            assert!(token.length == 1);
1459        }
1460    }
1461
1462    #[test]
1463    fn test_square_bracket_open() {
1464        {
1465            let code = r#"[]"#;
1466
1467            let tokenization_result = tokenize(code);
1468            let tokens = tokenization_result.unwrap();
1469
1470            assert_eq_code_and_tokens_length(code, &tokens);
1471
1472            assert!(tokens.len() == 2);
1473
1474            let token = &tokens[0];
1475
1476            assert!(token.id == TokenId::SquareBracketOpen);
1477            assert!(token.index == 0);
1478            assert!(token.length == 1);
1479        }
1480    }
1481
1482    #[test]
1483    fn test_suqare_bracket_close() {
1484        {
1485            let code = r#"[]"#;
1486
1487            let tokenization_result = tokenize(code);
1488            let tokens = tokenization_result.unwrap();
1489
1490            assert_eq_code_and_tokens_length(code, &tokens);
1491
1492            assert!(tokens.len() == 2);
1493
1494            let token = &tokens[1];
1495
1496            assert!(token.id == TokenId::SquareBracketClose);
1497            assert!(token.index == 1);
1498            assert!(token.length == 1);
1499        }
1500    }
1501
1502    #[test]
1503    fn test_string_normal() {
1504        {
1505            let code = r#""hallo""#;
1506
1507            let tokenization_result = tokenize(code);
1508            let tokens = tokenization_result.unwrap();
1509
1510            assert_eq_code_and_tokens_length(code, &tokens);
1511
1512            assert!(tokens.len() == 1);
1513
1514            let token = &tokens[0];
1515
1516            assert!(token.id == TokenId::StringNormal);
1517            assert!(token.index == 0);
1518            assert!(token.length == 7);
1519        }
1520    }
1521
1522    #[test]
1523    fn test_string_extended() {
1524        {
1525            let code = r#"""".hallo.""""#;
1526
1527            let tokenization_result = tokenize(code);
1528            let tokens = tokenization_result.unwrap();
1529
1530            assert_eq_code_and_tokens_length(code, &tokens);
1531
1532            assert!(tokens.len() == 1);
1533
1534            let token = &tokens[0];
1535
1536            assert!(token.id == TokenId::StringExtended);
1537            assert!(token.index == 0);
1538            assert!(token.length == 13);
1539        }
1540        {
1541            let code = r#"""",hallo.""""#;
1542
1543            let tokenization_result = tokenize(code);
1544            let tokens = tokenization_result.unwrap();
1545
1546            assert_eq_code_and_tokens_length(code, &tokens);
1547
1548            assert!(tokens.len() == 1);
1549
1550            let token = &tokens[0];
1551
1552            assert!(token.id == TokenId::StringExtended);
1553            assert!(token.index == 0);
1554            assert!(token.length == 13);
1555        }
1556        {
1557            let code = r#"""".hallo,""""#;
1558
1559            let tokenization_result = tokenize(code);
1560            let tokens = tokenization_result.unwrap();
1561
1562            assert_eq_code_and_tokens_length(code, &tokens);
1563
1564            assert!(tokens.len() == 1);
1565
1566            let token = &tokens[0];
1567
1568            assert!(token.id == TokenId::StringExtended);
1569            assert!(token.index == 0);
1570            assert!(token.length == 13);
1571        }
1572        {
1573            let code = r#"""",hallo,""""#;
1574
1575            let tokenization_result = tokenize(code);
1576            let tokens = tokenization_result.unwrap();
1577
1578            assert_eq_code_and_tokens_length(code, &tokens);
1579
1580            assert!(tokens.len() == 1);
1581
1582            let token = &tokens[0];
1583
1584            assert!(token.id == TokenId::StringExtended);
1585            assert!(token.index == 0);
1586            assert!(token.length == 13);
1587        }
1588    }
1589
1590    #[test]
1591    fn test_comment_single_line() {
1592        {
1593            let code = r#";"#;
1594
1595            let tokenization_result = tokenize(code);
1596            let tokens = tokenization_result.unwrap();
1597
1598            assert_eq_code_and_tokens_length(code, &tokens);
1599
1600            assert!(tokens.len() == 1);
1601
1602            let token = &tokens[0];
1603
1604            assert!(token.id == TokenId::CommentSingleLine);
1605            assert!(token.index == 0);
1606            assert!(token.length == 1);
1607        }
1608        {
1609            let code = r#"; "#;
1610
1611            let tokenization_result = tokenize(code);
1612            let tokens = tokenization_result.unwrap();
1613
1614            assert_eq_code_and_tokens_length(code, &tokens);
1615
1616            assert!(tokens.len() == 1);
1617
1618            let token = &tokens[0];
1619
1620            assert!(token.id == TokenId::CommentSingleLine);
1621            assert!(token.index == 0);
1622            assert!(token.length == 2);
1623        }
1624        {
1625            let code = r#";a"#;
1626
1627            let tokenization_result = tokenize(code);
1628            let tokens = tokenization_result.unwrap();
1629
1630            assert_eq_code_and_tokens_length(code, &tokens);
1631
1632            assert!(tokens.len() == 1);
1633
1634            let token = &tokens[0];
1635
1636            assert!(token.id == TokenId::CommentSingleLine);
1637            assert!(token.index == 0);
1638            assert!(token.length == 2);
1639        }
1640        {
1641            let code = r#"; a"#;
1642
1643            let tokenization_result = tokenize(code);
1644            let tokens = tokenization_result.unwrap();
1645
1646            assert_eq_code_and_tokens_length(code, &tokens);
1647
1648            assert!(tokens.len() == 1);
1649
1650            let token = &tokens[0];
1651
1652            assert!(token.id == TokenId::CommentSingleLine);
1653            assert!(token.index == 0);
1654            assert!(token.length == 3);
1655        }
1656        {
1657            let code = r#";;"#;
1658
1659            let tokenization_result = tokenize(code);
1660            let tokens = tokenization_result.unwrap();
1661
1662            assert_eq_code_and_tokens_length(code, &tokens);
1663
1664            assert!(tokens.len() == 1);
1665
1666            let token = &tokens[0];
1667
1668            assert!(token.id == TokenId::CommentSingleLine);
1669            assert!(token.index == 0);
1670            assert!(token.length == 2);
1671        }
1672        {
1673            let code = r#";;;"#;
1674
1675            let tokenization_result = tokenize(code);
1676            let tokens = tokenization_result.unwrap();
1677
1678            assert_eq_code_and_tokens_length(code, &tokens);
1679
1680            assert!(tokens.len() == 1);
1681
1682            let token = &tokens[0];
1683
1684            assert!(token.id == TokenId::CommentSingleLine);
1685            assert!(token.index == 0);
1686            assert!(token.length == 3);
1687        }
1688    }
1689
1690    #[test]
1691    fn test_comment_multi_line_1() {
1692        assert_code_is_ok(";();");
1693    }
1694
1695    #[test]
1696    fn test_comment_multi_line_2() {
1697        assert_code_is_not_ok(";;();");
1698    }
1699
1700    #[test]
1701    fn test_comment_multi_line_3() {
1702        assert_code_is_not_ok(";();;");
1703    }
1704
1705    #[test]
1706    fn test_comment_multi_line_4() {
1707        // Guard
1708        assert_code_is_not_ok(";(;;();");
1709    }
1710
1711    #[test]
1712    fn test_comment_multi_line_5() {
1713        let code = r#";();"#;
1714
1715        let tokenization_result = tokenize(code);
1716        let tokens = tokenization_result.unwrap();
1717
1718        assert_eq_code_and_tokens_length(code, &tokens);
1719
1720        assert!(tokens.len() == 1);
1721
1722        let token = &tokens[0];
1723
1724        assert!(token.id == TokenId::CommentMultiLine);
1725        assert!(token.index == 0);
1726        assert!(token.length == 4);
1727    }
1728
1729    #[test]
1730    fn test_comment_multi_line_6() {
1731        let code = r#" ;(;();); "#;
1732
1733        let tokenization_result = tokenize(code);
1734        let tokens = tokenization_result.unwrap();
1735
1736        assert_eq_code_and_tokens_length(code, &tokens);
1737
1738        assert!(tokens.len() == 3);
1739
1740        let token = &tokens[1];
1741
1742        assert!(token.id == TokenId::CommentMultiLine);
1743        assert!(token.index == 1);
1744        assert!(token.length == 8);
1745    }
1746
1747    #[test]
1748    fn test_comment_multi_line_7() {
1749        let code = r#";(;(););"#;
1750
1751        let tokenization_result = tokenize(code);
1752        let tokens = tokenization_result.unwrap();
1753
1754        assert_eq_code_and_tokens_length(code, &tokens);
1755
1756        assert!(tokens.len() == 1);
1757
1758        let token = &tokens[0];
1759
1760        assert!(token.id == TokenId::CommentMultiLine);
1761        assert!(token.index == 0);
1762        assert!(token.length == 8);
1763    }
1764
1765    #[test]
1766    fn test_comment_multi_line_8() {
1767        let code = r#" ;(;(););"#;
1768
1769        let tokenization_result = tokenize(code);
1770        let tokens = tokenization_result.unwrap();
1771
1772        assert_eq_code_and_tokens_length(code, &tokens);
1773
1774        assert!(tokens.len() == 2);
1775
1776        let token = &tokens[1];
1777
1778        assert!(token.id == TokenId::CommentMultiLine);
1779        assert!(token.index == 1);
1780        assert!(token.length == 8);
1781    }
1782
1783    #[test]
1784    fn test_comment_multi_line_9() {
1785        let code = r#";(;();); "#;
1786
1787        let tokenization_result = tokenize(code);
1788        let tokens = tokenization_result.unwrap();
1789
1790        assert_eq_code_and_tokens_length(code, &tokens);
1791
1792        assert!(tokens.len() == 2);
1793
1794        let token = &tokens[0];
1795
1796        assert!(token.id == TokenId::CommentMultiLine);
1797        assert!(token.index == 0);
1798        assert!(token.length == 8);
1799    }
1800
1801    #[test]
1802    fn test_empty_normal_string() {
1803        let code = r#""""#;
1804
1805        let tokenization_result = tokenize(code);
1806        let tokens = tokenization_result.unwrap();
1807
1808        assert_eq_code_and_tokens_length(code, &tokens);
1809
1810        assert!(tokens.len() == 1);
1811
1812        let token = &tokens[0];
1813
1814        assert!(token.id == TokenId::StringNormal);
1815        assert!(token.index == 0);
1816        assert!(token.length == 2);
1817    }
1818
1819    #[test]
1820    fn test_unterminated_normal_string() {
1821        {
1822            let code = r#"""#;
1823            let tokenization_result = tokenize(code);
1824            match tokenization_result {
1825                Err(syntax_error) => match syntax_error {
1826                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1827                        parsing_error.error_code,
1828                        ErrorCode::UnterminatedNormalString
1829                    ),
1830                    _ => panic!(),
1831                },
1832                _ => panic!(),
1833            }
1834        }
1835        {
1836            let code = r#""a"#;
1837            let tokenization_result = tokenize(code);
1838
1839            match tokenization_result {
1840                Err(syntax_error) => match syntax_error {
1841                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1842                        parsing_error.error_code,
1843                        ErrorCode::UnterminatedNormalString
1844                    ),
1845                    _ => panic!(),
1846                },
1847                _ => panic!(),
1848            }
1849        }
1850        {
1851            let code = r#""a\"#;
1852            let tokenization_result = tokenize(code);
1853
1854            match tokenization_result {
1855                Err(syntax_error) => match syntax_error {
1856                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1857                        parsing_error.error_code,
1858                        ErrorCode::UnterminatedNormalString
1859                    ),
1860                    _ => panic!(),
1861                },
1862                _ => panic!(),
1863            }
1864        }
1865        {
1866            let code = r#""a\""#;
1867            let tokenization_result = tokenize(code);
1868
1869            match tokenization_result {
1870                Err(syntax_error) => match syntax_error {
1871                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1872                        parsing_error.error_code,
1873                        ErrorCode::UnterminatedNormalString
1874                    ),
1875                    _ => panic!(),
1876                },
1877                _ => panic!(),
1878            }
1879        }
1880        {
1881            let tokenization_result = tokenize(r#""a\"c"#);
1882
1883            match tokenization_result {
1884                Err(syntax_error) => match syntax_error {
1885                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1886                        parsing_error.error_code,
1887                        ErrorCode::UnterminatedNormalString
1888                    ),
1889                    _ => panic!(),
1890                },
1891                _ => panic!(),
1892            }
1893        }
1894    }
1895
1896    #[test]
1897    fn test_unterminated_extended_string() {
1898        {
1899            let code = r#"""""#;
1900            let tokenization_result = tokenize(code);
1901
1902            match tokenization_result {
1903                Err(syntax_error) => match syntax_error {
1904                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1905                        parsing_error.error_code,
1906                        ErrorCode::UnterminatedExtendedString
1907                    ),
1908                    _ => panic!(),
1909                },
1910                _ => panic!(),
1911            }
1912        }
1913        {
1914            let code = r#""""."#;
1915            let tokenization_result = tokenize(code);
1916
1917            match tokenization_result {
1918                Err(syntax_error) => match syntax_error {
1919                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1920                        parsing_error.error_code,
1921                        ErrorCode::UnterminatedExtendedString
1922                    ),
1923                    _ => panic!(),
1924                },
1925                _ => panic!(),
1926            }
1927        }
1928        {
1929            let code = r#"""".""#;
1930            let tokenization_result = tokenize(code);
1931
1932            match tokenization_result {
1933                Err(syntax_error) => match syntax_error {
1934                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1935                        parsing_error.error_code,
1936                        ErrorCode::UnterminatedExtendedString
1937                    ),
1938                    _ => panic!(),
1939                },
1940                _ => panic!(),
1941            }
1942        }
1943        {
1944            let code = r#""""."""#;
1945            let tokenization_result = tokenize(code);
1946
1947            match tokenization_result {
1948                Err(syntax_error) => match syntax_error {
1949                    SyntaxError::ParsingError(parsing_error) => assert_eq!(
1950                        parsing_error.error_code,
1951                        ErrorCode::UnterminatedExtendedString
1952                    ),
1953                    _ => panic!(),
1954                },
1955                _ => panic!(),
1956            }
1957        }
1958    }
1959}
1960
1961#[cfg(test)]
1962mod test_parser {
1963    use super::*;
1964
1965    fn assert_code_is_ok(code: &str) {
1966        // let code = code.as_bytes();
1967        let result = parse(code, true, true, true);
1968        match result {
1969            Ok(_) => (),
1970            Err(syntax_error) => match syntax_error {
1971                SyntaxError::ParsingError(parsing_error) => panic!(parsing_error.error_message),
1972                SyntaxError::AssumptionError(error_message) => panic!(error_message),
1973            },
1974        }
1975    }
1976
1977    fn assert_code_is_not_ok(code: &str) {
1978        // let code = code.as_bytes();
1979        let result = parse(code, true, true, true);
1980        match result {
1981            Ok(_) => {
1982                panic!();
1983            }
1984            Err(_) => (),
1985        }
1986    }
1987
1988    #[test]
1989    fn test_misc() {
1990        assert_code_is_ok("");
1991        assert_code_is_ok(" ");
1992
1993        assert_code_is_ok("()");
1994
1995        assert_code_is_ok("(())");
1996
1997        assert_code_is_ok("((()))");
1998
1999        assert_code_is_ok("((())())");
2000
2001        assert_code_is_ok("()");
2002        assert_code_is_ok("[]");
2003        assert_code_is_ok("{}");
2004
2005        assert_code_is_ok("(())");
2006        assert_code_is_ok("([])");
2007        assert_code_is_ok("({})");
2008
2009        assert_code_is_ok("[()]");
2010        assert_code_is_ok("[[]]");
2011        assert_code_is_ok("[{}]");
2012
2013        assert_code_is_ok("{()}");
2014        assert_code_is_ok("{[]}");
2015        assert_code_is_ok("{{}}");
2016    }
2017
2018    #[test]
2019    fn test_unexpected_closing_delimiter() {
2020        assert_code_is_not_ok(r#")"#);
2021        assert_code_is_not_ok(r#"]"#);
2022        assert_code_is_not_ok(r#"}"#);
2023    }
2024
2025    #[test]
2026    fn test_delimiter_mismatch() {
2027        assert_code_is_not_ok(r#"(]"#);
2028        assert_code_is_not_ok(r#"(}"#);
2029        assert_code_is_not_ok(r#"[)"#);
2030        assert_code_is_not_ok(r#"[}"#);
2031        assert_code_is_not_ok(r#"{)"#);
2032        assert_code_is_not_ok(r#"{]"#);
2033    }
2034
2035    #[test]
2036    fn test_unclosed_delimiter() {
2037        assert_code_is_not_ok(r#" ( "#);
2038
2039        assert_code_is_not_ok(r#"("#);
2040        assert_code_is_not_ok(r#"["#);
2041        assert_code_is_not_ok(r#"{"#);
2042    }
2043}
2044
2045#[cfg(test)]
2046mod test_lib {
2047    use super::{parse, ParseNode};
2048    use std::error::Error;
2049
2050    fn t1() -> Result<Vec<ParseNode>, Box<dyn Error>> {
2051        let code = " ( hello )) ";
2052
2053        let result = parse(code, false, false, false)?;
2054
2055        Ok(result)
2056    }
2057
2058    #[test]
2059    fn test_1() {
2060        let result = t1();
2061
2062        println!("-> {:?}", result);
2063    }
2064}