import birdie import gleam/int import gleam/list import gleam/string import gleeunit import nibble.{Expected} import nibble/lexer.{Span, Token} import node import parser import quasi_lexer pub fn main() -> Nil { gleeunit.main() } pub fn simple_quasi_lexer_test() { quasi_lexer.chars() |> quasi_lexer.run(on: "let x1 = e1") |> list.index_map(fn(token, index) { let Token(span, lexeme, value) = token assert lexeme == value let Span(row_start, col_start, row_end, col_end) = span assert row_start == row_end && row_start == 1 assert col_start == index + 1 assert col_end == col_start + 1 }) } pub fn quasi_lexer_off_by_one_test() { let input = "let x1 =\n e1" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let snap = tokens |> list.index_map(fn(token, index) { let Token(Span(rs, cs, re, ce), lexeme, _) = token "Token " <> int.to_string(index) <> ": '" <> lexeme <> "' at Span(row_start: " <> int.to_string(rs) <> ", col_start: " <> int.to_string(cs) <> ", row_end: " <> int.to_string(re) <> ", col_end: " <> int.to_string(ce) <> ")\n" }) |> list.fold("", fn(acc, line) { acc <> line }) birdie.snap(snap, title: "Quasi lexer spans with multiline input") } pub fn parse_let_successfully_test() { let input = "let" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let parser = parser.exact_string("let", node.Let) let assert Ok(_) = nibble.run(tokens, parser) } pub fn parse_let_failing_test() { let input = "lt" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let parser = parser.exact_string("let", node.Let) let assert Error(error) = nibble.run(tokens, parser) let assert [nibble.DeadEnd(Span(_, cs, _, _), Expected(msg, got: got), [])] = error let snap = "Msg: " <> msg <> "\n" <> "Got: " <> got <> "\n" <> "At column: " <> int.to_string(cs) <> "\n" birdie.snap(snap, title: "Should fail to parse 'lt' as node.Let") } pub fn parse_unix_line_ending_test() { let input = "\n" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let parser = parser.end_of_line() let assert Ok(node.EndOfLine) = nibble.run(tokens, parser) } pub fn parse_windows_line_ending_test() { let input = "\r\n" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let parser = parser.end_of_line() let assert Ok(node.EndOfLine) = nibble.run(tokens, parser) } pub fn parse_line_ending_fails_on_lone_carriage_return_test() { let input = "\r" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let parser = parser.end_of_line() let assert Error(error) = nibble.run(tokens, parser) let snap = "Input: \\r (lone carriage return)\n" <> "Number of errors: " <> int.to_string(list.length(error)) <> "\n" <> { error |> list.index_map(fn(dead_end, idx) { let nibble.DeadEnd(Span(rs, cs, re, ce), reason, context) = dead_end "Error " <> int.to_string(idx + 1) <> ":\n" <> " Reason: " <> string.inspect(reason) <> "\n" <> " Span: (row_start: " <> int.to_string(rs) <> ", col_start: " <> int.to_string(cs) <> ", row_end: " <> int.to_string(re) <> ", col_end: " <> int.to_string(ce) <> ")\n" <> " Context: " <> string.inspect(context) <> "\n" }) |> string.join("") } birdie.snap(snap, title: "Line ending should reject lone carriage return") } pub fn parse_line_ending_fails_on_other_chars_test() { let input = "x" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let parser = parser.end_of_line() let assert Error(_) = nibble.run(tokens, parser) } pub fn parse_line_ending_after_content_test() { let input = "let\n" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let parser = { use _ <- nibble.do(parser.let_keyword()) parser.end_of_line() } let assert Ok(node.EndOfLine) = nibble.run(tokens, parser) } pub fn parse_multiple_line_endings_test() { let input = "\n\r\n\n" let tokens = quasi_lexer.chars() |> quasi_lexer.run(on: input) let parser = { use eol1 <- nibble.do(parser.end_of_line()) use eol2 <- nibble.do(parser.end_of_line()) use eol3 <- nibble.do(parser.end_of_line()) nibble.return([eol1, eol2, eol3]) } let assert Ok(nodes) = nibble.run(tokens, parser) let snap = "Input: \\n\\r\\n\\n (Unix, Windows, Unix line endings)\n" <> "Parsed " <> int.to_string(list.length(nodes)) <> " line endings:\n" <> { nodes |> list.index_map(fn(n, idx) { " " <> int.to_string(idx + 1) <> ". " <> string.inspect(n) <> "\n" }) |> string.join("") } birdie.snap( snap, title: "Multiple line endings should all parse as EndOfLine", ) }