nml/src/elements/section.rs

304 lines
8.5 KiB
Rust
Raw Normal View History

2024-07-26 11:21:00 +02:00
use crate::compiler::compiler::Compiler;
use crate::compiler::compiler::Target;
use crate::document::document::Document;
use crate::document::element::ElemKind;
use crate::document::element::Element;
use crate::document::element::ReferenceableElement;
use crate::lua::kernel::CTX;
use crate::parser::parser::Parser;
use crate::parser::rule::RegexRule;
use crate::parser::source::Source;
use crate::parser::source::Token;
use ariadne::Fmt;
use ariadne::Label;
use ariadne::Report;
use ariadne::ReportKind;
use mlua::Error::BadArgument;
use mlua::Function;
use mlua::Lua;
2024-07-19 11:52:12 +02:00
use regex::Regex;
2024-07-26 11:21:00 +02:00
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
2024-07-19 11:52:12 +02:00
#[derive(Debug)]
pub struct Section {
2024-07-26 11:21:00 +02:00
pub(self) location: Token,
pub(self) title: String, // Section title
pub(self) depth: usize, // Section depth
pub(self) kind: u8, // Section kind, e.g numbered, unnumbred, ...
2024-07-21 15:56:56 +02:00
pub(self) reference: Option<String>, // Section reference name
2024-07-19 11:52:12 +02:00
}
2024-07-26 11:21:00 +02:00
impl Element for Section {
fn location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Block }
fn element_name(&self) -> &'static str { "Section" }
fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> {
match compiler.target() {
Target::HTML => {
Ok(format!(
r#"<h{0} id="{1}">{2}</h{0}>"#,
2024-07-26 11:21:00 +02:00
self.depth,
Compiler::refname(compiler.target(), self.title.as_str()),
2024-07-29 16:45:14 +02:00
Compiler::sanitize(compiler.target(), self.title.as_str())
))
},
2024-07-26 11:21:00 +02:00
Target::LATEX => Err("Unimplemented compiler".to_string()),
}
}
2024-07-19 11:52:12 +02:00
}
2024-07-26 11:21:00 +02:00
impl ReferenceableElement for Section {
fn reference_name(&self) -> Option<&String> { self.reference.as_ref() }
fn refcount_key(&self) -> &'static str { "section" }
2024-07-26 15:06:09 +02:00
fn compile_reference(
&self,
compiler: &Compiler,
_document: &dyn Document,
2024-07-26 15:06:09 +02:00
reference: &super::reference::Reference,
_refid: usize,
2024-07-26 15:06:09 +02:00
) -> Result<String, String> {
match compiler.target() {
Target::HTML => {
let caption = reference.caption().map_or(
format!(
"({})",
Compiler::sanitize(compiler.target(), self.title.as_str())
),
|cap| cap.clone(),
);
Ok(format!(
"<a class=\"section-ref\" href=\"#{}\">{caption}</a>",
Compiler::refname(compiler.target(), self.title.as_str())
))
}
_ => todo!(""),
}
2024-07-26 15:06:09 +02:00
}
2024-07-19 11:52:12 +02:00
}
pub struct SectionRule {
re: [Regex; 1],
}
impl SectionRule {
pub fn new() -> Self {
2024-07-26 11:21:00 +02:00
Self {
re: [Regex::new(r"(?:^|\n)(#{1,})(?:\{(.*)\})?((\*|\+){1,})?(.*)").unwrap()],
}
2024-07-19 11:52:12 +02:00
}
}
2024-07-26 11:21:00 +02:00
pub mod section_kind {
pub const NONE: u8 = 0x00;
pub const NO_TOC: u8 = 0x01;
pub const NO_NUMBER: u8 = 0x02;
2024-07-19 11:52:12 +02:00
}
impl RegexRule for SectionRule {
fn name(&self) -> &'static str { "Section" }
fn regexes(&self) -> &[Regex] { &self.re }
2024-07-26 11:21:00 +02:00
fn on_regex_match(
&self,
_: usize,
parser: &dyn Parser,
document: &dyn Document,
token: Token,
matches: regex::Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
2024-07-19 11:52:12 +02:00
let mut result = vec![];
2024-07-26 11:21:00 +02:00
let section_depth = match matches.get(1) {
2024-07-19 11:52:12 +02:00
Some(depth) => {
if depth.len() > 6 {
result.push(
Report::build(ReportKind::Error, token.source(), depth.start())
.with_message("Invalid section depth")
.with_label(
Label::new((token.source(), depth.range()))
.with_message(format!("Section is of depth {}, which is greather than {} (maximum depth allowed)",
depth.len().fg(parser.colors().info),
6.fg(parser.colors().info)))
.with_color(parser.colors().error))
.finish());
2024-07-26 11:21:00 +02:00
return result;
2024-07-19 11:52:12 +02:00
}
depth.len()
2024-07-26 11:21:00 +02:00
}
2024-07-19 11:52:12 +02:00
_ => panic!("Empty section depth"),
};
// [Optional] Reference name
2024-07-26 11:21:00 +02:00
let section_refname = matches.get(2).map_or_else(
|| None,
2024-07-19 11:52:12 +02:00
|refname| {
// Check for duplicate reference
2024-08-02 13:36:04 +02:00
if let Some(elem_reference) = document.get_reference(refname.as_str())
2024-07-19 11:52:12 +02:00
{
2024-08-02 13:36:04 +02:00
let elem = document.get_from_reference(&elem_reference).unwrap();
2024-07-19 11:52:12 +02:00
result.push(
Report::build(ReportKind::Warning, token.source(), refname.start())
.with_message("Duplicate reference name")
.with_label(
Label::new((token.source(), refname.range()))
.with_message(format!("Reference with name `{}` is already defined in `{}`",
refname.as_str().fg(parser.colors().highlight),
2024-08-02 13:36:04 +02:00
elem.location().source().name().as_str().fg(parser.colors().highlight)))
2024-07-19 11:52:12 +02:00
.with_message(format!("`{}` conflicts with previously defined reference to {}",
refname.as_str().fg(parser.colors().highlight),
2024-08-02 13:36:04 +02:00
elem.element_name().fg(parser.colors().highlight)))
2024-07-19 11:52:12 +02:00
.with_color(parser.colors().warning))
2024-07-26 11:21:00 +02:00
.with_label(
2024-08-02 13:36:04 +02:00
Label::new((elem.location().source(), elem.location().start()..elem.location().end() ))
2024-07-26 11:21:00 +02:00
.with_message(format!("`{}` previously defined here",
refname.as_str().fg(parser.colors().highlight)))
2024-07-19 11:52:12 +02:00
.with_color(parser.colors().warning))
.with_note(format!("Previous reference was overwritten"))
.finish());
}
2024-07-26 11:21:00 +02:00
Some(refname.as_str().to_string())
},
);
2024-07-19 11:52:12 +02:00
// Section kind
2024-07-26 11:21:00 +02:00
let section_kind = match matches.get(3) {
Some(kind) => match kind.as_str() {
"*+" | "+*" => section_kind::NO_NUMBER | section_kind::NO_TOC,
"*" => section_kind::NO_NUMBER,
"+" => section_kind::NO_TOC,
"" => section_kind::NONE,
_ => {
result.push(
2024-07-19 11:52:12 +02:00
Report::build(ReportKind::Error, token.source(), kind.start())
.with_message("Invalid section numbering kind")
.with_label(
Label::new((token.source(), kind.range()))
.with_message(format!("Section numbering kind must be a combination of `{}` for unnumbered, and `{}` for non-listing; got `{}`",
"*".fg(parser.colors().info),
"+".fg(parser.colors().info),
kind.as_str().fg(parser.colors().highlight)))
.with_color(parser.colors().error))
.with_help(format!("Leave empty for a numbered listed section"))
.finish());
2024-07-26 11:21:00 +02:00
return result;
2024-07-19 11:52:12 +02:00
}
2024-07-26 11:21:00 +02:00
},
2024-07-19 11:52:12 +02:00
_ => section_kind::NONE,
};
// Spacing + Section name
2024-07-26 11:21:00 +02:00
let section_name = match matches.get(5) {
2024-07-19 11:52:12 +02:00
Some(name) => {
2024-07-26 11:21:00 +02:00
let split = name
.as_str()
.chars()
2024-07-19 11:52:12 +02:00
.position(|c| !c.is_whitespace())
.unwrap_or(0);
let section_name = &name.as_str()[split..];
2024-07-26 11:21:00 +02:00
if section_name.is_empty()
// No name
2024-07-19 11:52:12 +02:00
{
result.push(
Report::build(ReportKind::Error, token.source(), name.start())
2024-07-26 11:21:00 +02:00
.with_message("Missing section name")
.with_label(
Label::new((token.source(), name.range()))
.with_message("Sections require a name before line end")
.with_color(parser.colors().error),
)
.finish(),
);
2024-07-19 11:52:12 +02:00
return result;
}
// No spacing
2024-07-26 11:21:00 +02:00
if split == 0 {
2024-07-19 11:52:12 +02:00
result.push(
Report::build(ReportKind::Warning, token.source(), name.start())
.with_message("Missing section spacing")
.with_label(
Label::new((token.source(), name.range()))
.with_message("Sections require at least one whitespace before the section's name")
.with_color(parser.colors().warning))
.with_help(format!("Add a space before `{}`", section_name.fg(parser.colors().highlight)))
.finish());
return result;
}
section_name.to_string()
2024-07-26 11:21:00 +02:00
}
_ => panic!("Empty section name"),
2024-07-19 11:52:12 +02:00
};
2024-07-26 11:21:00 +02:00
parser.push(
document,
Box::new(Section {
2024-07-21 15:56:56 +02:00
location: token.clone(),
2024-07-26 11:21:00 +02:00
title: section_name,
depth: section_depth,
kind: section_kind,
reference: section_refname,
}),
);
return result;
2024-07-19 11:52:12 +02:00
}
2024-07-21 15:56:56 +02:00
2024-07-30 11:01:22 +02:00
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> {
2024-07-21 15:56:56 +02:00
let mut bindings = vec![];
2024-07-26 11:21:00 +02:00
bindings.push((
"push".to_string(),
lua.create_function(
|_, (title, depth, kind, reference): (String, usize, String, Option<String>)| {
let kind = match kind.as_str() {
"*+" | "+*" => section_kind::NO_NUMBER | section_kind::NO_TOC,
"*" => section_kind::NO_NUMBER,
"+" => section_kind::NO_TOC,
"" => section_kind::NONE,
_ => {
return Err(BadArgument {
to: Some("push".to_string()),
pos: 3,
name: Some("kind".to_string()),
cause: Arc::new(mlua::Error::external(format!(
"Unknown section kind specified"
))),
})
}
};
CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| {
ctx.parser.push(
ctx.document,
Box::new(Section {
location: ctx.location.clone(),
title,
depth,
kind,
reference,
}),
);
})
});
Ok(())
},
)
.unwrap(),
));
2024-07-30 11:01:22 +02:00
Some(bindings)
2024-07-21 15:56:56 +02:00
}
2024-07-19 11:52:12 +02:00
}