diff options
author | Tomasz Chojnacki <tomaszchojnacki2001@gmail.com> | 2023-02-21 19:46:12 +0100 |
---|---|---|
committer | Tomasz Chojnacki <tomaszchojnacki2001@gmail.com> | 2023-02-21 19:46:12 +0100 |
commit | 16509ecd4f31ef421a464a98c8e0ea73bb5cb111 (patch) | |
tree | 7ac2145ed18cf868abe0b877fecb5988d11fd2ac /aoc-2020-gleam/src/util | |
parent | 9c94df2676e8b857eac6c94e827086bf0e8cb850 (diff) | |
download | gleam_aoc2020-16509ecd4f31ef421a464a98c8e0ea73bb5cb111.tar.gz gleam_aoc2020-16509ecd4f31ef421a464a98c8e0ea73bb5cb111.zip |
Refactor previous days
Diffstat (limited to 'aoc-2020-gleam/src/util')
-rw-r--r-- | aoc-2020-gleam/src/util/input_util.gleam | 5 | ||||
-rw-r--r-- | aoc-2020-gleam/src/util/parser.gleam | 197 |
2 files changed, 105 insertions, 97 deletions
diff --git a/aoc-2020-gleam/src/util/input_util.gleam b/aoc-2020-gleam/src/util/input_util.gleam index 3e6b076..2589a7d 100644 --- a/aoc-2020-gleam/src/util/input_util.gleam +++ b/aoc-2020-gleam/src/util/input_util.gleam @@ -1,8 +1,9 @@ import gleam/list import gleam/string +import gleam/function +import gleam/bool import gleam/erlang/file import ext/intx -import ext/stringx import ext/resultx pub fn read_text(filename: String) -> String { @@ -16,7 +17,7 @@ pub fn read_lines(filename: String) -> List(String) { |> read_text() |> string.split(on: "\n") |> list.map(with: string.trim) - |> list.filter(for: stringx.is_not_empty) + |> list.filter(for: function.compose(string.is_empty, bool.negate)) } pub fn read_numbers(filename: String) -> List(Int) { diff --git a/aoc-2020-gleam/src/util/parser.gleam b/aoc-2020-gleam/src/util/parser.gleam index ff8e479..59c6f62 100644 --- a/aoc-2020-gleam/src/util/parser.gleam +++ b/aoc-2020-gleam/src/util/parser.gleam @@ -9,7 +9,13 @@ import gleam/option.{None, Option, Some} // Heavily inspired by https://fsharpforfunandprofit.com/posts/understanding-parser-combinators/ -const eof = "end of input" +// TODO: - make distinction between atomic (int, string literal) and non-atomic (then, sequence) +// parsers and only override error sources for atomic parsers +// - report where the error occured in the error struct + +const eof = "EOF" + +const unknown = "UNKNOWN" const whitespace_range = " \t\n" @@ -23,7 +29,7 @@ fn q_d(string: String) -> String { pub type ParseError { InvalidInput(expected: String, found: String) - InvalidOperation(at: String) + InvalidOperation(ran: String, with: String) InvalidParser } @@ -35,7 +41,7 @@ pub opaque type Parser(a) { } fn create(function: fn(String) -> ParseResult(a)) { - Parser(function, "unknown") + Parser(function, unknown) } fn run(parser: Parser(a), on input: String) -> ParseResult(a) { @@ -75,63 +81,58 @@ pub fn parse_entire( } } -pub fn grapheme_satisfying(predicate) { +fn gc_satisfying(rule predicate: fn(String) -> Bool) -> Parser(String) { create(fn(input) { case string.pop_grapheme(input) { Ok(#(value, remaining)) -> case predicate(value) { True -> Ok(#(value, remaining)) - False -> Error(InvalidInput("", found: q_s(value))) + False -> Error(InvalidInput(expected: unknown, found: q_s(value))) } - Error(_) -> Error(InvalidInput("", found: eof)) + Error(_) -> Error(InvalidInput(expected: unknown, found: eof)) } }) } -pub fn any_grapheme() -> Parser(String) { - grapheme_satisfying(function.constant(True)) - |> labeled(with: "any grapheme") +pub fn any_gc() -> Parser(String) { + gc_satisfying(rule: function.constant(True)) + |> labeled(with: "any_gc") } -pub fn grapheme_literal(expected: String) -> Parser(String) { - grapheme_satisfying(fn(g) { g == expected }) - |> labeled(with: q_s(expected)) +pub fn gc_in(range allowed: String) -> Parser(String) { + gc_satisfying(rule: string.contains(allowed, _)) + |> labeled(with: "gc_in(range: " <> q_d(allowed) <> ")") } -pub fn grapheme_in(range allowed: String) -> Parser(String) { - grapheme_satisfying(string.contains(allowed, _)) - |> labeled(with: "grapheme from set " <> q_d(allowed)) +pub fn gc_not_in(range denied: String) -> Parser(String) { + gc_satisfying(rule: function.compose(string.contains(denied, _), bool.negate)) + |> labeled(with: "gc_not_in(range: " <> q_d(denied) <> ")") } -pub fn grapheme_not_in(range denied: String) -> Parser(String) { - grapheme_satisfying(function.compose(string.contains(denied, _), bool.negate)) - |> labeled(with: "grapheme NOT from set " <> q_d(denied)) +pub fn ws_gc() -> Parser(String) { + gc_in(range: whitespace_range) + |> labeled(with: "ws_gc") } -pub fn whitespace_grapheme() -> Parser(String) { - grapheme_in(range: whitespace_range) - |> labeled(with: "whitespace grapheme") +pub fn non_ws_gc() -> Parser(String) { + gc_not_in(range: whitespace_range) + |> labeled(with: "non_ws_gc") } -pub fn whitespaces() -> Parser(String) { - string_of_many(of: whitespace_grapheme()) +pub fn ws0() -> Parser(String) { + str_of_many0(of: ws_gc()) } -pub fn whitespaces1() -> Parser(String) { - string_of_many1(of: whitespace_grapheme()) +pub fn ws1() -> Parser(String) { + str_of_many1(of: ws_gc()) } -pub fn nonwhitespace_grapheme() -> Parser(String) { - grapheme_not_in(range: whitespace_range) - |> labeled(with: "nonwhitespace grapheme") +pub fn str0_until_ws() -> Parser(String) { + str_of_many0(of: non_ws_gc()) } -pub fn string_until_whitespace() -> Parser(String) { - string_of_many(of: nonwhitespace_grapheme()) -} - -pub fn string1_until_whitespace() -> Parser(String) { - string_of_many1(of: nonwhitespace_grapheme()) +pub fn str1_until_ws() -> Parser(String) { + str_of_many1(of: non_ws_gc()) } pub fn ignore(parser: Parser(a)) -> Parser(Nil) { @@ -143,13 +144,11 @@ pub fn then(first: Parser(a), second: Parser(b)) -> Parser(#(a, b)) { create(fn(input) { use parsed1 <- result.then(run(first, on: input)) let #(value1, remaining1) = parsed1 - use parsed2 <- result.then(run(second, on: remaining1)) let #(value2, remaining2) = parsed2 - Ok(#(#(value1, value2), remaining2)) }) - |> labeled(with: first.label <> " then " <> second.label) + |> labeled(with: first.label <> " |> then(" <> second.label <> ")") } pub fn then_skip(first: Parser(a), second: Parser(b)) -> Parser(a) { @@ -158,13 +157,13 @@ pub fn then_skip(first: Parser(a), second: Parser(b)) -> Parser(a) { |> map(with: pair.first) } -pub fn ignore_and_then(first: Parser(a), second: Parser(b)) -> Parser(b) { +pub fn drop_then(first: Parser(a), second: Parser(b)) -> Parser(b) { first |> then(second) |> map(with: pair.second) } -pub fn then_third(two: Parser(#(a, b)), third: Parser(c)) -> Parser(#(a, b, c)) { +pub fn then_3rd(two: Parser(#(a, b)), third: Parser(c)) -> Parser(#(a, b, c)) { two |> then(third) |> map(with: fn(tuple) { @@ -179,14 +178,14 @@ pub fn or(first: Parser(a), else second: Parser(a)) -> Parser(a) { |> run(on: input) |> result.or(run(second, on: input)) }) - |> labeled(with: "(" <> first.label <> " or " <> second.label <> ")") + |> labeled(with: first.label <> " |> or(else: " <> second.label <> ")") } -pub fn optional(parser: Parser(a)) -> Parser(Option(a)) { +pub fn opt(parser: Parser(a)) -> Parser(Option(a)) { parser |> map(with: Some) |> or(else: succeeding(with: None)) - |> labeled(with: "optional " <> parser.label) + |> labeled(with: "opt(" <> parser.label <> ")") } pub fn any(of parsers: List(Parser(a))) -> Parser(a) { @@ -194,7 +193,7 @@ pub fn any(of parsers: List(Parser(a))) -> Parser(a) { |> list.reduce(with: or) |> result.unwrap(or: failing(with: InvalidParser)) |> labeled( - "(any of [" <> { + "any(of: [" <> { parsers |> list.map(with: fn(p) { p.label }) |> string.join(with: ", ") @@ -203,30 +202,37 @@ pub fn any(of parsers: List(Parser(a))) -> Parser(a) { } pub fn digit() -> Parser(String) { - grapheme_in(range: "0123456789") + gc_in(range: "0123456789") |> labeled(with: "digit") } -pub fn map(parser: Parser(a), with mapper: fn(a) -> b) -> Parser(b) { +fn flat_map( + parser: Parser(a), + with mapper: fn(a) -> Result(b, ParseError), +) -> Parser(b) { create(fn(input) { use parsed <- result.then(run(parser, on: input)) let #(value, remaining) = parsed - Ok(#(mapper(value), remaining)) + value + |> mapper + |> result.map(with: fn(new_value) { #(new_value, remaining) }) }) |> labeled(with: parser.label) } +pub fn map(parser: Parser(a), with mapper: fn(a) -> b) -> Parser(b) { + flat_map(parser, with: fn(value) { Ok(mapper(value)) }) +} + pub fn map2(parser: Parser(#(a, b)), with mapper: fn(a, b) -> c) -> Parser(c) { - parser - |> map(with: fn(args) { mapper(args.0, args.1) }) + map(parser, with: fn(args) { mapper(args.0, args.1) }) } pub fn map3( parser: Parser(#(a, b, c)), with mapper: fn(a, b, c) -> d, ) -> Parser(d) { - parser - |> map(with: fn(args) { mapper(args.0, args.1, args.2) }) + map(parser, with: fn(args) { mapper(args.0, args.1, args.2) }) } fn succeeding(with value: a) -> Parser(a) { @@ -242,7 +248,7 @@ fn lift2(function: fn(a, b) -> c) -> fn(Parser(a), Parser(b)) -> Parser(c) { function |> succeeding |> then(x_parser) - |> then_third(y_parser) + |> then_3rd(y_parser) |> map3(with: fn(f, x, y) { f(x, y) }) } } @@ -257,7 +263,7 @@ pub fn sequence(of parsers: List(Parser(a))) -> Parser(List(a)) { |> prepend_parser(head, _) } |> labeled( - with: "(sequence of [" <> { + with: "sequence(of: [" <> { parsers |> list.map(with: fn(p) { p.label }) |> string.join(", ") @@ -265,7 +271,7 @@ pub fn sequence(of parsers: List(Parser(a))) -> Parser(List(a)) { ) } -pub fn string_of_sequence(of parsers: List(Parser(String))) -> Parser(String) { +pub fn str_of_sequence(of parsers: List(Parser(String))) -> Parser(String) { parsers |> sequence |> map(with: string.concat) @@ -281,14 +287,14 @@ fn do_zero_or_more(input: String, with parser: Parser(a)) -> #(List(a), String) } } -pub fn many(of parser: Parser(a)) -> Parser(List(a)) { +pub fn many0(of parser: Parser(a)) -> Parser(List(a)) { create(fn(input) { Ok(do_zero_or_more(input, with: parser)) }) - |> labeled(with: "(at least zero of " <> parser.label <> ")") + |> labeled(with: "many(of: " <> parser.label <> ")") } -pub fn string_of_many(of parser: Parser(String)) -> Parser(String) { +pub fn str_of_many0(of parser: Parser(String)) -> Parser(String) { parser - |> many + |> many0 |> map(with: string.concat) } @@ -299,91 +305,92 @@ pub fn many1(of parser: Parser(a)) -> Parser(List(a)) { let #(previous, rest) = do_zero_or_more(rest, with: parser) Ok(#([value, ..previous], rest)) }) - |> labeled(with: "(at least one of " <> parser.label <> ")") + |> labeled(with: "many1(of: " <> parser.label <> ")") } -pub fn string_of_many1(of parser: Parser(String)) -> Parser(String) { +pub fn str_of_many1(of parser: Parser(String)) -> Parser(String) { parser |> many1 |> map(with: string.concat) } -pub fn separated(parser: Parser(a), by separator: Parser(b)) -> Parser(List(a)) { +pub fn sep0(parser: Parser(a), by separator: Parser(b)) -> Parser(List(a)) { parser - |> then(many(of: ignore_and_then(separator, parser))) + |> then(many0(of: drop_then(separator, parser))) |> map2(with: fn(p, ps) { [p, ..ps] }) |> labeled( - with: "(at least zero of " <> parser.label <> " separated by " <> separator.label <> ")", + with: "sep0(" <> parser.label <> ", by: " <> separator.label <> ")", ) } -pub fn separated1(parser: Parser(a), by separator: Parser(b)) -> Parser(List(a)) { +pub fn sep1(parser: Parser(a), by separator: Parser(b)) -> Parser(List(a)) { parser - |> separated(by: separator) + |> sep0(by: separator) |> or(else: succeeding(with: [])) |> labeled( - with: "(at least one of " <> parser.label <> " separated by " <> separator.label <> ")", + with: "sep1(" <> parser.label <> ", by: " <> separator.label <> ")", ) } pub fn int() -> Parser(Int) { - let int_string_parser = string_of_many1(digit()) - create(fn(input) { - use parsed <- result.then(run(int_string_parser, on: input)) - let #(int_string, remaining) = parsed - + digit() + |> str_of_many1 + |> flat_map(with: fn(int_string) { int_string |> int.parse - |> result.map(with: fn(int) { #(int, remaining) }) - |> result.replace_error(InvalidOperation( - at: "int.parse(" <> int_string <> ")", - )) + |> result.replace_error(InvalidOperation(ran: "int.parse", with: int_string)) }) |> labeled(with: "int") } -pub fn any_string_greedy() -> Parser(String) { - any_grapheme() - |> string_of_many - |> labeled(with: "any string") +pub fn any_str_greedy() -> Parser(String) { + any_gc() + |> str_of_many0 + |> labeled(with: "any_str_greedy") } -pub fn string_literal(expected: String) -> Parser(String) { +pub fn literal(expected: String) -> Parser(String) { expected - |> string.to_graphemes() - |> list.map(with: grapheme_literal) - |> string_of_sequence + |> string.to_graphemes + |> list.map(with: fn(eg) { gc_satisfying(fn(g) { g == eg }) }) + |> str_of_sequence |> labeled(with: q_d(expected)) } -pub fn string_of_exactly( - parser: Parser(String), - length length: Int, -) -> Parser(String) { +pub fn str_of_len(parser: Parser(String), length: Int) -> Parser(String) { parser |> list.repeat(times: length) - |> string_of_sequence - |> labeled(with: "string of length " <> int.to_string(length)) + |> str_of_sequence + |> labeled( + with: "str_of_len(" <> parser.label <> "," <> int.to_string(length) <> ")", + ) } -pub fn any_string_of_exactly(length length: Int) -> Parser(String) { - string_of_exactly(any_grapheme(), length: length) +pub fn any_str_of_len(length: Int) -> Parser(String) { + str_of_len(any_gc(), length) } -pub fn repeated(parser: Parser(a), times times: Int) -> Parser(List(a)) { +pub fn repeat(parser: Parser(a), times times: Int) -> Parser(List(a)) { parser |> list.repeat(times: times) |> sequence - |> labeled(with: "exactly " <> int.to_string(times) <> " of " <> parser.label) + |> labeled( + with: parser.label <> " |> repeat(times: " <> int.to_string(times) <> ")", + ) } -pub fn matching(parser: Parser(a), rule predicate: fn(a) -> Bool) -> Parser(a) { +pub fn satisfying(parser: Parser(a), rule predicate: fn(a) -> Bool) -> Parser(a) { create(fn(input) { use parsed <- result.then(run(parser, on: input)) - let #(value, remaining) = parsed + let #(value, _) = parsed case predicate(value) { True -> Ok(parsed) - False -> Error(InvalidOperation(at: string.inspect(predicate))) + False -> + Error(InvalidOperation( + ran: string.inspect(predicate), + with: string.inspect(value), + )) } }) + |> labeled(with: parser.label) } |