diff options
Diffstat (limited to 'aoc2023/build/packages/tom/src/tom.gleam')
-rw-r--r-- | aoc2023/build/packages/tom/src/tom.gleam | 1317 |
1 files changed, 1317 insertions, 0 deletions
diff --git a/aoc2023/build/packages/tom/src/tom.gleam b/aoc2023/build/packages/tom/src/tom.gleam new file mode 100644 index 0000000..e19ce3e --- /dev/null +++ b/aoc2023/build/packages/tom/src/tom.gleam @@ -0,0 +1,1317 @@ +//// A pure Gleam TOML parser! +//// +//// ```gleam +//// import tom +//// +//// const config = " +//// [person] +//// name = \"Lucy\" +//// is_cool = true +//// " +//// +//// pub fn main() { +//// // Parse a string of TOML +//// let assert Ok(parsed) = tom.parse(config) +//// +//// // Now you can work with the data directly, or you can use the `get_*` +//// // functions to retrieve values. +//// +//// tom.get_string(parsed, ["person", "name"]) +//// // -> Ok("Lucy") +//// +//// let is_cool = tom.get_bool(parsed, ["person", "is_cool"]) +//// // -> Ok(True) +//// } +//// ``` + +import gleam/int +import gleam/list +import gleam/float +import gleam/string +import gleam/result +import gleam/map.{type Map} + +/// A TOML document. +pub type Toml { + Int(Int) + Float(Float) + /// Infinity is a valid number in TOML but Gleam does not support it, so this + /// variant represents the infinity values. + Infinity(Sign) + /// NaN is a valid number in TOML but Gleam does not support it, so this + /// variant represents the NaN values. + Nan(Sign) + Bool(Bool) + String(String) + Date(Date) + Time(Time) + DateTime(DateTime) + Array(List(Toml)) + ArrayOfTables(List(Map(String, Toml))) + Table(Map(String, Toml)) + InlineTable(Map(String, Toml)) +} + +pub type DateTime { + DateTimeValue(date: Date, time: Time, offset: Offset) +} + +pub type Date { + DateValue(year: Int, month: Int, day: Int) +} + +pub type Time { + TimeValue(hour: Int, minute: Int, second: Int, millisecond: Int) +} + +pub type Offset { + Local + Offset(direction: Sign, hours: Int, minutes: Int) +} + +pub type Sign { + Positive + Negative +} + +/// An error that can occur when parsing a TOML document. +pub type ParseError { + /// An unexpected character was encountered when parsing the document. + Unexpected(got: String, expected: String) + /// More than one items have the same key in the document. + KeyAlreadyInUse(key: List(String)) +} + +type Tokens = + List(String) + +type Parsed(a) = + Result(#(a, Tokens), ParseError) + +/// A number of any kind, returned by the `get_number` function. +pub type Number { + NumberInt(Int) + NumberFloat(Float) + NumberInfinity(Sign) + NumberNan(Sign) +} + +/// An error that can occur when retrieving a value from a TOML document with +/// one of the `get_*` functions. +pub type GetError { + /// There was no value at the given key. + NotFound(key: List(String)) + /// The value at the given key was not of the expected type. + WrongType(key: List(String), expected: String, got: String) +} + +// TODO: test +/// Get a value of any type from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1") +/// get(parsed, ["a", "b", "c"]) +/// // -> Ok(Int(1)) +/// ``` +/// +pub fn get(toml: Map(String, Toml), key: List(String)) -> Result(Toml, GetError) { + case key { + [] -> Error(NotFound([])) + [k] -> result.replace_error(map.get(toml, k), NotFound([k])) + [k, ..key] -> { + case map.get(toml, k) { + Ok(Table(t)) -> push_key(get(t, key), k) + Ok(other) -> Error(WrongType([k], "Table", classify(other))) + Error(_) -> Error(NotFound([k])) + } + } + } +} + +// TODO: test +/// Get an int from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1") +/// get_int(parsed, ["a", "b", "c"]) +/// // -> Ok(1) +/// ``` +/// +pub fn get_int( + toml: Map(String, Toml), + key: List(String), +) -> Result(Int, GetError) { + case get(toml, key) { + Ok(Int(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Int", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a float from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1.1") +/// get_float(parsed, ["a", "b", "c"]) +/// // -> Ok(1.1) +/// ``` +/// +pub fn get_float( + toml: Map(String, Toml), + key: List(String), +) -> Result(Float, GetError) { + case get(toml, key) { + Ok(Float(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Float", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a bool from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = true") +/// get_bool(parsed, ["a", "b", "c"]) +/// // -> Ok(True) +/// ``` +/// +pub fn get_bool( + toml: Map(String, Toml), + key: List(String), +) -> Result(Bool, GetError) { + case get(toml, key) { + Ok(Bool(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Bool", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a string from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = \"ok\"") +/// get_string(parsed, ["a", "b", "c"]) +/// // -> Ok("ok") +/// ``` +/// +pub fn get_string( + toml: Map(String, Toml), + key: List(String), +) -> Result(String, GetError) { + case get(toml, key) { + Ok(String(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "String", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a date from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1979-05-27") +/// get_date(parsed, ["a", "b", "c"]) +/// // -> Ok("1979-05-27") +/// ``` +/// +pub fn get_date( + toml: Map(String, Toml), + key: List(String), +) -> Result(Date, GetError) { + case get(toml, key) { + Ok(Date(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Date", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a time from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 07:32:00") +/// get_time(parsed, ["a", "b", "c"]) +/// // -> Ok("07:32:00") +/// ``` +/// +pub fn get_time( + toml: Map(String, Toml), + key: List(String), +) -> Result(Time, GetError) { + case get(toml, key) { + Ok(Time(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Time", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a date-time from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1979-05-27T07:32:00") +/// get_date_time(parsed, ["a", "b", "c"]) +/// // -> Ok("1979-05-27T07:32:00") +/// ``` +/// +pub fn get_date_time( + toml: Map(String, Toml), + key: List(String), +) -> Result(DateTime, GetError) { + case get(toml, key) { + Ok(DateTime(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "DateTime", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get an array from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = [1, 2]") +/// get_array(parsed, ["a", "b", "c"]) +/// // -> Ok([Int(1), Int(2)]) +/// ``` +/// +pub fn get_array( + toml: Map(String, Toml), + key: List(String), +) -> Result(List(Toml), GetError) { + case get(toml, key) { + Ok(Array(i)) -> Ok(i) + Ok(ArrayOfTables(i)) -> Ok(list.map(i, Table)) + Ok(other) -> Error(WrongType(key, "Array", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a table from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = { d = 1 }") +/// get_table(parsed, ["a", "b", "c"]) +/// // -> Ok(map.from_list([#("d", Int(1))])) +/// ``` +/// +pub fn get_table( + toml: Map(String, Toml), + key: List(String), +) -> Result(Map(String, Toml), GetError) { + case get(toml, key) { + Ok(Table(i)) -> Ok(i) + Ok(InlineTable(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Table", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a number of any kind from a TOML document. +/// This could be an int, a float, a NaN, or an infinity. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = { d = inf }") +/// get_number(parsed, ["a", "b", "c"]) +/// // -> Ok(NumberInfinity(Positive))) +/// ``` +/// +pub fn get_number( + toml: Map(String, Toml), + key: List(String), +) -> Result(Number, GetError) { + case get(toml, key) { + Ok(Int(x)) -> Ok(NumberInt(x)) + Ok(Float(x)) -> Ok(NumberFloat(x)) + Ok(Nan(x)) -> Ok(NumberNan(x)) + Ok(Infinity(x)) -> Ok(NumberInfinity(x)) + Ok(other) -> Error(WrongType(key, "Number", classify(other))) + Error(e) -> Error(e) + } +} + +fn classify(toml: Toml) -> String { + case toml { + Int(_) -> "Int" + Float(_) -> "Float" + Nan(Positive) -> "NaN" + Nan(Negative) -> "Negative NaN" + Infinity(Positive) -> "Infinity" + Infinity(Negative) -> "Negative Infinity" + Bool(_) -> "Bool" + String(_) -> "String" + Date(_) -> "Date" + Time(_) -> "Time" + DateTime(_) -> "DateTime" + Array(_) -> "Array" + ArrayOfTables(_) -> "Array" + Table(_) -> "Table" + InlineTable(_) -> "Table" + } +} + +fn push_key(result: Result(t, GetError), key: String) -> Result(t, GetError) { + case result { + Ok(t) -> Ok(t) + Error(NotFound(path)) -> Error(NotFound([key, ..path])) + Error(WrongType(path, expected, got)) -> + Error(WrongType([key, ..path], expected, got)) + } +} + +pub fn parse(input: String) -> Result(Map(String, Toml), ParseError) { + let input = string.to_graphemes(input) + let input = drop_comments(input, []) + let input = skip_whitespace(input) + use toml, input <- do(parse_table(input, map.new())) + case parse_tables(input, toml) { + Ok(toml) -> Ok(reverse_arrays_of_tables_table(toml)) + Error(e) -> Error(e) + } +} + +fn parse_tables( + input: Tokens, + toml: Map(String, Toml), +) -> Result(Map(String, Toml), ParseError) { + case input { + ["[", "[", ..input] -> { + case parse_array_of_tables(input) { + Error(e) -> Error(e) + Ok(#(#(key, table), input)) -> { + case insert(toml, key, ArrayOfTables([table])) { + Ok(toml) -> parse_tables(input, toml) + Error(e) -> Error(e) + } + } + } + } + ["[", ..input] -> { + case parse_table_and_header(input) { + Error(e) -> Error(e) + Ok(#(#(key, table), input)) -> { + case insert(toml, key, Table(table)) { + Ok(toml) -> parse_tables(input, toml) + Error(e) -> Error(e) + } + } + } + } + [g, ..] -> Error(Unexpected(g, "[")) + [] -> Ok(toml) + } +} + +fn parse_array_of_tables( + input: Tokens, +) -> Parsed(#(List(String), Map(String, Toml))) { + let input = skip_line_whitespace(input) + use key, input <- do(parse_key(input, [])) + use input <- expect(input, "]") + use input <- expect(input, "]") + use table, input <- do(parse_table(input, map.new())) + Ok(#(#(key, table), input)) +} + +fn parse_table_header(input: Tokens) -> Parsed(List(String)) { + let input = skip_line_whitespace(input) + use key, input <- do(parse_key(input, [])) + use input <- expect(input, "]") + let input = skip_line_whitespace(input) + use input <- expect_end_of_line(input) + Ok(#(key, input)) +} + +fn parse_table_and_header( + input: Tokens, +) -> Parsed(#(List(String), Map(String, Toml))) { + use key, input <- do(parse_table_header(input)) + use table, input <- do(parse_table(input, map.new())) + Ok(#(#(key, table), input)) +} + +fn parse_table( + input: Tokens, + toml: Map(String, Toml), +) -> Parsed(Map(String, Toml)) { + let input = skip_whitespace(input) + case input { + ["[", ..] | [] -> Ok(#(toml, input)) + _ -> + case parse_key_value(input, toml) { + Ok(#(toml, input)) -> + case skip_line_whitespace(input) { + [] -> Ok(#(toml, [])) + ["\n", ..in] | ["\r\n", ..in] -> parse_table(in, toml) + [g, ..] -> Error(Unexpected(g, "\n")) + } + e -> e + } + } +} + +fn parse_key_value( + input: Tokens, + toml: Map(String, Toml), +) -> Parsed(Map(String, Toml)) { + use key, input <- do(parse_key(input, [])) + let input = skip_line_whitespace(input) + use input <- expect(input, "=") + let input = skip_line_whitespace(input) + use value, input <- do(parse_value(input)) + case insert(toml, key, value) { + Ok(toml) -> Ok(#(toml, input)) + Error(e) -> Error(e) + } +} + +fn insert( + table: Map(String, Toml), + key: List(String), + value: Toml, +) -> Result(Map(String, Toml), ParseError) { + case insert_loop(table, key, value) { + Ok(table) -> Ok(table) + Error(path) -> Error(KeyAlreadyInUse(path)) + } +} + +fn insert_loop( + table: Map(String, Toml), + key: List(String), + value: Toml, +) -> Result(Map(String, Toml), List(String)) { + case key { + [] -> panic as "unreachable" + [k] -> { + case map.get(table, k) { + Error(Nil) -> Ok(map.insert(table, k, value)) + Ok(old) -> merge(table, k, old, value) + } + } + [k, ..key] -> { + case map.get(table, k) { + Error(Nil) -> { + case insert_loop(map.new(), key, value) { + Ok(inner) -> Ok(map.insert(table, k, Table(inner))) + Error(path) -> Error([k, ..path]) + } + } + Ok(ArrayOfTables([inner, ..rest])) -> { + case insert_loop(inner, key, value) { + Ok(inner) -> + Ok(map.insert(table, k, ArrayOfTables([inner, ..rest]))) + Error(path) -> Error([k, ..path]) + } + } + Ok(Table(inner)) -> { + case insert_loop(inner, key, value) { + Ok(inner) -> Ok(map.insert(table, k, Table(inner))) + Error(path) -> Error([k, ..path]) + } + } + Ok(_) -> Error([k]) + } + } + } +} + +fn merge( + table: Map(String, Toml), + key: String, + old: Toml, + new: Toml, +) -> Result(Map(String, Toml), List(String)) { + case old, new { + // When both are arrays of tables then they are merged together + ArrayOfTables(tables), ArrayOfTables(new) -> + Ok(map.insert(table, key, ArrayOfTables(list.append(new, tables)))) + + _, _ -> Error([key]) + } +} + +fn expect_end_of_line(input: Tokens, next: fn(Tokens) -> Parsed(a)) -> Parsed(a) { + case input { + ["\n", ..input] -> next(input) + ["\r\n", ..input] -> next(input) + [g, ..] -> Error(Unexpected(g, "\n")) + [] -> Error(Unexpected("EOF", "\n")) + } +} + +fn parse_value(input) -> Parsed(Toml) { + case input { + ["t", "r", "u", "e", ..input] -> Ok(#(Bool(True), input)) + ["f", "a", "l", "s", "e", ..input] -> Ok(#(Bool(False), input)) + + ["n", "a", "n", ..input] -> Ok(#(Nan(Positive), input)) + ["+", "n", "a", "n", ..input] -> Ok(#(Nan(Positive), input)) + ["-", "n", "a", "n", ..input] -> Ok(#(Nan(Negative), input)) + + ["i", "n", "f", ..input] -> Ok(#(Infinity(Positive), input)) + ["+", "i", "n", "f", ..input] -> Ok(#(Infinity(Positive), input)) + ["-", "i", "n", "f", ..input] -> Ok(#(Infinity(Negative), input)) + + ["[", ..input] -> parse_array(input, []) + ["{", ..input] -> parse_inline_table(input, map.new()) + + ["0", "x", ..input] -> parse_hex(input, 0, Positive) + ["+", "0", "x", ..input] -> parse_hex(input, 0, Positive) + ["-", "0", "x", ..input] -> parse_hex(input, 0, Negative) + + ["0", "o", ..input] -> parse_octal(input, 0, Positive) + ["+", "0", "o", ..input] -> parse_octal(input, 0, Positive) + ["-", "0", "o", ..input] -> parse_octal(input, 0, Negative) + + ["0", "b", ..input] -> parse_binary(input, 0, Positive) + ["+", "0", "b", ..input] -> parse_binary(input, 0, Positive) + ["-", "0", "b", ..input] -> parse_binary(input, 0, Negative) + + ["+", ..input] -> parse_number(input, 0, Positive) + ["-", ..input] -> parse_number(input, 0, Negative) + ["0", ..] + | ["1", ..] + | ["2", ..] + | ["3", ..] + | ["4", ..] + | ["5", ..] + | ["6", ..] + | ["7", ..] + | ["8", ..] + | ["9", ..] -> parse_number(input, 0, Positive) + + ["\"", "\"", "\"", ..input] -> parse_multi_line_string(input, "") + ["\"", ..input] -> parse_string(input, "") + + ["'", "'", "'", ..input] -> parse_multi_line_literal_string(input, "") + ["'", ..input] -> parse_literal_string(input, "") + + [g, ..] -> Error(Unexpected(g, "value")) + [] -> Error(Unexpected("EOF", "value")) + } +} + +fn parse_key(input: Tokens, segments: List(String)) -> Parsed(List(String)) { + use segment, input <- do(parse_key_segment(input)) + let segments = [segment, ..segments] + let input = skip_line_whitespace(input) + + case input { + [".", ..input] -> parse_key(input, segments) + _ -> Ok(#(list.reverse(segments), input)) + } +} + +fn parse_key_segment(input: Tokens) -> Parsed(String) { + let input = skip_line_whitespace(input) + case input { + ["=", ..] -> Error(Unexpected("=", "Key")) + ["\n", ..] -> Error(Unexpected("\n", "Key")) + ["\r\n", ..] -> Error(Unexpected("\r\n", "Key")) + ["[", ..] -> Error(Unexpected("[", "Key")) + ["\"", ..input] -> parse_key_quoted(input, "\"", "") + ["'", ..input] -> parse_key_quoted(input, "'", "") + _ -> parse_key_bare(input, "") + } +} + +fn parse_key_quoted( + input: Tokens, + close: String, + name: String, +) -> Parsed(String) { + case input { + [g, ..input] if g == close -> Ok(#(name, input)) + [g, ..input] -> parse_key_quoted(input, close, name <> g) + [] -> Error(Unexpected("EOF", close)) + } +} + +fn parse_key_bare(input: Tokens, name: String) -> Parsed(String) { + case input { + [" ", ..input] if name != "" -> Ok(#(name, input)) + ["=", ..] if name != "" -> Ok(#(name, input)) + [".", ..] if name != "" -> Ok(#(name, input)) + ["]", ..] if name != "" -> Ok(#(name, input)) + [",", ..] if name != "" -> Error(Unexpected(",", "=")) + ["\n", ..] if name != "" -> Error(Unexpected("\n", "=")) + ["\r\n", ..] if name != "" -> Error(Unexpected("\r\n", "=")) + ["\n", ..] -> Error(Unexpected("\n", "key")) + ["\r\n", ..] -> Error(Unexpected("\r\n", "key")) + ["]", ..] -> Error(Unexpected("]", "key")) + [",", ..] -> Error(Unexpected(",", "key")) + [g, ..input] -> parse_key_bare(input, name <> g) + [] -> Error(Unexpected("EOF", "key")) + } +} + +fn skip_line_whitespace(input: Tokens) -> Tokens { + list.drop_while(input, fn(g) { g == " " || g == "\t" }) +} + +fn skip_whitespace(input: Tokens) -> Tokens { + case input { + [" ", ..input] -> skip_whitespace(input) + ["\t", ..input] -> skip_whitespace(input) + ["\n", ..input] -> skip_whitespace(input) + ["\r\n", ..input] -> skip_whitespace(input) + input -> input + } +} + +fn drop_comments(input: Tokens, acc: Tokens) -> Tokens { + case input { + ["#", ..input] -> + input + |> list.drop_while(fn(g) { g != "\n" }) + |> drop_comments(acc) + [g, ..input] -> drop_comments(input, [g, ..acc]) + [] -> list.reverse(acc) + } +} + +fn do( + result: Result(#(a, Tokens), ParseError), + next: fn(a, Tokens) -> Result(b, ParseError), +) -> Result(b, ParseError) { + case result { + Ok(#(a, input)) -> next(a, input) + Error(e) -> Error(e) + } +} + +fn expect( + input: Tokens, + expected: String, + next: fn(Tokens) -> Parsed(a), +) -> Parsed(a) { + case input { + [g, ..input] if g == expected -> next(input) + [g, ..] -> Error(Unexpected(g, expected)) + [] -> Error(Unexpected("EOF", expected)) + } +} + +fn parse_inline_table( + input: Tokens, + properties: Map(String, Toml), +) -> Parsed(Toml) { + let input = skip_whitespace(input) + case input { + ["}", ..input] -> Ok(#(InlineTable(properties), input)) + _ -> + case parse_inline_table_property(input, properties) { + Ok(#(properties, input)) -> { + let input = skip_whitespace(input) + case input { + ["}", ..input] -> Ok(#(InlineTable(properties), input)) + [",", ..input] -> { + let input = skip_whitespace(input) + parse_inline_table(input, properties) + } + [g, ..] -> Error(Unexpected(g, "}")) + [] -> Error(Unexpected("EOF", "}")) + } + } + Error(e) -> Error(e) + } + } +} + +fn parse_inline_table_property( + input: Tokens, + properties: Map(String, Toml), +) -> Parsed(Map(String, Toml)) { + let input = skip_whitespace(input) + use key, input <- do(parse_key(input, [])) + let input = skip_line_whitespace(input) + use input <- expect(input, "=") + let input = skip_line_whitespace(input) + use value, input <- do(parse_value(input)) + case insert(properties, key, value) { + Ok(properties) -> Ok(#(properties, input)) + Error(e) -> Error(e) + } +} + +fn parse_array(input: Tokens, elements: List(Toml)) -> Parsed(Toml) { + let input = skip_whitespace(input) + case input { + ["]", ..input] -> Ok(#(Array(list.reverse(elements)), input)) + _ -> { + use element, input <- do(parse_value(input)) + let elements = [element, ..elements] + let input = skip_whitespace(input) + case input { + ["]", ..input] -> Ok(#(Array(list.reverse(elements)), input)) + [",", ..input] -> { + let input = skip_whitespace(input) + parse_array(input, elements) + } + [g, ..] -> Error(Unexpected(g, "]")) + [] -> Error(Unexpected("EOF", "]")) + } + } + } +} + +fn parse_hex(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_hex(input, number, sign) + ["0", ..input] -> parse_hex(input, number * 16 + 0, sign) + ["1", ..input] -> parse_hex(input, number * 16 + 1, sign) + ["2", ..input] -> parse_hex(input, number * 16 + 2, sign) + ["3", ..input] -> parse_hex(input, number * 16 + 3, sign) + ["4", ..input] -> parse_hex(input, number * 16 + 4, sign) + ["5", ..input] -> parse_hex(input, number * 16 + 5, sign) + ["6", ..input] -> parse_hex(input, number * 16 + 6, sign) + ["7", ..input] -> parse_hex(input, number * 16 + 7, sign) + ["8", ..input] -> parse_hex(input, number * 16 + 8, sign) + ["9", ..input] -> parse_hex(input, number * 16 + 9, sign) + ["a", ..input] -> parse_hex(input, number * 16 + 10, sign) + ["b", ..input] -> parse_hex(input, number * 16 + 11, sign) + ["c", ..input] -> parse_hex(input, number * 16 + 12, sign) + ["d", ..input] -> parse_hex(input, number * 16 + 13, sign) + ["e", ..input] -> parse_hex(input, number * 16 + 14, sign) + ["f", ..input] -> parse_hex(input, number * 16 + 15, sign) + ["A", ..input] -> parse_hex(input, number * 16 + 10, sign) + ["B", ..input] -> parse_hex(input, number * 16 + 11, sign) + ["C", ..input] -> parse_hex(input, number * 16 + 12, sign) + ["D", ..input] -> parse_hex(input, number * 16 + 13, sign) + ["E", ..input] -> parse_hex(input, number * 16 + 14, sign) + ["F", ..input] -> parse_hex(input, number * 16 + 15, sign) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> -number + } + Ok(#(Int(number), input)) + } + } +} + +fn parse_octal(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_octal(input, number, sign) + ["0", ..input] -> parse_octal(input, number * 8 + 0, sign) + ["1", ..input] -> parse_octal(input, number * 8 + 1, sign) + ["2", ..input] -> parse_octal(input, number * 8 + 2, sign) + ["3", ..input] -> parse_octal(input, number * 8 + 3, sign) + ["4", ..input] -> parse_octal(input, number * 8 + 4, sign) + ["5", ..input] -> parse_octal(input, number * 8 + 5, sign) + ["6", ..input] -> parse_octal(input, number * 8 + 6, sign) + ["7", ..input] -> parse_octal(input, number * 8 + 7, sign) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> -number + } + Ok(#(Int(number), input)) + } + } +} + +fn parse_binary(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_binary(input, number, sign) + ["0", ..input] -> parse_binary(input, number * 2 + 0, sign) + ["1", ..input] -> parse_binary(input, number * 2 + 1, sign) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> -number + } + Ok(#(Int(number), input)) + } + } +} + +fn parse_number(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_number(input, number, sign) + ["0", ..input] -> parse_number(input, number * 10 + 0, sign) + ["1", ..input] -> parse_number(input, number * 10 + 1, sign) + ["2", ..input] -> parse_number(input, number * 10 + 2, sign) + ["3", ..input] -> parse_number(input, number * 10 + 3, sign) + ["4", ..input] -> parse_number(input, number * 10 + 4, sign) + ["5", ..input] -> parse_number(input, number * 10 + 5, sign) + ["6", ..input] -> parse_number(input, number * 10 + 6, sign) + ["7", ..input] -> parse_number(input, number * 10 + 7, sign) + ["8", ..input] -> parse_number(input, number * 10 + 8, sign) + ["9", ..input] -> parse_number(input, number * 10 + 9, sign) + + ["-", ..input] -> parse_date(input, number) + [":", ..input] if number < 24 -> parse_time_minute(input, number) + + [".", ..input] -> parse_float(input, int.to_float(number), sign, 0.1) + + ["e", "+", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Positive) + ["e", "-", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Negative) + ["e", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Positive) + ["E", "+", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Positive) + ["E", "-", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Negative) + ["E", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Positive) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> -number + } + Ok(#(Int(number), input)) + } + } +} + +fn parse_exponent( + input: Tokens, + n: Float, + n_sign: Sign, + ex: Int, + ex_sign: Sign, +) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_exponent(input, n, n_sign, ex, ex_sign) + ["0", ..input] -> parse_exponent(input, n, n_sign, ex * 10, ex_sign) + ["1", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 1, ex_sign) + ["2", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 2, ex_sign) + ["3", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 3, ex_sign) + ["4", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 4, ex_sign) + ["5", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 5, ex_sign) + ["6", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 6, ex_sign) + ["7", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 7, ex_sign) + ["8", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 8, ex_sign) + ["9", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 9, ex_sign) + + // Anything else and the number is terminated + input -> { + let number = case n_sign { + Positive -> n + Negative -> n *. -1.0 + } + let exponent = + int.to_float(case ex_sign { + Positive -> ex + Negative -> -ex + }) + let multiplier = case float.power(10.0, exponent) { + Ok(multiplier) -> multiplier + Error(_) -> 1.0 + } + Ok(#(Float(number *. multiplier), input)) + } + } +} + +fn parse_float( + input: Tokens, + number: Float, + sign: Sign, + unit: Float, +) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_float(input, number, sign, unit) + ["0", ..input] -> parse_float(input, number, sign, unit *. 0.1) + ["1", ..input] -> + parse_float(input, number +. 1.0 *. unit, sign, unit *. 0.1) + ["2", ..input] -> + parse_float(input, number +. 2.0 *. unit, sign, unit *. 0.1) + ["3", ..input] -> + parse_float(input, number +. 3.0 *. unit, sign, unit *. 0.1) + ["4", ..input] -> + parse_float(input, number +. 4.0 *. unit, sign, unit *. 0.1) + ["5", ..input] -> + parse_float(input, number +. 5.0 *. unit, sign, unit *. 0.1) + ["6", ..input] -> + parse_float(input, number +. 6.0 *. unit, sign, unit *. 0.1) + ["7", ..input] -> + parse_float(input, number +. 7.0 *. unit, sign, unit *. 0.1) + ["8", ..input] -> + parse_float(input, number +. 8.0 *. unit, sign, unit *. 0.1) + ["9", ..input] -> + parse_float(input, number +. 9.0 *. unit, sign, unit *. 0.1) + + ["e", "+", ..input] -> parse_exponent(input, number, sign, 0, Positive) + ["e", "-", ..input] -> parse_exponent(input, number, sign, 0, Negative) + ["e", ..input] -> parse_exponent(input, number, sign, 0, Positive) + ["E", "+", ..input] -> parse_exponent(input, number, sign, 0, Positive) + ["E", "-", ..input] -> parse_exponent(input, number, sign, 0, Negative) + ["E", ..input] -> parse_exponent(input, number, sign, 0, Positive) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> number *. -1.0 + } + Ok(#(Float(number), input)) + } + } +} + +fn parse_string(input: Tokens, string: String) -> Parsed(Toml) { + case input { + ["\"", ..input] -> Ok(#(String(string), input)) + ["\\", "t", ..input] -> parse_string(input, string <> "\t") + ["\\", "n", ..input] -> parse_string(input, string <> "\n") + ["\\", "r", ..input] -> parse_string(input, string <> "\r") + ["\\", "\"", ..input] -> parse_string(input, string <> "\"") + ["\\", "\\", ..input] -> parse_string(input, string <> "\\") + [] -> Error(Unexpected("EOF", "\"")) + ["\n", ..] -> Error(Unexpected("\n", "\"")) + ["\r\n", ..] -> Error(Unexpected("\r\n", "\"")) + [g, ..input] -> parse_string(input, string <> g) + } +} + +fn parse_multi_line_string(input: Tokens, string: String) -> Parsed(Toml) { + case input { + ["\"", "\"", "\"", ..input] -> Ok(#(String(string), input)) + ["\\", "\n", ..input] -> + parse_multi_line_string(skip_whitespace(input), string) + ["\\", "\r\n", ..input] -> + parse_multi_line_string(skip_whitespace(input), string) + ["\r\n", ..input] if string == "" -> parse_multi_line_string(input, string) + ["\n", ..input] if string == "" -> parse_multi_line_string(input, string) + ["\r\n", ..input] if string == "" -> parse_multi_line_string(input, string) + ["\\", "t", ..input] -> parse_multi_line_string(input, string <> "\t") + ["\\", "n", ..input] -> parse_multi_line_string(input, string <> "\n") + ["\\", "r", ..input] -> parse_multi_line_string(input, string <> "\r") + ["\\", "\"", ..input] -> parse_multi_line_string(input, string <> "\"") + ["\\", "\\", ..input] -> parse_multi_line_string(input, string <> "\\") + [] -> Error(Unexpected("EOF", "\"")) + [g, ..input] -> parse_multi_line_string(input, string <> g) + } +} + +fn parse_multi_line_literal_string( + input: Tokens, + string: String, +) -> Parsed(Toml) { + case input { + [] -> Error(Unexpected("EOF", "\"")) + ["'", "'", "'", "'", ..] -> Error(Unexpected("''''", "'''")) + ["'", "'", "'", ..input] -> Ok(#(String(string), input)) + ["\n", ..input] if string == "" -> + parse_multi_line_literal_string(input, string) + ["\r\n", ..input] if string == "" -> + parse_multi_line_literal_string(input, string) + [g, ..input] -> parse_multi_line_literal_string(input, string <> g) + } +} + +fn parse_literal_string(input: Tokens, string: String) -> Parsed(Toml) { + case input { + [] -> Error(Unexpected("EOF", "\"")) + ["\n", ..] -> Error(Unexpected("\n", "'")) + ["\r\n", ..] -> Error(Unexpected("\r\n", "'")) + ["'", ..input] -> Ok(#(String(string), input)) + [g, ..input] -> parse_literal_string(input, string <> g) + } +} + +fn reverse_arrays_of_tables(toml: Toml) -> Toml { + case toml { + ArrayOfTables(tables) -> + ArrayOfTables(reverse_arrays_of_tables_array(tables, [])) + + Table(table) -> Table(reverse_arrays_of_tables_table(table)) + + _ -> toml + } +} + +fn reverse_arrays_of_tables_table(table: Map(String, Toml)) -> Map(String, Toml) { + map.map_values(table, fn(_, v) { reverse_arrays_of_tables(v) }) +} + +fn reverse_arrays_of_tables_array( + array: List(Map(String, Toml)), + acc: List(Map(String, Toml)), +) -> List(Map(String, Toml)) { + case array { + [] -> acc + [first, ..rest] -> { + let first = reverse_arrays_of_tables_table(first) + reverse_arrays_of_tables_array(rest, [first, ..acc]) + } + } +} + +fn parse_time_minute(input: Tokens, hours: Int) -> Parsed(Toml) { + use minutes, input <- do(parse_number_under_60(input, "minutes")) + use #(seconds, ms), input <- do(parse_time_s_ms(input)) + let time = TimeValue(hours, minutes, seconds, ms) + Ok(#(Time(time), input)) +} + +fn parse_hour_minute(input: Tokens) -> Parsed(#(Int, Int)) { + use hours, input <- do(case input { + ["0", "0", ":", ..input] -> Ok(#(0, input)) + ["0", "1", ":", ..input] -> Ok(#(1, input)) + ["0", "2", ":", ..input] -> Ok(#(2, input)) + ["0", "3", ":", ..input] -> Ok(#(3, input)) + ["0", "4", ":", ..input] -> Ok(#(4, input)) + ["0", "5", ":", ..input] -> Ok(#(5, input)) + ["0", "6", ":", ..input] -> Ok(#(6, input)) + ["0", "7", ":", ..input] -> Ok(#(7, input)) + ["0", "8", ":", ..input] -> Ok(#(8, input)) + ["0", "9", ":", ..input] -> Ok(#(9, input)) + ["1", "0", ":", ..input] -> Ok(#(10, input)) + ["1", "1", ":", ..input] -> Ok(#(11, input)) + ["1", "2", ":", ..input] -> Ok(#(12, input)) + ["1", "3", ":", ..input] -> Ok(#(13, input)) + ["1", "4", ":", ..input] -> Ok(#(14, input)) + ["1", "5", ":", ..input] -> Ok(#(15, input)) + ["1", "6", ":", ..input] -> Ok(#(16, input)) + ["1", "7", ":", ..input] -> Ok(#(17, input)) + ["1", "8", ":", ..input] -> Ok(#(18, input)) + ["1", "9", ":", ..input] -> Ok(#(19, input)) + ["2", "0", ":", ..input] -> Ok(#(20, input)) + ["2", "1", ":", ..input] -> Ok(#(21, input)) + ["2", "2", ":", ..input] -> Ok(#(22, input)) + ["2", "3", ":", ..input] -> Ok(#(23, input)) + [g, ..] -> Error(Unexpected(g, "time")) + [] -> Error(Unexpected("EOF", "time")) + }) + + use minutes, input <- do(parse_number_under_60(input, "minutes")) + Ok(#(#(hours, minutes), input)) +} + +fn parse_time_value(input: Tokens) -> Parsed(Time) { + use #(hours, minutes), input <- do(parse_hour_minute(input)) + use #(seconds, ms), input <- do(parse_time_s_ms(input)) + let time = TimeValue(hours, minutes, seconds, ms) + Ok(#(time, input)) +} + +fn parse_time_s_ms(input: Tokens) -> Parsed(#(Int, Int)) { + case input { + [":", ..input] -> { + use seconds, input <- do(parse_number_under_60(input, "seconds")) + case input { + [".", ..input] -> parse_time_ms(input, seconds, 0) + _ -> Ok(#(#(seconds, 0), input)) + } + } + + _ -> Ok(#(#(0, 0), input)) + } +} + +fn parse_time_ms(input: Tokens, seconds: Int, ms: Int) -> Parsed(#(Int, Int)) { + case input { + ["0", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 0) + ["1", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 1) + ["2", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 2) + ["3", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 3) + ["4", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 4) + ["5", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 5) + ["6", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 6) + ["7", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 7) + ["8", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 8) + ["9", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 9) + + // Anything else and the number is terminated + _ -> Ok(#(#(seconds, ms), input)) + } +} + +fn parse_number_under_60(input: Tokens, expected: String) -> Parsed(Int) { + case input { + ["0", "0", ..input] -> Ok(#(0, input)) + ["0", "1", ..input] -> Ok(#(1, input)) + ["0", "2", ..input] -> Ok(#(2, input)) + ["0", "3", ..input] -> Ok(#(3, input)) + ["0", "4", ..input] -> Ok(#(4, input)) + ["0", "5", ..input] -> Ok(#(5, input)) + ["0", "6", ..input] -> Ok(#(6, input)) + ["0", "7", ..input] -> Ok(#(7, input)) + ["0", "8", ..input] -> Ok(#(8, input)) + ["0", "9", ..input] -> Ok(#(9, input)) + ["1", "0", ..input] -> Ok(#(10, input)) + ["1", "1", ..input] -> Ok(#(11, input)) + ["1", "2", ..input] -> Ok(#(12, input)) + ["1", "3", ..input] -> Ok(#(13, input)) + ["1", "4", ..input] -> Ok(#(14, input)) + ["1", "5", ..input] -> Ok(#(15, input)) + ["1", "6", ..input] -> Ok(#(16, input)) + ["1", "7", ..input] -> Ok(#(17, input)) + ["1", "8", ..input] -> Ok(#(18, input)) + ["1", "9", ..input] -> Ok(#(19, input)) + ["2", "0", ..input] -> Ok(#(20, input)) + ["2", "1", ..input] -> Ok(#(21, input)) + ["2", "2", ..input] -> Ok(#(22, input)) + ["2", "3", ..input] -> Ok(#(23, input)) + ["2", "4", ..input] -> Ok(#(24, input)) + ["2", "5", ..input] -> Ok(#(25, input)) + ["2", "6", ..input] -> Ok(#(26, input)) + ["2", "7", ..input] -> Ok(#(27, input)) + ["2", "8", ..input] -> Ok(#(28, input)) + ["2", "9", ..input] -> Ok(#(29, input)) + ["3", "0", ..input] -> Ok(#(30, input)) + ["3", "1", ..input] -> Ok(#(31, input)) + ["3", "2", ..input] -> Ok(#(32, input)) + ["3", "3", ..input] -> Ok(#(33, input)) + ["3", "4", ..input] -> Ok(#(34, input)) + ["3", "5", ..input] -> Ok(#(35, input)) + ["3", "6", ..input] -> Ok(#(36, input)) + ["3", "7", ..input] -> Ok(#(37, input)) + ["3", "8", ..input] -> Ok(#(38, input)) + ["3", "9", ..input] -> Ok(#(39, input)) + ["4", "0", ..input] -> Ok(#(40, input)) + ["4", "1", ..input] -> Ok(#(41, input)) + ["4", "2", ..input] -> Ok(#(42, input)) + ["4", "3", ..input] -> Ok(#(43, input)) + ["4", "4", ..input] -> Ok(#(44, input)) + ["4", "5", ..input] -> Ok(#(45, input)) + ["4", "6", ..input] -> Ok(#(46, input)) + ["4", "7", ..input] -> Ok(#(47, input)) + ["4", "8", ..input] -> Ok(#(48, input)) + ["4", "9", ..input] -> Ok(#(49, input)) + ["5", "0", ..input] -> Ok(#(50, input)) + ["5", "1", ..input] -> Ok(#(51, input)) + ["5", "2", ..input] -> Ok(#(52, input)) + ["5", "3", ..input] -> Ok(#(53, input)) + ["5", "4", ..input] -> Ok(#(54, input)) + ["5", "5", ..input] -> Ok(#(55, input)) + ["5", "6", ..input] -> Ok(#(56, input)) + ["5", "7", ..input] -> Ok(#(57, input)) + ["5", "8", ..input] -> Ok(#(58, input)) + ["5", "9", ..input] -> Ok(#(59, input)) + + [g, ..] -> Error(Unexpected(g, expected)) + [] -> Error(Unexpected("EOF", expected)) + } +} + +fn parse_date(input: Tokens, year: Int) -> Parsed(Toml) { + case input { + ["0", "1", "-", ..input] -> parse_date_day(input, year, 1) + ["0", "2", "-", ..input] -> parse_date_day(input, year, 2) + ["0", "3", "-", ..input] -> parse_date_day(input, year, 3) + ["0", "4", "-", ..input] -> parse_date_day(input, year, 4) + ["0", "5", "-", ..input] -> parse_date_day(input, year, 5) + ["0", "6", "-", ..input] -> parse_date_day(input, year, 6) + ["0", "7", "-", ..input] -> parse_date_day(input, year, 7) + ["0", "8", "-", ..input] -> parse_date_day(input, year, 8) + ["0", "9", "-", ..input] -> parse_date_day(input, year, 9) + ["1", "0", "-", ..input] -> parse_date_day(input, year, 10) + ["1", "1", "-", ..input] -> parse_date_day(input, year, 11) + ["1", "2", "-", ..input] -> parse_date_day(input, year, 12) + + [g, ..] -> Error(Unexpected(g, "date month")) + [] -> Error(Unexpected("EOF", "date month")) + } +} + +fn parse_date_day(input: Tokens, year: Int, month: Int) -> Parsed(Toml) { + case input { + ["0", "1", ..input] -> parse_date_end(input, year, month, 1) + ["0", "2", ..input] -> parse_date_end(input, year, month, 2) + ["0", "3", ..input] -> parse_date_end(input, year, month, 3) + ["0", "4", ..input] -> parse_date_end(input, year, month, 4) + ["0", "5", ..input] -> parse_date_end(input, year, month, 5) + ["0", "6", ..input] -> parse_date_end(input, year, month, 6) + ["0", "7", ..input] -> parse_date_end(input, year, month, 7) + ["0", "8", ..input] -> parse_date_end(input, year, month, 8) + ["0", "9", ..input] -> parse_date_end(input, year, month, 9) + ["1", "0", ..input] -> parse_date_end(input, year, month, 10) + ["1", "1", ..input] -> parse_date_end(input, year, month, 11) + ["1", "2", ..input] -> parse_date_end(input, year, month, 12) + ["1", "3", ..input] -> parse_date_end(input, year, month, 13) + ["1", "4", ..input] -> parse_date_end(input, year, month, 14) + ["1", "5", ..input] -> parse_date_end(input, year, month, 15) + ["1", "6", ..input] -> parse_date_end(input, year, month, 16) + ["1", "7", ..input] -> parse_date_end(input, year, month, 17) + ["1", "8", ..input] -> parse_date_end(input, year, month, 18) + ["1", "9", ..input] -> parse_date_end(input, year, month, 19) + ["2", "0", ..input] -> parse_date_end(input, year, month, 20) + ["2", "1", ..input] -> parse_date_end(input, year, month, 21) + ["2", "2", ..input] -> parse_date_end(input, year, month, 22) + ["2", "3", ..input] -> parse_date_end(input, year, month, 23) + ["2", "4", ..input] -> parse_date_end(input, year, month, 24) + ["2", "5", ..input] -> parse_date_end(input, year, month, 25) + ["2", "6", ..input] -> parse_date_end(input, year, month, 26) + ["2", "7", ..input] -> parse_date_end(input, year, month, 27) + ["2", "8", ..input] -> parse_date_end(input, year, month, 28) + ["2", "9", ..input] -> parse_date_end(input, year, month, 29) + ["3", "0", ..input] -> parse_date_end(input, year, month, 30) + ["3", "1", ..input] -> parse_date_end(input, year, month, 31) + + [g, ..] -> Error(Unexpected(g, "date day")) + [] -> Error(Unexpected("EOF", "date day")) + } +} + +fn parse_date_end( + input: Tokens, + year: Int, + month: Int, + day: Int, +) -> Parsed(Toml) { + let date = DateValue(year, month, day) + case input { + [" ", ..input] | ["T", ..input] -> { + use time, input <- do(parse_time_value(input)) + use offset, input <- do(parse_offset(input)) + Ok(#(DateTime(DateTimeValue(date, time, offset)), input)) + } + + _ -> Ok(#(Date(date), input)) + } +} + +fn parse_offset(input: Tokens) -> Parsed(Offset) { + case input { + ["Z", ..input] -> Ok(#(Offset(Positive, 0, 0), input)) + ["+", ..input] -> parse_offset_hours(input, Positive) + ["-", ..input] -> parse_offset_hours(input, Negative) + _ -> Ok(#(Local, input)) + } +} + +fn parse_offset_hours(input: Tokens, sign: Sign) -> Parsed(Offset) { + use #(hours, minutes), input <- do(parse_hour_minute(input)) + Ok(#(Offset(sign, hours, minutes), input)) +} |