Implement underline and strikethrough tokens

This commit is contained in:
Juno Takano 2026-01-06 22:59:47 -03:00
commit 0d910634c6
9 changed files with 182 additions and 29 deletions

View file

@ -41,7 +41,7 @@ fn lex(text: &str, map: LexMap, config: &Config) -> Vec<Token> {
continue;
}
if point::parse(lexeme, &mut state, &mut tokens) {
if point::parse(lexeme, &mut state, &mut tokens, &mut iterator) {
continue;
}

View file

@ -5,10 +5,10 @@ use crate::{
syntax::content::{
Parseable as _,
parser::{
context, Inline,
Inline, context,
lexeme::Lexeme,
state::State,
token::{Token, code::Code, anchor::Anchor},
state::{AnchorBuffer, State},
token::{Token, anchor::Anchor, code::Code},
},
},
};
@ -29,7 +29,7 @@ pub fn parse(
} else if Anchor::probe(lexeme) {
log!("Inline Context: None -> Anchor on {lexeme}");
state.context.inline = Inline::Anchor;
state.buffers.anchor.clear();
state.buffers.anchor = AnchorBuffer::default();
if lexeme.match_as_char('|') {
state.buffers.anchor.candidate.leading = true;

View file

@ -1,10 +1,15 @@
use std::{iter::Peekable, slice::Iter};
use crate::{
prelude::*,
syntax::content::{
Parseable as _,
parser::{
lexeme::Lexeme,
token::{Token, oblique::Oblique, bold::Bold},
token::{
Token, oblique::Oblique, bold::Bold, underline::Underline,
strike::Strike,
},
state::State,
},
},
@ -14,14 +19,28 @@ pub fn parse(
lexeme: &Lexeme,
state: &mut State,
tokens: &mut Vec<Token>,
iterator: &mut Peekable<Iter<'_, Lexeme>>,
) -> bool {
if Oblique::probe(lexeme) {
log!("Oblique probed {lexeme}");
if Underline::probe(lexeme) {
log!("Underline probed: {lexeme}");
tokens
.push(Token::Underline(Underline::new(!state.switches.underline)));
state.switches.underline = !state.switches.underline;
iterator.next();
return true;
} else if Oblique::probe(lexeme) {
log!("Oblique probed: {lexeme}");
tokens.push(Token::Oblique(Oblique::new(!state.switches.oblique)));
state.switches.oblique = !state.switches.oblique;
return true;
} else if Strike::probe(lexeme) {
log!("Strike probed: {lexeme}");
tokens.push(Token::Strike(Strike::new(!state.switches.crossout)));
state.switches.crossout = !state.switches.crossout;
iterator.next();
return true;
} else if Bold::probe(lexeme) {
log!("Bold probed {lexeme}");
log!("Bold probed: {lexeme}");
tokens.push(Token::Bold(Bold::new(!state.switches.bold)));
state.switches.bold = !state.switches.bold;
return true;

View file

@ -9,13 +9,15 @@ pub mod delimiter {
pub flanking: Vec<char>,
pub punctuation: Vec<char>,
pub whitespace: Vec<char>,
pub double: Vec<char>,
}
impl Default for Delimiters {
fn default() -> Self {
Delimiters {
atomic: vec!['`', '|'],
flanking: vec!['_', '*', '(', ')', '\'', '"'],
double: vec!['_', '~'],
flanking: vec!['_', '*', '~', '(', ')', '\'', '"'],
punctuation: vec![',', '.', ';', ':', '?', '!'],
whitespace: vec!['\n', ' '],
}
@ -55,12 +57,21 @@ pub mod delimiter {
let mut iterator = text.chars().peekable();
while let Some(c) = iterator.next() {
// if the current char is a boundary
// if current char is a boundary
if delimiters.is_boundary(c) {
atomized.push(c.to_string());
continue;
// if the current char is a flanking delimiter
// if current char is a double delimiter and the next its double
} else if delimiters.double.contains(&c)
&& iterator.peek().is_some_and(|n| *n == c)
{
atomized.push(c.to_string());
iterator.next();
atomized.push(c.to_string());
continue;
// if current char is a flanking delimiter
} else if delimiters.flanking.contains(&c) {
// if next char is a boundary
if iterator
@ -70,7 +81,7 @@ pub mod delimiter {
atomized.push(c.to_string());
continue;
// if the previous char was whitespace
// if previous char was whitespace
} else if let Some(last_string) = atomized.last()
&& let Some(last_char) = last_string.chars().last()
&& delimiters.whitespace.contains(&last_char)
@ -82,7 +93,7 @@ pub mod delimiter {
// if there is a last atomized element
if let Some(last) = atomized.last_mut() {
// if the last atomized element is a boundary
// if last atomized element is a boundary
if delimiters.is_str_delimiter(last) {
atomized.push(c.to_string());
} else {

View file

@ -15,8 +15,10 @@ pub struct State {
#[derive(Clone, Debug)]
pub struct Switches {
pub oblique: bool,
pub bold: bool,
pub oblique: bool,
pub crossout: bool,
pub underline: bool,
}
#[derive(Clone, Debug)]
@ -24,21 +26,13 @@ pub struct Buffers {
pub anchor: AnchorBuffer,
}
#[derive(Clone, Debug)]
#[derive(Default, Clone, Debug)]
pub struct AnchorBuffer {
pub candidate: Anchor,
pub text: String,
pub destination: String,
}
impl AnchorBuffer {
pub fn clear(&mut self) {
self.candidate = Anchor::default();
self.text = String::default();
self.destination = String::default();
}
}
impl std::fmt::Display for AnchorBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let display_text = if self.text.is_empty() {
@ -72,8 +66,10 @@ impl Default for State {
},
dom_ids: HashMap::default(),
switches: Switches {
oblique: false,
bold: false,
crossout: false,
oblique: false,
underline: false,
},
buffers: Buffers {
anchor: AnchorBuffer {

View file

@ -12,12 +12,15 @@ pub mod oblique;
pub mod paragraph;
pub mod preformat;
pub mod span;
pub mod strike;
pub mod underline;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Token {
Anchor(anchor::Anchor),
Bold(bold::Bold),
Code(code::Code),
Strike(strike::Strike),
Header(header::Header),
Item(item::Item),
LineBreak(linebreak::LineBreak),
@ -27,6 +30,7 @@ pub enum Token {
Paragraph(paragraph::Paragraph),
PreFormat(preformat::PreFormat),
Span(span::Span),
Underline(underline::Underline),
}
impl Token {
@ -35,6 +39,7 @@ impl Token {
Token::Anchor(ref d) => d.render(),
Token::Bold(ref d) => d.render(),
Token::Code(ref d) => d.render(),
Token::Strike(ref d) => d.render(),
Token::Header(ref d) => d.render(),
Token::Item(ref d) => d.render(),
Token::LineBreak(ref d) => d.render(),
@ -44,6 +49,7 @@ impl Token {
Token::Paragraph(ref d) => d.render(),
Token::PreFormat(ref d) => d.render(),
Token::Span(ref d) => d.render(),
Token::Underline(ref d) => d.render(),
}
}
}
@ -54,6 +60,7 @@ impl std::fmt::Display for Token {
Token::Anchor(ref d) => format!("{d}"),
Token::Bold(ref d) => format!("{d}"),
Token::Code(ref d) => format!("{d}"),
Token::Strike(ref d) => format!("{d}"),
Token::Header(ref d) => format!("{d}"),
Token::Item(ref d) => format!("{d}"),
Token::LineBreak(ref d) => format!("{d}"),
@ -63,6 +70,7 @@ impl std::fmt::Display for Token {
Token::Paragraph(ref d) => format!("{d}"),
Token::PreFormat(ref d) => format!("{d}"),
Token::Span(ref d) => format!("{d}"),
Token::Underline(ref d) => format!("{d}"),
};
write!(f, "Tk:{data}")

View file

@ -0,0 +1,58 @@
use crate::{
syntax::content::{Parseable, Lexeme},
};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Strike {
open: bool,
}
impl Strike {
pub fn new(open: bool) -> Strike {
Strike { open }
}
}
impl Parseable for Strike {
fn probe(lexeme: &Lexeme) -> bool {
lexeme.match_as_char('~') && lexeme.match_next_as_char('~')
}
fn lex(_lexeme: &Lexeme) -> Strike {
panic!("Attempt to lex a strike tag directly from a lexeme")
}
fn render(&self) -> String {
let tag = if self.open { "<s>" } else { "</s>" };
String::from(tag)
}
}
impl std::fmt::Display for Strike {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let display_open_state = if self.open { "open" } else { "closed" };
write!(f, "Strike [{display_open_state}]")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render() {
let code_open = Strike::new(true);
assert_eq!(code_open.render(), "<s>");
let code_closed = Strike::new(false);
assert_eq!(code_closed.render(), "</s>");
}
#[test]
#[should_panic(
expected = "Attempt to lex a strike tag directly from a lexeme"
)]
fn lex() {
Strike::lex(&Lexeme::new("", ""));
}
}

View file

@ -0,0 +1,61 @@
use crate::{
syntax::content::{Parseable, Lexeme},
};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Underline {
open: bool,
}
impl Underline {
pub fn new(open: bool) -> Underline {
Underline { open }
}
}
impl Parseable for Underline {
fn probe(lexeme: &Lexeme) -> bool {
lexeme.match_as_char('_') && lexeme.match_next_as_char('_')
}
fn lex(_lexeme: &Lexeme) -> Underline {
panic!("Attempt to lex an underline tag directly from a lexeme")
}
fn render(&self) -> String {
if self.open {
String::from("<u>")
} else {
String::from("</u>")
}
}
}
impl std::fmt::Display for Underline {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let display_open_state = if self.open { "open" } else { "closed" };
write!(f, "Underline [{display_open_state}]")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render() {
let code_open = Underline::new(true);
assert_eq!(code_open.render(), "<u>");
let code_closed = Underline::new(false);
assert_eq!(code_closed.render(), "</u>");
}
#[test]
#[should_panic(
expected = "Attempt to lex an underline tag directly from a lexeme"
)]
fn lex() {
Underline::lex(&Lexeme::new("", ""));
}
}