aboutsummaryrefslogtreecommitdiff
path: root/aoc-2020-gleam
diff options
context:
space:
mode:
authorTomasz Chojnacki <tomaszchojnacki2001@gmail.com>2023-02-21 19:46:12 +0100
committerTomasz Chojnacki <tomaszchojnacki2001@gmail.com>2023-02-21 19:46:12 +0100
commit16509ecd4f31ef421a464a98c8e0ea73bb5cb111 (patch)
tree7ac2145ed18cf868abe0b877fecb5988d11fd2ac /aoc-2020-gleam
parent9c94df2676e8b857eac6c94e827086bf0e8cb850 (diff)
downloadgleam_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.gleam10
-rw-r--r--aoc-2020-gleam/src/days/day04.gleam30
-rw-r--r--aoc-2020-gleam/src/days/day06.gleam8
-rw-r--r--aoc-2020-gleam/src/ext/stringx.gleam5
-rw-r--r--aoc-2020-gleam/src/util/input_util.gleam5
-rw-r--r--aoc-2020-gleam/src/util/parser.gleam197
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)
}