205 lines
4.8 KiB
V
205 lines
4.8 KiB
V
module yaml
|
|
|
|
import strings
|
|
|
|
// write_spaces appends `n` spaces to `sb`.
|
|
fn write_spaces(mut sb strings.Builder, n int) {
|
|
for _ in 0 .. n {
|
|
sb.write_u8(` `)
|
|
}
|
|
}
|
|
|
|
// emit_yaml_any streams `value` into `sb` as block-style YAML.
|
|
fn emit_yaml_any(mut sb strings.Builder, value Any, indent int) {
|
|
match value {
|
|
map[string]Any { emit_yaml_map(mut sb, value, indent) }
|
|
[]Any { emit_yaml_array(mut sb, value, indent) }
|
|
else { emit_yaml_scalar(mut sb, value) }
|
|
}
|
|
}
|
|
|
|
// emit_yaml_map writes `value` as a block-style YAML mapping. An empty map
|
|
// is emitted as the inline `{}` form; otherwise each key is JSON-quoted (see
|
|
// `write_json_escaped_string`) and nested containers indent one level deeper.
|
|
fn emit_yaml_map(mut sb strings.Builder, value map[string]Any, indent int) {
|
|
if value.len == 0 {
|
|
sb.write_string('{}')
|
|
return
|
|
}
|
|
mut first := true
|
|
for key, item in value {
|
|
if !first {
|
|
sb.write_u8(`\n`)
|
|
}
|
|
first = false
|
|
write_spaces(mut sb, indent)
|
|
write_json_escaped_string(mut sb, key)
|
|
match item {
|
|
map[string]Any, []Any {
|
|
sb.write_u8(`:`)
|
|
sb.write_u8(`\n`)
|
|
emit_yaml_any(mut sb, item, indent + 2)
|
|
}
|
|
else {
|
|
sb.write_string(': ')
|
|
emit_yaml_scalar(mut sb, item)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// emit_yaml_array writes `value` as a block-style YAML sequence. An empty
|
|
// array is emitted as the inline `[]` form; otherwise each item is prefixed
|
|
// with `- ` and nested containers indent one level deeper.
|
|
fn emit_yaml_array(mut sb strings.Builder, value []Any, indent int) {
|
|
if value.len == 0 {
|
|
sb.write_string('[]')
|
|
return
|
|
}
|
|
mut first := true
|
|
for item in value {
|
|
if !first {
|
|
sb.write_u8(`\n`)
|
|
}
|
|
first = false
|
|
write_spaces(mut sb, indent)
|
|
match item {
|
|
map[string]Any, []Any {
|
|
sb.write_u8(`-`)
|
|
sb.write_u8(`\n`)
|
|
emit_yaml_any(mut sb, item, indent + 2)
|
|
}
|
|
else {
|
|
sb.write_string('- ')
|
|
emit_yaml_scalar(mut sb, item)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// emit_yaml_scalar writes `value` as a single YAML scalar token: strings go
|
|
// through `write_json_escaped_string`, booleans / numbers / null print their
|
|
// literal form. The container branch is type-required by V's exhaustive
|
|
// `match` over `Any` but is unreachable: `emit_yaml_any` routes maps and
|
|
// arrays to their dedicated emitters before falling back here.
|
|
fn emit_yaml_scalar(mut sb strings.Builder, value Any) {
|
|
match value {
|
|
string { write_json_escaped_string(mut sb, value) }
|
|
bool { sb.write_string(if value { 'true' } else { 'false' }) }
|
|
f64 { sb.write_string(value.str()) }
|
|
i64 { sb.write_string(value.str()) }
|
|
int { sb.write_string(value.str()) }
|
|
u64 { sb.write_string(value.str()) }
|
|
Null { sb.write_string('null') }
|
|
[]Any, map[string]Any { emit_yaml_any(mut sb, value, 0) }
|
|
}
|
|
}
|
|
|
|
// write_json_escaped_string writes `value` as a JSON string literal directly
|
|
// into `sb`. Matches `json2.encode`'s rules: standard short escapes for control
|
|
// chars, `\u00XX` for the rest below 0x20, and UTF-8 bytes passed through
|
|
// verbatim (no per-byte `\uXXXX` re-escape). Passes safe runs through in bulk
|
|
// via `write_string` on the original slice — the overwhelmingly common case for
|
|
// human YAML — and only switches to per-byte handling at escape boundaries.
|
|
fn write_json_escaped_string(mut sb strings.Builder, value string) {
|
|
sb.write_u8(`"`)
|
|
mut start := 0
|
|
for i := 0; i < value.len; i++ {
|
|
c := value[i]
|
|
if c >= 0x20 && c != `"` && c != `\\` {
|
|
continue
|
|
}
|
|
if start < i {
|
|
sb.write_string(value[start..i])
|
|
}
|
|
match c {
|
|
`"` {
|
|
sb.write_string('\\"')
|
|
}
|
|
`\\` {
|
|
sb.write_string('\\\\')
|
|
}
|
|
`\n` {
|
|
sb.write_string('\\n')
|
|
}
|
|
`\r` {
|
|
sb.write_string('\\r')
|
|
}
|
|
`\t` {
|
|
sb.write_string('\\t')
|
|
}
|
|
`\b` {
|
|
sb.write_string('\\b')
|
|
}
|
|
`\f` {
|
|
sb.write_string('\\f')
|
|
}
|
|
else {
|
|
sb.write_string('\\u00')
|
|
hex := '0123456789abcdef'
|
|
sb.write_u8(hex[(c >> 4) & 0xf])
|
|
sb.write_u8(hex[c & 0xf])
|
|
}
|
|
}
|
|
|
|
start = i + 1
|
|
}
|
|
if start < value.len {
|
|
sb.write_string(value[start..])
|
|
}
|
|
sb.write_u8(`"`)
|
|
}
|
|
|
|
// emit_any_as_json writes `a` as a compact JSON document.
|
|
fn emit_any_as_json(mut sb strings.Builder, a Any) {
|
|
match a {
|
|
map[string]Any {
|
|
sb.write_u8(`{`)
|
|
mut first := true
|
|
for key, value in a {
|
|
if !first {
|
|
sb.write_u8(`,`)
|
|
}
|
|
first = false
|
|
write_json_escaped_string(mut sb, key)
|
|
sb.write_u8(`:`)
|
|
emit_any_as_json(mut sb, value)
|
|
}
|
|
sb.write_u8(`}`)
|
|
}
|
|
[]Any {
|
|
sb.write_u8(`[`)
|
|
mut first := true
|
|
for value in a {
|
|
if !first {
|
|
sb.write_u8(`,`)
|
|
}
|
|
first = false
|
|
emit_any_as_json(mut sb, value)
|
|
}
|
|
sb.write_u8(`]`)
|
|
}
|
|
string {
|
|
write_json_escaped_string(mut sb, a)
|
|
}
|
|
bool {
|
|
sb.write_string(if a { 'true' } else { 'false' })
|
|
}
|
|
f64 {
|
|
sb.write_string(a.str())
|
|
}
|
|
i64 {
|
|
sb.write_string(a.str())
|
|
}
|
|
int {
|
|
sb.write_string(a.str())
|
|
}
|
|
u64 {
|
|
sb.write_string(a.str())
|
|
}
|
|
Null {
|
|
sb.write_string('null')
|
|
}
|
|
}
|
|
}
|