// Shared validation for the text and binary incremental writers.
///|
priv struct WriterFrame {
kind : IonType
mut count : Int32
mut field : SymbolToken?
}
///|
/// The position of the next value in an incremental writer.
pub struct WritePosition {
depth : Int32
parent : IonType?
index : Int32
field : SymbolToken?
annotations : Array[SymbolToken]
}
///|
/// Container, field, and annotation validation shared by incremental codecs.
struct WriterState {
frames : Array[WriterFrame]
mut annotations : Array[SymbolToken]
mut count : Int32
mut busy : Bool
mut failed : Bool
mut finished : Bool
}
///|
pub fn WriterState::new() -> WriterState {
{
frames: [],
annotations: [],
count: 0,
busy: false,
failed: false,
finished: false,
}
}
///|
pub fn WriterState::check(self : WriterState) -> Unit raise IonError {
if self.failed {
raise DataModel("incremental writer failed during output")
}
if self.finished {
raise DataModel("incremental writer is finished")
}
if self.busy {
raise DataModel("concurrent incremental writer operation")
}
}
///|
pub fn WriterState::set_field_name(
self : WriterState,
name : SymbolToken,
) -> Unit raise IonError {
self.check()
match self.frames.last() {
Some(frame) if frame.kind == Struct => {
if frame.field is Some(_) {
raise DataModel("field name already pending")
}
frame.field = Some(name)
}
_ => raise DataModel("field names require an open struct")
}
}
///|
pub fn WriterState::set_annotations(
self : WriterState,
annotations : Array[SymbolToken],
) -> Unit raise IonError {
self.check()
self.annotations = annotations.copy()
}
///|
/// Validate the next slot without consuming pending metadata.
pub fn WriterState::position(
self : WriterState,
) -> WritePosition raise IonError {
self.check()
match self.frames.last() {
Some(frame) => {
if frame.kind == Struct && frame.field is None {
raise DataModel("a struct value requires a field name")
}
{
depth: self.frames.length(),
parent: Some(frame.kind),
index: frame.count,
field: frame.field,
annotations: self.annotations,
}
}
None =>
{
depth: 0,
parent: None,
index: self.count,
field: None,
annotations: self.annotations,
}
}
}
///|
/// Consume the validated slot after successful encoding/output.
pub fn WriterState::commit(self : WriterState) -> Unit {
match self.frames.last() {
Some(frame) => {
frame.count += 1
frame.field = None
}
None => self.count += 1
}
self.annotations = []
}
///|
pub fn WriterState::enter(self : WriterState, kind : IonType) -> Unit {
self.commit()
self.frames.push({ kind, count: 0, field: None, })
}
///|
pub fn WriterState::closing(
self : WriterState,
) -> (IonType, Int32, Int32) raise IonError {
self.check()
if !self.annotations.is_empty() {
raise DataModel("annotations have no following value")
}
match self.frames.last() {
Some(frame) => {
if frame.field is Some(_) {
raise DataModel("field name has no following value")
}
(frame.kind, frame.count, self.frames.length() - 1)
}
None => raise DataModel("no container to step out of")
}
}
///|
pub fn WriterState::leave(self : WriterState) -> Unit {
ignore(self.frames.pop())
}
///|
pub fn WriterState::check_finished(self : WriterState) -> Unit raise IonError {
self.check()
if !self.frames.is_empty() {
raise DataModel("incremental writer has unclosed containers")
}
if !self.annotations.is_empty() {
raise DataModel("annotations have no following value")
}
}
///|
pub fn WriterState::finish(self : WriterState) -> Unit {
self.finished = true
}
///|
pub fn WriterState::begin_output(self : WriterState) -> Unit raise IonError {
self.check()
self.busy = true
}
///|
pub fn WriterState::end_output(self : WriterState) -> Unit {
self.busy = false
}
///|
pub fn WriterState::fail(self : WriterState) -> Unit {
self.failed = true
}
///|
/// Validate the container kind accepted by step-in.
pub fn check_container_kind(kind : IonType) -> Unit raise IonError {
match kind {
List | Sexp | Struct => ()
_ => raise DataModel("step-in requires list, sexp, or struct")
}
}