///|
fn parse_node_props(
ctx : TagContext,
scanner : Scanner,
) -> (String?, String?) raise ParseError {
let mut tag : String? = None
let mut anchor : String? = None
let mut progressed = true
while progressed {
progressed = false
if scanner.eat('!') {
tag = Some(read_tag(ctx, scanner))
progressed = true
} else if scanner.eat('&') {
anchor = Some(read_anchor(scanner))
progressed = true
}
if progressed {
scanner.skip_ws_and_comments()
}
}
(tag, anchor)
}
///|
fn parse_alias(scanner : Scanner) -> String? raise ParseError {
if scanner.eat('*') {
Some(read_anchor(scanner))
} else {
None
}
}
///|
fn apply_node_props(
value : Yaml,
tag : String?,
anchor : String?,
coerce_binary : Bool,
) -> Yaml raise ParseError {
let (base, existing_tag, existing_anchor) = extract_node_props(value)
let final_tag = match tag {
Some(t) =>
match existing_tag {
Some(_) =>
raise ParseError::InvalidTagValue(tag="tag", value="")
None => Some(t)
}
None => existing_tag
}
let final_anchor = match anchor {
Some(a) =>
match existing_anchor {
Some(_) =>
raise ParseError::InvalidTagValue(
tag="anchor",
value="",
)
None => Some(a)
}
None => existing_anchor
}
let mut node = base
match final_tag {
None => ()
Some(t) => {
if !coerce_binary && is_core_tag(t, "binary") {
node = node
} else {
node = coerce_tag(t, node)
}
node = Yaml::Tagged(tag=t, value=node)
}
}
match final_anchor {
None => ()
Some(a) => node = Yaml::Anchor(name=a, value=node)
}
node
}
///|
fn extract_node_props(
value : Yaml,
) -> (Yaml, String?, String?) raise ParseError {
match value {
Anchor(name~, value~) => {
let (base, tag, anchor) = extract_node_props(value)
if anchor is Some(_) {
raise ParseError::InvalidTagValue(
tag="anchor",
value="",
)
}
(base, tag, Some(name))
}
Tagged(tag~, value~) => {
let (base, existing_tag, anchor) = extract_node_props(value)
if existing_tag is Some(_) {
raise ParseError::InvalidTagValue(tag="tag", value="")
}
(base, Some(tag), anchor)
}
_ => (value, None, None)
}
}
///|
///|
fn coerce_tag(tag : String, value : Yaml) -> Yaml raise ParseError {
if is_core_tag(tag, "null") {
return Yaml::Null
}
if is_core_tag(tag, "bool") {
match value {
Bool(_) => value
String(s) => parse_tagged_bool(tag, s)
_ => value
}
} else if is_core_tag(tag, "int") {
match value {
Int(_) => value
String(s) => parse_tagged_int(tag, s)
_ => value
}
} else if is_core_tag(tag, "float") {
match value {
Float(_) => value
String(s) => parse_tagged_float(tag, s)
_ => value
}
} else if is_core_tag(tag, "binary") {
match value {
Binary(_) => value
String(s) => parse_tagged_binary(tag, s)
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
} else if is_core_tag(tag, "timestamp") {
match value {
String(s) => parse_tagged_timestamp(tag, s)
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
} else if is_core_tag(tag, "map") {
validate_tagged_map(tag, value)
} else if is_core_tag(tag, "seq") {
validate_tagged_seq(tag, value)
} else if is_core_tag(tag, "set") {
validate_tagged_set(tag, value)
} else if is_core_tag(tag, "omap") {
validate_tagged_omap(tag, value)
} else if is_core_tag(tag, "pairs") {
validate_tagged_pairs(tag, value)
} else if is_core_tag(tag, "str") {
value
} else {
value
}
}
///|
fn parse_tagged_bool(tag : String, raw : String) -> Yaml raise ParseError {
let lowered = raw.to_lower()
if lowered is "true" {
Yaml::Bool(true)
} else if lowered is "false" {
Yaml::Bool(false)
} else {
raise ParseError::InvalidTagValue(tag~, value=raw)
}
}
///|
fn parse_tagged_int(tag : String, raw : String) -> Yaml raise ParseError {
match parse_number(raw) {
Some(Yaml::Int(v)) => Yaml::Int(v)
_ => raise ParseError::InvalidTagValue(tag~, value=raw)
}
}
///|
fn parse_tagged_float(tag : String, raw : String) -> Yaml raise ParseError {
match parse_number(raw) {
Some(Yaml::Float(v)) => Yaml::Float(v)
Some(Yaml::Int(v)) => Yaml::Float(Double::from_int(v))
_ => raise ParseError::InvalidTagValue(tag~, value=raw)
}
}
///|
fn parse_tagged_binary(tag : String, raw : String) -> Yaml raise ParseError {
Yaml::Binary(decode_base64(tag, raw))
}
///|
fn validate_tagged_map(tag : String, value : Yaml) -> Yaml raise ParseError {
match value {
Map(_) => value
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
}
///|
fn validate_tagged_seq(tag : String, value : Yaml) -> Yaml raise ParseError {
match value {
Seq(_) => value
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
}
///|
fn validate_tagged_set(tag : String, value : Yaml) -> Yaml raise ParseError {
match value {
Map(items) => {
for pair in items {
if pair.1 != Yaml::Null && pair.1 != Yaml::String("") {
raise ParseError::InvalidTagValue(tag~, value="")
}
}
value
}
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
}
///|
fn validate_tagged_pairs(tag : String, value : Yaml) -> Yaml raise ParseError {
match value {
Seq(items) => {
for item in items {
match item {
Map(entries) =>
if entries.length() != 1 {
raise ParseError::InvalidTagValue(tag~, value="")
}
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
}
value
}
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
}
///|
fn validate_tagged_omap(tag : String, value : Yaml) -> Yaml raise ParseError {
match value {
Seq(items) => {
let seen : Array[Yaml] = []
for item in items {
match item {
Map(entries) => {
if entries.length() != 1 {
raise ParseError::InvalidTagValue(tag~, value="")
}
let key = entries[0].0
if find_yaml_index(seen, key) is Some(_) {
raise ParseError::InvalidTagValue(tag~, value="")
}
seen.push(key)
}
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
}
value
}
_ => raise ParseError::InvalidTagValue(tag~, value="")
}
}
///|
fn find_yaml_index(items : Array[Yaml], key : Yaml) -> Int32? {
for i in 0.. String raise ParseError {
if scanner.eat('<') {
return read_verbatim_tag(scanner)
}
let sb = StringBuilder::new()
while scanner.peek() is Some(c) && !is_prop_break(c) {
ignore(scanner.advance())
sb.write_char(c)
}
let body = sb.to_string()
let full = if body is "" { "!" } else { "!" + body }
for c in body {
if c is '{' || c is '}' || c is '[' || c is ']' {
raise ParseError::InvalidTagValue(tag="tag", value=full)
}
}
ctx.expand(full)
}
///|
fn read_verbatim_tag(scanner : Scanner) -> String raise ParseError {
let sb = StringBuilder::new()
while true {
match scanner.peek() {
None => raise ParseError::UnexpectedEof(pos=scanner.position())
Some('>') => {
ignore(scanner.advance())
return sb.to_string()
}
Some(c) => {
ignore(scanner.advance())
sb.write_char(c)
}
}
} nobreak {
sb.to_string()
}
}
///|
fn is_core_tag(tag : String, name : String) -> Bool {
let full = "tag:yaml.org,2002:" + name
let short = "!!" + name
let verbatim = "!"
tag == full || tag == short || tag == verbatim
}
///|
///|
fn read_anchor(scanner : Scanner) -> String raise ParseError {
let sb = StringBuilder::new()
while scanner.peek() is Some(c) && is_anchor_char(c) {
ignore(scanner.advance())
sb.write_char(c)
}
let name = sb.to_string()
if name is "" {
raise ParseError::UnexpectedChar(pos=scanner.position(), found=' ')
}
name
}
///|
fn is_anchor_char(c : Char) -> Bool {
if c.is_whitespace() {
return false
}
!is_anchor_break(c)
}
///|
fn is_anchor_break(c : Char) -> Bool {
c is '[' || c is ']' || c is '{' || c is '}' || c is ',' || c is '#'
}
///|
fn is_prop_break(c : Char) -> Bool {
c.is_whitespace() || c is ',' || c is ':' || c is ']' || c is '}' || c is '#'
}