use crate::error;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(super) enum Op {
Table,
Symbol,
Column,
At,
Flush,
}
impl Op {
const fn bit(self) -> u8 {
match self {
Op::Table => 1,
Op::Symbol => 1 << 1,
Op::Column => 1 << 2,
Op::At => 1 << 3,
Op::Flush => 1 << 4,
}
}
const fn descr(self) -> &'static str {
match self {
Op::Table => "table",
Op::Symbol => "symbol",
Op::Column => "column",
Op::At => "at",
Op::Flush => "flush",
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum OpCase {
Init,
TableWritten,
SymbolWritten,
ColumnWritten,
MayFlushOrTable,
}
impl OpCase {
const fn allowed_ops(self) -> u8 {
match self {
OpCase::Init => Op::Table.bit() | Op::Flush.bit(),
OpCase::TableWritten => Op::Symbol.bit() | Op::Column.bit(),
OpCase::SymbolWritten => Op::Symbol.bit() | Op::Column.bit() | Op::At.bit(),
OpCase::ColumnWritten => Op::Column.bit() | Op::At.bit(),
OpCase::MayFlushOrTable => Op::Flush.bit() | Op::Table.bit(),
}
}
#[inline(always)]
const fn allows(self, op: Op) -> bool {
self.allowed_ops() & op.bit() != 0
}
fn next_op_descr(self) -> &'static str {
match self {
OpCase::Init => "should have called `table` or `flush` instead",
OpCase::TableWritten => "should have called `symbol` or `column` instead",
OpCase::SymbolWritten => "should have called `symbol`, `column` or `at` instead",
OpCase::ColumnWritten => "should have called `column` or `at` instead",
OpCase::MayFlushOrTable => "should have called `flush` or `table` instead",
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(super) struct OpState {
op_case: OpCase,
}
impl OpState {
pub(super) const fn new() -> Self {
Self {
op_case: OpCase::Init,
}
}
#[inline(always)]
pub(super) fn check(self, op: Op) -> crate::Result<()> {
if self.op_case.allows(op) {
Ok(())
} else {
Err(self.bad_op_error(op))
}
}
#[cold]
#[inline(never)]
fn bad_op_error(self, op: Op) -> crate::Error {
error::fmt!(
InvalidApiCall,
"State error: Bad call to `{}`, {}.",
op.descr(),
self.op_case.next_op_descr()
)
}
pub(super) const fn can_set_marker(self) -> bool {
self.op_case.allows(Op::Table)
}
pub(super) fn ensure_marker_can_be_set(self) -> crate::Result<()> {
self.ensure_rewind_point_can_be_set("marker")
}
pub(super) fn ensure_bookmark_can_be_set(self) -> crate::Result<()> {
self.ensure_rewind_point_can_be_set("bookmark")
}
fn ensure_rewind_point_can_be_set(self, what: &'static str) -> crate::Result<()> {
if self.can_set_marker() {
Ok(())
} else {
Err(error::fmt!(
InvalidApiCall,
"Can't set the {} whilst constructing a line. \
A {} may only be set on an empty buffer or after `at` or \
`at_now` is called.",
what,
what
))
}
}
pub(super) fn missing_marker_error() -> crate::Error {
error::fmt!(InvalidApiCall, "Can't rewind to the marker: No marker set.")
}
pub(super) const fn allows_symbol(self) -> bool {
self.op_case.allows(Op::Symbol)
}
pub(super) fn record_table(&mut self) {
self.op_case = OpCase::TableWritten;
}
pub(super) fn record_symbol(&mut self) {
self.op_case = OpCase::SymbolWritten;
}
pub(super) fn record_column(&mut self) {
self.op_case = OpCase::ColumnWritten;
}
pub(super) fn finish_row(&mut self) {
self.op_case = OpCase::MayFlushOrTable;
}
}
#[cfg(test)]
mod tests {
use super::{Op, OpState};
use crate::ErrorCode;
#[test]
fn op_state_reports_exact_error_messages() {
let mut state = OpState::new();
state.check(Op::Flush).unwrap();
state.record_table();
let err = state.check(Op::Flush).unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidApiCall);
assert_eq!(
err.msg(),
"State error: Bad call to `flush`, should have called `symbol` or `column` instead."
);
state.record_symbol();
let err = state.check(Op::Flush).unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidApiCall);
assert_eq!(
err.msg(),
"State error: Bad call to `flush`, should have called `symbol`, `column` or `at` instead."
);
state.record_column();
let err = state.check(Op::Flush).unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidApiCall);
assert_eq!(
err.msg(),
"State error: Bad call to `flush`, should have called `column` or `at` instead."
);
state.finish_row();
let err = state.check(Op::Symbol).unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidApiCall);
assert_eq!(
err.msg(),
"State error: Bad call to `symbol`, should have called `flush` or `table` instead."
);
}
#[test]
fn op_state_tracks_marker_and_field_separator_rules() {
let mut state = OpState::new();
assert!(state.can_set_marker());
assert!(!state.allows_symbol());
state.record_table();
assert!(!state.can_set_marker());
assert!(state.allows_symbol());
state.record_symbol();
assert!(!state.can_set_marker());
assert!(state.allows_symbol());
state.record_column();
assert!(!state.can_set_marker());
assert!(!state.allows_symbol());
state.finish_row();
assert!(state.can_set_marker());
assert!(!state.allows_symbol());
}
#[test]
fn op_state_reports_marker_errors() {
let mut state = OpState::new();
state.record_table();
let err = state.ensure_marker_can_be_set().unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidApiCall);
assert_eq!(
err.msg(),
concat!(
"Can't set the marker whilst constructing a line. ",
"A marker may only be set on an empty buffer or after ",
"`at` or `at_now` is called."
)
);
let err = OpState::missing_marker_error();
assert_eq!(err.code(), ErrorCode::InvalidApiCall);
assert_eq!(err.msg(), "Can't rewind to the marker: No marker set.");
}
}