std/yaml
import std/yaml to use it. Source: src/std/yaml.iyi at v0.18.0.
What it reads:
* Block mappings and block sequences, nested by indentation; a sequence
may sit at the same indentation as the key it belongs to; `- key: v`
opens a mapping inside a sequence entry.
* Flow collections `[a, b]` and `{a: b}`, nested, across lines, with a
trailing comma allowed and `[k: v]` as a one-pair mapping.
* Plain scalars, folded across lines; single-quoted scalars with `''`;
double-quoted scalars with every escape the format names (`\n`, `\t`,
`\"`, `\\`, `\/`, `\0`, `\a`, `\b`, `\e`, `\f`, `\r`, `\v`, `\ `,
`\N`, `\_`, `\L`, `\P`, `\xXX`, `\uXXXX`, `\UXXXXXXXX`, an escaped line
break), both quoted styles folded across lines.
* Block scalars `|` and `>` with `-`, `+` and an explicit indentation
digit, chomped as the header says.
* Anchors `&a`, aliases `*a`, merge keys `<<: *a` and `<<: [*a, *b]`.
A key the mapping spells itself wins over a merged one, wherever it is
spelled; among merged mappings the first wins.
* The core-schema tags `!!str`, `!!int`, `!!float`, `!!bool`, `!!null`,
`!!seq` and `!!map`, on a value or a key; any other tag is refused.
* Streams: `%YAML` and `%TAG` directives are skipped, `---` opens a
document and `...` ends one. `parse_all` answers every document;
`parse` answers the first (nil for an empty stream) after checking the
whole stream.
* Plain scalars resolve as the 1.2 core schema says: `null`, `Null`,
`NULL`, `~` and the empty scalar are nil; `true`/`false` in the three
spellings are booleans; decimal, `0o` octal and `0x` hexadecimal are
`Int64`; `1.5`, `.5`, `7.`, `1e3`, `1.0e+20`, `.inf`, `-.inf`, `.nan`
are `Float64`; a decimal integer past the `Int64` range is read as a
`Float64`; everything else is a `String`. So `yes`, `no`, `on`,
`off`, `1_000`, `1.2.3`, `22:30` and `2001-12-14` are strings here,
and `0777` is the decimal 777, where a 1.1 reader would make some of
them booleans, octal numbers, or a timestamp. Keys resolve the same
way: `1: one` has the `Int64` key 1, `true: x` the `Bool` key true.What it refuses, each with a sentence that says where:
a UTF-16 stream (named by its byte order mark), a tab in indentation,
a control character (C0, DEL or C1, but tab and U+0085) or the
noncharacter U+FFFE or U+FFFF, anywhere, a quoted string that does
not close, a flow collection that does not close, an unknown escape or
an escape that names a surrogate, a mapping entry or sequence entry at
an indentation that belongs to nothing, text after a value on its
line, a value whose text is not a node, a duplicate key, a second `<<`
among them (the second spelling of a key is a mistake, not an
override; `<<: [*a, *b]` merges two), an alias to an anchor that is
not defined or is still being built (a cycle), aliases that together
expand to more than ALIAS_NODE_LIMIT nodes (the "billion laughs"
stream), a merge whose value is not a mapping or a sequence of
mappings, an explicit `?` key, an anchor or an alias on a block
mapping's key (a flow mapping's key may carry either), a collection
as a key, a second tag or a second anchor on one node, a tag it
does not know (the verbatim `!<tag:yaml.org,2002:str>` spellings of
the core tags are known), a directive not followed by `---`, and
nesting past DEPTH_LIMIT levels.YAML.parse? answers nil for any of these; YAML.parse panics with the sentence. YAML.dump writes a value back as block YAML that this parser reads to an equal value: numbers, booleans and nil stay what they are as values and as keys, a string that would read as something else is quoted, and a character a reader would not take raw (a control, a byte order mark, a 1.1 line break) is escaped. "Something else" includes what a YAML 1.1 reader makes of it, since PyYAML is what reads most of these files on the other end: yes, off, = and anything starting with a digit are quoted too. A collection cannot be written as a key.
derive yaml_serializable makes a type read and write itself as a mapping, a key per field, as std/json's derive serializable does for JSON; YAML.to_yaml, YAML.from_yaml, YAML.to_any and YAML.read_value are what it writes with.
Any holds a document: raw is the union, as_x/as_x? are the pairs, []/[]?/dig/dig? walk it, == is structural and hash agrees with it.
The type tests on Any#raw are ordered with the reference types first. A chain that tests Nil and Bool before Array/Hash over this union sends an array down the hash arm today (a compiler defect being fixed); every dispatch here goes through Any#kind, which is written in the order that works, and nothing else in the file tests the union.
yaml_serializablemacro
derive yaml_serializable, in a struct or class whose fields are written with their types: the type reads and writes itself as a YAML mapping, a key per field, named as the field is, in the order the fields are written.
pub struct Coord
getter x : Float64
getter name : String
derive yaml_serializable
end Coord.from_yaml("x: 1.5\nname: a\n").x # => 1.5
coord.to_yaml # => "x: 1.5\nname: a\n"It is std/json's derive serializable over YAML, field for field: a value is read and written as YAML.read_value and YAML.to_any read and write its type (the built-in scalars, an Array or Hash of them, an Any, another serializable type, each of them or nil), and to_yaml writes through YAML.dump, so a string that would read as something else is quoted. A key the type has no field for is passed over; a field with no key keeps its default when it is written with one, as does a field with a default whose key holds null where its type does not admit nil; a field with neither is refused by name unless its type admits nil, which it then holds. Anchors, aliases and << merges are the parser's, so a mapping merged into another fills its fields.
The macro is named for its format because a derive names a macro bare: with std/json's serializable imported as well, a second serializable was refused as "'serializable' is ambiguous here", and derive takes no qualified name. A type that is both writes derive serializable, yaml_serializable; what the two generate shares no method (to_json, from_json and initialize(pull) against to_yaml, from_yaml, to_yaml_any and initialize(yaml)).
pub macro yaml_serializable(declaration)Anystruct
The value a document holds.
pub struct Any
alias Type = Nil | Bool | Int64 | Float64 | String | Array(Any) | Hash(Any, Any)
def !=(other : Any) : Bool
# Structural: same kind, same value; a mapping equals a mapping with the
# same pairs in any order. `1` and `1.0` are different values.
def ==(other : Any) : Bool
def [](key : Any) : Any
def [](key : String) : Any
# An Int32 indexes a sequence and reaches an integer key of a mapping:
# `1: one` has the `Int64` key 1, and `doc[1]` panicked "expected a
# sequence, not a mapping" while `[]?` and `dig?` answered nil.
def [](index : Int32) : Any
def []?(key : Any) : Any | ::Nil
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
# A float, or an integer widened to one.
def as_f : Float64
def as_f? : Float64 | ::Nil
def as_h : Hash(Any, Any)
def as_h? : Hash(Any, Any) | ::Nil
def as_i : Int64
def as_i? : Int64 | ::Nil
def as_nil : Nil
def as_nil? : 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 `==`: the same kind and value hash the same, a mapping's
# hash does not depend on its order, and `0.0`/`-0.0` share one. A
# sequence mixes its items as an `Array` does, a mapping sums one mixed
# tuple hash per pair, and an integer hashes as an `Int64`. `31 * h +
# item`, a sum of `31 * key + value` and the integer's own low bits
# gave a 150 by 150 grid of `[i, j]` 4,769 hashes and of `{x: i, y: j}`
# 299, `{a: 1, b: 2}` the hash of `{a: 2, b: 1}` and -1 that of 0, and
# 40,000 such mappings took 987 ms to insert as keys; they take 19 ms.
def hash : Int32
def initialize(raw : Type)
def initialize(value : Int32)
def inspect : String
# Which member of the union `raw` is, as one of the KIND_ constants.
# Reference types are tested first on purpose (see the header).
def kind : Int32
# The name of the kind, for the sentences below.
def kind_name : String
def null? : Bool
def raw : Type
# Elements of a sequence, pairs of a mapping, characters of a string.
def size : Int32
# A string as itself, a number or boolean as written, nil as nothing,
# a collection the way the prelude prints an array or a hash.
def to_s : String
endParserclass
Reads one stream. Every failure is recorded once in @error and every loop returns as soon as one is recorded; nothing is raised inside.
pub class Parser
def error : String | ::Nil
def initialize(text : String)
# Every document in the stream, or nil with `error` set.
def parse_all : Array(Any) | ::Nil
endToYAMLtrait
A type that can write itself as an Any. YAML.to_yaml and YAML.to_any accept any value whose type implements it, as a derive yaml_serializable type does.
pub trait ToYAML
abstract def to_yaml_any : Any
endYAMLstruct
pub struct YAML
# Block YAML for `value`, ending in a line break.
def self.dump(value : Any) : String
# The document `text` holds, read into `type`: `Nil`, `Bool`, `Int32`,
# `Int64`, `UInt8`, `UInt64`, `Float64`, `String`, `Any`, a type with
# `derive yaml_serializable`, one of those or `nil`, or an `Array` or a
# `Hash` of those. Text that is not YAML panics as `parse` does; a value
# of another kind, or an integer outside the type, is refused.
def self.from_yaml(text : String, type : T.class) : T forall T
def initialize
def self.parse(text : String) : Any
# The first document of `text`, or nil when the stream is not YAML this
# module reads (the whole stream is checked, not just the first
# document). An empty stream is a null document.
def self.parse?(text : String) : Any | ::Nil
def self.parse_all(text : String) : Array(Any)
def self.parse_all?(text : String) : Array(Any) | ::Nil
def self.read_value(value : Any, type : Nil.class) : 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 : 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(K, V).class) : Hash(K, V) forall K, V
# A type that reads itself, the `initialize(yaml)` a `derive
# yaml_serializable` wrote; a type that admits nil reads null as the nil
# and anything else as the other type, as std/json's `read_value` does.
def self.read_value(value : Any, type : T.class) : T forall T
# The `Any` a value is written as: the value or `nil`, the way
# `JSON.to_json` takes it. A nil is taken out here, before the rest
# dispatches, for the reason std/json's `write_or_null` gives: handed to
# `write_value` whole, `Int32 | Nil` matches the `ToYAML` overload as
# one type and does not build.
def self.to_any(val : T) : Any forall T
# Block YAML for any value `to_any` knows, written as `dump` writes it.
def self.to_yaml(obj : T) : String forall T
def self.write_value(val : Nil) : Any
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 YAML integer is read as an `Int64`, and one past it as a float, so a
# `UInt64` above 9223372036854775807 would read back as a different
# number: it is refused rather than 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 : Any) : Any
def self.write_value(val : Array(T)) : Any forall T
# A key of any type `to_any` writes as a scalar: YAML keys are typed,
# so `Hash(Int32, String)` is written with integer keys and reads back
# as itself. A collection key is refused by `dump`.
def self.write_value(val : Hash(K, V)) : Any forall K, V
def self.write_value(val : T) : Any forall T : ToYAML
end
YAML, read and written in iyi alone: a parser and an emitter for the YAML 1.2 core schema over the bytes of a
String, with no library under them.