aboutsummaryrefslogtreecommitdiff
path: root/aoc-2020-gleam/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'aoc-2020-gleam/src/util')
-rw-r--r--aoc-2020-gleam/src/util/parser.gleam196
1 files changed, 168 insertions, 28 deletions
diff --git a/aoc-2020-gleam/src/util/parser.gleam b/aoc-2020-gleam/src/util/parser.gleam
index cad8b35..ff8e479 100644
--- a/aoc-2020-gleam/src/util/parser.gleam
+++ b/aoc-2020-gleam/src/util/parser.gleam
@@ -3,14 +3,27 @@ import gleam/list
import gleam/function
import gleam/pair
import gleam/result
-import ext/intx
+import gleam/int
+import gleam/bool
+import gleam/option.{None, Option, Some}
// Heavily inspired by https://fsharpforfunandprofit.com/posts/understanding-parser-combinators/
const eof = "end of input"
+const whitespace_range = " \t\n"
+
+fn q_s(string: String) -> String {
+ "'" <> string <> "'"
+}
+
+fn q_d(string: String) -> String {
+ "\"" <> string <> "\""
+}
+
pub type ParseError {
InvalidInput(expected: String, found: String)
+ InvalidOperation(at: String)
InvalidParser
}
@@ -55,33 +68,75 @@ pub fn parse_entire(
input: String,
with parser: Parser(a),
) -> Result(a, ParseError) {
- let quot = fn(string) { "\"" <> string <> "\"" }
case parse_partial(input, with: parser) {
Ok(#(value, "")) -> Ok(value)
- Ok(#(_, rest)) -> Error(InvalidInput(expected: eof, found: quot(rest)))
+ Ok(#(_, rest)) -> Error(InvalidInput(expected: eof, found: q_d(rest)))
Error(error) -> Error(error)
}
}
-pub fn any_grapheme() -> Parser(String) {
+pub fn grapheme_satisfying(predicate) {
create(fn(input) {
- input
- |> string.pop_grapheme
- |> result.replace_error(InvalidInput("", found: eof))
+ case string.pop_grapheme(input) {
+ Ok(#(value, remaining)) ->
+ case predicate(value) {
+ True -> Ok(#(value, remaining))
+ False -> Error(InvalidInput("", found: q_s(value)))
+ }
+ Error(_) -> Error(InvalidInput("", found: eof))
+ }
})
+}
+
+pub fn any_grapheme() -> Parser(String) {
+ grapheme_satisfying(function.constant(True))
|> labeled(with: "any grapheme")
}
pub fn grapheme_literal(expected: String) -> Parser(String) {
- let quot = fn(grapheme) { "'" <> grapheme <> "'" }
- create(fn(input) {
- case run(any_grapheme(), on: input) {
- Ok(#(value, _)) as ok if value == expected -> ok
- Ok(#(value, _)) -> Error(InvalidInput("", found: quot(value)))
- Error(_) -> Error(InvalidInput("", found: eof))
- }
- })
- |> labeled(with: quot(expected))
+ grapheme_satisfying(fn(g) { g == expected })
+ |> labeled(with: q_s(expected))
+}
+
+pub fn grapheme_in(range allowed: String) -> Parser(String) {
+ grapheme_satisfying(string.contains(allowed, _))
+ |> labeled(with: "grapheme from set " <> q_d(allowed))
+}
+
+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 whitespace_grapheme() -> Parser(String) {
+ grapheme_in(range: whitespace_range)
+ |> labeled(with: "whitespace grapheme")
+}
+
+pub fn whitespaces() -> Parser(String) {
+ string_of_many(of: whitespace_grapheme())
+}
+
+pub fn whitespaces1() -> Parser(String) {
+ string_of_many1(of: whitespace_grapheme())
+}
+
+pub fn nonwhitespace_grapheme() -> Parser(String) {
+ grapheme_not_in(range: whitespace_range)
+ |> labeled(with: "nonwhitespace grapheme")
+}
+
+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 ignore(parser: Parser(a)) -> Parser(Nil) {
+ parser
+ |> map(function.constant(Nil))
}
pub fn then(first: Parser(a), second: Parser(b)) -> Parser(#(a, b)) {
@@ -103,6 +158,12 @@ 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) {
+ first
+ |> then(second)
+ |> map(with: pair.second)
+}
+
pub fn then_third(two: Parser(#(a, b)), third: Parser(c)) -> Parser(#(a, b, c)) {
two
|> then(third)
@@ -121,6 +182,13 @@ pub fn or(first: Parser(a), else second: Parser(a)) -> Parser(a) {
|> labeled(with: "(" <> first.label <> " or " <> second.label <> ")")
}
+pub fn optional(parser: Parser(a)) -> Parser(Option(a)) {
+ parser
+ |> map(with: Some)
+ |> or(else: succeeding(with: None))
+ |> labeled(with: "optional " <> parser.label)
+}
+
pub fn any(of parsers: List(Parser(a))) -> Parser(a) {
parsers
|> list.reduce(with: or)
@@ -135,10 +203,7 @@ pub fn any(of parsers: List(Parser(a))) -> Parser(a) {
}
pub fn digit() -> Parser(String) {
- "0123456789"
- |> string.to_graphemes
- |> list.map(with: grapheme_literal)
- |> any
+ grapheme_in(range: "0123456789")
|> labeled(with: "digit")
}
@@ -200,6 +265,12 @@ pub fn sequence(of parsers: List(Parser(a))) -> Parser(List(a)) {
)
}
+pub fn string_of_sequence(of parsers: List(Parser(String))) -> Parser(String) {
+ parsers
+ |> sequence
+ |> map(with: string.concat)
+}
+
fn do_zero_or_more(input: String, with parser: Parser(a)) -> #(List(a), String) {
case run(parser, on: input) {
Ok(#(value, rest)) -> {
@@ -215,6 +286,12 @@ pub fn many(of parser: Parser(a)) -> Parser(List(a)) {
|> labeled(with: "(at least zero of " <> parser.label <> ")")
}
+pub fn string_of_many(of parser: Parser(String)) -> Parser(String) {
+ parser
+ |> many
+ |> map(with: string.concat)
+}
+
pub fn many1(of parser: Parser(a)) -> Parser(List(a)) {
create(fn(input) {
use parsed <- result.then(run(parser, on: input))
@@ -225,17 +302,49 @@ pub fn many1(of parser: Parser(a)) -> Parser(List(a)) {
|> labeled(with: "(at least one of " <> parser.label <> ")")
}
-pub fn int() -> Parser(Int) {
- digit()
+pub fn string_of_many1(of parser: Parser(String)) -> Parser(String) {
+ parser
|> many1
- |> map(with: function.compose(string.concat, intx.force_parse))
+ |> map(with: string.concat)
+}
+
+pub fn separated(parser: Parser(a), by separator: Parser(b)) -> Parser(List(a)) {
+ parser
+ |> then(many(of: ignore_and_then(separator, parser)))
+ |> map2(with: fn(p, ps) { [p, ..ps] })
+ |> labeled(
+ with: "(at least zero of " <> parser.label <> " separated by " <> separator.label <> ")",
+ )
+}
+
+pub fn separated1(parser: Parser(a), by separator: Parser(b)) -> Parser(List(a)) {
+ parser
+ |> separated(by: separator)
+ |> or(else: succeeding(with: []))
+ |> labeled(
+ with: "(at least one of " <> parser.label <> " separated 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
+
+ int_string
+ |> int.parse
+ |> result.map(with: fn(int) { #(int, remaining) })
+ |> result.replace_error(InvalidOperation(
+ at: "int.parse(" <> int_string <> ")",
+ ))
+ })
|> labeled(with: "int")
}
-pub fn any_string() -> Parser(String) {
+pub fn any_string_greedy() -> Parser(String) {
any_grapheme()
- |> many
- |> map(with: string.concat)
+ |> string_of_many
|> labeled(with: "any string")
}
@@ -243,7 +352,38 @@ pub fn string_literal(expected: String) -> Parser(String) {
expected
|> string.to_graphemes()
|> list.map(with: grapheme_literal)
+ |> string_of_sequence
+ |> labeled(with: q_d(expected))
+}
+
+pub fn string_of_exactly(
+ parser: Parser(String),
+ length length: Int,
+) -> Parser(String) {
+ parser
+ |> list.repeat(times: length)
+ |> string_of_sequence
+ |> labeled(with: "string of length " <> int.to_string(length))
+}
+
+pub fn any_string_of_exactly(length length: Int) -> Parser(String) {
+ string_of_exactly(any_grapheme(), length: length)
+}
+
+pub fn repeated(parser: Parser(a), times times: Int) -> Parser(List(a)) {
+ parser
+ |> list.repeat(times: times)
|> sequence
- |> map(with: string.concat)
- |> labeled(with: "\"" <> expected <> "\"")
+ |> labeled(with: "exactly " <> int.to_string(times) <> " of " <> parser.label)
+}
+
+pub fn matching(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
+ case predicate(value) {
+ True -> Ok(parsed)
+ False -> Error(InvalidOperation(at: string.inspect(predicate)))
+ }
+ })
}