ghall_test.gleam

  1import birdie
  2import gleam/int
  3import gleam/list
  4import gleam/string
  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 assert Error(error) = nibble.run(tokens, parser)
 90
 91  snapshot_helpers.snap_parse_error(
 92    input,
 93    error,
 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 assert Ok(nodes) = nibble.run(tokens, parser)
125
126  snapshot_helpers.snap_parse_success(
127    input,
128    nodes,
129    "Multiple line endings should all parse as EndOfLine",
130  )
131}
132
133pub fn demo_visual_error_rendering_test() {
134  let input = "let x = 42\r"
135  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
136
137  // Try to parse "let" followed by a line ending
138  let parser = {
139    use _ <- nibble.do(parser.let_keyword())
140    use _ <- nibble.do(parser.exact_string(" x = 42", node.Let))
141    parser.end_of_line()
142  }
143
144  let assert Error(errors) = nibble.run(tokens, parser)
145
146  snapshot_helpers.snap_parse_error(
147    input,
148    errors,
149    "Visual error demo: shows escaped chars, spans, and parser context",
150  )
151}