Switch from pears to modified party parser
This commit is contained in:
parent
af699e4b23
commit
3f8af51754
|
@ -11,6 +11,7 @@ links = [
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gleam_stdlib = "~> 0.34 or ~> 1.0"
|
gleam_stdlib = "~> 0.34 or ~> 1.0"
|
||||||
pears = "~> 0.3"
|
pears = "~> 0.3"
|
||||||
|
party = "~> 1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gleeunit = "~> 1.0"
|
gleeunit = "~> 1.0"
|
||||||
|
|
|
@ -4,10 +4,12 @@
|
||||||
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.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" },
|
||||||
|
{ name = "party", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "party", source = "hex", outer_checksum = "13D70704E008F1AB163E6E5AED717014473898100B26852E158BD6E9BAE583BC" },
|
||||||
{ name = "pears", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "pears", source = "hex", outer_checksum = "F823EDF5C7F8606A0B7764C071B6EE8515FC341D6FC69902E81120633DF7E983" },
|
{ 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"}
|
||||||
pears = { version = "~> 0.3" }
|
pears = { version = "~> 0.3" }
|
||||||
|
|
892
src/jasper.gleam
892
src/jasper.gleam
|
@ -2,34 +2,23 @@ import gleam/float
|
||||||
import gleam/int
|
import gleam/int
|
||||||
import gleam/string
|
import gleam/string
|
||||||
import gleam/result
|
import gleam/result
|
||||||
import gleam/option.{None, Some}
|
|
||||||
import gleam/dict.{type Dict}
|
import gleam/dict.{type Dict}
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import pears.{type Parser}
|
import jasper/internal/parsing.{
|
||||||
import pears/chars.{type Char, digit, string}
|
type ParseError, type Parser, Parser, Position, Unexpected, between, char,
|
||||||
import pears/combinators.{
|
choice, choice_char, concat, digit, do, either, end, go, lazy, left, letter,
|
||||||
alt, between, choice, eof, just, lazy, left, many0, many1, map, maybe, none_of,
|
many, many1_concat, many_concat, map, pair, perhaps, perhaps_default,
|
||||||
one_of, pair, recognize, right, sep_by0, seq, to,
|
perhaps_empty, return, right, satisfy, sep, string, to,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ParseError {
|
pub type JsonParseError =
|
||||||
UnexpectedToken(found: Char)
|
Nil
|
||||||
UnexpectedEndOfInput
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_parser(
|
pub type JsonParserError =
|
||||||
input: String,
|
ParseError(JsonParseError)
|
||||||
parser: pears.Parser(Char, a),
|
|
||||||
) -> Result(a, ParseError) {
|
pub type JsonParser(a) =
|
||||||
case parser(chars.input(input)) {
|
Parser(a, JsonParseError)
|
||||||
Ok(pears.Parsed(_, j)) -> Ok(j)
|
|
||||||
Error(e) ->
|
|
||||||
Error(case e {
|
|
||||||
pears.UnexpectedToken(_, _, f) -> UnexpectedToken(f)
|
|
||||||
pears.UnexpectedEndOfInput(_, _) -> UnexpectedEndOfInput
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type JsonObject =
|
pub type JsonObject =
|
||||||
Dict(String, JsonValue)
|
Dict(String, JsonValue)
|
||||||
|
@ -46,129 +35,417 @@ pub type JsonValue {
|
||||||
Null
|
Null
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ws0() -> Parser(Char, List(Char)) {
|
fn ws(options: JsonParseOptions) -> JsonParser(String) {
|
||||||
one_of([" ", "\n", "\r", "\t"])
|
many_concat(
|
||||||
|> many0()
|
satisfy(fn(c) {
|
||||||
|
string.contains(
|
||||||
|
case options.ecma_whitespace {
|
||||||
|
True ->
|
||||||
|
" \n\r\t\u{000B}\u{000C}\u{00A0}\u{2028}\u{2029}\u{FEFF}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{202F}\u{205F}\u{3000}"
|
||||||
|
False -> " \n\r\t"
|
||||||
|
},
|
||||||
|
c,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn padded(p: Parser(_, a)) {
|
fn padded(p: JsonParser(a), options: JsonParseOptions) -> JsonParser(a) {
|
||||||
left(p, ws0())
|
left(p, ws(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn symbol(s: String) {
|
fn symbol(s: String, options: JsonParseOptions) -> JsonParser(String) {
|
||||||
padded(string(s))
|
char(s)
|
||||||
|
|> padded(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_parser() -> Parser(Char, JsonValue) {
|
fn json_null_parser() -> JsonParser(Nil) {
|
||||||
let hex_digit =
|
string("null")
|
||||||
one_of([
|
|> to(Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_boolean_parser() -> JsonParser(Bool) {
|
||||||
|
either(to(string("true"), True), to(string("false"), False))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hex_digit_parser() -> JsonParser(String) {
|
||||||
|
choice_char([
|
||||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e",
|
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e",
|
||||||
"f", "A", "B", "C", "D", "E", "F",
|
"f", "A", "B", "C", "D", "E", "F",
|
||||||
])
|
])
|
||||||
|
}
|
||||||
|
|
||||||
let unicode_escape_digits =
|
fn json_number_parser(options: JsonParseOptions) -> JsonParser(Float) {
|
||||||
recognize(seq([hex_digit, hex_digit, hex_digit, hex_digit]))
|
let sign_parser =
|
||||||
|
either(char("+"), char("-"))
|
||||||
|
|> perhaps_empty
|
||||||
|
let int_parser =
|
||||||
|
either(
|
||||||
|
char("0"),
|
||||||
|
concat(
|
||||||
|
choice_char(["1", "2", "3", "4", "5", "6", "7", "8", "9"]),
|
||||||
|
many_concat(digit()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
let exp_parser =
|
||||||
|
either(char("e"), char("E"))
|
||||||
|
|> concat(sign_parser)
|
||||||
|
|> concat(many1_concat(digit()))
|
||||||
|
|> perhaps_empty
|
||||||
|
let min_double = -1.7976931348623158e308
|
||||||
|
let max_double = 1.7976931348623158e308
|
||||||
|
|
||||||
let escape =
|
case options.ecma_numbers {
|
||||||
just("\\")
|
True -> {
|
||||||
|> right(
|
use sign <- do(sign_parser)
|
||||||
|
let inf = case sign {
|
||||||
|
"-" -> min_double
|
||||||
|
_ -> max_double
|
||||||
|
}
|
||||||
choice([
|
choice([
|
||||||
just("\\"),
|
to(string("Infinity"), inf),
|
||||||
just("/"),
|
to(string("NaN"), 0.0),
|
||||||
just("\""),
|
{
|
||||||
to(just("b"), "\u{0008}"),
|
use hex <- do(
|
||||||
to(just("f"), "\u{000C}"),
|
either(string("0x"), string("0X"))
|
||||||
to(just("n"), "\n"),
|
|> right(many1_concat(hex_digit_parser())),
|
||||||
to(just("r"), "\r"),
|
)
|
||||||
to(just("t"), "\t"),
|
let assert Ok(n) = int.base_parse(sign <> hex, 16)
|
||||||
map(right(just("u"), unicode_escape_digits), fn(value) {
|
return(int.to_float(n))
|
||||||
let assert Ok(number) = int.base_parse(string.concat(value), 16)
|
},
|
||||||
|
{
|
||||||
|
use n <- do(either(
|
||||||
|
{
|
||||||
|
use ns <- do(int_parser)
|
||||||
|
use ds <- do(
|
||||||
|
right(
|
||||||
|
char("."),
|
||||||
|
many1_concat(digit())
|
||||||
|
|> perhaps_default("0"),
|
||||||
|
)
|
||||||
|
|> perhaps_default("0"),
|
||||||
|
)
|
||||||
|
return(ns <> "." <> ds)
|
||||||
|
},
|
||||||
|
concat(to(char("."), "0."), many1_concat(digit())),
|
||||||
|
))
|
||||||
|
use ex <- do(exp_parser)
|
||||||
|
{ sign <> n <> ex }
|
||||||
|
|> float.parse
|
||||||
|
|> result.unwrap(inf)
|
||||||
|
|> return
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
False -> {
|
||||||
|
use sign <- do(
|
||||||
|
char("-")
|
||||||
|
|> perhaps_empty,
|
||||||
|
)
|
||||||
|
let inf = case sign {
|
||||||
|
"-" -> min_double
|
||||||
|
_ -> max_double
|
||||||
|
}
|
||||||
|
use ns <- do(int_parser)
|
||||||
|
use ds <- do(
|
||||||
|
right(char("."), many1_concat(digit()))
|
||||||
|
|> perhaps_default("0"),
|
||||||
|
)
|
||||||
|
use ex <- do(exp_parser)
|
||||||
|
{ sign <> ns <> "." <> ds <> ex }
|
||||||
|
|> float.parse
|
||||||
|
|> result.unwrap(inf)
|
||||||
|
|> return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_to_codepoint(value: String) -> String {
|
||||||
|
let assert Ok(number) = int.base_parse(value, 16)
|
||||||
let assert Ok(codepoint) = string.utf_codepoint(number)
|
let assert Ok(codepoint) = string.utf_codepoint(number)
|
||||||
string.from_utf_codepoints([codepoint])
|
string.from_utf_codepoints([codepoint])
|
||||||
}),
|
}
|
||||||
|
|
||||||
|
fn valid_string_char(c: String) -> Bool {
|
||||||
|
let assert [p] = string.to_utf_codepoints(c)
|
||||||
|
let i = string.utf_codepoint_to_int(p)
|
||||||
|
i >= 0x20 && i <= 0x10FFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_string_parser(options: JsonParseOptions) -> JsonParser(String) {
|
||||||
|
let hex_digit = hex_digit_parser()
|
||||||
|
let unicode_escape =
|
||||||
|
map(
|
||||||
|
right(
|
||||||
|
char("u"),
|
||||||
|
hex_digit
|
||||||
|
|> concat(hex_digit)
|
||||||
|
|> concat(hex_digit)
|
||||||
|
|> concat(hex_digit),
|
||||||
|
),
|
||||||
|
string_to_codepoint,
|
||||||
|
)
|
||||||
|
|
||||||
|
let escape =
|
||||||
|
char("\\")
|
||||||
|
|> right(
|
||||||
|
choice(case options.ecma_strings {
|
||||||
|
True -> [
|
||||||
|
map(
|
||||||
|
right(char("x"), concat(hex_digit, hex_digit)),
|
||||||
|
string_to_codepoint,
|
||||||
|
),
|
||||||
|
unicode_escape,
|
||||||
|
char("\\"),
|
||||||
|
char("/"),
|
||||||
|
char("'"),
|
||||||
|
char("\""),
|
||||||
|
to(char("b"), "\u{0008}"),
|
||||||
|
to(char("f"), "\u{000C}"),
|
||||||
|
to(char("n"), "\n"),
|
||||||
|
to(char("r"), "\r"),
|
||||||
|
to(char("t"), "\t"),
|
||||||
|
to(char("v"), "\u{000B}"),
|
||||||
|
to(char("0"), "\u{0000}"),
|
||||||
|
to(
|
||||||
|
choice([
|
||||||
|
string("\r\n"),
|
||||||
|
char("\r"),
|
||||||
|
char("\n"),
|
||||||
|
char("\u{2028}"),
|
||||||
|
char("\u{2029}"),
|
||||||
]),
|
]),
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
satisfy(fn(c) { !string.contains("123456789", c) }),
|
||||||
|
]
|
||||||
|
False -> [
|
||||||
|
unicode_escape,
|
||||||
|
char("\\"),
|
||||||
|
char("/"),
|
||||||
|
char("\""),
|
||||||
|
to(char("b"), "\u{0008}"),
|
||||||
|
to(char("f"), "\u{000C}"),
|
||||||
|
to(char("n"), "\n"),
|
||||||
|
to(char("r"), "\r"),
|
||||||
|
to(char("t"), "\t"),
|
||||||
|
]
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
let str =
|
let str = fn(q) {
|
||||||
none_of(["\"", "\\"])
|
satisfy(fn(c) { c != q && c != "\\" && valid_string_char(c) })
|
||||||
|> alt(escape)
|
|> either(escape)
|
||||||
|> many0()
|
|> many_concat()
|
||||||
|> map(string.concat)
|
|> between(char(q), char(q))
|
||||||
|> between(just("\""), just("\""))
|
|
||||||
|
|
||||||
let value = lazy(value_parser)
|
|
||||||
|
|
||||||
let num =
|
|
||||||
maybe(just("-"))
|
|
||||||
|> pair(
|
|
||||||
alt(
|
|
||||||
to(just("0"), ["0"]),
|
|
||||||
recognize(pair(
|
|
||||||
one_of(["1", "2", "3", "4", "5", "6", "7", "8", "9"]),
|
|
||||||
many0(digit()),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
|> map(string.concat),
|
|
||||||
)
|
|
||||||
|> pair(maybe(
|
|
||||||
just(".")
|
|
||||||
|> right(many1(digit()))
|
|
||||||
|> map(string.concat),
|
|
||||||
))
|
|
||||||
|> pair(
|
|
||||||
recognize(maybe(
|
|
||||||
alt(just("e"), just("E"))
|
|
||||||
|> pair(maybe(one_of(["+", "-"])))
|
|
||||||
|> pair(many1(digit())),
|
|
||||||
))
|
|
||||||
|> map(string.concat),
|
|
||||||
)
|
|
||||||
|> map(fn(p) {
|
|
||||||
case p {
|
|
||||||
#(#(#(neg, ns), ds), ex) -> {
|
|
||||||
{
|
|
||||||
option.unwrap(neg, "") <> ns <> "." <> option.unwrap(ds, "0") <> ex
|
|
||||||
}
|
}
|
||||||
|> float.parse
|
|
||||||
|> result.unwrap(case neg {
|
case options.ecma_strings {
|
||||||
Some(_) -> -1.7976931348623158e308
|
True -> either(str("\""), str("'"))
|
||||||
None -> 1.7976931348623158e308
|
False -> str("\"")
|
||||||
})
|
|
||||||
|> Number
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_multiline_comment() -> JsonParser(String) {
|
||||||
|
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("*/"))
|
||||||
|
}
|
||||||
|
|
||||||
let bool =
|
fn parse_singleline_comment() -> JsonParser(String) {
|
||||||
alt(to(string("true"), Boolean(True)), to(string("false"), Boolean(False)))
|
right(
|
||||||
|
string("//"),
|
||||||
|
many_concat(satisfy(fn(c) { !string.contains("\r\n\u{2028}\u{2029}", c) })),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let null = to(string("null"), Null)
|
fn comment_parser(options: JsonParseOptions) -> JsonParser(String) {
|
||||||
|
either(parse_multiline_comment(), parse_singleline_comment())
|
||||||
|
|> padded(options)
|
||||||
|
}
|
||||||
|
|
||||||
let array =
|
fn allow_comments(options: JsonParseOptions, p: Parser(_, _)) {
|
||||||
sep_by0(value, symbol(","))
|
case options.comments {
|
||||||
|> between(symbol("["), symbol("]"))
|
True -> left(p, many(comment_parser(options)))
|
||||||
|> map(Array)
|
False -> p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let obj =
|
fn allow_trailing_comma(options: JsonParseOptions, p: Parser(_, _)) {
|
||||||
str
|
case options.trailing_comma {
|
||||||
|> left(symbol(":"))
|
True -> right(perhaps(symbol(",", options)), p)
|
||||||
|> pair(value)
|
False -> p
|
||||||
|> sep_by0(symbol(","))
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_array_parser(options: JsonParseOptions) -> JsonParser(JsonArray) {
|
||||||
|
let json_value_parser = lazy(fn() { json_value_parser(options) })
|
||||||
|
let allow_comments = allow_comments(options, _)
|
||||||
|
sep(json_value_parser, allow_comments(symbol(",", options)))
|
||||||
|
|> between(
|
||||||
|
allow_comments(symbol("[", options)),
|
||||||
|
allow_trailing_comma(options, symbol("]", options)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo
|
||||||
|
fn ecmascript_identifier_parser() -> JsonParser(String) {
|
||||||
|
letter()
|
||||||
|
|> concat(
|
||||||
|
either(letter(), digit())
|
||||||
|
|> many_concat,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_object_parser(options: JsonParseOptions) -> JsonParser(JsonObject) {
|
||||||
|
let json_value_parser = lazy(fn() { json_value_parser(options) })
|
||||||
|
let allow_comments = allow_comments(options, _)
|
||||||
|
let key_parser = case options.ecma_object_keys {
|
||||||
|
True -> either(json_string_parser(options), ecmascript_identifier_parser())
|
||||||
|
False -> json_string_parser(options)
|
||||||
|
}
|
||||||
|
allow_comments(key_parser)
|
||||||
|
|> left(allow_comments(symbol(":", options)))
|
||||||
|
|> pair(json_value_parser)
|
||||||
|
|> sep(allow_comments(symbol(",", options)))
|
||||||
|> map(dict.from_list)
|
|> map(dict.from_list)
|
||||||
|> between(symbol("{"), symbol("}"))
|
|> between(
|
||||||
|> map(Object)
|
allow_comments(symbol("{", options)),
|
||||||
|
allow_trailing_comma(options, symbol("}", options)),
|
||||||
choice([num, bool, null, map(str, String), array, obj])
|
)
|
||||||
|> padded()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_parser() -> Parser(Char, JsonValue) {
|
fn json_value_parser(options: JsonParseOptions) -> JsonParser(JsonValue) {
|
||||||
value_parser()
|
choice([
|
||||||
|> between(ws0(), eof())
|
to(json_null_parser(), Null),
|
||||||
|
map(json_boolean_parser(), Boolean),
|
||||||
|
map(json_number_parser(options), Number),
|
||||||
|
map(json_string_parser(options), String),
|
||||||
|
map(json_array_parser(options), Array),
|
||||||
|
map(json_object_parser(options), Object),
|
||||||
|
])
|
||||||
|
|> padded(options)
|
||||||
|
|> allow_comments(options, _)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_json(value: String) -> Result(JsonValue, ParseError) {
|
fn json_parser(options: JsonParseOptions) -> JsonParser(JsonValue) {
|
||||||
run_parser(value, json_parser())
|
json_value_parser(options)
|
||||||
|
|> between(allow_comments(options, ws(options)), end())
|
||||||
|
}
|
||||||
|
|
||||||
|
import gleam/io
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
// io.debug(parse_jsonc(
|
||||||
|
// " /* asdf */ [1 , // asdf \n 2/* asdf */, { \"k\"/* asdf */ :// asdf\n 3}]",
|
||||||
|
// ))
|
||||||
|
// io.debug(parse_jsonc(
|
||||||
|
// "{
|
||||||
|
// \"foo\":/* * / */ \"121234\",
|
||||||
|
// \"foo\":/* *//* /*** */ \"121234\"
|
||||||
|
// } // asdf",
|
||||||
|
// ))
|
||||||
|
io.debug(parse_json5("{bar: -0x14,\"foo\": \"a\\x23\\\r\nsd\\Af\",}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type JsonParseOptions {
|
||||||
|
JsonParseOptions(
|
||||||
|
comments: Bool,
|
||||||
|
trailing_comma: Bool,
|
||||||
|
ecma_object_keys: Bool,
|
||||||
|
ecma_strings: Bool,
|
||||||
|
ecma_numbers: Bool,
|
||||||
|
ecma_whitespace: Bool,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const json_options = JsonParseOptions(
|
||||||
|
comments: False,
|
||||||
|
trailing_comma: False,
|
||||||
|
ecma_object_keys: False,
|
||||||
|
ecma_strings: False,
|
||||||
|
ecma_numbers: False,
|
||||||
|
ecma_whitespace: False,
|
||||||
|
)
|
||||||
|
|
||||||
|
const jsonc_options = JsonParseOptions(
|
||||||
|
comments: True,
|
||||||
|
trailing_comma: False,
|
||||||
|
ecma_object_keys: False,
|
||||||
|
ecma_strings: False,
|
||||||
|
ecma_numbers: False,
|
||||||
|
ecma_whitespace: False,
|
||||||
|
)
|
||||||
|
|
||||||
|
const json5_options = JsonParseOptions(
|
||||||
|
comments: True,
|
||||||
|
trailing_comma: True,
|
||||||
|
ecma_object_keys: True,
|
||||||
|
ecma_strings: True,
|
||||||
|
ecma_numbers: True,
|
||||||
|
ecma_whitespace: True,
|
||||||
|
)
|
||||||
|
|
||||||
|
const jsonl_options = json_options
|
||||||
|
|
||||||
|
pub fn parse_json_custom(
|
||||||
|
value: String,
|
||||||
|
options: JsonParseOptions,
|
||||||
|
) -> Result(JsonValue, JsonParserError) {
|
||||||
|
go(json_parser(options), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_json(value: String) -> Result(JsonValue, JsonParserError) {
|
||||||
|
parse_json_custom(value, json_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_jsonc(value: String) -> Result(JsonValue, JsonParserError) {
|
||||||
|
parse_json_custom(value, jsonc_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_json5(value: String) -> Result(JsonValue, JsonParserError) {
|
||||||
|
parse_json_custom(value, json5_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_jsonl(value: String) -> List(String) {
|
||||||
|
case string.last(value) {
|
||||||
|
Ok("\n") -> string.drop_right(value, 1)
|
||||||
|
_ -> value
|
||||||
|
}
|
||||||
|
|> string.split("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_jsonl(value: String) -> Result(List(JsonValue), JsonParserError) {
|
||||||
|
let parse = go(json_parser(jsonl_options), _)
|
||||||
|
list.try_map(split_jsonl(value), parse)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_jsonl_all(
|
||||||
|
value: String,
|
||||||
|
) -> List(Result(JsonValue, JsonParserError)) {
|
||||||
|
let parse = go(json_parser(jsonl_options), _)
|
||||||
|
list.map(split_jsonl(value), parse)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_jsonl_valid(value: String) -> List(JsonValue) {
|
||||||
|
value
|
||||||
|
|> parse_jsonl_all
|
||||||
|
|> result.values
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stringify_json_spaced_rec(
|
fn stringify_json_spaced_rec(
|
||||||
|
@ -269,213 +546,188 @@ pub fn stringify_jsonl(values: List(JsonValue)) -> String {
|
||||||
|> list.map(stringify_json)
|
|> list.map(stringify_json)
|
||||||
|> string.join("\n")
|
|> string.join("\n")
|
||||||
}
|
}
|
||||||
|
// pub type JsonQuery {
|
||||||
|
// Root
|
||||||
|
// Key(query: JsonQuery, key: String)
|
||||||
|
// KeyOr(query: JsonQuery, key: String, or: JsonValue)
|
||||||
|
// Index(query: JsonQuery, index: Int)
|
||||||
|
// IndexOr(query: JsonQuery, index: Int, or: JsonValue)
|
||||||
|
// Filter(query: JsonQuery, predicate: fn(JsonValue) -> Bool)
|
||||||
|
// Map(query: JsonQuery, mapping: fn(JsonValue) -> JsonValue)
|
||||||
|
// MapKeys(query: JsonQuery, mapping: fn(String) -> String)
|
||||||
|
// MapValues(query: JsonQuery, mapping: fn(String, JsonValue) -> JsonValue)
|
||||||
|
// FilterMap(query: JsonQuery, mapping: fn(JsonValue) -> Result(JsonValue, Nil))
|
||||||
|
// ForEach(query: JsonQuery)
|
||||||
|
// ForEachOk(query: JsonQuery)
|
||||||
|
// }
|
||||||
|
|
||||||
fn split_jsonl(value: String) -> List(String) {
|
// type InvJsonQuery {
|
||||||
case string.last(value) {
|
// InvEnd
|
||||||
Ok("\n") -> string.drop_right(value, 1)
|
// InvKey(key: String, query: InvJsonQuery)
|
||||||
_ -> value
|
// InvKeyOr(key: String, or: JsonValue, query: InvJsonQuery)
|
||||||
}
|
// InvIndex(index: Int, query: InvJsonQuery)
|
||||||
|> string.split("\n")
|
// InvIndexOr(index: Int, or: JsonValue, query: InvJsonQuery)
|
||||||
}
|
// InvFilter(predicate: fn(JsonValue) -> Bool, query: InvJsonQuery)
|
||||||
|
// InvMap(mapping: fn(JsonValue) -> JsonValue, query: InvJsonQuery)
|
||||||
|
// InvMapKeys(mapping: fn(String) -> String, query: InvJsonQuery)
|
||||||
|
// InvMapValues(mapping: fn(String, JsonValue) -> JsonValue, query: InvJsonQuery)
|
||||||
|
// InvFilterMap(
|
||||||
|
// mapping: fn(JsonValue) -> Result(JsonValue, Nil),
|
||||||
|
// query: InvJsonQuery,
|
||||||
|
// )
|
||||||
|
// InvForEach(query: InvJsonQuery)
|
||||||
|
// InvForEachOk(query: InvJsonQuery)
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn parse_jsonl(value: String) -> Result(List(JsonValue), ParseError) {
|
// fn invert_query_rec(query: JsonQuery, state: InvJsonQuery) -> InvJsonQuery {
|
||||||
let parse = run_parser(_, json_parser())
|
// case query {
|
||||||
list.try_map(split_jsonl(value), parse)
|
// Root -> state
|
||||||
}
|
// Key(query, key) -> invert_query_rec(query, InvKey(key, state))
|
||||||
|
// KeyOr(query, key, o) -> invert_query_rec(query, InvKeyOr(key, o, state))
|
||||||
|
// Index(query, index) -> invert_query_rec(query, InvIndex(index, state))
|
||||||
|
// IndexOr(query, index, or) ->
|
||||||
|
// invert_query_rec(query, InvIndexOr(index, or, state))
|
||||||
|
// Filter(query, predicate) ->
|
||||||
|
// invert_query_rec(query, InvFilter(predicate, state))
|
||||||
|
// Map(query, mapping) -> invert_query_rec(query, InvMap(mapping, state))
|
||||||
|
// MapKeys(query, mapping) ->
|
||||||
|
// invert_query_rec(query, InvMapKeys(mapping, state))
|
||||||
|
// MapValues(query, mapping) ->
|
||||||
|
// invert_query_rec(query, InvMapValues(mapping, state))
|
||||||
|
// FilterMap(query, mapping) ->
|
||||||
|
// invert_query_rec(query, InvFilterMap(mapping, state))
|
||||||
|
// ForEach(query) -> invert_query_rec(query, InvForEach(state))
|
||||||
|
// ForEachOk(query) -> invert_query_rec(query, InvForEachOk(state))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn parse_jsonl_all(value: String) -> List(Result(JsonValue, ParseError)) {
|
// fn invert_query(query: JsonQuery) -> InvJsonQuery {
|
||||||
let parse = run_parser(_, json_parser())
|
// invert_query_rec(query, InvEnd)
|
||||||
list.map(split_jsonl(value), parse)
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_jsonl_valid(value: String) -> List(JsonValue) {
|
// pub type JsonQueryError {
|
||||||
value
|
// UnexpectedType(JsonValue)
|
||||||
|> parse_jsonl_all
|
// MissingObjectKey(JsonValue, key: String)
|
||||||
|> result.values
|
// IndexOutOfBounds(JsonValue, index: Int)
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub type JsonQuery {
|
// fn query_json_rec(
|
||||||
Root
|
// json: JsonValue,
|
||||||
Key(query: JsonQuery, key: String)
|
// query: InvJsonQuery,
|
||||||
KeyOr(query: JsonQuery, key: String, or: JsonValue)
|
// ) -> Result(JsonValue, JsonQueryError) {
|
||||||
Index(query: JsonQuery, index: Int)
|
// case query {
|
||||||
IndexOr(query: JsonQuery, index: Int, or: JsonValue)
|
// InvEnd -> Ok(json)
|
||||||
Filter(query: JsonQuery, predicate: fn(JsonValue) -> Bool)
|
// InvKey(key, q) ->
|
||||||
Map(query: JsonQuery, mapping: fn(JsonValue) -> JsonValue)
|
// case json {
|
||||||
MapKeys(query: JsonQuery, mapping: fn(String) -> String)
|
// Object(obj) as j ->
|
||||||
MapValues(query: JsonQuery, mapping: fn(String, JsonValue) -> JsonValue)
|
// obj
|
||||||
FilterMap(query: JsonQuery, mapping: fn(JsonValue) -> Result(JsonValue, Nil))
|
// |> dict.get(key)
|
||||||
ForEach(query: JsonQuery)
|
// |> result.replace_error(MissingObjectKey(j, key))
|
||||||
ForEachOk(query: JsonQuery)
|
// j -> Error(UnexpectedType(j))
|
||||||
}
|
// }
|
||||||
|
// |> result.map(query_json_rec(_, q))
|
||||||
|
// |> result.flatten
|
||||||
|
// InvKeyOr(key, or, q) ->
|
||||||
|
// case json {
|
||||||
|
// Object(obj) ->
|
||||||
|
// obj
|
||||||
|
// |> dict.get(key)
|
||||||
|
// |> result.unwrap(or)
|
||||||
|
// |> Ok
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// |> result.map(query_json_rec(_, q))
|
||||||
|
// |> result.flatten
|
||||||
|
// InvIndex(index, q) ->
|
||||||
|
// case json {
|
||||||
|
// Array(arr) as j ->
|
||||||
|
// arr
|
||||||
|
// |> list.at(index)
|
||||||
|
// |> result.replace_error(IndexOutOfBounds(j, index))
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// |> result.map(query_json_rec(_, q))
|
||||||
|
// |> result.flatten
|
||||||
|
// InvIndexOr(index, or, q) ->
|
||||||
|
// case json {
|
||||||
|
// Array(arr) ->
|
||||||
|
// arr
|
||||||
|
// |> list.at(index)
|
||||||
|
// |> result.unwrap(or)
|
||||||
|
// |> Ok
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// |> result.map(query_json_rec(_, q))
|
||||||
|
// |> result.flatten
|
||||||
|
// InvFilter(predicate, q) ->
|
||||||
|
// case json {
|
||||||
|
// Array(arr) ->
|
||||||
|
// arr
|
||||||
|
// |> list.filter(predicate)
|
||||||
|
// |> Array
|
||||||
|
// |> query_json_rec(q)
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// InvMap(mapping, q) ->
|
||||||
|
// case json {
|
||||||
|
// Array(arr) ->
|
||||||
|
// arr
|
||||||
|
// |> list.map(mapping)
|
||||||
|
// |> Array
|
||||||
|
// |> query_json_rec(q)
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// InvMapKeys(mapping, q) ->
|
||||||
|
// case json {
|
||||||
|
// Object(obj) ->
|
||||||
|
// obj
|
||||||
|
// |> dict.to_list
|
||||||
|
// |> list.map(fn(kv) { #(mapping(kv.0), kv.1) })
|
||||||
|
// |> dict.from_list
|
||||||
|
// |> Object
|
||||||
|
// |> query_json_rec(q)
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// InvMapValues(mapping, q) ->
|
||||||
|
// case json {
|
||||||
|
// Object(obj) ->
|
||||||
|
// obj
|
||||||
|
// |> dict.map_values(mapping)
|
||||||
|
// |> Object
|
||||||
|
// |> query_json_rec(q)
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// InvFilterMap(mapping, q) ->
|
||||||
|
// case json {
|
||||||
|
// Array(arr) ->
|
||||||
|
// arr
|
||||||
|
// |> list.filter_map(mapping)
|
||||||
|
// |> Array
|
||||||
|
// |> query_json_rec(q)
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// InvForEach(q) ->
|
||||||
|
// case json {
|
||||||
|
// Array(arr) ->
|
||||||
|
// arr
|
||||||
|
// |> list.map(query_json_rec(_, q))
|
||||||
|
// |> result.all
|
||||||
|
// |> result.map(Array)
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// InvForEachOk(q) ->
|
||||||
|
// case json {
|
||||||
|
// Array(arr) ->
|
||||||
|
// arr
|
||||||
|
// |> list.map(query_json_rec(_, q))
|
||||||
|
// |> result.values
|
||||||
|
// |> Array
|
||||||
|
// |> Ok
|
||||||
|
// j -> Error(UnexpectedType(j))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
type InvJsonQuery {
|
// pub fn query_json(json: JsonValue, query: JsonQuery) {
|
||||||
InvEnd
|
// query_json_rec(json, invert_query(query))
|
||||||
InvKey(key: String, query: InvJsonQuery)
|
// }
|
||||||
InvKeyOr(key: String, or: JsonValue, query: InvJsonQuery)
|
|
||||||
InvIndex(index: Int, query: InvJsonQuery)
|
|
||||||
InvIndexOr(index: Int, or: JsonValue, query: InvJsonQuery)
|
|
||||||
InvFilter(predicate: fn(JsonValue) -> Bool, query: InvJsonQuery)
|
|
||||||
InvMap(mapping: fn(JsonValue) -> JsonValue, query: InvJsonQuery)
|
|
||||||
InvMapKeys(mapping: fn(String) -> String, query: InvJsonQuery)
|
|
||||||
InvMapValues(mapping: fn(String, JsonValue) -> JsonValue, query: InvJsonQuery)
|
|
||||||
InvFilterMap(
|
|
||||||
mapping: fn(JsonValue) -> Result(JsonValue, Nil),
|
|
||||||
query: InvJsonQuery,
|
|
||||||
)
|
|
||||||
InvForEach(query: InvJsonQuery)
|
|
||||||
InvForEachOk(query: InvJsonQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invert_query_rec(query: JsonQuery, state: InvJsonQuery) -> InvJsonQuery {
|
|
||||||
case query {
|
|
||||||
Root -> state
|
|
||||||
Key(query, key) -> invert_query_rec(query, InvKey(key, state))
|
|
||||||
KeyOr(query, key, o) -> invert_query_rec(query, InvKeyOr(key, o, state))
|
|
||||||
Index(query, index) -> invert_query_rec(query, InvIndex(index, state))
|
|
||||||
IndexOr(query, index, or) ->
|
|
||||||
invert_query_rec(query, InvIndexOr(index, or, state))
|
|
||||||
Filter(query, predicate) ->
|
|
||||||
invert_query_rec(query, InvFilter(predicate, state))
|
|
||||||
Map(query, mapping) -> invert_query_rec(query, InvMap(mapping, state))
|
|
||||||
MapKeys(query, mapping) ->
|
|
||||||
invert_query_rec(query, InvMapKeys(mapping, state))
|
|
||||||
MapValues(query, mapping) ->
|
|
||||||
invert_query_rec(query, InvMapValues(mapping, state))
|
|
||||||
FilterMap(query, mapping) ->
|
|
||||||
invert_query_rec(query, InvFilterMap(mapping, state))
|
|
||||||
ForEach(query) -> invert_query_rec(query, InvForEach(state))
|
|
||||||
ForEachOk(query) -> invert_query_rec(query, InvForEachOk(state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invert_query(query: JsonQuery) -> InvJsonQuery {
|
|
||||||
invert_query_rec(query, InvEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type JsonQueryError {
|
|
||||||
UnexpectedType(JsonValue)
|
|
||||||
MissingObjectKey(JsonValue, key: String)
|
|
||||||
IndexOutOfBounds(JsonValue, index: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_json_rec(
|
|
||||||
json: JsonValue,
|
|
||||||
query: InvJsonQuery,
|
|
||||||
) -> Result(JsonValue, JsonQueryError) {
|
|
||||||
case query {
|
|
||||||
InvEnd -> Ok(json)
|
|
||||||
InvKey(key, q) ->
|
|
||||||
case json {
|
|
||||||
Object(obj) as j ->
|
|
||||||
obj
|
|
||||||
|> dict.get(key)
|
|
||||||
|> result.replace_error(MissingObjectKey(j, key))
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
|> result.map(query_json_rec(_, q))
|
|
||||||
|> result.flatten
|
|
||||||
InvKeyOr(key, or, q) ->
|
|
||||||
case json {
|
|
||||||
Object(obj) ->
|
|
||||||
obj
|
|
||||||
|> dict.get(key)
|
|
||||||
|> result.unwrap(or)
|
|
||||||
|> Ok
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
|> result.map(query_json_rec(_, q))
|
|
||||||
|> result.flatten
|
|
||||||
InvIndex(index, q) ->
|
|
||||||
case json {
|
|
||||||
Array(arr) as j ->
|
|
||||||
arr
|
|
||||||
|> list.at(index)
|
|
||||||
|> result.replace_error(IndexOutOfBounds(j, index))
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
|> result.map(query_json_rec(_, q))
|
|
||||||
|> result.flatten
|
|
||||||
InvIndexOr(index, or, q) ->
|
|
||||||
case json {
|
|
||||||
Array(arr) ->
|
|
||||||
arr
|
|
||||||
|> list.at(index)
|
|
||||||
|> result.unwrap(or)
|
|
||||||
|> Ok
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
|> result.map(query_json_rec(_, q))
|
|
||||||
|> result.flatten
|
|
||||||
InvFilter(predicate, q) ->
|
|
||||||
case json {
|
|
||||||
Array(arr) ->
|
|
||||||
arr
|
|
||||||
|> list.filter(predicate)
|
|
||||||
|> Array
|
|
||||||
|> query_json_rec(q)
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
InvMap(mapping, q) ->
|
|
||||||
case json {
|
|
||||||
Array(arr) ->
|
|
||||||
arr
|
|
||||||
|> list.map(mapping)
|
|
||||||
|> Array
|
|
||||||
|> query_json_rec(q)
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
InvMapKeys(mapping, q) ->
|
|
||||||
case json {
|
|
||||||
Object(obj) ->
|
|
||||||
obj
|
|
||||||
|> dict.to_list
|
|
||||||
|> list.map(fn(kv) { #(mapping(kv.0), kv.1) })
|
|
||||||
|> dict.from_list
|
|
||||||
|> Object
|
|
||||||
|> query_json_rec(q)
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
InvMapValues(mapping, q) ->
|
|
||||||
case json {
|
|
||||||
Object(obj) ->
|
|
||||||
obj
|
|
||||||
|> dict.map_values(mapping)
|
|
||||||
|> Object
|
|
||||||
|> query_json_rec(q)
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
InvFilterMap(mapping, q) ->
|
|
||||||
case json {
|
|
||||||
Array(arr) ->
|
|
||||||
arr
|
|
||||||
|> list.filter_map(mapping)
|
|
||||||
|> Array
|
|
||||||
|> query_json_rec(q)
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
InvForEach(q) ->
|
|
||||||
case json {
|
|
||||||
Array(arr) ->
|
|
||||||
arr
|
|
||||||
|> list.map(query_json_rec(_, q))
|
|
||||||
|> result.all
|
|
||||||
|> result.map(Array)
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
InvForEachOk(q) ->
|
|
||||||
case json {
|
|
||||||
Array(arr) ->
|
|
||||||
arr
|
|
||||||
|> list.map(query_json_rec(_, q))
|
|
||||||
|> result.values
|
|
||||||
|> Array
|
|
||||||
|> Ok
|
|
||||||
j -> Error(UnexpectedType(j))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query_json(json: JsonValue, query: JsonQuery) {
|
|
||||||
query_json_rec(json, invert_query(query))
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,417 @@
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,8 +2,7 @@ import gleeunit
|
||||||
import gleeunit/should
|
import gleeunit/should
|
||||||
import gleam/dict
|
import gleam/dict
|
||||||
import jasper.{
|
import jasper.{
|
||||||
type JsonValue, Array, Boolean, Index, IndexOutOfBounds, Key, MissingObjectKey,
|
type JsonValue, Array, Boolean, Null, Number, Object, String, parse_json,
|
||||||
Null, Number, Object, Root, String, UnexpectedType, parse_json, query_json,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
@ -77,45 +76,44 @@ pub fn parse_objects_test() {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// pub fn query_test() {
|
||||||
pub fn query_test() {
|
// query_json(String("foo"), Root)
|
||||||
query_json(String("foo"), Root)
|
// |> should.equal(Ok(String("foo")))
|
||||||
|> should.equal(Ok(String("foo")))
|
// query_json(
|
||||||
query_json(
|
// String("foo"),
|
||||||
String("foo"),
|
// Root
|
||||||
Root
|
// |> Key("foo"),
|
||||||
|> Key("foo"),
|
// )
|
||||||
)
|
// |> should.equal(Error(UnexpectedType(String("foo"))))
|
||||||
|> should.equal(Error(UnexpectedType(String("foo"))))
|
// query_json(
|
||||||
query_json(
|
// String("foo"),
|
||||||
String("foo"),
|
// Root
|
||||||
Root
|
// |> Index(2),
|
||||||
|> Index(2),
|
// )
|
||||||
)
|
// |> should.equal(Error(UnexpectedType(String("foo"))))
|
||||||
|> should.equal(Error(UnexpectedType(String("foo"))))
|
// query_json(
|
||||||
query_json(
|
// Array([String("foo")]),
|
||||||
Array([String("foo")]),
|
// Root
|
||||||
Root
|
// |> Index(2),
|
||||||
|> Index(2),
|
// )
|
||||||
)
|
// |> should.equal(Error(IndexOutOfBounds(Array([String("foo")]), 2)))
|
||||||
|> should.equal(Error(IndexOutOfBounds(Array([String("foo")]), 2)))
|
// query_json(
|
||||||
query_json(
|
// Object(dict.from_list([#("bar", Array([String("foo")]))])),
|
||||||
Object(dict.from_list([#("bar", Array([String("foo")]))])),
|
// Root
|
||||||
Root
|
// |> Key("bar")
|
||||||
|> Key("bar")
|
// |> Index(2),
|
||||||
|> Index(2),
|
// )
|
||||||
)
|
// |> should.equal(Error(IndexOutOfBounds(Array([String("foo")]), 2)))
|
||||||
|> should.equal(Error(IndexOutOfBounds(Array([String("foo")]), 2)))
|
// query_json(
|
||||||
query_json(
|
// Object(dict.from_list([#("bar", Array([String("foo")]))])),
|
||||||
Object(dict.from_list([#("bar", Array([String("foo")]))])),
|
// Root
|
||||||
Root
|
// |> Key("foo")
|
||||||
|> Key("foo")
|
// |> Index(2),
|
||||||
|> Index(2),
|
// )
|
||||||
)
|
// |> should.equal(
|
||||||
|> should.equal(
|
// Error(MissingObjectKey(
|
||||||
Error(MissingObjectKey(
|
// Object(dict.from_list([#("bar", Array([String("foo")]))])),
|
||||||
Object(dict.from_list([#("bar", Array([String("foo")]))])),
|
// "foo",
|
||||||
"foo",
|
// )),
|
||||||
)),
|
// )
|
||||||
)
|
// }
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue