#![no_std]
#![forbid(unsafe_code)]
#![warn(
// Groups
future_incompatible,
nonstandard_style,
rust_2018_compatibility, // unsure if needed with Cargo.toml having edition="2018"
rust_2018_idioms,
unused,
clippy::all,
clippy::pedantic,
// Individual lints not included in above groups and desired.
macro_use_extern_crate,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
// missing_doc_code_examples, // maybe someday
private_doc_tests,
// single_use_lifetimes, // annoying hits on invisible derived impls
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_results,
variant_size_differences,
)]
#![allow(
explicit_outlives_requirements, // annoying hits on invisible derived impls
clippy::non_ascii_literal,
)]
use parser::{CharClassifier, DatumAllocator, AllocError, OperatorBindings};
mod error;
#[doc(inline)]
pub use error::Error;
pub mod datum;
#[doc(inline)]
pub use datum::{Datum, DerefTryMut};
pub mod text;
#[doc(inline)]
pub use text::{Text, TextBase, TextConcat, TextChunk};
pub mod combiner;
#[doc(inline)]
pub use combiner::Combiner;
pub mod parser;
pub mod premade {
pub mod inmem;
impl super::SourcePosition for () {
#[inline] fn empty() -> Self { }
}
}
pub trait SourcePosition
where Self: Clone,
{
fn empty() -> Self;
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct SourceIterItem<SourcePosition> {
pub ch: char,
pub pos: SourcePosition,
}
pub trait SourceStream<DA>: Iterator<Item = SourceIterItem<<DA::TT as TextBase>::Pos>>
where DA: DatumAllocator,
{
fn peek(&mut self) -> Option<&<Self as Iterator>::Item>;
fn next_accum(&mut self, dalloc: &mut DA)
-> Result<Option<<Self as Iterator>::Item>,
AllocError>;
fn accum_done(&mut self, dalloc: &mut DA) -> Result<DA::TT, AllocError>;
}
#[derive(Debug)]
pub struct Parser<CC, DA, OB> {
pub classifier: CC,
pub allocator: DA,
pub bindings: OB,
}
impl<CC, DA, OB> Parser<CC, DA, OB>
where CC: CharClassifier,
DA: DatumAllocator,
DA::TT: TextConcat<DA>,
OB: OperatorBindings<DA>,
{
#[inline]
pub fn parse<S>(&mut self, source: S) -> ParseIter<'_, Self, S>
where S: SourceStream<DA>,
{
ParseIter::new(self, source)
}
}
#[derive(Debug)]
pub struct ParseIter<'p, Prsr, SrcStrm> {
parser: &'p mut Prsr,
src_strm: SrcStrm,
nest_depth: usize,
}
impl<'p, CC, DA, OB, S>
Iterator
for ParseIter<'p, Parser<CC, DA, OB>, S>
where CC: CharClassifier,
DA: DatumAllocator,
DA::TT: TextConcat<DA>,
OB: OperatorBindings<DA>,
Parser<CC, DA, OB>: 'p,
S: SourceStream<DA>,
{
type Item = ParseIterItem<DA, OB>;
fn next(&mut self) -> Option<Self::Item> {
self.do_next().transpose()
}
}
pub type ParseIterItem<DA, OB> = ParseResult<DA, OB>;
type ParseDatum<DA> = Datum<<DA as DatumAllocator>::TT,
<DA as DatumAllocator>::ET,
<DA as DatumAllocator>::DR>;
type ParseError<DA, OB> = Error<<<DA as DatumAllocator>::TT as TextBase>::Pos,
<OB as OperatorBindings<DA>>::CE>;
type ParseResult<DA, OB> = Result<ParseDatum<DA>, ParseError<DA, OB>>;
type ParseResultOption<DA, OB> = Result<Option<ParseDatum<DA>>, ParseError<DA, OB>>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum ParseTextMode {
Base,
Operator,
Operands,
}
impl<'p, CC, DA, OB, S>
ParseIter<'p, Parser<CC, DA, OB>, S>
where CC: CharClassifier,
DA: DatumAllocator,
DA::TT: TextConcat<DA>,
OB: OperatorBindings<DA>,
Parser<CC, DA, OB>: 'p,
S: SourceStream<DA>,
{
#[inline]
fn new(parser: &'p mut Parser<CC, DA, OB>, src_strm: S) -> Self {
Self {
parser,
src_strm,
nest_depth: 0,
}
}
#[inline]
fn do_next(&mut self) -> ParseResultOption<DA, OB> {
Self::parse_next(ParseTextMode::Base,
&mut self.src_strm,
&mut self.nest_depth,
&mut self.parser.allocator,
&self.parser.classifier,
&self.parser.bindings)
}
fn parse_next(
mode: ParseTextMode,
srcstrm: &mut S,
ndepth: &mut usize,
dalloc: &mut DA,
chcls: &CC,
bindings: &OB,
)
-> ParseResultOption<DA, OB>
{
loop {
if mode == ParseTextMode::Operator {
Self::skip_whitespace(srcstrm, chcls);
}
let ch = match srcstrm.peek() {
Some(&SourceIterItem{ch, ..}) => ch,
None =>
return if *ndepth == 0 {
Ok(None)
} else {
Err(Error::MissingEndChar)
}
};
if chcls.is_nest_start(ch) {
*ndepth += 1;
let result = Self::parse_nested(srcstrm, ndepth,
dalloc, chcls, bindings);
*ndepth -= 1;
if let Ok(None) = &result {
continue
} else {
return result
}
}
else if chcls.is_nest_end(ch) {
return Self::check_end_char(srcstrm, *ndepth, chcls).map(|_| None)
}
else {
return Self::parse_text(mode, srcstrm, ndepth, dalloc, chcls)
.map(|text| Some(Datum::Text(text)))
}
}
}
#[allow(unused_results)]
fn parse_text(
mode: ParseTextMode,
srcstrm: &mut S,
ndepth: &mut usize,
dalloc: &mut DA,
chcls: &CC,
)
-> Result<DA::TT, ParseError<DA, OB>>
{
#[inline]
fn is_end_char<CC>(ch: char, chclass: &CC, mode: ParseTextMode) -> bool
where CC: CharClassifier,
{
match mode {
ParseTextMode::Base
=> chclass.is_nest_start(ch),
ParseTextMode::Operator
=> chclass.is_whitespace(ch)
|| chclass.is_nest_start(ch)
|| chclass.is_nest_end(ch),
ParseTextMode::Operands
=> chclass.is_nest_end(ch),
}
}
let mut text = DA::TT::empty();
macro_rules! concat_accum {
() => {
let accum = srcstrm.accum_done(dalloc)?;
text = text.concat(accum, dalloc)?;
}
}
let mut nest_level: usize = 0;
while let Some(&SourceIterItem{ch, ..}) = srcstrm.peek() {
if nest_level == 0 && is_end_char(ch, chcls, mode) {
break;
}
else if chcls.is_nest_escape(ch) {
concat_accum!(); srcstrm.next(); srcstrm.next_accum(dalloc)?;
}
else if chcls.is_nest_start(ch) {
srcstrm.next_accum(dalloc)?;
nest_level += 1;
}
else if chcls.is_nest_end(ch) {
if nest_level > 0 {
srcstrm.next_accum(dalloc)?;
nest_level -= 1;
} else {
Self::check_end_char(srcstrm, *ndepth, chcls)?;
break;
}
}
else {
srcstrm.next_accum(dalloc)?;
}
}
if nest_level == 0 {
concat_accum!();
Ok(text)
} else {
Err(Error::MissingEndChar)
}
}
#[allow(unused_results)]
fn parse_nested(
srcstrm: &mut S,
ndepth: &mut usize,
dalloc: &mut DA,
chcls: &CC,
bindings: &OB,
)
-> ParseResultOption<DA, OB>
{
let end = |ss: &mut S| {
if let Some(SourceIterItem{ch, ..}) = ss.next() {
debug_assert!(chcls.is_nest_end(ch));
Ok(())
} else {
Err(Error::MissingEndChar)
}
};
let start = srcstrm.next();
debug_assert_eq!(start.map(|SourceIterItem{ch, ..}| chcls.is_nest_start(ch)),
Some(true));
let operator = Self::parse_next(ParseTextMode::Operator, srcstrm, ndepth,
dalloc, chcls, bindings)?;
if let Some(&SourceIterItem{ch, ..}) = srcstrm.peek() {
if chcls.is_whitespace(ch) { srcstrm.next(); }
}
Ok(if let Some(operator) = operator {
if let Some(combiner) = bindings.lookup(&operator) {
match combiner {
Combiner::Operative(opr) => {
let operands = Self::parse_text(ParseTextMode::Operands,
srcstrm, ndepth, dalloc, chcls)?;
end(srcstrm)?;
opr(operator, operands, dalloc)?
},
Combiner::Applicative(apl) => {
let arguments = Self::parse_all(ParseTextMode::Base,
srcstrm, ndepth,
dalloc, chcls, bindings)?;
end(srcstrm)?;
apl(operator, arguments, dalloc)?
}
}
} else {
let operands = Self::parse_all(ParseTextMode::Base, srcstrm, ndepth,
dalloc, chcls, bindings)?;
end(srcstrm)?;
Some(Datum::Combination {
operator: dalloc.new_datum(operator)?,
operands: dalloc.new_datum(operands)?,
})
}
} else {
end(srcstrm)?;
Some(Datum::EmptyNest)
})
}
fn parse_all(
mode: ParseTextMode,
srcstrm: &mut S,
ndepth: &mut usize,
dalloc: &mut DA,
chcls: &CC,
bindings: &OB,
)
-> ParseResult<DA, OB>
{
let mut head = Datum::EmptyList;
let mut tail = &mut head;
loop {
let it = Self::parse_next(mode, srcstrm, ndepth, dalloc, chcls, bindings)?;
if let Some(next_it) = it {
*tail = Datum::List {
elem: dalloc.new_datum(next_it)?,
next: dalloc.new_datum(Datum::EmptyList)?,
};
if let Datum::List{ref mut next, ..} = tail {
if let Some(next) = DerefTryMut::get_mut(next) {
tail = next;
} else {
return Err(Error::FailedDerefTryMut);
}
} else {
unreachable!()
}
} else {
break;
}
}
Ok(head)
}
#[inline]
#[allow(unused_results)]
fn skip_whitespace(srcstrm: &mut S, chcls: &CC) {
while srcstrm.peek()
.map_or(false,
|&SourceIterItem{ch, ..}| chcls.is_whitespace(ch))
{
srcstrm.next(); }
}
#[inline]
fn check_end_char(srcstrm: &mut S, ndepth: usize, chcls: &CC)
-> Result<(), ParseError<DA, OB>>
{
{
debug_assert_eq!(srcstrm.peek().map(|&SourceIterItem{ch, ..}|
chcls.is_nest_end(ch)),
Some(true));
}
if ndepth > 0 {
Ok(())
} else {
let n = srcstrm.next().unwrap();
Err(Error::UnbalancedEndChar(n.pos))
}
}
}