1import birdie
  2import gleam/int
  3import gleam/list
  4import gleam/result
  5import gleeunit
  6import nibble.{Expected}
  7import nibble/lexer.{Span, Token}
  8import node
  9import parser
 10import quasi_lexer
 11import snapshot_helpers
 12
 13pub fn main() -> Nil {
 14  gleeunit.main()
 15}
 16
 17pub fn simple_quasi_lexer_test() {
 18  quasi_lexer.chars()
 19  |> quasi_lexer.run(on: "let x1 = e1")
 20  |> list.index_map(fn(token, index) {
 21    let Token(span, lexeme, value) = token
 22    assert lexeme == value
 23
 24    let Span(row_start, col_start, row_end, col_end) = span
 25    assert row_start == row_end && row_start == 1
 26    assert col_start == index + 1
 27    assert col_end == col_start + 1
 28  })
 29}
 30
 31pub fn quasi_lexer_off_by_one_test() {
 32  let input = "let x1 =\n  e1"
 33  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 34
 35  snapshot_helpers.snap_lexer_output(
 36    input,
 37    tokens,
 38    "Quasi lexer spans with multiline input",
 39  )
 40}
 41
 42pub fn parse_let_successfully_test() {
 43  let input = "let"
 44  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 45  let parser = parser.exact_string("let", node.Let)
 46  let assert Ok(_) = nibble.run(tokens, parser)
 47}
 48
 49pub fn parse_let_failing_test() {
 50  let input = "lt"
 51  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 52  let parser = parser.exact_string("let", node.Let)
 53  let assert Error(error) = nibble.run(tokens, parser)
 54  let assert [nibble.DeadEnd(Span(_, cs, _, _), Expected(msg, got: got), _)] =
 55    error
 56
 57  let snap =
 58    "Msg: "
 59    <> msg
 60    <> "\n"
 61    <> "Got: "
 62    <> got
 63    <> "\n"
 64    <> "At column: "
 65    <> int.to_string(cs)
 66    <> "\n"
 67
 68  birdie.snap(snap, title: "Should fail to parse 'lt' as node.Let")
 69}
 70
 71pub fn parse_unix_line_ending_test() {
 72  let input = "\n"
 73  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 74  let parser = parser.end_of_line()
 75  let assert Ok(node.EndOfLine) = nibble.run(tokens, parser)
 76}
 77
 78pub fn parse_windows_line_ending_test() {
 79  let input = "\r\n"
 80  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 81  let parser = parser.end_of_line()
 82  let assert Ok(node.EndOfLine) = nibble.run(tokens, parser)
 83}
 84
 85pub fn parse_line_ending_fails_on_lone_carriage_return_test() {
 86  let input = "\r"
 87  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 88  let parser = parser.end_of_line()
 89  let result = nibble.run(tokens, parser)
 90
 91  snapshot_helpers.snap_parse_result_nodes(
 92    input,
 93    result |> result.map(list.wrap),
 94    "Line ending should reject lone carriage return",
 95  )
 96}
 97
 98pub fn parse_line_ending_fails_on_other_chars_test() {
 99  let input = "x"
100  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
101  let parser = parser.end_of_line()
102  let assert Error(_) = nibble.run(tokens, parser)
103}
104
105pub fn parse_line_ending_after_content_test() {
106  let input = "let\n"
107  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
108  let parser = {
109    use _ <- nibble.do(parser.let_keyword())
110    parser.end_of_line()
111  }
112  let assert Ok(node.EndOfLine) = nibble.run(tokens, parser)
113}
114
115pub fn parse_multiple_line_endings_test() {
116  let input = "\n\r\n\n"
117  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
118  let parser = {
119    use eol1 <- nibble.do(parser.end_of_line())
120    use eol2 <- nibble.do(parser.end_of_line())
121    use eol3 <- nibble.do(parser.end_of_line())
122    nibble.return([eol1, eol2, eol3])
123  }
124  let result = nibble.run(tokens, parser)
125
126  snapshot_helpers.snap_parse_result_nodes(
127    input,
128    result,
129    "Multiple line endings should all parse as EndOfLine",
130  )
131}
132
133// Tests for printable parser (%x20-7F)
134
135pub fn parse_printable_space_test() {
136  let input = " "
137  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
138  let parser = parser.printable()
139  let assert Ok(node.Printable(" ")) = nibble.run(tokens, parser)
140}
141
142pub fn parse_printable_ascii_test() {
143  let input = "a"
144  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
145  let parser = parser.printable()
146  let assert Ok(node.Printable("a")) = nibble.run(tokens, parser)
147}
148
149pub fn parse_printable_tilde_test() {
150  let input = "~"
151  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
152  let parser = parser.printable()
153  let assert Ok(node.Printable("~")) = nibble.run(tokens, parser)
154}
155
156pub fn parse_printable_rejects_tab_test() {
157  let input = "\t"
158  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
159  let parser = parser.printable()
160  let assert Error(_) = nibble.run(tokens, parser)
161}
162
163pub fn parse_printable_rejects_newline_test() {
164  let input = "\n"
165  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
166  let parser = parser.printable()
167  let assert Error(_) = nibble.run(tokens, parser)
168}
169
170pub fn parse_printable_rejects_non_ascii_test() {
171  let input = "é"
172  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
173  let parser = parser.printable()
174  let assert Error(_) = nibble.run(tokens, parser)
175}
176
177// Tests for valid-non-ascii parser
178
179pub fn parse_valid_non_ascii_latin_test() {
180  let input = "é"
181  // é is U+00E9, in range 0x80-0xD7FF
182  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
183  let parser = parser.valid_non_ascii()
184  let assert Ok(node.ValidNonAscii("é")) = nibble.run(tokens, parser)
185}
186
187pub fn parse_valid_non_ascii_emoji_test() {
188  let input = "🎉"
189  // 🎉 is U+1F389, in range 0x10000-0x1FFFD
190  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
191  let parser = parser.valid_non_ascii()
192  let assert Ok(node.ValidNonAscii("🎉")) = nibble.run(tokens, parser)
193}
194
195pub fn parse_valid_non_ascii_chinese_test() {
196  let input = "中"
197  // 中 is U+4E2D, in range 0xE000-0xFFFD
198  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
199  let parser = parser.valid_non_ascii()
200  let assert Ok(node.ValidNonAscii("中")) = nibble.run(tokens, parser)
201}
202
203pub fn parse_valid_non_ascii_rejects_ascii_test() {
204  let input = "a"
205  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
206  let parser = parser.valid_non_ascii()
207  let assert Error(_) = nibble.run(tokens, parser)
208}
209
210pub fn parse_valid_non_ascii_rejects_tab_test() {
211  let input = "\t"
212  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
213  let parser = parser.valid_non_ascii()
214  let assert Error(_) = nibble.run(tokens, parser)
215}
216
217// Tests for not-end-of-line parser
218
219pub fn parse_not_end_of_line_printable_test() {
220  let input = "a"
221  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
222  let parser = parser.not_end_of_line()
223  let assert Ok(node.NotEndOfLine(node.Printable("a"))) =
224    nibble.run(tokens, parser)
225}
226
227pub fn parse_not_end_of_line_space_test() {
228  let input = " "
229  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
230  let parser = parser.not_end_of_line()
231  let assert Ok(node.NotEndOfLine(node.Printable(" "))) =
232    nibble.run(tokens, parser)
233}
234
235pub fn parse_not_end_of_line_tab_test() {
236  let input = "\t"
237  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
238  let parser = parser.not_end_of_line()
239  let assert Ok(node.NotEndOfLine(node.Tab)) = nibble.run(tokens, parser)
240}
241
242pub fn parse_not_end_of_line_valid_non_ascii_test() {
243  let input = "λ"
244  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
245  let parser = parser.not_end_of_line()
246  let assert Ok(node.NotEndOfLine(node.ValidNonAscii("λ"))) =
247    nibble.run(tokens, parser)
248}
249
250pub fn parse_not_end_of_line_rejects_newline_test() {
251  let input = "\n"
252  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
253  let parser = parser.not_end_of_line()
254  let assert Error(_) = nibble.run(tokens, parser)
255}
256
257pub fn parse_not_end_of_line_rejects_carriage_return_test() {
258  let input = "\r"
259  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
260  let parser = parser.not_end_of_line()
261  let assert Error(_) = nibble.run(tokens, parser)
262}