Update party and remove internal parsing lib

This commit is contained in:
LilyRose2798 2024-04-10 13:42:41 +10:00
parent 9a3bceff68
commit bde07bb394
4 changed files with 176 additions and 616 deletions

View file

@ -10,7 +10,6 @@ links = [
[dependencies] [dependencies]
gleam_stdlib = "~> 0.34 or ~> 1.0" gleam_stdlib = "~> 0.34 or ~> 1.0"
pears = "~> 0.3"
party = "~> 1.0" party = "~> 1.0"
[dev-dependencies] [dev-dependencies]

View file

@ -3,13 +3,11 @@
packages = [ packages = [
{ name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" }, { name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" },
{ name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" }, { name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
{ name = "party", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "party", source = "hex", outer_checksum = "13D70704E008F1AB163E6E5AED717014473898100B26852E158BD6E9BAE583BC" }, { name = "party", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "party", source = "hex", outer_checksum = "6169240A11DE6793FDFB77E7375E79441B6D7FB3D3EF8274B3C109ABAFF1A8FD" },
{ name = "pears", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "pears", source = "hex", outer_checksum = "F823EDF5C7F8606A0B7764C071B6EE8515FC341D6FC69902E81120633DF7E983" },
] ]
[requirements] [requirements]
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" } gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
gleeunit = { version = "~> 1.0" } gleeunit = { version = "~> 1.0" }
party = { version = "~> 1.0"} party = { version = "~> 1.0" }
pears = { version = "~> 0.3" }

View file

@ -4,21 +4,16 @@ import gleam/string
import gleam/result import gleam/result
import gleam/dict.{type Dict} import gleam/dict.{type Dict}
import gleam/list import gleam/list
import jasper/internal/parsing.{ import party.{
type ParseError, type Parser, Parser, Position, Unexpected, between, char, type ParseError, type Parser, char, choice, digit, do, either, end, go, lazy,
choice, choice_char, concat, digit, do, either, end, go, lazy, left, letter, letter, many1_concat, many_concat, map, perhaps, return, satisfy, sep, string,
many, many1_concat, many_concat, map, pair, perhaps, perhaps_default, until,
perhaps_empty, return, right, satisfy, sep, string, to,
} }
pub type JsonParseError = pub fn to(p: Parser(a, e), v: b) -> Parser(b, e) {
Nil use _ <- do(p)
return(v)
pub type JsonParserError = }
ParseError(JsonParseError)
pub type JsonParser(a) =
Parser(a, JsonParseError)
pub type JsonObject = pub type JsonObject =
Dict(String, JsonValue) Dict(String, JsonValue)
@ -35,8 +30,26 @@ pub type JsonValue {
Null Null
} }
fn ws(options: JsonParseOptions) -> JsonParser(String) { fn multiline_comment() -> Parser(String, e) {
many_concat( use _ <- do(string("/*"))
use comment <- do(until(satisfy(fn(_) { True }), string("*/")))
return(string.concat(comment))
}
fn singleline_comment() -> Parser(String, e) {
use _ <- do(string("//"))
use comment <- do(
many_concat(satisfy(fn(c) { !string.contains("\r\n\u{2028}\u{2029}", c) })),
)
return(comment)
}
fn comment() -> Parser(String, e) {
either(multiline_comment(), singleline_comment())
}
fn ws(options: JsonParseOptions) -> Parser(String, e) {
let ws =
satisfy(fn(c) { satisfy(fn(c) {
string.contains( string.contains(
case options.ecma_whitespace { case options.ecma_whitespace {
@ -46,52 +59,46 @@ fn ws(options: JsonParseOptions) -> JsonParser(String) {
}, },
c, c,
) )
}), })
) many_concat(case options.comments {
True -> either(ws, comment())
False -> ws
})
} }
fn padded(p: JsonParser(a), options: JsonParseOptions) -> JsonParser(a) { fn symbol(s: String, options: JsonParseOptions) -> Parser(String, e) {
left(p, ws(options)) use _ <- do(ws(options))
use c <- do(char(s))
return(c)
} }
fn symbol(s: String, options: JsonParseOptions) -> JsonParser(String) { fn json_null() -> Parser(Nil, e) {
char(s) to(string("null"), Nil)
|> padded(options)
} }
fn json_null_parser() -> JsonParser(Nil) { fn json_boolean() -> Parser(Bool, e) {
string("null")
|> to(Nil)
}
fn json_boolean_parser() -> JsonParser(Bool) {
either(to(string("true"), True), to(string("false"), False)) either(to(string("true"), True), to(string("false"), False))
} }
fn hex_digit_parser() -> JsonParser(String) { fn hex_digit() -> Parser(String, e) {
choice_char([ satisfy(fn(c) { string.contains("0123456789abcdefABCDEF", c) })
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e",
"f", "A", "B", "C", "D", "E", "F",
])
} }
fn json_number_parser(options: JsonParseOptions) -> JsonParser(Float) { fn json_number(options: JsonParseOptions) -> Parser(Float, e) {
let sign_parser = let sign_parser = perhaps(either(char("+"), char("-")))
either(char("+"), char("-"))
|> perhaps_empty
let int_parser = let int_parser =
either( either(char("0"), {
char("0"), use d <- do(satisfy(fn(c) { string.contains("123456789", c) }))
concat( use ds <- do(many_concat(digit()))
choice_char(["1", "2", "3", "4", "5", "6", "7", "8", "9"]), return(d <> ds)
many_concat(digit()), })
),
)
let exp_parser = let exp_parser =
either(char("e"), char("E")) perhaps({
|> concat(sign_parser) use e <- do(either(char("e"), char("E")))
|> concat(many1_concat(digit())) use sign <- do(sign_parser)
|> perhaps_empty use digits <- do(many1_concat(digit()))
return(e <> result.unwrap(sign, "") <> digits)
})
let min_double = -1.7976931348623158e308 let min_double = -1.7976931348623158e308
let max_double = 1.7976931348623158e308 let max_double = 1.7976931348623158e308
@ -99,38 +106,41 @@ fn json_number_parser(options: JsonParseOptions) -> JsonParser(Float) {
True -> { True -> {
use sign <- do(sign_parser) use sign <- do(sign_parser)
let inf = case sign { let inf = case sign {
"-" -> min_double Ok("-") -> min_double
_ -> max_double _ -> max_double
} }
choice([ choice([
to(string("Infinity"), inf), to(string("Infinity"), inf),
to(string("NaN"), 0.0), to(string("NaN"), 0.0),
{ {
use hex <- do( use _ <- do(either(string("0x"), string("0X")))
either(string("0x"), string("0X")) use hex <- do(many1_concat(hex_digit()))
|> right(many1_concat(hex_digit_parser())), let assert Ok(n) = int.base_parse(result.unwrap(sign, "") <> hex, 16)
)
let assert Ok(n) = int.base_parse(sign <> hex, 16)
return(int.to_float(n)) return(int.to_float(n))
}, },
{ {
use n <- do(either( use n <- do(
{ either(
use ns <- do(int_parser) {
use ds <- do( use ns <- do(int_parser)
right( use ds <- do(
char("."), perhaps({
many1_concat(digit()) use _ <- do(char("."))
|> perhaps_default("0"), use ds <- do(perhaps(many1_concat(digit())))
return(result.unwrap(ds, "0"))
}),
) )
|> perhaps_default("0"), return(ns <> "." <> result.unwrap(ds, "0"))
) },
return(ns <> "." <> ds) {
}, use _ <- do(char("."))
concat(to(char("."), "0."), many1_concat(digit())), use ds <- do(many1_concat(digit()))
)) return("0." <> ds)
},
),
)
use ex <- do(exp_parser) use ex <- do(exp_parser)
{ sign <> n <> ex } { result.unwrap(sign, "") <> n <> result.unwrap(ex, "") }
|> float.parse |> float.parse
|> result.unwrap(inf) |> result.unwrap(inf)
|> return |> return
@ -138,21 +148,27 @@ fn json_number_parser(options: JsonParseOptions) -> JsonParser(Float) {
]) ])
} }
False -> { False -> {
use sign <- do( use sign <- do(perhaps(char("-")))
char("-")
|> perhaps_empty,
)
let inf = case sign { let inf = case sign {
"-" -> min_double Ok("-") -> min_double
_ -> max_double _ -> max_double
} }
use ns <- do(int_parser) use ns <- do(int_parser)
use ds <- do( use ds <- do(
right(char("."), many1_concat(digit())) perhaps({
|> perhaps_default("0"), use _ <- do(char("."))
use ds <- do(many1_concat(digit()))
return(ds)
}),
) )
use ex <- do(exp_parser) use ex <- do(exp_parser)
{ sign <> ns <> "." <> ds <> ex } {
result.unwrap(sign, "")
<> ns
<> "."
<> result.unwrap(ds, "0")
<> result.unwrap(ex, "")
}
|> float.parse |> float.parse
|> result.unwrap(inf) |> result.unwrap(inf)
|> return |> return
@ -172,29 +188,27 @@ fn valid_string_char(c: String) -> Bool {
i >= 0x20 && i <= 0x10FFFF i >= 0x20 && i <= 0x10FFFF
} }
fn json_string_parser(options: JsonParseOptions) -> JsonParser(String) { fn json_string(options: JsonParseOptions) -> Parser(String, e) {
let hex_digit = hex_digit_parser() let unicode_escape = {
let unicode_escape = use _ <- do(char("u"))
map( use a <- do(hex_digit())
right( use b <- do(hex_digit())
char("u"), use c <- do(hex_digit())
hex_digit use d <- do(hex_digit())
|> concat(hex_digit) return(string_to_codepoint(a <> b <> c <> d))
|> concat(hex_digit) }
|> concat(hex_digit),
),
string_to_codepoint,
)
let escape = let escape = {
char("\\") use _ <- do(char("\\"))
|> right( use c <- do(
choice(case options.ecma_strings { choice(case options.ecma_strings {
True -> [ True -> [
map( {
right(char("x"), concat(hex_digit, hex_digit)), use _ <- do(char("x"))
string_to_codepoint, use a <- do(hex_digit())
), use b <- do(hex_digit())
return(string_to_codepoint(a <> b))
},
unicode_escape, unicode_escape,
char("\\"), char("\\"),
char("/"), char("/"),
@ -232,12 +246,19 @@ fn json_string_parser(options: JsonParseOptions) -> JsonParser(String) {
] ]
}), }),
) )
return(c)
}
let str = fn(q) { let str = fn(q) {
satisfy(fn(c) { c != q && c != "\\" && valid_string_char(c) }) use _ <- do(char(q))
|> either(escape) use str <- do(
|> many_concat() many_concat(either(
|> between(char(q), char(q)) escape,
satisfy(fn(c) { c != q && c != "\\" && valid_string_char(c) }),
)),
)
use _ <- do(char(q))
return(str)
} }
case options.ecma_strings { case options.ecma_strings {
@ -246,106 +267,67 @@ fn json_string_parser(options: JsonParseOptions) -> JsonParser(String) {
} }
} }
fn parse_multiline_comment() -> JsonParser(String) { fn allow_trailing_comma(p: Parser(a, e), options: JsonParseOptions) {
Parser(fn(source, pos) {
let assert Position(row, col) = pos
case source {
[h, ..t] ->
case h {
"\n" -> Ok(#(h, t, Position(row + 1, 0)))
"*" ->
case t {
["/", ..] -> Error(Unexpected(pos, h))
_ -> Ok(#(h, t, Position(row, col + 1)))
}
_ -> Ok(#(h, t, Position(row, col + 1)))
}
[] -> Error(Unexpected(pos, "EOF"))
}
})
|> many_concat
|> between(string("/*"), string("*/"))
}
fn parse_singleline_comment() -> JsonParser(String) {
right(
string("//"),
many_concat(satisfy(fn(c) { !string.contains("\r\n\u{2028}\u{2029}", c) })),
)
}
fn comment_parser(options: JsonParseOptions) -> JsonParser(String) {
either(parse_multiline_comment(), parse_singleline_comment())
|> padded(options)
}
fn allow_comments(options: JsonParseOptions, p: Parser(_, _)) {
case options.comments {
True -> left(p, many(comment_parser(options)))
False -> p
}
}
fn allow_trailing_comma(options: JsonParseOptions, p: Parser(_, _)) {
case options.trailing_comma { case options.trailing_comma {
True -> right(perhaps(symbol(",", options)), p) True -> {
use _ <- do(perhaps(symbol(",", options)))
use v <- do(p)
return(v)
}
False -> p False -> p
} }
} }
fn json_array_parser(options: JsonParseOptions) -> JsonParser(JsonArray) { fn json_array(options: JsonParseOptions) -> Parser(JsonArray, e) {
let json_value_parser = lazy(fn() { json_value_parser(options) }) use _ <- do(symbol("[", options))
let allow_comments = allow_comments(options, _) use values <- do(sep(lazy(fn() { json_value(options) }), symbol(",", options)))
sep(json_value_parser, allow_comments(symbol(",", options))) use _ <- do(allow_trailing_comma(symbol("]", options), options))
|> between( return(values)
allow_comments(symbol("[", options)),
allow_trailing_comma(options, symbol("]", options)),
)
} }
// todo // todo
fn ecmascript_identifier_parser() -> JsonParser(String) { fn ecmascript_identifier() -> Parser(String, e) {
letter() use c <- do(letter())
|> concat( use cs <- do(many_concat(either(letter(), digit())))
either(letter(), digit()) return(c <> cs)
|> many_concat,
)
} }
fn json_object_parser(options: JsonParseOptions) -> JsonParser(JsonObject) { fn json_object(options: JsonParseOptions) -> Parser(JsonObject, e) {
let json_value_parser = lazy(fn() { json_value_parser(options) }) use _ <- do(symbol("{", options))
let allow_comments = allow_comments(options, _) use key_value_pairs <- do(sep(
let key_parser = case options.ecma_object_keys { {
True -> either(json_string_parser(options), ecmascript_identifier_parser()) use _ <- do(ws(options))
False -> json_string_parser(options) use key <- do(case options.ecma_object_keys {
} True -> either(json_string(options), ecmascript_identifier())
allow_comments(key_parser) False -> json_string(options)
|> left(allow_comments(symbol(":", options))) })
|> pair(json_value_parser) use _ <- do(symbol(":", options))
|> sep(allow_comments(symbol(",", options))) use value <- do(lazy(fn() { json_value(options) }))
|> map(dict.from_list) return(#(key, value))
|> between( },
allow_comments(symbol("{", options)), symbol(",", options),
allow_trailing_comma(options, symbol("}", options)), ))
) use _ <- do(allow_trailing_comma(symbol("}", options), options))
return(dict.from_list(key_value_pairs))
} }
fn json_value_parser(options: JsonParseOptions) -> JsonParser(JsonValue) { fn json_value(options: JsonParseOptions) -> Parser(JsonValue, e) {
use _ <- do(ws(options))
choice([ choice([
to(json_null_parser(), Null), to(json_null(), Null),
map(json_boolean_parser(), Boolean), map(json_boolean(), Boolean),
map(json_number_parser(options), Number), map(json_number(options), Number),
map(json_string_parser(options), String), map(json_string(options), String),
map(json_array_parser(options), Array), map(json_array(options), Array),
map(json_object_parser(options), Object), map(json_object(options), Object),
]) ])
|> padded(options)
|> allow_comments(options, _)
} }
fn json_parser(options: JsonParseOptions) -> JsonParser(JsonValue) { fn json_parser(options: JsonParseOptions) -> Parser(JsonValue, e) {
json_value_parser(options) use value <- do(json_value(options))
|> between(allow_comments(options, ws(options)), end()) use _ <- do(ws(options))
use _ <- do(end())
return(value)
} }
pub type JsonParseOptions { pub type JsonParseOptions {
@ -391,19 +373,19 @@ const jsonl_options = json_options
pub fn parse_json_custom( pub fn parse_json_custom(
value: String, value: String,
options: JsonParseOptions, options: JsonParseOptions,
) -> Result(JsonValue, JsonParserError) { ) -> Result(JsonValue, ParseError(e)) {
go(json_parser(options), value) go(json_parser(options), value)
} }
pub fn parse_json(value: String) -> Result(JsonValue, JsonParserError) { pub fn parse_json(value: String) -> Result(JsonValue, ParseError(e)) {
parse_json_custom(value, json_options) parse_json_custom(value, json_options)
} }
pub fn parse_jsonc(value: String) -> Result(JsonValue, JsonParserError) { pub fn parse_jsonc(value: String) -> Result(JsonValue, ParseError(e)) {
parse_json_custom(value, jsonc_options) parse_json_custom(value, jsonc_options)
} }
pub fn parse_json5(value: String) -> Result(JsonValue, JsonParserError) { pub fn parse_json5(value: String) -> Result(JsonValue, ParseError(e)) {
parse_json_custom(value, json5_options) parse_json_custom(value, json5_options)
} }
@ -415,14 +397,12 @@ fn split_jsonl(value: String) -> List(String) {
|> string.split("\n") |> string.split("\n")
} }
pub fn parse_jsonl(value: String) -> Result(List(JsonValue), JsonParserError) { pub fn parse_jsonl(value: String) -> Result(List(JsonValue), ParseError(e)) {
let parse = go(json_parser(jsonl_options), _) let parse = go(json_parser(jsonl_options), _)
list.try_map(split_jsonl(value), parse) list.try_map(split_jsonl(value), parse)
} }
pub fn parse_jsonl_all( pub fn parse_jsonl_all(value: String) -> List(Result(JsonValue, ParseError(e))) {
value: String,
) -> List(Result(JsonValue, JsonParserError)) {
let parse = go(json_parser(jsonl_options), _) let parse = go(json_parser(jsonl_options), _)
list.map(split_jsonl(value), parse) list.map(split_jsonl(value), parse)
} }

View file

@ -1,417 +0,0 @@
import gleam/string
import gleam/result
import gleam/list
/// The custom error type for the parser,
/// which can itself be parameterized by a user-defined error type.
/// The user-defined error type is useful for, for example,
/// adding a `int.parse` call into your parser pipeline.
/// See `try` for using this feature.
pub type ParseError(e) {
Unexpected(pos: Position, error: String)
UserError(pos: Position, error: e)
}
/// The type for positions within a string.
pub type Position {
Position(row: Int, col: Int)
}
/// The parser type, parameterized by the type it parses and
/// the user-defined error type it can return.
pub type Parser(a, e) {
Parser(
parse: fn(List(String), Position) ->
Result(#(a, List(String), Position), ParseError(e)),
)
}
/// Apply a parser to a list of graphemes (holding on to extra result info that is hidden from the library user).
fn run(
p: Parser(a, e),
src: List(String),
pos: Position,
) -> Result(#(a, List(String), Position), ParseError(e)) {
case p {
Parser(f) -> f(src, pos)
}
}
/// Apply a parser to a string.
pub fn go(p: Parser(a, e), src: String) -> Result(a, ParseError(e)) {
case run(p, string.to_graphemes(src), Position(1, 1)) {
Ok(#(x, _, _)) -> Ok(x)
Error(e) -> Error(e)
}
}
/// Get the current parser position.
pub fn pos() -> Parser(Position, e) {
Parser(fn(source, p) { Ok(#(p, source, p)) })
}
/// Parse a character if it matches the predicate.
pub fn satisfy(when pred: fn(String) -> Bool) -> Parser(String, e) {
Parser(fn(source, pos) {
let assert Position(row, col) = pos
case source {
[h, ..t] ->
case pred(h) {
True ->
case h {
"\n" -> Ok(#(h, t, Position(row + 1, 0)))
_ -> Ok(#(h, t, Position(row, col + 1)))
}
False -> Error(Unexpected(pos, h))
}
[] -> Error(Unexpected(pos, "EOF"))
}
})
}
/// Parse a lowercase letter.
pub fn lowercase_letter() -> Parser(String, e) {
satisfy(when: fn(c) { string.contains("abcdefghijklmnopqrstuvwxyz", c) })
}
/// Parse an uppercase letter.
pub fn uppercase_letter() -> Parser(String, e) {
satisfy(when: fn(c) { string.contains("ABCDEFGHIJKLMNOPQRSTUVWXYZ", c) })
}
/// Parse a lowercase or uppercase letter.
pub fn letter() -> Parser(String, e) {
either(lowercase_letter(), uppercase_letter())
}
/// Parse a specific character.
pub fn char(c) -> Parser(String, e) {
satisfy(when: fn(c2) { c == c2 })
}
/// Parse a digit.
pub fn digit() -> Parser(String, e) {
satisfy(fn(c) { string.contains("0123456789", c) })
}
/// Parse a sequence of digits.
pub fn digits() -> Parser(String, e) {
many1_concat(digit())
}
/// Parse the first parser, or the second if the first fails.
pub fn either(p: Parser(a, e), q: Parser(a, e)) -> Parser(a, e) {
Parser(fn(source, pos) { result.or(run(p, source, pos), run(q, source, pos)) })
}
/// Parse with the first parser in the list that doesn't fail.
pub fn choice(ps: List(Parser(a, e))) -> Parser(a, e) {
Parser(fn(source, pos) {
case ps {
[] -> panic as "choice doesn't accept an empty list of parsers"
// TODO: should this be an Unexpected instead?
[p] -> run(p, source, pos)
[p, ..t] ->
case run(p, source, pos) {
Ok(#(x, r, pos2)) -> Ok(#(x, r, pos2))
Error(_) -> run(choice(t), source, pos)
}
}
})
}
/// Parse an alphanumeric character.
pub fn alphanum() -> Parser(String, e) {
either(digit(), letter())
}
/// Parse zero or more whitespace characters.
pub fn whitespace() -> Parser(String, e) {
many_concat(choice([char(" "), char("\t"), char("\n")]))
}
/// Parse one or more whitespace characters.
pub fn whitespace1() -> Parser(String, e) {
many1_concat(choice([char(" "), char("\t"), char("\n")]))
}
/// Keep trying the parser until it fails, and return the array of parsed results.
/// This cannot fail because it parses zero or more times!
pub fn many(p: Parser(a, e)) -> Parser(List(a), e) {
Parser(fn(source, pos) {
case run(p, source, pos) {
Error(_) -> Ok(#([], source, pos))
Ok(#(x, r, pos2)) ->
result.map(run(many(p), r, pos2), fn(res) {
#([x, ..res.0], res.1, res.2)
})
}
})
}
/// Parse a certain string as many times as possible, returning everything that was parsed.
/// This cannot fail because it parses zero or more times!
pub fn many_concat(p: Parser(String, e)) -> Parser(String, e) {
many(p)
|> map(string.concat)
}
/// Keep trying the parser until it fails, and return the array of parsed results.
/// This can fail, because it must parse successfully at least once!
pub fn many1(p: Parser(a, e)) -> Parser(List(a), e) {
Parser(fn(source, pos) {
case run(p, source, pos) {
Error(e) -> Error(e)
Ok(#(x, r, pos2)) ->
result.map(run(many(p), r, pos2), fn(res) {
#([x, ..res.0], res.1, res.2)
})
}
})
}
/// Parse a certain string as many times as possible, returning everything that was parsed.
/// This can fail, because it must parse successfully at least once!
pub fn many1_concat(p: Parser(String, e)) -> Parser(String, e) {
many1(p)
|> map(string.concat)
}
/// Do the first parser, ignore its result, then do the second parser.
pub fn seq(p: Parser(a, e), q: Parser(b, e)) -> Parser(b, e) {
use _ <- do(p)
q
}
/// Parse a sequence separated by the given separator parser.
pub fn sep(parser: Parser(a, e), by s: Parser(b, e)) -> Parser(List(a), e) {
use mb_a <- do(perhaps(parser))
case mb_a {
Ok(a) -> {
use rest <- do(many(right(s, parser)))
return([a, ..rest])
}
Error(Nil) -> return([])
}
}
/// Parse a sequence separated by the given separator parser.
/// This only succeeds if at least one element of the sequence was parsed.
pub fn sep1(parser: Parser(a, e), by s: Parser(b, e)) -> Parser(List(a), e) {
use sequence <- do(sep(parser, by: s))
case sequence {
[] -> fail()
_ -> return(sequence)
}
}
/// Do `p`, then apply `f` to the result if it succeeded.
pub fn map(p: Parser(a, e), f: fn(a) -> b) -> Parser(b, e) {
Parser(fn(source, pos) {
case run(p, source, pos) {
Ok(#(x, r, pos2)) -> Ok(#(f(x), r, pos2))
Error(e) -> Error(e)
}
})
}
/// Do `p`, the apply `f` to the result if it succeeded.
/// `f` itself can fail with the user-defined error type,
/// and if it does the result is a `UserError` with the error.
pub fn try(p: Parser(a, e), f: fn(a) -> Result(b, e)) -> Parser(b, e) {
Parser(fn(source, pos) {
case run(p, source, pos) {
Ok(#(x, r, pos2)) ->
case f(x) {
Ok(a) -> Ok(#(a, r, pos2))
Error(e) -> Error(UserError(pos2, e))
}
Error(e) -> Error(e)
}
})
}
/// Transform the user-defined error type
/// with a user-provided conversion function.
pub fn error_map(p: Parser(a, e), f: fn(e) -> f) -> Parser(a, f) {
Parser(fn(source, pos) {
case run(p, source, pos) {
Ok(res) -> Ok(res)
Error(e) ->
case e {
UserError(pos, e) -> Error(UserError(pos, f(e)))
Unexpected(pos, s) -> Error(Unexpected(pos, s))
}
}
})
}
/// Try running a parser, but still succeed (with `Error(Nil)`) if it failed.
pub fn perhaps(p: Parser(a, e)) -> Parser(Result(a, Nil), e) {
Parser(fn(source, pos) {
case run(p, source, pos) {
Ok(#(x, r, pos2)) -> Ok(#(Ok(x), r, pos2))
Error(_) -> Ok(#(Error(Nil), source, pos))
}
})
}
/// Do each parser in the list, returning the result of the last parser.
pub fn all(ps: List(Parser(a, e))) -> Parser(a, e) {
case ps {
[p] -> p
[h, ..t] -> {
use _ <- do(h)
all(t)
}
_ -> panic as "all(parsers) doesn't accept an empty list of parsers"
}
// TODO: should this be an Unexpected instead?
}
/// Parse an exact string of characters.
pub fn string(s: String) -> Parser(String, e) {
case string.pop_grapheme(s) {
Ok(#(h, t)) -> {
use c <- do(char(h))
use rest <- do(string(t))
return(c <> rest)
}
Error(_) -> return("")
}
}
/// Negate a parser: if it succeeds, this fails, and vice versa.
/// Example: `seq(string("if"), not(either(alphanum(), char("_"))))`
pub fn not(p: Parser(a, e)) -> Parser(Nil, e) {
Parser(fn(source, pos) {
case run(p, source, pos) {
Ok(_) -> Error(Unexpected(pos, ""))
// todo: better error message here (add a label system)
Error(_) -> Ok(#(Nil, source, pos))
}
})
}
/// Parses successfully only when at the end of the input string.
pub fn end() -> Parser(Nil, e) {
Parser(fn(source, pos) {
case source {
[] -> Ok(#(Nil, source, pos))
[h, ..] -> Error(Unexpected(pos, h))
}
})
}
/// Run a parser as normal, but the parser itself isn't evaluated until it is used.
/// This is needed for recursive grammars, such as `E := n | E + E` where `n` is a number.
/// Example: `lazy(digit)` instead of `digit()`.
pub fn lazy(p: fn() -> Parser(a, e)) -> Parser(a, e) {
Parser(fn(source, pos) { run(p(), source, pos) })
}
/// A monadic bind for pleasant interplay with gleam's `use` syntax.
/// example:
/// ```
/// fn identifier() -> Parser(String, e) {
/// use pos <- do(pos())
/// use first <- do(lowercase_letter())
/// use rest <- do(many(alt(alphanum(), char("_"))))
/// return(Ident(pos, first <> string.concat(rest)))
/// }
/// ```
pub fn do(p: Parser(a, e), f: fn(a) -> Parser(b, e)) -> Parser(b, e) {
Parser(fn(source, pos) {
case run(p, source, pos) {
Ok(#(x, r, pos2)) -> run(f(x), r, pos2)
Error(e) -> Error(e)
}
})
}
/// A monadic return for pleasant interplay with gleam's `use` syntax.
/// see `do` for more details and an example.
/// This is redundant if the last `do` is a `map` instead.
/// But I prefer using it, stylistically.
pub fn return(x) {
Parser(fn(source, pos) { Ok(#(x, source, pos)) })
}
/// Immediately fail regardless of the next input
pub fn fail() -> Parser(a, e) {
Parser(fn(source, pos) {
case source {
[] -> Error(Unexpected(pos, "EOF"))
[h, ..] -> Error(Unexpected(pos, h))
}
})
}
pub fn left(p: Parser(a, e), q: Parser(b, e)) -> Parser(a, e) {
use x <- do(p)
use _ <- do(q)
return(x)
}
pub fn right(p: Parser(a, e), q: Parser(b, e)) -> Parser(b, e) {
use _ <- do(p)
use x <- do(q)
return(x)
}
pub fn pair(p: Parser(a, e), q: Parser(b, e)) -> Parser(#(a, b), e) {
use x <- do(p)
use y <- do(q)
return(#(x, y))
}
pub fn between(
p: Parser(a, e),
q: Parser(b, e),
r: Parser(c, e),
) -> Parser(a, e) {
use _ <- do(q)
use x <- do(p)
use _ <- do(r)
return(x)
}
pub fn concat(p: Parser(String, e), q: Parser(String, e)) -> Parser(String, e) {
use x <- do(p)
use y <- do(q)
return(x <> y)
}
pub fn choice_char(vs: List(String)) -> Parser(String, e) {
vs
|> list.map(char)
|> choice
}
pub fn perhaps_default(p: Parser(a, e), d: a) -> Parser(a, e) {
use x <- do(perhaps(p))
return(result.unwrap(x, d))
}
pub fn perhaps_empty(p: Parser(String, e)) -> Parser(String, e) {
perhaps_default(p, "")
}
pub fn to(p: Parser(a, e), v: b) -> Parser(b, e) {
use _ <- do(p)
return(v)
}
pub fn any() -> Parser(String, e) {
Parser(fn(source, pos) {
let assert Position(row, col) = pos
case source {
[h, ..t] ->
case h {
"\n" -> Ok(#(h, t, Position(row + 1, 0)))
_ -> Ok(#(h, t, Position(row, col + 1)))
}
[] -> Error(Unexpected(pos, "EOF"))
}
})
}