use std::fmt;
use super::core::Token;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct LineToken {
pub source_tokens: Vec<Token>,
pub token_spans: Vec<std::ops::Range<usize>>,
pub line_type: LineType,
}
impl LineToken {
pub fn source_token_pairs(&self) -> Vec<(Token, std::ops::Range<usize>)> {
self.source_tokens
.iter()
.zip(self.token_spans.iter())
.map(|(token, span)| (token.clone(), span.clone()))
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum LineType {
BlankLine,
DataMarkerLine,
DataLine,
SubjectLine,
ListLine,
SubjectOrListItemLine,
ParagraphLine,
DialogLine,
Indent,
Dedent,
DocumentStart,
}
impl fmt::Display for LineType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
LineType::BlankLine => "BLANK_LINE",
LineType::DataMarkerLine => "DATA_MARKER_LINE",
LineType::DataLine => "DATA_LINE",
LineType::SubjectLine => "SUBJECT_LINE",
LineType::ListLine => "LIST_LINE",
LineType::SubjectOrListItemLine => "SUBJECT_OR_LIST_ITEM_LINE",
LineType::ParagraphLine => "PARAGRAPH_LINE",
LineType::DialogLine => "DIALOG_LINE",
LineType::Indent => "INDENT",
LineType::Dedent => "DEDENT",
LineType::DocumentStart => "DOCUMENT_START",
};
write!(f, "{name}")
}
}
impl LineType {
pub fn to_grammar_string(&self) -> String {
let name = match self {
LineType::BlankLine => "blank-line",
LineType::DataMarkerLine => "data-marker-line",
LineType::DataLine => "data-line",
LineType::SubjectLine => "subject-line",
LineType::ListLine => "list-line",
LineType::SubjectOrListItemLine => "subject-or-list-item-line",
LineType::ParagraphLine => "paragraph-line",
LineType::DialogLine => "dialog-line",
LineType::Indent => "indent",
LineType::Dedent => "dedent",
LineType::DocumentStart => "document-start-line",
};
format!("<{name}>")
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum LineContainer {
Token(LineToken),
Container { children: Vec<LineContainer> },
}
impl LineContainer {
pub fn is_empty(&self) -> bool {
match self {
LineContainer::Token(_) => false,
LineContainer::Container { children, .. } => children.is_empty(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tokenize_indented_marker() {
use crate::lex::lexing::tokenize;
let source = " ::";
let tokens_with_range = tokenize(source);
let tokens: Vec<crate::lex::token::Token> =
tokens_with_range.into_iter().map(|(t, _)| t).collect();
println!("Tokens: {tokens:?}");
}
#[test]
fn test_token_type_to_grammar_string() {
assert_eq!(LineType::BlankLine.to_grammar_string(), "<blank-line>");
assert_eq!(
LineType::DataMarkerLine.to_grammar_string(),
"<data-marker-line>"
);
assert_eq!(LineType::SubjectLine.to_grammar_string(), "<subject-line>");
assert_eq!(LineType::ListLine.to_grammar_string(), "<list-line>");
assert_eq!(
LineType::SubjectOrListItemLine.to_grammar_string(),
"<subject-or-list-item-line>"
);
assert_eq!(
LineType::ParagraphLine.to_grammar_string(),
"<paragraph-line>"
);
assert_eq!(LineType::Indent.to_grammar_string(), "<indent>");
assert_eq!(LineType::Dedent.to_grammar_string(), "<dedent>");
assert_eq!(
LineType::DocumentStart.to_grammar_string(),
"<document-start-line>"
);
}
#[test]
fn test_token_sequence_formatting() {
let tokens = [
LineType::SubjectLine,
LineType::Indent,
LineType::ParagraphLine,
LineType::Dedent,
];
let formatted = tokens
.iter()
.map(|t| t.to_grammar_string())
.collect::<Vec<_>>()
.join("");
assert_eq!(formatted, "<subject-line><indent><paragraph-line><dedent>");
}
#[test]
fn test_blank_line_group_formatting() {
let tokens = [
LineType::BlankLine,
LineType::BlankLine,
LineType::BlankLine,
];
let formatted = tokens
.iter()
.map(|t| t.to_grammar_string())
.collect::<Vec<_>>()
.join("");
assert_eq!(formatted, "<blank-line><blank-line><blank-line>");
}
#[test]
fn test_complex_pattern_formatting() {
let tokens = [
LineType::BlankLine,
LineType::SubjectLine,
LineType::BlankLine,
LineType::Indent,
LineType::ParagraphLine,
LineType::Dedent,
];
let formatted = tokens
.iter()
.map(|t| t.to_grammar_string())
.collect::<Vec<_>>()
.join("");
assert_eq!(
formatted,
"<blank-line><subject-line><blank-line><indent><paragraph-line><dedent>"
);
}
#[test]
fn test_line_token_source_token_pairs() {
let line_token = LineToken {
source_tokens: vec![
Token::Text("hello".to_string()),
Token::Whitespace(1),
Token::Text("world".to_string()),
],
token_spans: vec![0..5, 5..6, 6..11],
line_type: LineType::ParagraphLine,
};
let pairs = line_token.source_token_pairs();
assert_eq!(pairs.len(), 3);
assert_eq!(pairs[0].1, 0..5);
assert_eq!(pairs[1].1, 5..6);
assert_eq!(pairs[2].1, 6..11);
match &pairs[0].0 {
Token::Text(s) => assert_eq!(s, "hello"),
_ => panic!("Expected Text token"),
}
}
}