examples · three programs
Nothing here is a mock-up.
Three programs in src/examples/, each runnable with one
command, and the output each of them prints — captured from a real run,
not written by hand. If a number in this page were wrong, the program
next to it would disagree.
01 · the tour
Everything it reads, in one file.
A configuration file of the shape one is actually written in: block collections, a literal scalar that keeps its line breaks, an anchor reused further down, a flow sequence. Then the core schema deciding what an unquoted scalar is, a stream of two documents, and four refusals with the position each one is at.
moon run src/examples/tour
read as JSON:
{"server":{"host":"0.0.0.0","port":12000,"motd":"welcome\nto the party\n"},"limits":{"files":1000,"bytes":1048576},"upload":{"files":1000,"bytes":1048576},"tags":["http","native"]}
written back:
server:
host: 0.0.0.0
port: 12000
motd: |
welcome
to the party
limits: &a0
files: 1000
bytes: 1048576
upload: *a0
tags:
- http
- native
resolution:
[1,1.5,31,true,null,"yes","1"]
stream of 2 documents:
{"first":1}
{"second":2}
a: [1, 2 => 1:4: ends mid-document
a: *nope => 1:4: no anchor named `nope`
a: 1 ⏎ a: 2 => 2:1: the key `a` is written twice
a: !!nope 1 => {"a":1}
a: &a [x, x] ⏎ b: &b [*a, *a] ⏎ c: &c [*b, *b] ⏎ d: [*c, *c] ⏎ => 2:8: expands to 9 nodes, over 8
///|
/// Everything moonyaml reads, in one file.
///
/// moon run src/examples/tour
#coverage.skip
fn main {
run() catch {
e => println("refused: \{e}")
}
}
///|
fn run() -> Unit raise {
// A configuration file, of the shape one is actually written in: block
// collections, a literal scalar that keeps its line breaks, and an anchor
// reused further down.
let config =
#|server:
#| host: 0.0.0.0
#| port: 12000
#| motd: |
#| welcome
#| to the party
#|limits: &shared {files: 1000, bytes: 1048576}
#|upload: *shared
#|tags: [http, native]
#|
let tree = @moonyaml.loads(config)
println("read as JSON:")
println(" \{tree.stringify()}")
println("written back:")
for line in @moonyaml.dumps(tree).split("\n") {
if line.length() > 0 {
println(" \{line}")
}
}
// The core schema decides what an unquoted scalar is; quoting it makes it a
// string whatever it looks like.
let scalars =
#|[1, 1.5, 0x1f, true, ~, yes, "1"]
#|
println("resolution:")
println(" \{@moonyaml.loads(scalars).stringify()}")
// A stream is several documents, and each is read in its own right.
let stream =
#|---
#|first: 1
#|---
#|second: 2
#|...
#|
let docs = @moonyaml.loads_all(stream)
println("stream of \{docs.length()} documents:")
for d in docs {
println(" \{d.stringify()}")
}
// Ambiguity is refused with a position rather than guessed at.
show_refusal("a: [1, 2")
show_refusal("a: *nope")
show_refusal("a: 1\na: 2", flavor=@moonyaml.Flavor::new(duplicates=Reject))
// A tag from a vocabulary this reader does not have is not a fault: §10.4 says
// the node resolves as if it carried no tag, and refusing it would refuse the
// `!!python/object` in a file somebody else wrote.
println(" a: !!nope 1 => \{@moonyaml.loads("a: !!nope 1").stringify()}")
// And an alias that expands past its budget is refused too, which is what
// stops a dozen lines from becoming billions of nodes.
show_refusal(
"a: &a [x, x]\nb: &b [*a, *a]\nc: &c [*b, *b]\nd: [*c, *c]\n",
flavor=@moonyaml.Flavor::new(budget=8),
)
}
///|
/// Read a document that will not load, and print why.
fn show_refusal(
src : String,
flavor~ : @moonyaml.Flavor = @moonyaml.strict,
) -> Unit {
let one_line = src.replace_all(old="\n", new=" ⏎ ")
let said = @moonyaml.loads(src, flavor~).stringify() catch {
e => e.to_string()
}
println(" \{one_line} => \{said}")
}
Two things to notice. dumps invented the anchor name
&a0, because a value has no anchor names — the
Json tree only knows that upload and
limits are the same object. And the last line is the
"billion laughs" cut short: four lines of aliases that would expand to
sixteen nodes, refused the moment the count passes a budget of eight,
with the count and the limit in the message.
02 · the versions
One document, read five ways.
yes is a boolean in 1.1 and a string in 1.2.
014 is octal twelve in 1.1 and decimal fourteen in 1.2.
12:30 is a sexagesimal only in 1.1. The same file, five
readings, and not one of them is a bug:
moon run src/examples/versions
the document:
enabled: yes
mask: 014
starts: 12:30
when: 2001-12-15 02:59:43
read as:
1.2.2 (core schema): {"enabled":"yes","mask":14,"starts":"12:30","when":"2001-12-15 02:59:43"}
1.1 (legacy types, merge key): {"enabled":true,"mask":12,"starts":750,"when":"2001-12-15 02:59:43"}
1.0 (1.0 types): {"enabled":true,"mask":12,"starts":750,"when":"2001-12-15 02:59:43"}
and as StrictYAML, where every scalar is a string:
strict: {"enabled":"yes","mask":"014","starts":"12:30","when":"2001-12-15 02:59:43"}
strict, on flow style: refused — 1:4: flow style is not allowed here
strict, on an anchor: refused — 1:4: an anchor is not allowed here
and by the version the document asks for:
any, no directive: {"enabled":"yes","mask":14,"starts":"12:30","when":"2001-12-15 02:59:43"}
any, %YAML 1.1: {"enabled":true,"mask":12}
any, %YAML 1.2: {"enabled":"yes","mask":14}
// The same document, read by every version this library implements.
//
// moon run src/examples/versions
///|
#coverage.skip
fn main {
// A document written for no version in particular: a configuration file with a
// word, an octal and a time in it. Which of those are what they look like is
// the whole of the difference between the versions.
let src =
#|enabled: yes
#|mask: 014
#|starts: 12:30
#|when: 2001-12-15 02:59:43
#|
println("the document:")
for line in src.split("\n") {
if line.length() > 0 {
println(" " + line.to_owned())
}
}
println("")
println("read as:")
each("1.2.2 (core schema)", src, text => @moonyaml.loads(text))
each("1.1 (legacy types, merge key)", src, text => @yaml11.loads(text))
each("1.0 (1.0 types)", src, text => @yaml10.loads(text))
// StrictYAML is not a version of YAML with different scalars: it is a language
// that keeps the shape of YAML and drops everything that is ambiguous. Every
// scalar it reads is a string, and flow style, anchors, aliases, tags and a key
// written twice are refused.
println("")
println("and as StrictYAML, where every scalar is a string:")
each("strict", src, text => @strict.loads(text))
each("strict, on flow style", "a: [1, 2]", text => @strict.loads(text))
each("strict, on an anchor", "a: &x 1", text => @strict.loads(text))
// A stream's first document may say which version the rest are written for,
// which is what `any` reads to pick a schema; without a directive it is 1.2.2.
println("")
println("and by the version the document asks for:")
each("any, no directive", src, text => @any.loads(text))
each("any, %YAML 1.1", "%YAML 1.1\n---\nenabled: yes\nmask: 014\n", text => {
@any.loads(text)
})
each("any, %YAML 1.2", "%YAML 1.2\n---\nenabled: yes\nmask: 014\n", text => {
@any.loads(text)
})
}
///|
/// Read `src` one way and print what came back, or the refusal.
fn each(
name : String,
src : String,
read : (StringView) -> Json raise @moonyaml.Refused,
) -> Unit {
let said = read(src[:]).stringify() catch {
e => "refused — " + e.to_string()
}
println(" \{name}: \{said}")
}
The last three lines are any, which is the only reader
here that looks at the document to decide: %YAML 1.1 at
the top makes yes a boolean, %YAML 1.2
makes it a string, and no directive at all means 1.2.2.
03 · the document
Read it, write it, and get the file back.
parse gives a tree that keeps what the file said — the
comment above the document, the comment beside the image line, the
anchor, the alias standing in for its value, the flow style of
ports, the literal block scalar. emit writes
it back and the program compares the two strings:
moon run src/examples/document
the document:
# The deployment this file describes.
name: web
defaults: &defaults
replicas: 3
image: nginx:1.27
front:
<<: *defaults
image: nginx:1.27-alpine # the only difference
ports: [80, 443]
motd: |
one
two
written back: is it the same document?
yes, byte for byte
and what it means:
{"name":"web","defaults":{"replicas":3,"image":"nginx:1.27"},"front":{"<<":{"replicas":3,"image":"nginx:1.27"},"image":"nginx:1.27-alpine"},"ports":[80,443],"motd":"one\ntwo\n"}
a document built from a value, with an indent of four:
a: 1
b:
- x
- y: 2
written in flow style, ten deep:
[1, 2, 3]
// A document read as a document: what it says, not only what it means.
//
// moon run src/examples/document
///|
#coverage.skip
fn main raise {
let src =
#|# The deployment this file describes.
#|name: web
#|defaults: &defaults
#| replicas: 3
#| image: nginx:1.27
#|front:
#| <<: *defaults
#| image: nginx:1.27-alpine # the only difference
#|ports: [80, 443]
#|motd: |
#| one
#| two
#|
println("the document:")
for line in src.split("\n") {
if line.length() > 0 {
println(" " + line.to_owned())
}
}
// `parse` keeps the comments, the flow style of `ports`, the anchor on
// `defaults` and the alias in place of its value — everything `loads` folds
// into a `Json` tree on the way to being a value.
let doc = @moonyaml.parse(src)
println("")
println("written back: is it the same document?")
let written = @moonyaml.emit(doc)
println(if written == src { " yes, byte for byte" } else { " no:" })
if written != src {
for line in written.split("\n") {
println(" " + line.to_owned())
}
}
// What it means, for a caller who wants the value: the merge key folded in, the
// alias resolved, the tags gone.
println("")
println("and what it means:")
println(" " + doc.to_json().stringify())
// A tree built from a value is a document too, and it writes as one.
println("")
println("a document built from a value, with an indent of four:")
let value = @moonyaml.loads(
(
#|a: 1
#|b:
#| - x
#| - y: 2
#|
),
)
let built = @moonyaml.Node::of_json(value)
for line in @moonyaml.emit(built, indent=4).split("\n") {
if line.length() > 0 {
println(" " + line.to_owned())
}
}
// The writer's other knob: how far a line may run before a collection is written
// as a block instead.
println("")
println("written in flow style, ten deep:")
let list = @moonyaml.loads(
(
#|- 1
#|- 2
#|- 3
#|
),
)
println(
" " +
@moonyaml.dumps(list, style=@moonyaml.flow).replace_all(old="\n", new=" "),
)
}
"yes, byte for byte" is printed only when
emit(parse(src)) == src. Below it, the same document
through to_json — which is where the alias stops being an
alias and the merge key is folded in — then a tree built from a value
at indent=4, then the writer in flow style.
04 · run them
Three commands.
# everything it reads, in one file
moon run src/examples/tour
# one document, read five ways
moon run src/examples/versions
# the document model, and a round trip
moon run src/examples/document