graphql_minify/
lexer.rs

1use crate::block_string::{dedent_block_lines_mut, print_block_string, BlockStringToken};
2use logos::{Lexer, Logos};
3
4#[derive(Debug, PartialEq, Clone, Default)]
5/// An enumeration of errors that can occur during the lexing process.
6pub enum LexingError {
7  #[default]
8  UnknownToken,
9  /// First value is the index of the first character of the unterminated string
10  UnterminatedString(usize),
11}
12
13#[derive(Logos, Debug, PartialEq)]
14#[logos(skip r"([\s,]+|#[^\r\n]*)+")]
15#[logos(error = LexingError)]
16pub(crate) enum Token<'a> {
17  #[token("{")]
18  BraceOpen,
19
20  #[token("}")]
21  BraceClose,
22
23  #[token("(")]
24  ParenOpen,
25
26  #[token(")")]
27  ParenClose,
28
29  #[token("[")]
30  BracketOpen,
31
32  #[token("]")]
33  BracketClose,
34
35  #[token(":")]
36  Colon,
37
38  #[token("=")]
39  Equals,
40
41  #[token("!")]
42  Exclamation,
43
44  #[token("?")]
45  Question,
46
47  #[token("&")]
48  Ampersand,
49
50  #[token("|")]
51  Pipe,
52
53  #[token("...")]
54  Ellipsis,
55
56  #[regex(r#"""""#)]
57  BlockStringDelimiter,
58
59  #[regex(r#""([^"\\]*(\\.[^"\\]*)*)""#, |lexer| match lexer.slice() {
60      s if s.contains(['\n', '\r']) => Err(LexingError::UnterminatedString(lexer.span().start)),
61      s => Ok(s),
62  })]
63  String(&'a str),
64
65  #[regex("-?[0-9]+")]
66  Int(&'a str),
67
68  #[regex("-?[0-9]+\\.[0-9]+(e-?[0-9]+)?")]
69  Float(&'a str),
70
71  #[regex("true|false")]
72  Bool(&'a str),
73
74  #[regex("@[a-zA-Z_][a-zA-Z0-9_]*")]
75  Directive(&'a str),
76
77  #[regex("\\$[a-zA-Z_][a-zA-Z0-9_]*")]
78  Variable(&'a str),
79
80  #[regex(r"[a-zA-Z_][a-zA-Z0-9_]*")]
81  Identifier(&'a str),
82}
83
84impl<'a> Token<'a> {
85  pub(crate) fn parse_block_string(&self, lexer: &mut Lexer<'a, Token<'a>>) -> String {
86    let mut lines = vec![];
87    let mut current_line = String::new();
88
89    let remainder = lexer.remainder();
90    let mut block_lexer = BlockStringToken::lexer(remainder);
91
92    while let Some(Ok(token)) = block_lexer.next() {
93      match token {
94        BlockStringToken::NewLine => {
95          lines.push(current_line);
96          current_line = String::new();
97        }
98        BlockStringToken::Text | BlockStringToken::Quote | BlockStringToken::EscapeSeq => {
99          current_line.push_str(block_lexer.slice())
100        }
101        BlockStringToken::EscapedTripleQuote => current_line.push_str(r#"""""#),
102        BlockStringToken::TripleQuote => break,
103      }
104    }
105
106    if !current_line.is_empty() {
107      lines.push(current_line);
108    }
109
110    lexer.bump(remainder.len() - block_lexer.remainder().len());
111
112    dedent_block_lines_mut(&mut lines);
113    print_block_string(lines.join("\n"))
114  }
115}