use o3::buffer::pool;
use crate::field::{
self,
metadata::{
block, lower,
section::{
rules,
state::decode::{Completion, Final, Indexed, Sink, Start},
storage,
},
strings,
},
};
pub struct Session<P: rules::Policy> {
section: P::State,
input: lower::Input<P::Block>,
scratch: storage::Scratch,
pending: Pending<<P::State as Sink<P>>::Rule, <P::State as Sink<P>>::Direct>,
}
#[derive(Clone, Copy)]
enum Pending<R, D> {
Ready,
Buffered(R, block::Name),
Direct(D),
}
impl<R, D> Pending<R, D> {
fn state(&self) -> lower::State {
match self {
Self::Ready => lower::State::Ready,
Self::Buffered(_, _) | Self::Direct(_) => lower::State::Pending,
}
}
fn try_ready(&self) -> Result<(), lower::Error> {
match self {
Self::Ready => Ok(()),
Self::Buffered(_, _) | Self::Direct(_) => {
Err(lower::Error::State(lower::State::Pending))
}
}
}
}
impl<P: rules::Policy> Session<P> {
pub fn try_new(
lease: pool::Span,
kind: rules::Kind,
connect: block::Connect,
mut scratch: storage::Scratch,
) -> Result<Self, (storage::Scratch, lower::Error)> {
let writer = match storage::Writer::try_new(lease) {
Ok(writer) => writer,
Err(cause) => return Err((scratch, lower::Error::Capacity { cause })),
};
scratch.clear();
Ok(Self {
section: <P::State as Sink<P>>::h2(kind, connect),
input: lower::Input::new(writer),
scratch,
pending: Pending::Ready,
})
}
pub fn validate(&mut self, check: block::Validation) {
rules::Validate::value(&mut self.section, check);
}
pub fn begin(
&mut self,
name: Option<block::Named<'_>>,
indexing: field::Indexing,
) -> Result<Start<(), ()>, lower::Error> {
self.pending.try_ready()?;
let Some(name) = name else {
self.input.try_ready()?;
return Ok(Start::Skip);
};
let start =
<P::State as Sink<P>>::begin(&mut self.section, &mut self.input, name, indexing)?;
Ok(match start {
Start::Scratch(done) => {
self.pending = Pending::Direct(done);
self.input.try_scratch(&mut self.scratch)?;
Start::Direct(())
}
Start::Direct(done) => {
self.pending = Pending::Direct(done);
Start::Direct(())
}
Start::Buffered(rule) => {
self.pending = Pending::Buffered(rule, name.proof());
Start::Buffered(())
}
Start::Skip => Start::Skip,
})
}
pub fn indexed(&mut self, name: Option<block::Named<'_>>) -> Result<Indexed<()>, lower::Error> {
self.pending.try_ready()?;
let Some(name) = name else {
self.input.try_ready()?;
return Ok(Indexed::Skip);
};
let indexed = <P::State as Sink<P>>::indexed(&mut self.section, &mut self.input, name)?;
Ok(match indexed {
Indexed::Skip => Indexed::Skip,
Indexed::Buffered(rule) => {
self.pending = Pending::Buffered(rule, name.proof());
Indexed::Buffered(())
}
})
}
pub fn field<A: rules::Push>(
&mut self,
name: block::Named<'_>,
value: block::Valued<'_>,
indexing: field::Indexing,
) -> Result<(), lower::Error> {
let Pending::Buffered(rule, expected) = self.pending else {
return Err(lower::Error::State(self.pending.state()));
};
let actual = name.proof();
if actual != expected {
let cause = lower::Error::Name {
expected: expected.head(),
actual: actual.head(),
};
return Err(cause);
}
rules::Validate::value(&mut self.section, value.validation());
<P::State as Sink<P>>::field::<A>(
&mut self.section,
&mut self.input,
&mut self.scratch,
rule,
name.span(),
value.span(),
indexing,
)?;
self.pending = Pending::Ready;
Ok(())
}
pub fn direct<A: rules::Push>(&mut self) -> Result<(), lower::Error> {
let Pending::Direct(done) = self.pending else {
return Err(lower::Error::State(self.pending.state()));
};
done.try_finish::<A>(&mut self.section, &mut self.input, &mut self.scratch)?;
self.pending = Pending::Ready;
Ok(())
}
pub fn string(&mut self, cursor: strings::Cursor, input: &[u8]) -> lower::Step {
self.input.string(&mut self.scratch, cursor, input)
}
pub fn rollback(&mut self) {
self.input.rollback(&mut self.scratch);
if self.pending.try_ready().is_err() {
rules::Validate::value(&mut self.section, block::Validation::Malformed);
self.pending = Pending::Ready;
}
}
pub fn finish(self) -> (storage::Scratch, Result<Final<P>, lower::Error>) {
if let Err(error) = self.pending.try_ready() {
return (self.recycle(), Err(error));
}
let Self {
section,
input,
mut scratch,
..
} = self;
let result = <P::State as Sink<P>>::seal(section, input, &mut scratch)
.map(Final::new)
.map_err(Into::into);
(scratch, result)
}
pub fn recycle(self) -> storage::Scratch {
let Self {
input, mut scratch, ..
} = self;
input.recycle(&mut scratch);
scratch
}
}