iyi

Standard library, 0.18.0

std/toml

TOML 1.0.0 (https://toml.io/en/v1.0.0), read and written in iyi alone: a reader that answers a document as an Any or a TOMLError saying what is wrong and where, a writer whose text reads back to the value it was given, and derive toml_serializable.

import std/toml to use it. Source: src/std/toml.iyi at v0.18.0.

What it reads, which is all of 1.0.0:

* Keys bare (`A-Za-z0-9_-`, digits alone too, always a string),
  quoted as a basic or a literal string (the empty `""` included), and
  dotted, with blanks around the dots.
* Basic strings with the nine escapes the format has (`\b`, `\t`,
  `\n`, `\f`, `\r`, `\"`, `\\`, `\uXXXX`, `\UXXXXXXXX`, the last two
  naming a Unicode scalar value), literal strings, and the multi-line
  forms of both: the line break right after the opening `"""` or `'''`
  is dropped, a line-ending backslash drops the blanks and line breaks
  after it, and one or two quotes may stand just before the closing
  three. A CRLF inside a multi-line string is read as LF, as the spec
  allows and Python's `tomllib` does.
* Integers decimal (`+`/`-`, no leading zero but `0`, `+0` and `-0`),
  `0x`, `0o` and `0b` (no sign, leading zeros allowed), an underscore
  only between two digits, into an `Int64`; floats with a fraction, an
  exponent or both (a digit on each side of the point), `inf` and
  `nan` with either sign, into a `Float64`, the nearest double to the
  decimal (the prelude's `String#to_f` rounds correctly).
* `true` and `false`.
* Offset date-times, local date-times, local dates and local times,
  each a `TOMLDateTime` that keeps its kind. `T`, `t` or a space
  between date and time, `Z`/`z` or `+HH:MM`/`-HH:MM` for the offset.
  Fractional seconds are kept to the nanosecond; digits past the ninth
  are dropped, which the spec allows ("truncated, not rounded").
* Arrays of anything, mixed, across lines, with comments and a
  trailing comma; inline tables, on one line, closed to extension.
* `[table]` and `[[array.of.tables]]` headers, with blanks inside the
  brackets, and the tables dotted keys open.

Why its own date-time type and not std/time's Time: three of the four kinds are not instants (a local date-time has no zone, a local date no time, a local time no date), and an offset date-time keeps the offset it was written with, which a Time turned to UTC would lose and dump would then not write back. And std/time reads the clock and the zone files, which this module, pure computation over a String, has no reason to reach. TOMLDateTime#unix_seconds gives an offset date-time's instant to whoever wants a Time of it.

What it refuses, each a TOMLError naming the line and the column (counted in characters, from 1) where the trouble is:

* Text that is not UTF-8, and a byte order mark (the grammar has none,
  and `tomllib` refuses one too).
* A control character other than tab in a comment or a string
  (U+0000-U+0008, U+000A-U+001F, U+007F), a CR not before an LF, a
  line break in a single-line string, an escape the format does not
  have (TOML 1.1's `\e` and `\xHH` among them) or one naming a
  surrogate or past U+10FFFF, a string that does not close, six or
  more quotes closing a multi-line string.
* A key with no value or a value with no key, a key/value pair
  followed by more than a comment on its line, a bare key with a
  character outside its set, a multi-line string as a key.
* An integer with a leading zero, a doubled, leading or trailing
  underscore, a sign on `0x`/`0o`/`0b`, an uppercase prefix, or a value
  past the `Int64` range (the spec requires the refusal; `tomllib`
  reads any size, so the gate compares these as this reader's own); a
  float with no digit on one side of its point (`.7`, `7.`, `3.e+20`).
* A date that is not one (February 30th, month 13, hour 24, minute or
  second 60 - a leap second is refused, as `tomllib`'s datetime does),
  a time without its seconds (allowed only from TOML 1.1), an offset
  past 23:59, a date-time missing a part.
* An array or an inline table that does not close, a missing comma, a
  trailing comma or a line break in an inline table.
* Every redefinition the spec forbids: a key given twice (`a` and
  `"a"` are one key), a `[table]` declared twice, a `[table]` header
  naming a table that dotted keys made, dotted keys adding to a table
  a `[header]` declared or to one dotted keys made under another
  header, a header or dotted key going into an inline table or an
  array written as a value, `[[x]]` where `x` is a table or a static
  array, `[x]` where `x` is an array of tables, a value where a table
  is. A header may still go into a table dotted keys made
  (`[fruit.apple.texture]` after `apple.color = "red"` under
  `[fruit]`), and a super-table may be declared after its sub-table.
  These follow the spec's text and its examples, and where the text is
  silent, what `tomllib` does: dotted keys may add to a table a header
  made only on the way to another (`[a.b.c]` then `[a]` `b.d = 1`),
  and once they have, that table may not be declared by a header.
* Nesting past DEPTH_LIMIT levels, counting tables, arrays and inline
  tables alike, so `==`, `dump` and `hash` over any value this reader
  answers stay within a task's stack.

TOML.parse(text) answers Any | TOMLError; TOML.parse?(text) answers nil where parse answers the error, as JSON.parse? and YAML.parse? do. Neither panics on anything a document says. TOML.parse(text).or_panic panics with the sentence, and TOML.from_toml panics as YAML.from_yaml does.

TOML.dump(value) writes a table as TOML that this reader and tomllib read to an equal value: a table's plain keys first, as key = value, then each sub-table under a [header] and each non-empty array whose items are all tables as [[header]] entries; other arrays and tables inside arrays are written inline. Keys are bare where the bare set allows and quoted otherwise; strings are basic strings, with ", \ and every control character escaped (so no string spans lines); floats are the shortest text that reads back as the same double, inf, -inf and nan as TOML spells them. A value that is not a table cannot be a document and is refused, a panic: that is the program's mistake.

derive toml_serializable makes a type read and write itself as a table, a key per field, as std/yaml's derive yaml_serializable does: to_toml, self.from_toml(text), initialize(toml : Any) and the ToTOML trait's to_toml_any. TOML has no null: a nil field is left out when written and a missing key reads as nil where the field admits it; a nil anywhere else (inside an array, as a hash value) cannot be written and is refused by name.

Any holds a value: raw is the union, as_x/as_x? are the pairs, []/[]?/dig/dig? walk it, == is structural (two NaNs are equal, a document that says nan twice says the same thing) and hash agrees with it. As in std/yaml, every test of the union goes through Any#kind, which tests the reference types first.

toml_serializablemacro

derive toml_serializable, in a struct or class whose fields are written with their types: the type reads and writes itself as a TOML table, a key per field, named as the field is.

  pub struct Coord
    getter x : Float64
    getter name : String
    derive toml_serializable
  end
  Coord.from_toml("x = 1.5\nname = \"a\"\n").x   # => 1.5
  coord.to_toml                                 # => "x = 1.5\nname = \"a\"\n"

It is std/yaml's derive yaml_serializable over TOML, field for field: a value is read and written as TOML.read_value and TOML.to_any take its type (Bool, Int32, Int64, UInt8, UInt64, Float64, String, TOMLDateTime, Any, an Array or a Hash(String, _) of them, another derived type, each of them or nil). TOML has no null, so a nil field is not written, and a missing key is read as YAML's derive reads one: a field with a default keeps it, a nilable field holds nil, and any other is refused by name. A key the type has no field for is passed over. A type that is several writes derive serializable, yaml_serializable, toml_serializable: what this one generates shares no method with theirs (to_toml, from_toml, to_toml_any and initialize(toml : Any) of this module's Any).

std/tomliyi doc src/std/toml.iyi
pub macro toml_serializable(declaration)

Anystruct

The value a document holds. A document is a table: TOML.parse answers an Any whose raw is a Hash(String, Any).

std/tomliyi doc src/std/toml.iyi
pub struct Any
  alias Type = Bool | Int64 | Float64 | String | TOMLDateTime | Array(Any) | Hash(String, Any)
  def !=(other : Any) : Bool
  # Structural: same kind, same value; a table equals a table with the
  # same pairs in any order. `1` and `1.0` are different values, two NaNs
  # the same one, and `0.0` equals `-0.0` as the doubles do. Arrays and
  # tables are compared in methods of their own and scalars in a third,
  # so the frames a deep value recurses through stay small: with all of
  # it here, comparing two arrays nested 100 deep overflowed a task's
  # stack in a plain build.
  def ==(other : Any) : Bool
  def [](key : String) : Any
  def [](index : Int32) : Any
  def []?(key : String) : Any | ::Nil
  def []?(index : Int32) : Any | ::Nil
  def as_a : Array(Any)
  def as_a? : Array(Any) | ::Nil
  def as_bool : Bool
  def as_bool? : Bool | ::Nil
  def as_datetime : TOMLDateTime
  def as_datetime? : TOMLDateTime | ::Nil
  # A float, or an integer widened to one.
  def as_f : Float64
  def as_f? : Float64 | ::Nil
  def as_h : Hash(String, Any)
  def as_h? : Hash(String, Any) | ::Nil
  def as_i : Int64
  def as_i? : Int64 | ::Nil
  def as_s : String
  def as_s? : String | ::Nil
  def dig(key : String | Int32) : Any
  def dig(key : String | Int32, *rest : String | Int32) : Any
  def dig?(key : String | Int32) : Any | ::Nil
  def dig?(key : String | Int32, *rest : String | Int32) : Any | ::Nil
  # Agrees with `==`: a table's hash does not depend on its order, `0.0`
  # and `-0.0` share one and so do all NaNs. Mixed as std/yaml's `Any`
  # mixes, for the spread that file measured.
  def hash : Int32
  def initialize(raw : Type)
  def initialize(value : Int32)
  # A value as TOML writes it inline: strings quoted and escaped, floats
  # as `dump` spells them, tables as inline tables.
  def inspect : String
  # Which member of the union `raw` is, as one of the KIND_ constants.
  # Reference types are tested first, for the reason std/yaml's header
  # gives.
  def kind : Int32
  # The name of the kind, for the sentences below.
  def kind_name : String
  def raw : Type
  # Items of an array, keys of a table, characters of a string.
  def size : Int32
  # A string as itself, anything else as `inspect` writes it.
  def to_s : String
end

TOMLstruct

std/tomliyi doc src/std/toml.iyi
pub struct TOML
  # TOML for a table, ending in a line break unless the table is empty.
  def self.dump(value : Any) : String
  # The document `text` holds, read into `type`: `Bool`, `Int32`, `Int64`,
  # `UInt8`, `UInt64`, `Float64`, `String`, `TOMLDateTime`, `Any`, a type
  # with `derive toml_serializable`, one of those or nil, or an `Array` or a
  # `Hash(String, _)` of those. Text that is not TOML panics with the
  # `TOMLError`'s sentence; a value of another kind, or an integer outside
  # the type, is refused.
  def self.from_toml(text : String, type : T.class) : T forall T
  def initialize
  # The document `text` holds, a table, or what is wrong with it.
  def self.parse(text : String) : Any | TOMLError
  # The document, or nil where `parse` answers a `TOMLError`.
  def self.parse?(text : String) : Any | ::Nil
  def self.read_value(value : Any, type : Bool.class) : Bool
  def self.read_value(value : Any, type : Int32.class) : Int32
  def self.read_value(value : Any, type : Int64.class) : Int64
  def self.read_value(value : Any, type : UInt8.class) : UInt8
  def self.read_value(value : Any, type : UInt64.class) : UInt64
  # A float, or an integer widened to one: `y = 2` is the Float64 2.0.
  def self.read_value(value : Any, type : Float64.class) : Float64
  def self.read_value(value : Any, type : String.class) : String
  def self.read_value(value : Any, type : TOMLDateTime.class) : TOMLDateTime
  def self.read_value(value : Any, type : Any.class) : Any
  def self.read_value(value : Any, type : Array(T).class) : Array(T) forall T
  def self.read_value(value : Any, type : Hash(String, V).class) : Hash(String, V) forall V
  # A type that reads itself, the `initialize(toml)` a `derive
  # toml_serializable` wrote; a type that admits nil reads as the other
  # type, since a value that is there is never null.
  def self.read_value(value : Any, type : T.class) : T forall T
  # The `Any` a value is written as. TOML has no null: a nil is refused
  # here, named, and a derived type leaves a nil field out before it gets
  # this far. Taken out before the rest dispatches, for the reason
  # std/json's `write_or_null` gives.
  def self.to_any(val : T) : Any forall T
  # TOML for any table `to_any` makes of `obj`, as `dump` writes it.
  def self.to_toml(obj : T) : String forall T
  def self.write_value(val : Bool) : Any
  def self.write_value(val : Int32) : Any
  def self.write_value(val : Int64) : Any
  def self.write_value(val : UInt8) : Any
  # A TOML integer is an `Int64`: a `UInt64` past it cannot be written.
  def self.write_value(val : UInt64) : Any
  def self.write_value(val : Float64) : Any
  def self.write_value(val : String) : Any
  def self.write_value(val : TOMLDateTime) : Any
  def self.write_value(val : Any) : Any
  def self.write_value(val : Array(T)) : Any forall T
  # TOML keys are strings, so a hash is written when its keys are.
  def self.write_value(val : Hash(String, V)) : Any forall V
  def self.write_value(val : T) : Any forall T : ToTOML
end

TOMLDateTimestruct

A TOML date, time or date-time, with its kind. An offset date-time has every field and offset_minutes (0 for Z); a local date-time every field but the offset; a local date the date fields alone, a local time the time fields alone, the others 0. Two values are equal when their kind and every field are: 07:32:00Z and 08:32:00+01:00 are one instant written two ways, and this keeps which way (unix_seconds says they are the same instant).

std/tomliyi doc src/std/toml.iyi
pub struct TOMLDateTime
  def !=(other : TOMLDateTime) : Bool
  def ==(other : TOMLDateTime) : Bool
  def day : Int32
  def self.days_in_month(year : Int32, month : Int32) : Int32
  def hash : Int32
  def hour : Int32
  # Fields that do not make a value of the kind are the caller's mistake,
  # refused with what is wrong; `problem` says it without panicking.
  def initialize(kind : Int32, year : Int32, month : Int32, day : Int32, hour : Int32, minute : Int32, second : Int32, nanosecond : Int32, offset_minutes : Int32)
  def inspect : String
  def kind : Int32
  def kind_name : String
  def self.leap_year?(year : Int32) : Bool
  def self.local_date(year : Int32, month : Int32, day : Int32) : TOMLDateTime
  def local_date? : Bool
  def self.local_date_time(year : Int32, month : Int32, day : Int32, hour : Int32, minute : Int32, second : Int32, nanosecond : Int32 = 0) : TOMLDateTime
  def local_date_time? : Bool
  def self.local_time(hour : Int32, minute : Int32, second : Int32, nanosecond : Int32 = 0) : TOMLDateTime
  def local_time? : Bool
  def minute : Int32
  def month : Int32
  def nanosecond : Int32
  def offset? : Bool
  def self.offset_date_time(year : Int32, month : Int32, day : Int32, hour : Int32, minute : Int32, second : Int32, nanosecond : Int32 = 0, offset_minutes : Int32 = 0) : TOMLDateTime
  def offset_minutes : Int32
  # `value` in decimal with zeros in front to `width` digits.
  def self.pad(value : Int32, width : Int32) : String
  # Nil when the fields make a value of `kind`, else what is wrong with
  # them, as the reader and `new` both say it.
  def self.problem(kind : Int32, year : Int32, month : Int32, day : Int32, hour : Int32, minute : Int32, second : Int32, nanosecond : Int32, offset_minutes : Int32) : String | ::Nil
  def second : Int32
  # As TOML writes it (RFC 3339 for an offset date-time): `T` between
  # date and time, `Z` for a zero offset, the fraction with its trailing
  # zeros dropped and left out when it is zero.
  def to_s : String
  # Seconds since 1970-01-01T00:00:00Z of an offset date-time, the
  # fraction left in `nanosecond`. The other three kinds name no instant.
  def unix_seconds : Int64
  def year : Int32
end

TOMLErrorstruct

What is wrong with a document, and where: line and column count from 1, a column in characters. message is "<reason> at line L, column C".

std/tomliyi doc src/std/toml.iyi
pub struct TOMLError
  def column : Int32
  def initialize(reason : String, line : Int32, column : Int32)
  def line : Int32
  def reason : String
  def to_s : String
end

ToTOMLtrait

What TOML.write_value and TOML.to_any take besides the built-in types: a value whose type implements it, as a derive toml_serializable type does.

std/tomliyi doc src/std/toml.iyi
pub trait ToTOML
  abstract def to_toml_any : Any
end

Error for TOMLErrorimpl

std/tomliyi doc src/std/toml.iyi
impl Error for TOMLError
  def message : String
end