Add support for inline extension blocks

main
idylls 2 years ago
parent e58a701b40
commit 366fd53a67
Signed by: idylls
GPG Key ID: 8A7167CBC2CC9F0F

@ -70,7 +70,9 @@ where
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Error {}
pub enum Error {
UnclosedExtension,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Header<S> {
@ -79,13 +81,20 @@ pub struct Header<S> {
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Inline<S> {
Text(S),
pub struct Text<S>(pub S);
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Extension<S>(pub S);
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ParagraphPiece<S> {
Text(Text<S>),
Extension(Extension<S>),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Paragraph<S> {
pieces: Vec<Spanned<Inline<S>>>,
pieces: Vec<Spanned<ParagraphPiece<S>>>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -96,6 +105,7 @@ pub enum Block<S> {
pub type Result<T> = core::result::Result<T, Spanned<Error>>;
#[derive(Copy, Clone)]
struct State<'a> {
corpus: &'a str,
current_offset: ByteOffset,
@ -119,20 +129,27 @@ impl<'a> State<'a> {
))
}
fn peek_n(&self, buf: &mut [Option<(char, ByteOffset)>]) {
let mut self_ = *self;
for slot in buf {
*slot = self_.next();
}
}
fn next(&mut self) -> Option<(char, ByteOffset)> {
if self.is_eof() {
return None;
}
let out = Some((
self.corpus
.char_at_byte_offset(self.current_offset)
.unwrap()
.unwrap(),
self.current_offset,
));
let char = self
.corpus
.char_at_byte_offset(self.current_offset)
.unwrap()
.unwrap();
let out = Some((char, self.current_offset));
self.current_offset.0 += 1;
self.current_offset.0 += char.len_utf8();
out
}
@ -141,6 +158,12 @@ impl<'a> State<'a> {
self.next();
}
fn forward_n(&mut self, n: usize) {
for _ in 0..n {
self.forward();
}
}
fn skip_whitespace(&mut self) {
while let Some((ch, _)) = self.peek() {
if !ch.is_whitespace() {
@ -156,7 +179,6 @@ fn header<'a, S>(state: &mut State<'a>) -> Result<Header<S>>
where
S: From<&'a str>,
{
let start = state.current_offset;
let mut level = 1;
state.forward();
@ -190,45 +212,72 @@ where
})
}
fn inline_text<'a, S>(state: &mut State<'a>) -> Result<(S, bool)>
fn extension<'a, S>(state: &mut State<'a>) -> Result<Extension<S>>
where
S: From<&'a str>,
{
debug_assert!({
let mut peek_buf = [None; 2];
state.peek_n(&mut peek_buf);
matches!(peek_buf, [Some(('@', _)), Some(('{', _))])
});
state.forward();
state.forward();
let start = state.current_offset;
let mut end: Option<ByteOffset> = None;
while let Some((ch, bo)) = state.next() {
match ch {
'\n' => match end {
Some(end) => {
return Ok((state.corpus[start.0..end.0].into(), true))
}
None => end = Some(bo),
},
loop {
let mut peek_buf = [None; 2];
state.peek_n(&mut peek_buf);
match peek_buf {
[None, _] => {
return Err(spanned(
start,
state.current_offset,
Error::UnclosedExtension,
))
}
[Some(('}', bo)), Some(('@', _))] => {
state.forward();
state.forward();
let content = &state.corpus[start.0..bo.0];
return Ok(Extension(content.into()));
}
_ => {
end = None;
state.forward();
}
}
}
Ok((
state.corpus
[start.0..end.map(|e| e.0).unwrap_or_else(|| state.corpus.len())]
.into(),
true,
))
}
fn inline<'a, S>(state: &mut State<'a>) -> Result<(Inline<S>, bool)>
fn text<'a, S>(state: &mut State<'a>) -> Result<Text<S>>
where
S: From<&'a str>,
{
match state.peek() {
_ => {
let (inline, done) = inline_text(state)?;
Ok((Inline::Text(inline), done))
let start = state.current_offset;
loop {
let mut peek_buf = [None; 2];
state.peek_n(&mut peek_buf);
match peek_buf {
[None, _]
| [Some(('\n', _)), Some(('\n', _))]
| [Some(('\n', _)), None]
| [Some(('@', _)), Some(('{', _))] => break,
_ => state.forward(),
}
}
let end = state.current_offset;
Ok(Text(state.corpus[start.0..end.0].into()))
}
fn paragraph<'a, S>(state: &mut State<'a>) -> Result<Paragraph<S>>
@ -239,14 +288,35 @@ where
loop {
let start = state.current_offset;
let (piece, done) = inline::<S>(state)?;
let end = state.current_offset;
pieces.push(spanned(start, end, piece));
// detect what type of thing we're about to parse.
let mut peek_buf = [None; 2];
state.peek_n(&mut peek_buf);
if done {
break;
let piece = match peek_buf {
// eof
[None, _] => break,
// double newline
[Some(('\n', _)), Some(('\n', _))] => {
state.forward_n(2);
break;
}
// newline eof
[Some(('\n', _)), None] => {
state.forward();
break;
}
// extension
[Some(('@', _)), Some(('{', _))] => {
ParagraphPiece::Extension(extension(state)?)
}
// regular text
_ => ParagraphPiece::Text(text(state)?),
};
let end = state.current_offset;
pieces.push(spanned(start, end, piece));
}
Ok(Paragraph { pieces })
@ -353,7 +423,7 @@ mod test {
pieces: vec![spanned(
0,
corpus.len(),
Inline::Text("Hello, world")
ParagraphPiece::Text(Text("Hello, world"))
)]
})
)],
@ -374,8 +444,8 @@ mod test {
Block::Paragraph(Paragraph {
pieces: vec![spanned(
0,
14,
Inline::Text("Hello, world")
12,
ParagraphPiece::Text(Text("Hello, world"))
)]
})
),
@ -385,12 +455,36 @@ mod test {
Block::Paragraph(Paragraph {
pieces: vec![spanned(
14,
corpus.len(),
Inline::Text("Goodbye, world")
corpus.len() - 1,
ParagraphPiece::Text(Text("Goodbye, world"))
)]
})
)
],
)
}
#[test]
fn paragraph_extension_1() {
let corpus = "Hello @{world}@";
let output = parse(corpus).unwrap();
assert_eq!(
&output,
&[spanned(
0,
corpus.len(),
Block::Paragraph(Paragraph {
pieces: vec![
spanned(0, 6, ParagraphPiece::Text(Text("Hello "))),
spanned(
6,
corpus.len(),
ParagraphPiece::Extension(Extension("world"))
)
]
})
)]
);
}
}

Loading…
Cancel
Save