///|
/// The stable diagnostic codes of the YAML reader, writer and converter.
pub let diagnostic_codes : ReadOnlyArray[String] = [
"yaml/text-without-key", "yaml/unsupported-folded", "yaml/unsupported-item-block",
"yaml/mixed-collection", "yaml/indentation", "yaml/unsupported-nested-list", "yaml/missing-value",
"yaml/no-data", "yaml/duplicate-key", "yaml/depth-limit", "yaml/node-limit", "yaml/work-limit",
"yaml/output-limit", "yaml/invalid-limits", "yaml/diagnostic-limit", "yaml/unrepresentable-value",
"yaml/unrepresentable-document", "yaml/invalid-highlight", "yaml/unresolved-highlight",
"yaml/ambiguous-highlight", "yaml/unapplied-highlight-style",
]
///|
/// The result of reading a YAML body.
pub struct ReadResult {
/// The body nodes in source order, with shared statement nodes.
nodes : Array[@cst.Node]
document : Document?
diagnostics : ReadOnlyArray[@diagrams.Diagnostic]
}
///|
fn is_blank(text : String) -> Bool {
java_trim(text).is_empty()
}
///|
/// A block content line: leading space, trimmed text, trailing space.
fn content_node(line : @envelope.Line) -> @cst.Node {
let (a, b) = trim_range(line.text, 0, line.text.length())
let marks = if b > a { [(a, b, @cst.TokenKind::Text)] } else { [] }
let origin = Array::makei(line.text.length() + 1, i => i)
@cst.Node::new(Data, marks_to_tokens(line, line.text.length(), origin, marks))
}
///|
fn blank_node(line : @envelope.Line) -> @cst.Node {
let origin = Array::makei(line.text.length() + 1, i => i)
@cst.Node::new(Blank, marks_to_tokens(line, line.text.length(), origin, []))
}
///|
/// Read the YAML body of a scanned block with the pinned native line rules.
/// Reading keeps every line form, including forms that conversion refuses,
/// such as folded text. A `key: |` line takes the following lines that are
/// text, comments or deeper than the key; blank lines are not content, as
/// upstream removes them before the YAML parser runs. Tabs count as four
/// spaces. Comments are ` #` outside quotes, or `#` at the start.
pub fn read(
scan : @envelope.Scan,
limits~ : @envelope.Limits = @envelope.Limits::new(),
) -> ReadResult {
let diagnostics = scan.diagnostics.iter().to_array()
fn report(diagnostic : @diagrams.Diagnostic) {
if diagnostics.length() < limits.diagnostics {
diagnostics.push(diagnostic)
} else if diagnostics.length() == limits.diagnostics {
diagnostics.push(
@diagrams.Diagnostic::error(
"yaml/diagnostic-limit", "Diagnostic limit reached",
),
)
}
}
let made : Map[Int32, @cst.Node] = Map([])
let lines : Array[Line] = []
let bodies = scan.lines.iter().filter(line => line.role is Body).to_array()
let mut work = 0
let mut stopped = false
let mut i = 0
while i < bodies.length() {
let line = bodies[i]
work += line.text.length() + 1
if work > limits.work {
report(
@diagrams.Diagnostic::error(
"yaml/work-limit", "YAML reading exceeds its work limit",
),
)
stopped = true
break
}
if is_blank(line.text) {
made[line.offset] = blank_node(line)
i += 1
continue
}
let (e, origin, class) = classify_line(line.text)
let tokens = line_tokens(line, e, origin, class)
guard class.content() is Some(content) else {
made[line.offset] = @cst.Node::new(Comment, tokens)
i += 1
continue
}
made[line.offset] = @cst.Node::new(Data, tokens)
if lines.length() >= limits.nodes {
report(
@diagrams.Diagnostic::error(
"yaml/node-limit",
"YAML data exceeds the node limit",
span=scan.span(line.offset, line.offset + line.text.length()),
),
)
stopped = true
break
}
let mut end = line.offset + line.text.length()
let mut content = content
i += 1
if class.ty == Block {
let native = class.native_indent()
let block = []
while i < bodies.length() {
let next = bodies[i]
if is_blank(next.text) {
made[next.offset] = blank_node(next)
i += 1
continue
}
let (_, _, inner) = classify_line(next.text)
if inner.ty != TextOnly &&
inner.ty != Empty &&
inner.native_indent() <= native {
break
}
work += next.text.length() + 1
block.push(java_trim(next.text))
made[next.offset] = content_node(next)
end = next.offset + next.text.length()
i += 1
}
if content is Pair(item, key, _) {
content = Pair(item, key, Block(ReadOnlyArray::from_array(block)))
}
}
lines.push({
indent: class.count,
content,
span: Some(scan.span(line.offset, end)),
})
}
if stopped {
for line in bodies {
if !made.contains(line.offset) {
let origin = Array::makei(line.text.length() + 1, i => i)
made[line.offset] = @cst.Node::new(
Data,
marks_to_tokens(line, line.text.length(), origin, [
(0, line.text.length(), Text),
]),
)
}
}
}
let nodes = scan.line_nodes(line => made.get(line.offset))
let (selectors, highlight_issues) = @data.read_selectors(scan, "yaml")
for diagnostic in highlight_issues {
report(diagnostic)
}
if !limits.valid() {
report(
@diagrams.Diagnostic::error(
"yaml/invalid-limits", "YAML limits must be positive",
),
)
}
let failed = stopped || diagnostics.iter().any(d => d.severity == Error)
{
nodes,
document: if failed {
None
} else {
Some({
name: scan.name,
statements: scan.statements,
highlights: ReadOnlyArray::from_array(selectors),
lines: ReadOnlyArray::from_array(lines),
})
},
diagnostics: ReadOnlyArray::from_array(diagnostics),
}
}