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 | |
parent | 9c94df2676e8b857eac6c94e827086bf0e8cb850 (diff) | |
download | gleam_aoc2020-16509ecd4f31ef421a464a98c8e0ea73bb5cb111.tar.gz gleam_aoc2020-16509ecd4f31ef421a464a98c8e0ea73bb5cb111.zip |
Refactor previous days
Diffstat (limited to 'aoc-2020-gleam')
-rw-r--r-- | aoc-2020-gleam/src/days/day02.gleam | 10 | ||||
-rw-r--r-- | aoc-2020-gleam/src/days/day04.gleam | 30 | ||||
-rw-r--r-- | aoc-2020-gleam/src/days/day06.gleam | 8 | ||||
-rw-r--r-- | aoc-2020-gleam/src/ext/stringx.gleam | 5 | ||||
-rw-r--r-- | aoc-2020-gleam/src/util/input_util.gleam | 5 | ||||
-rw-r--r-- | aoc-2020-gleam/src/util/parser.gleam | 197 |
6 files changed, 129 insertions, 126 deletions
diff --git a/aoc-2020-gleam/src/days/day02.gleam b/aoc-2020-gleam/src/days/day02.gleam index bc8df7c..3f77384 100644 --- a/aoc-2020-gleam/src/days/day02.gleam +++ b/aoc-2020-gleam/src/days/day02.gleam @@ -18,15 +18,15 @@ type Line { fn parse_line(string: String) -> Line { let policy_parser = p.int() - |> p.then_skip(p.grapheme_literal("-")) + |> p.then_skip(p.literal("-")) |> p.then(p.int()) - |> p.then_skip(p.grapheme_literal(" ")) - |> p.then_third(p.any_grapheme()) - |> p.then_skip(p.string_literal(": ")) + |> p.then_skip(p.literal(" ")) + |> p.then_3rd(p.any_gc()) + |> p.then_skip(p.literal(": ")) |> p.map3(with: fn(min, max, grapheme) { Policy(min, max, grapheme) }) |> p.labeled(with: "policy") - let password_parser = p.labeled(p.any_string_greedy(), with: "password") + let password_parser = p.labeled(p.any_str_greedy(), with: "password") let line_parser = policy_parser diff --git a/aoc-2020-gleam/src/days/day04.gleam b/aoc-2020-gleam/src/days/day04.gleam index 322d5c9..53abb0c 100644 --- a/aoc-2020-gleam/src/days/day04.gleam +++ b/aoc-2020-gleam/src/days/day04.gleam @@ -20,25 +20,25 @@ type Passport { fn parse_passports(from text: String) -> List(Passport) { let key_parser = - p.any_string_of_exactly(length: 3) + p.any_str_of_len(3) |> p.labeled(with: "key") let value_parser = - p.string1_until_whitespace() + p.str1_until_ws() |> p.labeled(with: "value") let field_parser = key_parser - |> p.then_skip(p.grapheme_literal(":")) + |> p.then_skip(p.literal(":")) |> p.then(value_parser) |> p.labeled(with: "field") let passport_parser = field_parser - |> p.separated1(by: p.whitespace_grapheme()) + |> p.sep1(by: p.ws_gc()) |> p.map(with: function.compose(map.from_list, Passport)) |> p.labeled(with: "passport") let input_parser = passport_parser - |> p.separated1(by: p.string_literal("\n\n")) - |> p.then_skip(p.optional(p.whitespace_grapheme())) + |> p.sep1(by: p.literal("\n\n")) + |> p.then_skip(p.opt(p.ws_gc())) |> p.labeled(with: "input") text @@ -61,7 +61,7 @@ fn is_valid1(passport: Passport) -> Bool { fn is_valid2(passport: Passport) -> Bool { let int_between = fn(min, max) { p.int() - |> p.matching(rule: fn(number) { min <= number && number <= max }) + |> p.satisfying(rule: fn(number) { min <= number && number <= max }) |> p.ignore } @@ -73,32 +73,32 @@ fn is_valid2(passport: Passport) -> Bool { "hgt", p.or( int_between(150, 193) - |> p.then(p.string_literal("cm")), + |> p.then(p.literal("cm")), int_between(59, 76) - |> p.then(p.string_literal("in")), + |> p.then(p.literal("in")), ) |> p.ignore, ), #( "hcl", - p.then( - p.grapheme_literal("#"), - p.grapheme_in(range: "0123456789abcdef") - |> p.repeated(times: 6), + p.literal("#") + |> p.then( + p.gc_in(range: "0123456789abcdef") + |> p.repeat(times: 6), ) |> p.ignore, ), #( "ecl", eye_colors - |> list.map(with: p.string_literal) + |> list.map(with: p.literal) |> p.any |> p.ignore, ), #( "pid", p.digit() - |> p.string_of_exactly(length: 9) + |> p.str_of_len(9) |> p.ignore, ), ] diff --git a/aoc-2020-gleam/src/days/day06.gleam b/aoc-2020-gleam/src/days/day06.gleam index 9308166..6c3481c 100644 --- a/aoc-2020-gleam/src/days/day06.gleam +++ b/aoc-2020-gleam/src/days/day06.gleam @@ -22,7 +22,7 @@ fn alphabet() -> Set(String) { fn parse_input(text: String) -> Input { let answers_parser = - p.string1_until_whitespace() + p.str1_until_ws() |> p.map(fn(answer_string) { answer_string |> string.to_graphemes @@ -32,13 +32,13 @@ fn parse_input(text: String) -> Input { let group_parser = answers_parser - |> p.separated1(by: p.whitespace_grapheme()) + |> p.sep1(by: p.ws_gc()) |> p.labeled(with: "group") let input_parser = group_parser - |> p.separated1(by: p.string_literal("\n\n")) - |> p.then_skip(p.optional(p.whitespace_grapheme())) + |> p.sep1(by: p.literal("\n\n")) + |> p.then_skip(p.opt(p.ws_gc())) |> p.labeled(with: "input") text diff --git a/aoc-2020-gleam/src/ext/stringx.gleam b/aoc-2020-gleam/src/ext/stringx.gleam deleted file mode 100644 index 6096465..0000000 --- a/aoc-2020-gleam/src/ext/stringx.gleam +++ /dev/null @@ -1,5 +0,0 @@ -import gleam/string - -pub fn is_not_empty(str: String) -> Bool { - !string.is_empty(str) -} 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) } |