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
 11
 12pub fn main() -> Nil {
 13  gleeunit.main()
 14}
 15
 16pub fn simple_quasi_lexer_test() {
 17  quasi_lexer.chars()
 18  |> quasi_lexer.run(on: "let x1 = e1")
 19  |> list.index_map(fn(token, index) {
 20    let Token(span, lexeme, value) = token
 21    assert lexeme == value
 22
 23    let Span(row_start, col_start, row_end, col_end) = span
 24    assert row_start == row_end && row_start == 1
 25    assert col_start == index + 1
 26    assert col_end == col_start + 1
 27  })
 28}
 29
 30pub fn quasi_lexer_off_by_one_test() {
 31  let input = "let x1 =\n  e1"
 32  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 33
 34  let snap =
 35    tokens
 36    |> list.index_map(fn(token, index) {
 37      let Token(Span(rs, cs, re, ce), lexeme, _) = token
 38      "Token "
 39      <> int.to_string(index)
 40      <> ": '"
 41      <> lexeme
 42      <> "' at Span(row_start: "
 43      <> int.to_string(rs)
 44      <> ", col_start: "
 45      <> int.to_string(cs)
 46      <> ", row_end: "
 47      <> int.to_string(re)
 48      <> ", col_end: "
 49      <> int.to_string(ce)
 50      <> ")\n"
 51    })
 52    |> list.fold("", fn(acc, line) { acc <> line })
 53
 54  birdie.snap(snap, title: "Quasi lexer spans with multiline input")
 55}
 56
 57pub fn parse_let_successfully_test() {
 58  let input = "let"
 59  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 60  let parser = parser.exact_string("let", node.Let)
 61  let assert Ok(_) = nibble.run(tokens, parser)
 62}
 63
 64pub fn parse_let_failing_test() {
 65  let input = "lt"
 66  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 67  let parser = parser.exact_string("let", node.Let)
 68  let assert Error(error) = nibble.run(tokens, parser)
 69  let assert [nibble.DeadEnd(Span(_, cs, _, _), Expected(msg, got: got), [])] =
 70    error
 71
 72  let snap =
 73    "Msg: "
 74    <> msg
 75    <> "\n"
 76    <> "Got: "
 77    <> got
 78    <> "\n"
 79    <> "At column: "
 80    <> int.to_string(cs)
 81    <> "\n"
 82
 83  birdie.snap(snap, title: "Should fail to parse 'lt' as node.Let")
 84}
 85
 86pub fn parse_unix_line_ending_test() {
 87  let input = "\n"
 88  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 89  let parser = parser.end_of_line()
 90  let assert Ok(node.EndOfLine) = nibble.run(tokens, parser)
 91}
 92
 93pub fn parse_windows_line_ending_test() {
 94  let input = "\r\n"
 95  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
 96  let parser = parser.end_of_line()
 97  let assert Ok(node.EndOfLine) = nibble.run(tokens, parser)
 98}
 99
100pub fn parse_line_ending_fails_on_lone_carriage_return_test() {
101  let input = "\r"
102  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
103  let parser = parser.end_of_line()
104  let assert Error(error) = nibble.run(tokens, parser)
105
106  let snap =
107    "Input: \\r (lone carriage return)\n"
108    <> "Number of errors: "
109    <> int.to_string(list.length(error))
110    <> "\n"
111    <> {
112      error
113      |> list.index_map(fn(dead_end, idx) {
114        let nibble.DeadEnd(Span(rs, cs, re, ce), reason, context) = dead_end
115        "Error "
116        <> int.to_string(idx + 1)
117        <> ":\n"
118        <> "  Reason: "
119        <> string.inspect(reason)
120        <> "\n"
121        <> "  Span: (row_start: "
122        <> int.to_string(rs)
123        <> ", col_start: "
124        <> int.to_string(cs)
125        <> ", row_end: "
126        <> int.to_string(re)
127        <> ", col_end: "
128        <> int.to_string(ce)
129        <> ")\n"
130        <> "  Context: "
131        <> string.inspect(context)
132        <> "\n"
133      })
134      |> string.join("")
135    }
136
137  birdie.snap(snap, title: "Line ending should reject lone carriage return")
138}
139
140pub fn parse_line_ending_fails_on_other_chars_test() {
141  let input = "x"
142  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
143  let parser = parser.end_of_line()
144  let assert Error(_) = nibble.run(tokens, parser)
145}
146
147pub fn parse_line_ending_after_content_test() {
148  let input = "let\n"
149  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
150  let parser = {
151    use _ <- nibble.do(parser.let_keyword())
152    parser.end_of_line()
153  }
154  let assert Ok(node.EndOfLine) = nibble.run(tokens, parser)
155}
156
157pub fn parse_multiple_line_endings_test() {
158  let input = "\n\r\n\n"
159  let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input)
160  let parser = {
161    use eol1 <- nibble.do(parser.end_of_line())
162    use eol2 <- nibble.do(parser.end_of_line())
163    use eol3 <- nibble.do(parser.end_of_line())
164    nibble.return([eol1, eol2, eol3])
165  }
166  let assert Ok(nodes) = nibble.run(tokens, parser)
167
168  let snap =
169    "Input: \\n\\r\\n\\n (Unix, Windows, Unix line endings)\n"
170    <> "Parsed "
171    <> int.to_string(list.length(nodes))
172    <> " line endings:\n"
173    <> {
174      nodes
175      |> list.index_map(fn(n, idx) {
176        "  " <> int.to_string(idx + 1) <> ". " <> string.inspect(n) <> "\n"
177      })
178      |> string.join("")
179    }
180
181  birdie.snap(
182    snap,
183    title: "Multiple line endings should all parse as EndOfLine",
184  )
185}