///|
fn yaml_tag(tag : @yaml.Tag?) -> String {
match tag {
None => ""
Some(t) =>
if t.handle == "tag:yaml.org,2002:" || t.handle == "!!" {
t.suffix
} else {
let full = t.handle + t.suffix
full.strip_prefix("tag:yaml.org,2002:").unwrap_or(full[:]).to_owned()
}
}
}
///|
fn Vm::yaml_scalar(
self : Vm,
s : String,
style : @yaml.TScalarStyle,
tag : @yaml.Tag?,
loc : Location,
) -> (Json, String, String) raise {
self.tick(loc)
let tag = yaml_tag(tag)
if tag == "binary" {
let cleaned = s.replace_all(old="\n", new="").replace_all(old="\r", new="")
let bytes = self.decode_base64(cleaned, loc) catch {
Error({ kind: Limit, .. }) as error => raise error
_ =>
raise problem(
Runtime,
loc,
yaml_conversion_error(
"yaml: !!binary value contains invalid base64 data",
),
)
}
let value = decode_go_utf8(bytes)
return (Json::string(value), "str", value)
}
if tag == "str" ||
(tag == "" && !(style is @yaml.Plain)) ||
(tag != "" && !["int", "float", "bool", "null", "timestamp"].contains(tag)) {
return (Json::string(s), "str", s)
}
let (value, kind, key) = yaml_resolve(s)
if tag == "timestamp" {
if !yaml_timestamp(s) {
raise problem(
Runtime,
loc,
yaml_conversion_error(
"yaml: cannot decode !!str `" + s + "` as a !!timestamp",
),
)
}
return (Json::string(s), "str", s)
}
if tag != "" &&
tag != kind &&
!(tag == "int" && kind == "uint") &&
!(tag == "float" && kind == "int") {
raise problem(
Runtime,
loc,
yaml_conversion_error(
"yaml: cannot decode !!" +
(if kind == "uint" { "int" } else { kind }) +
" `" +
s +
"` as a !!" +
tag,
),
)
}
if tag == "float" && kind == "int" && value is Number(n, ..) {
return (value, "float", yaml_float_key(n))
}
(value, kind, key)
}
///|
fn yaml_resolve(s : String) -> (Json, String, String) {
if ["y", "Y", "yes", "Yes", "YES", "true", "True", "TRUE", "on", "On", "ON"].contains(
s,
) {
return (Json::boolean(true), "bool", "true")
}
if [
"n", "N", "no", "No", "NO", "false", "False", "FALSE", "off", "Off", "OFF",
].contains(s) {
return (Json::boolean(false), "bool", "false")
}
if ["", "~", "null", "Null", "NULL"].contains(s) {
return (Json::null(), "null", "")
}
if [".nan", ".NaN", ".NAN"].contains(s) {
return (Json::number(0.0 / 0.0), "float", ".nan")
}
if [".inf", ".Inf", ".INF", "+.inf", "+.Inf", "+.INF"].contains(s) {
return (Json::number(1.0 / 0.0), "float", ".inf")
}
if ["-.inf", "-.Inf", "-.INF"].contains(s) {
return (Json::number(-1.0 / 0.0), "float", "-.inf")
}
if s.is_empty() || !"+-0123456789.".contains_char(s.to_array()[0]) {
return (Json::string(s), "str", s)
}
let plain = s.replace_all(old="_", new="")
if s.has_prefix(".") {
let chars = s.to_array()
for i, c in chars {
if c == '_' &&
(
i == 0 ||
i + 1 == chars.length() ||
chars[i - 1] < '0' ||
chars[i - 1] > '9' ||
chars[i + 1] < '0' ||
chars[i + 1] > '9'
) {
return (Json::string(s), "str", s)
}
}
}
if !s.has_prefix(".") {
let negative = plain.has_prefix("-")
let unsigned = if negative || plain.has_prefix("+") {
plain[1:].to_owned()
} else {
plain
}
let (base, digits) = if unsigned.has_prefix("0x") ||
unsigned.has_prefix("0X") {
(16, unsigned[2:].to_owned())
} else if unsigned.has_prefix("0o") || unsigned.has_prefix("0O") {
(8, unsigned[2:].to_owned())
} else if unsigned.has_prefix("0b") || unsigned.has_prefix("0B") {
(2, unsigned[2:].to_owned())
} else if unsigned.has_prefix("0") && unsigned.length() > 1 {
(8, unsigned[1:].to_owned())
} else {
(10, unsigned)
}
let integer = try @string.parse_uint64(digits[:], base~) catch {
_ => None
} noraise {
n => Some(n)
}
if integer is Some(n) &&
!digits.is_empty() &&
!digits.has_prefix("+") &&
!digits.has_prefix("-") {
if negative && n <= 9223372036854775808UL {
return (
Json::number(-n.to_double()),
"int",
if n == 0UL {
"0"
} else {
"-" + n.to_string()
},
)
} else if !negative {
return (
Json::number(n.to_double()),
if n > 9223372036854775807UL {
"uint"
} else {
"int"
},
n.to_string(),
)
}
}
}
let number = if yaml_float_syntax(plain) {
try @string.parse_double(plain[:]) catch {
_ => None
} noraise {
n => if n.is_inf() { None } else { Some(n) }
}
} else {
None
}
if number is Some(n) {
return (Json::number(n), "float", yaml_float_key(n))
}
(Json::string(s), "str", s)
}
///|
fn yaml_float_syntax(s : String) -> Bool {
let a = s.to_array()
let mut i = if !a.is_empty() && (a[0] == '+' || a[0] == '-') { 1 } else { 0 }
let mut digits = 0
while i < a.length() && a[i] >= '0' && a[i] <= '9' {
i += 1
digits += 1
}
if i < a.length() && a[i] == '.' {
i += 1
while i < a.length() && a[i] >= '0' && a[i] <= '9' {
i += 1
digits += 1
}
}
if digits == 0 {
return false
}
if i < a.length() && (a[i] == 'e' || a[i] == 'E') {
i += 1
if i < a.length() && (a[i] == '+' || a[i] == '-') {
i += 1
}
let start = i
while i < a.length() && a[i] >= '0' && a[i] <= '9' {
i += 1
}
if start == i {
return false
}
}
i == a.length()
}
///|
fn yaml_float_key(n : Double) -> String {
if n.is_nan() {
return ".nan"
}
if n.is_inf() {
return if n < 0 { "-.inf" } else { ".inf" }
}
let rounded = Float::from_double(n)
if rounded.is_inf() {
return if n < 0 { "-.inf" } else { ".inf" }
}
let scientific = @number.float32_shortest(n)
guard scientific.find("e") is Some(e) else { return scientific }
let exponent = try! @string.parse_int(scientific[e + 1:])
let negative = scientific.has_prefix("-")
let sign = if negative { "-" } else { "" }
let mantissa = scientific[if negative { 1 } else { 0 }:e].to_owned()
let digits = mantissa.replace_all(old=".", new="")
if exponent < -4 || exponent >= 6 {
return sign +
mantissa +
"e" +
(if exponent < 0 { "-" } else { "+" }) +
(if exponent.abs() < 10 { "0" } else { "" }) +
exponent.abs().to_string()
}
let point = exponent + 1
if point <= 0 {
sign + "0." + "0".repeat(-point) + digits
} else if point >= digits.length() {
sign + digits + "0".repeat(point - digits.length())
} else {
sign + digits[:point].to_owned() + "." + digits[point:].to_owned()
}
}
///|
fn yaml_timestamp(s : String) -> Bool {
let a = s.to_array()
let mut i = 0
let number = fn(min : Int, max : Int) {
let start = i
let mut n = 0
while i < a.length() && a[i] >= '0' && a[i] <= '9' && i - start < max {
n = n * 10 + a[i].to_int() - 48
i += 1
}
if i - start < min {
-1
} else {
n
}
}
let consume = fn(c : Char) {
if i < a.length() && a[i] == c {
i += 1
true
} else {
false
}
}
let year = number(4, 4)
if year < 0 || !consume('-') {
return false
}
let month = number(1, 2)
if !consume('-') {
return false
}
let day = number(1, 2)
if month < 1 || month > 12 {
return false
}
let days = [
31,
if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
29
} else {
28
},
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
]
if day < 1 || day > days[month - 1] {
return false
}
if i == a.length() {
return true
}
let zoned = consume('T') || consume('t')
if !zoned && !consume(' ') {
return false
}
let hour = number(1, 2)
if hour < 0 || hour > 23 || !consume(':') {
return false
}
let minute = number(1, 2)
if minute < 0 || minute > 59 || !consume(':') {
return false
}
let second = number(1, 2)
if second < 0 || second > 59 {
return false
}
if consume('.') || consume(',') {
let start = i
while i < a.length() && a[i] >= '0' && a[i] <= '9' {
i += 1
}
if i == start {
return false
}
}
if zoned {
if !consume('Z') {
if !(consume('+') || consume('-')) {
return false
}
let hour = number(2, 2)
if hour < 0 || hour > 24 || !consume(':') {
return false
}
let minute = number(2, 2)
if minute < 0 || minute > 60 {
return false
}
}
}
i == a.length()
}