Compare commits
No commits in common. "e43cf4a8f340a2d374ddac95e2e9570c9fece666" and "e4ce3edc4df0227ad7878386ab47aa84d010d9c3" have entirely different histories.
e43cf4a8f3
...
e4ce3edc4d
3 changed files with 291 additions and 468 deletions
|
@ -21,11 +21,13 @@ use regex::Regex;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use super::paragraph::Paragraph;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
pub(self) location: Token,
|
pub(self) location: Token,
|
||||||
/// Display content of link
|
/// Display content of link
|
||||||
pub(self) display: Vec<Box<dyn Element>>,
|
pub(self) display: Paragraph,
|
||||||
/// Url of link
|
/// Url of link
|
||||||
pub(self) url: String,
|
pub(self) url: String,
|
||||||
}
|
}
|
||||||
|
@ -43,9 +45,11 @@ impl Element for Link {
|
||||||
Compiler::sanitize(compiler.target(), self.url.as_str())
|
Compiler::sanitize(compiler.target(), self.url.as_str())
|
||||||
);
|
);
|
||||||
|
|
||||||
for elem in &self.display {
|
result += self
|
||||||
result += elem.compile(compiler, document)?.as_str();
|
.display
|
||||||
}
|
.compile(compiler, document)
|
||||||
|
.as_ref()
|
||||||
|
.map(|r| r.as_str())?;
|
||||||
|
|
||||||
result += "</a>";
|
result += "</a>";
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
@ -58,14 +62,13 @@ impl Element for Link {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerElement for Link {
|
impl ContainerElement for Link {
|
||||||
fn contained(&self) -> &Vec<Box<dyn Element>> { &self.display }
|
fn contained(&self) -> &Vec<Box<dyn Element>> { &self.display.content }
|
||||||
|
|
||||||
fn push(&mut self, elem: Box<dyn Element>) -> Result<(), String> {
|
fn push(&mut self, elem: Box<dyn Element>) -> Result<(), String> {
|
||||||
if elem.downcast_ref::<Link>().is_some() {
|
if elem.downcast_ref::<Link>().is_some() {
|
||||||
return Err("Tried to push a link inside of a link".to_string());
|
return Err("Tried to push a link inside of a link".to_string());
|
||||||
}
|
}
|
||||||
self.display.push(elem);
|
self.display.push(elem)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +152,7 @@ impl RegexRule for LinkRule {
|
||||||
);
|
);
|
||||||
return reports;
|
return reports;
|
||||||
}
|
}
|
||||||
Ok(mut paragraph) => std::mem::replace(&mut paragraph.content, vec![]),
|
Ok(paragraph) => *paragraph,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => panic!("Empty link name"),
|
_ => panic!("Empty link name"),
|
||||||
|
@ -212,9 +215,8 @@ impl RegexRule for LinkRule {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::elements::paragraph::Paragraph;
|
|
||||||
use crate::elements::style::Style;
|
use crate::elements::style::Style;
|
||||||
use crate::elements::text::Text;
|
use crate::elements::text::Text;
|
||||||
use crate::parser::langparser::LangParser;
|
use crate::parser::langparser::LangParser;
|
||||||
use crate::parser::source::SourceFile;
|
use crate::parser::source::SourceFile;
|
||||||
use crate::validate_document;
|
use crate::validate_document;
|
||||||
|
|
|
@ -1,251 +1,235 @@
|
||||||
use std::any::Any;
|
use std::{any::Any, cell::Ref, ops::Range, rc::Rc};
|
||||||
use std::cell::Ref;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ops::Range;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::compiler::compiler::Compiler;
|
use crate::{compiler::compiler::{Compiler, Target}, document::{document::{Document, DocumentAccessors}, element::{ElemKind, Element}}, parser::{parser::Parser, rule::Rule, source::{Cursor, Source, Token, VirtualSource}}};
|
||||||
use crate::compiler::compiler::Target;
|
use ariadne::{Label, Report, ReportKind};
|
||||||
use crate::document::document::Document;
|
use mlua::{Function, Lua};
|
||||||
use crate::document::document::DocumentAccessors;
|
|
||||||
use crate::document::element::ContainerElement;
|
|
||||||
use crate::document::element::ElemKind;
|
|
||||||
use crate::document::element::Element;
|
|
||||||
use crate::parser::parser::Parser;
|
|
||||||
use crate::parser::rule::Rule;
|
|
||||||
use crate::parser::source::Cursor;
|
|
||||||
use crate::parser::source::Source;
|
|
||||||
use crate::parser::source::Token;
|
|
||||||
use crate::parser::source::VirtualSource;
|
|
||||||
use crate::parser::util;
|
|
||||||
use crate::parser::util::process_escaped;
|
|
||||||
use crate::parser::util::Property;
|
|
||||||
use crate::parser::util::PropertyMapError;
|
|
||||||
use crate::parser::util::PropertyParser;
|
|
||||||
use ariadne::Label;
|
|
||||||
use ariadne::Report;
|
|
||||||
use ariadne::ReportKind;
|
|
||||||
use mlua::Function;
|
|
||||||
use mlua::Lua;
|
|
||||||
use regex::Match;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
use super::paragraph::Paragraph;
|
||||||
pub enum MarkerKind {
|
|
||||||
Open,
|
|
||||||
Close,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ListMarker {
|
|
||||||
pub(self) location: Token,
|
|
||||||
pub(self) numbered: bool,
|
|
||||||
pub(self) kind: MarkerKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element for ListMarker {
|
|
||||||
fn location(&self) -> &Token { &self.location }
|
|
||||||
|
|
||||||
fn kind(&self) -> ElemKind { ElemKind::Block }
|
|
||||||
|
|
||||||
fn element_name(&self) -> &'static str { "List Marker" }
|
|
||||||
|
|
||||||
fn to_string(&self) -> String { format!("{self:#?}") }
|
|
||||||
|
|
||||||
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
|
|
||||||
match compiler.target() {
|
|
||||||
Target::HTML => match (self.kind, self.numbered) {
|
|
||||||
(MarkerKind::Close, true) => Ok("</ol>".to_string()),
|
|
||||||
(MarkerKind::Close, false) => Ok("</ul>".to_string()),
|
|
||||||
(MarkerKind::Open, true) => Ok("<ol>".to_string()),
|
|
||||||
(MarkerKind::Open, false) => Ok("<ul>".to_string()),
|
|
||||||
},
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ListEntry {
|
pub struct ListEntry {
|
||||||
pub(self) location: Token,
|
location: Token,
|
||||||
pub(self) numbering: Vec<(bool, usize)>,
|
numbering: Vec<(bool, usize)>,
|
||||||
pub(self) content: Vec<Box<dyn Element>>,
|
content: Vec<Box<dyn Element>>,
|
||||||
pub(self) bullet: Option<String>,
|
|
||||||
|
// TODO bullet_maker : FnMut<...>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for ListEntry {
|
impl ListEntry {
|
||||||
fn location(&self) -> &Token { &self.location }
|
pub fn new(location: Token, numbering: Vec<(bool, usize)>, content: Vec<Box<dyn Element>>) -> Self {
|
||||||
|
Self { location, numbering, content }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn kind(&self) -> ElemKind { ElemKind::Block }
|
#[derive(Debug)]
|
||||||
|
pub struct List
|
||||||
|
{
|
||||||
|
location: Token,
|
||||||
|
entries: Vec<ListEntry>
|
||||||
|
}
|
||||||
|
|
||||||
fn element_name(&self) -> &'static str { "List Entry" }
|
impl List
|
||||||
|
{
|
||||||
|
pub fn new(location: Token) -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
location,
|
||||||
|
entries: Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_string(&self) -> String { format!("{self:#?}") }
|
pub fn push(&mut self, entry: ListEntry)
|
||||||
|
{
|
||||||
|
self.location.range = self.location.start()..entry.location.end();
|
||||||
|
self.entries.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
|
impl Element for List
|
||||||
match compiler.target() {
|
{
|
||||||
|
fn location(&self) -> &Token { &self.location }
|
||||||
|
|
||||||
|
fn kind(&self) -> ElemKind { ElemKind::Block }
|
||||||
|
|
||||||
|
fn element_name(&self) -> &'static str { "List" }
|
||||||
|
|
||||||
|
fn to_string(&self) -> String { format!("{self:#?}") }
|
||||||
|
|
||||||
|
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
|
||||||
|
match compiler.target()
|
||||||
|
{
|
||||||
Target::HTML => {
|
Target::HTML => {
|
||||||
let mut result = "<li>".to_string();
|
let mut result = String::new();
|
||||||
for elem in &self.content {
|
|
||||||
result += elem.compile(compiler, document)?.as_str();
|
//TODO: Do something about indexing
|
||||||
|
let mut current_list: Vec<bool> = vec![];
|
||||||
|
let mut match_stack = |result: &mut String, target: &Vec<(bool, usize)>| {
|
||||||
|
|
||||||
|
// Find index after which current_list and target differ
|
||||||
|
let mut match_idx = 0usize;
|
||||||
|
for i in 0..current_list.len()
|
||||||
|
{
|
||||||
|
if i >= target.len() || current_list[i] != target[i].0 { break }
|
||||||
|
else { match_idx = i+1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close until same match
|
||||||
|
for _ in match_idx..current_list.len()
|
||||||
|
{
|
||||||
|
result.push_str(["</ul>", "</ol>"][current_list.pop().unwrap() as usize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open
|
||||||
|
for i in match_idx..target.len()
|
||||||
|
{
|
||||||
|
result.push_str(["<ul>", "<ol>"][target[i].0 as usize]);
|
||||||
|
current_list.push(target[i].0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.entries.iter()
|
||||||
|
.try_for_each(|ent|
|
||||||
|
{
|
||||||
|
match_stack(&mut result, &ent.numbering);
|
||||||
|
result.push_str("<li>");
|
||||||
|
match ent.content.iter().enumerate()
|
||||||
|
.try_for_each(|(_idx, elem)| {
|
||||||
|
match elem.compile(compiler, document) {
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok(s) => { result.push_str(s.as_str()); Ok(()) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Err(e) => Err(e),
|
||||||
|
_ => {
|
||||||
|
result.push_str("</li>");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
result += "</li>";
|
match_stack(&mut result, &Vec::<(bool, usize)>::new());
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
Target::LATEX => Err("Unimplemented compiler".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_container(&self) -> Option<&dyn ContainerElement> { Some(self) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerElement for ListEntry {
|
/*
|
||||||
fn contained(&self) -> &Vec<Box<dyn Element>> { &self.content }
|
impl Element for ListEntry
|
||||||
|
{
|
||||||
fn push(&mut self, elem: Box<dyn Element>) -> Result<(), String> {
|
fn location(&self) -> &Token { &self.location }
|
||||||
if elem.kind() == ElemKind::Block {
|
fn kind(&self) -> ElemKind { ElemKind::Inline }
|
||||||
return Err("Cannot add block element inside a list".to_string());
|
fn element_name(&self) -> &'static str { "List" }
|
||||||
|
fn to_string(&self) -> String { format!("{self:#?}") }
|
||||||
|
fn compile(&self, compiler: &Compiler) -> Result<String, String> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref STATE_NAME : &'static str = "list.state";
|
||||||
|
static ref LIST_OPEN : [&'static str; 2] = ["<ul>", "<ol>"];
|
||||||
|
static ref LIST_CLOSE : [&'static str; 2] = ["</ul>", "</ol>"];
|
||||||
}
|
}
|
||||||
|
|
||||||
self.content.push(elem);
|
// TODO: State.shouldpreserve?
|
||||||
Ok(())
|
// Called upon every element
|
||||||
|
//let state = compiler.get_state_mut::<ListState, _>(*STATE_NAME)
|
||||||
|
//.or_else(|| {
|
||||||
|
// compiler.insert_state(STATE_NAME.to_string(), Box::new(ListState(Vec::new())) as Box<dyn Any>);
|
||||||
|
// compiler.get_state_mut::<ListState, _>(*STATE_NAME)
|
||||||
|
//}).unwrap();
|
||||||
|
|
||||||
|
match compiler.target()
|
||||||
|
{
|
||||||
|
Target::HTML => {
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
//TODO: Do something about indexing
|
||||||
|
//&self.numbering.iter()
|
||||||
|
// .zip(&state.0)
|
||||||
|
// .for_each(|((wants_numbered, _), is_numbered)|
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// });
|
||||||
|
|
||||||
|
result.push_str("<li>");
|
||||||
|
match self.content.iter()
|
||||||
|
.try_for_each(|ent| match ent.compile(compiler) {
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok(s) => Ok(result.push_str(s.as_str())),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
result.push_str("</li>");
|
||||||
|
//result.push_str(LIST_OPEN[self.numbered as usize]);
|
||||||
|
//self.entries.iter()
|
||||||
|
// .for_each(|(_index, entry)|
|
||||||
|
// result.push_str(format!("<li>{}</li>", compiler.compile(entry)).as_str()));
|
||||||
|
//result.push_str(LIST_CLOSE[self.numbered as usize]);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
Target::LATEX => Err("Unimplemented compiler".to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
pub struct ListRule {
|
pub struct ListRule
|
||||||
|
{
|
||||||
start_re: Regex,
|
start_re: Regex,
|
||||||
continue_re: Regex,
|
continue_re: Regex
|
||||||
properties: PropertyParser,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListRule {
|
impl ListRule {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut props = HashMap::new();
|
|
||||||
props.insert(
|
|
||||||
"offset".to_string(),
|
|
||||||
Property::new(false, "Entry numbering offset".to_string(), None),
|
|
||||||
);
|
|
||||||
props.insert(
|
|
||||||
"bullet".to_string(),
|
|
||||||
Property::new(false, "Entry bullet".to_string(), None),
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
start_re: Regex::new(r"(?:^|\n)(?:[^\S\r\n]+)([*-]+)(?:\[((?:\\.|[^\\\\])*?)\])?(.*)")
|
start_re: Regex::new(r"(?:^|\n)(?:[^\S\r\n]+)([*-]+).*").unwrap(),
|
||||||
.unwrap(),
|
continue_re: Regex::new(r"(?:^|\n)([^\S\r\n]+).*").unwrap(),
|
||||||
continue_re: Regex::new(r"(?:^|\n)([^\S\r\n]+)([^\s].*)").unwrap(),
|
|
||||||
properties: PropertyParser { properties: props },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_markers(
|
fn parse_depth(depth: &str, document: &dyn Document) -> Vec<(bool, usize)>
|
||||||
token: &Token,
|
{
|
||||||
parser: &dyn Parser,
|
|
||||||
document: &dyn Document,
|
|
||||||
current: &Vec<(bool, usize)>,
|
|
||||||
target: &Vec<(bool, usize)>,
|
|
||||||
) {
|
|
||||||
let mut start_pos = 0;
|
|
||||||
for i in 0..std::cmp::min(target.len(), current.len()) {
|
|
||||||
if current[i].0 != target[i].0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
start_pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close
|
|
||||||
for i in start_pos..current.len() {
|
|
||||||
parser.push(
|
|
||||||
document,
|
|
||||||
Box::new(ListMarker {
|
|
||||||
location: token.clone(),
|
|
||||||
kind: MarkerKind::Close,
|
|
||||||
numbered: current[current.len() - 1 - (i - start_pos)].0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open
|
|
||||||
for i in start_pos..target.len() {
|
|
||||||
parser.push(
|
|
||||||
document,
|
|
||||||
Box::new(ListMarker {
|
|
||||||
location: token.clone(),
|
|
||||||
kind: MarkerKind::Open,
|
|
||||||
numbered: target[i].0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_properties(&self, m: Match) -> Result<(Option<usize>, Option<String>), String> {
|
|
||||||
let processed = process_escaped('\\', "]", m.as_str());
|
|
||||||
let pm = self.properties.parse(processed.as_str())?;
|
|
||||||
|
|
||||||
let offset = match pm.get("offset", |_, s| s.parse::<usize>()) {
|
|
||||||
Ok((prop, val)) => Some(val),
|
|
||||||
Err(err) => match err {
|
|
||||||
PropertyMapError::ParseError(err) => {
|
|
||||||
return Err(format!("Failed to parse `offset`: {err}"))
|
|
||||||
}
|
|
||||||
PropertyMapError::NotFoundError(err) => None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let bullet = pm
|
|
||||||
.get("bullet", |_, s| -> Result<String, ()> { Ok(s.to_string()) })
|
|
||||||
.map(|(_, s)| s)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok((offset, bullet))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_depth(depth: &str, document: &dyn Document, offset: usize) -> Vec<(bool, usize)> {
|
|
||||||
let mut parsed = vec![];
|
let mut parsed = vec![];
|
||||||
// FIXME: Previous iteration used to recursively retrieve the list indent
|
// FIXME: Previous iteration used to recursively retrieve the list indent
|
||||||
let prev_entry = document
|
let prev_entry = document.last_element::<List>()
|
||||||
.last_element::<ListEntry>()
|
.and_then(|list| Ref::filter_map(list, |m| m.entries.last() ).ok() )
|
||||||
.and_then(|entry| Ref::filter_map(entry, |e| Some(&e.numbering)).ok());
|
.and_then(|entry| Ref::filter_map(entry, |e| Some(&e.numbering)).ok() );
|
||||||
|
|
||||||
let mut continue_match = true;
|
let mut continue_match = true;
|
||||||
depth.chars().enumerate().for_each(|(idx, c)| {
|
depth.chars().enumerate().for_each(|(idx, c)|
|
||||||
let number = if offset == 0 {
|
{
|
||||||
prev_entry
|
let number = prev_entry.as_ref()
|
||||||
.as_ref()
|
.and_then(|v| {
|
||||||
.and_then(|v| {
|
if !continue_match { return None }
|
||||||
if !continue_match {
|
let numbered = c == '-';
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let numbered = c == '-';
|
|
||||||
|
|
||||||
match v.get(idx) {
|
match v.get(idx)
|
||||||
None => None,
|
{
|
||||||
Some((prev_numbered, prev_idx)) => {
|
None => None,
|
||||||
if *prev_numbered != numbered {
|
Some((prev_numbered, prev_idx)) => {
|
||||||
continue_match = false;
|
if *prev_numbered != numbered { continue_match = false; None } // New depth
|
||||||
None
|
else if idx+1 == v.len() { Some(prev_idx+1) } // Increase from previous
|
||||||
}
|
else { Some(*prev_idx) } // Do nothing
|
||||||
// New depth
|
|
||||||
else if idx + 1 == v.len() {
|
|
||||||
Some(prev_idx + 1)
|
|
||||||
}
|
|
||||||
// Increase from previous
|
|
||||||
else {
|
|
||||||
Some(*prev_idx)
|
|
||||||
} // Do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.unwrap_or(1)
|
})
|
||||||
} else {
|
.or(Some(0usize))
|
||||||
offset
|
.unwrap();
|
||||||
};
|
|
||||||
|
|
||||||
match c {
|
match c
|
||||||
|
{
|
||||||
'*' => parsed.push((false, number)),
|
'*' => parsed.push((false, number)),
|
||||||
'-' => parsed.push((true, number)),
|
'-' => parsed.push((true, number)),
|
||||||
_ => panic!("Unimplemented"),
|
_ => panic!("Unimplemented")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -253,186 +237,101 @@ impl ListRule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rule for ListRule {
|
impl Rule for ListRule
|
||||||
|
{
|
||||||
fn name(&self) -> &'static str { "List" }
|
fn name(&self) -> &'static str { "List" }
|
||||||
|
|
||||||
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
|
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
|
||||||
self.start_re
|
self.start_re.find_at(cursor.source.content(), cursor.pos)
|
||||||
.find_at(cursor.source.content(), cursor.pos)
|
.map_or(None,
|
||||||
.map_or(None, |m| {
|
|m| Some((m.start(), Box::new([false;0]) as Box<dyn Any>)) )
|
||||||
Some((m.start(), Box::new([false; 0]) as Box<dyn Any>))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_match<'a>(
|
fn on_match<'a>(&self, parser: &dyn Parser, document: &'a dyn Document<'a>, cursor: Cursor, _match_data: Option<Box<dyn Any>>)
|
||||||
&self,
|
-> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
|
||||||
parser: &dyn Parser,
|
|
||||||
document: &'a dyn Document<'a>,
|
|
||||||
cursor: Cursor,
|
|
||||||
_match_data: Option<Box<dyn Any>>,
|
|
||||||
) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
|
|
||||||
let mut reports = vec![];
|
let mut reports = vec![];
|
||||||
|
|
||||||
let content = cursor.source.content();
|
let content = cursor.source.content();
|
||||||
let mut end_cursor = cursor.clone();
|
let (end_cursor, numbering, source) = match self.start_re.captures_at(content, cursor.pos) {
|
||||||
loop {
|
None => panic!("Unknown error"),
|
||||||
if let Some(captures) = self.start_re.captures_at(content, end_cursor.pos) {
|
Some(caps) => {
|
||||||
if captures.get(0).unwrap().start() != end_cursor.pos {
|
let mut end_pos = caps.get(0).unwrap().end();
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Advance cursor
|
|
||||||
end_cursor = end_cursor.at(captures.get(0).unwrap().end());
|
|
||||||
|
|
||||||
// Properties
|
let mut spacing = None; // Spacing used to continue list entry
|
||||||
let mut offset = None;
|
loop {
|
||||||
let mut bullet = None;
|
// If another entry starts on the next line, don't continue matching
|
||||||
if let Some(properties) = captures.get(2) {
|
match self.next_match(&cursor.at(end_pos))
|
||||||
match self.parse_properties(properties) {
|
|
||||||
Err(err) => {
|
|
||||||
reports.push(
|
|
||||||
Report::build(
|
|
||||||
ReportKind::Warning,
|
|
||||||
cursor.source.clone(),
|
|
||||||
properties.start(),
|
|
||||||
)
|
|
||||||
.with_message("Invalid List Entry Properties")
|
|
||||||
.with_label(
|
|
||||||
Label::new((cursor.source.clone(), properties.range()))
|
|
||||||
.with_message(err)
|
|
||||||
.with_color(parser.colors().warning),
|
|
||||||
)
|
|
||||||
.finish(),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(props) => (offset, bullet) = props,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get bullet from previous entry if it exists
|
|
||||||
if bullet.is_none() {
|
|
||||||
bullet = document
|
|
||||||
.last_element::<ListEntry>()
|
|
||||||
.and_then(|prev| prev.bullet.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Depth
|
|
||||||
let depth = ListRule::parse_depth(
|
|
||||||
captures.get(1).unwrap().as_str(),
|
|
||||||
document,
|
|
||||||
offset.unwrap_or(0),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Content
|
|
||||||
let entry_start = captures.get(0).unwrap().start();
|
|
||||||
let mut entry_content = captures.get(3).unwrap().as_str().to_string();
|
|
||||||
let mut spacing: Option<(Range<usize>, &str)> = None;
|
|
||||||
while let Some(captures) = self.continue_re.captures_at(content, end_cursor.pos) {
|
|
||||||
// Break if next element is another entry
|
|
||||||
if captures.get(0).unwrap().start() != end_cursor.pos
|
|
||||||
|| captures
|
|
||||||
.get(2)
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
.find(|c| c == '*' || c == '-')
|
|
||||||
== Some(0)
|
|
||||||
{
|
{
|
||||||
break;
|
Some((pos, _)) => {
|
||||||
}
|
if pos == end_pos { break }
|
||||||
// Advance cursor
|
|
||||||
end_cursor = end_cursor.at(captures.get(0).unwrap().end());
|
|
||||||
|
|
||||||
// Spacing
|
|
||||||
let current_spacing = captures.get(1).unwrap().as_str();
|
|
||||||
if let Some(spacing) = &spacing {
|
|
||||||
if spacing.1 != current_spacing {
|
|
||||||
reports.push(
|
|
||||||
Report::build(
|
|
||||||
ReportKind::Warning,
|
|
||||||
cursor.source.clone(),
|
|
||||||
captures.get(1).unwrap().start(),
|
|
||||||
)
|
|
||||||
.with_message("Invalid list entry spacing")
|
|
||||||
.with_label(
|
|
||||||
Label::new((
|
|
||||||
cursor.source.clone(),
|
|
||||||
captures.get(1).unwrap().range(),
|
|
||||||
))
|
|
||||||
.with_message("Spacing for list entries do not match")
|
|
||||||
.with_color(parser.colors().warning),
|
|
||||||
)
|
|
||||||
.with_label(
|
|
||||||
Label::new((cursor.source.clone(), spacing.0.clone()))
|
|
||||||
.with_message("Previous spacing")
|
|
||||||
.with_color(parser.colors().warning),
|
|
||||||
)
|
|
||||||
.finish(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
None => {},
|
||||||
spacing = Some((captures.get(1).unwrap().range(), current_spacing));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry_content += " ";
|
// Continue matching as current entry
|
||||||
entry_content += captures.get(2).unwrap().as_str();
|
match self.continue_re.captures_at(content, end_pos) {
|
||||||
|
None => break,
|
||||||
|
Some(continue_caps) => {
|
||||||
|
if continue_caps.get(0).unwrap().start() != end_pos { break }
|
||||||
|
|
||||||
|
// Get the spacing
|
||||||
|
let cap_spacing = continue_caps.get(1).unwrap();
|
||||||
|
match &spacing {
|
||||||
|
None => spacing = Some(cap_spacing.range()),
|
||||||
|
Some(spacing) => 'some: {
|
||||||
|
if content[cap_spacing.range()] == content[spacing.clone()] { break 'some }
|
||||||
|
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Warning, cursor.source.clone(), continue_caps.get(1).unwrap().start())
|
||||||
|
.with_message("Invalid list entry spacing")
|
||||||
|
.with_label(
|
||||||
|
Label::new((cursor.source.clone(), cap_spacing.range()))
|
||||||
|
.with_message("Spacing for list entries must match")
|
||||||
|
.with_color(parser.colors().warning))
|
||||||
|
.with_label(
|
||||||
|
Label::new((cursor.source.clone(), spacing.clone()))
|
||||||
|
.with_message("Previous spacing")
|
||||||
|
.with_color(parser.colors().warning))
|
||||||
|
.finish());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end_pos = continue_caps.get(0).unwrap().end();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse entry content
|
let start_pos = caps.get(1).unwrap().end();
|
||||||
let token = Token::new(end_cursor.pos..end_cursor.pos, end_cursor.source.clone());
|
let source = VirtualSource::new(
|
||||||
let entry_src = Rc::new(VirtualSource::new(
|
Token::new(start_pos..end_pos, cursor.source.clone()),
|
||||||
token.clone(),
|
|
||||||
"List Entry".to_string(),
|
"List Entry".to_string(),
|
||||||
entry_content,
|
content.as_str()[start_pos..end_pos].to_string(),
|
||||||
));
|
|
||||||
let parsed_content = match util::parse_paragraph(parser, entry_src, document) {
|
|
||||||
Err(err) => {
|
|
||||||
reports.push(
|
|
||||||
Report::build(ReportKind::Warning, token.source(), token.range.start)
|
|
||||||
.with_message("Unable to Parse List Entry")
|
|
||||||
.with_label(
|
|
||||||
Label::new((token.source(), token.range.clone()))
|
|
||||||
.with_message(err)
|
|
||||||
.with_color(parser.colors().warning),
|
|
||||||
)
|
|
||||||
.finish(),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(mut paragraph) => std::mem::replace(&mut paragraph.content, vec![]),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(previous_depth) = document
|
|
||||||
.last_element::<ListEntry>()
|
|
||||||
.map(|ent| ent.numbering.clone())
|
|
||||||
{
|
|
||||||
ListRule::push_markers(&token, parser, document, &previous_depth, &depth);
|
|
||||||
} else {
|
|
||||||
ListRule::push_markers(&token, parser, document, &vec![], &depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.push(
|
|
||||||
document,
|
|
||||||
Box::new(ListEntry {
|
|
||||||
location: Token::new(
|
|
||||||
entry_start..end_cursor.pos,
|
|
||||||
end_cursor.source.clone(),
|
|
||||||
),
|
|
||||||
numbering: depth,
|
|
||||||
content: parsed_content,
|
|
||||||
bullet,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close all lists
|
(cursor.at(end_pos),
|
||||||
let current = document
|
ListRule::parse_depth(caps.get(1).unwrap().as_str(), document),
|
||||||
.last_element::<ListEntry>()
|
source)
|
||||||
.map(|ent| ent.numbering.clone())
|
},
|
||||||
.unwrap();
|
};
|
||||||
let token = Token::new(end_cursor.pos..end_cursor.pos, end_cursor.source.clone());
|
|
||||||
ListRule::push_markers(&token, parser, document, ¤t, &Vec::new());
|
let parsed_entry = parser.parse(Rc::new(source), Some(document));
|
||||||
|
let mut parsed_paragraph = parsed_entry.last_element_mut::<Paragraph>().unwrap(); // Extract content from paragraph
|
||||||
|
let entry = ListEntry::new(
|
||||||
|
Token::new(cursor.pos..end_cursor.pos, cursor.source.clone()),
|
||||||
|
numbering,
|
||||||
|
std::mem::replace(&mut parsed_paragraph.content, Vec::new())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ger previous list, if none insert a new list
|
||||||
|
let mut list = match document.last_element_mut::<List>()
|
||||||
|
{
|
||||||
|
Some(last) => last,
|
||||||
|
None => {
|
||||||
|
parser.push(document,
|
||||||
|
Box::new(List::new(
|
||||||
|
Token::new(cursor.pos..end_cursor.pos, cursor.source.clone()))));
|
||||||
|
document.last_element_mut::<List>().unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
list.push(entry);
|
||||||
|
|
||||||
(end_cursor, reports)
|
(end_cursor, reports)
|
||||||
}
|
}
|
||||||
|
@ -440,78 +339,3 @@ impl Rule for ListRule {
|
||||||
// TODO
|
// TODO
|
||||||
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
|
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::compiler::compiler::Target;
|
|
||||||
use crate::elements::paragraph::Paragraph;
|
|
||||||
use crate::elements::text::Text;
|
|
||||||
use crate::parser::langparser::LangParser;
|
|
||||||
use crate::parser::source::SourceFile;
|
|
||||||
use crate::validate_document;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parser() {
|
|
||||||
let source = Rc::new(SourceFile::with_content(
|
|
||||||
"".to_string(),
|
|
||||||
r#"
|
|
||||||
* 1
|
|
||||||
*[offset=7] 2
|
|
||||||
continued
|
|
||||||
* 3
|
|
||||||
|
|
||||||
* New list
|
|
||||||
*-[bullet=(*)] A
|
|
||||||
*- B
|
|
||||||
* Back
|
|
||||||
*-* More nested
|
|
||||||
"#
|
|
||||||
.to_string(),
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
let parser = LangParser::default();
|
|
||||||
let compiler = Compiler::new(Target::HTML, None);
|
|
||||||
let doc = parser.parse(source, None);
|
|
||||||
|
|
||||||
validate_document!(doc.content().borrow(), 0,
|
|
||||||
ListMarker { numbered == false, kind == MarkerKind::Open };
|
|
||||||
ListEntry { numbering == vec![(false, 1)] } {
|
|
||||||
Text { content == "1" };
|
|
||||||
};
|
|
||||||
ListEntry { numbering == vec![(false, 7)] } {
|
|
||||||
Text { /*content == "2 continued"*/ };
|
|
||||||
};
|
|
||||||
ListEntry { numbering == vec![(false, 8)] } {
|
|
||||||
Text { content == "3" };
|
|
||||||
};
|
|
||||||
ListMarker { numbered == false, kind == MarkerKind::Close };
|
|
||||||
|
|
||||||
Paragraph;
|
|
||||||
|
|
||||||
ListMarker { numbered == false, kind == MarkerKind::Open };
|
|
||||||
ListEntry { numbering == vec![(false, 1)] } {
|
|
||||||
Text { content == "New list" };
|
|
||||||
};
|
|
||||||
ListMarker { numbered == true, kind == MarkerKind::Open };
|
|
||||||
ListEntry { numbering == vec![(false, 2), (true, 1)], bullet == Some("(*)".to_string()) } {
|
|
||||||
Text { content == "A" };
|
|
||||||
};
|
|
||||||
ListEntry { numbering == vec![(false, 2), (true, 2)], bullet == Some("(*)".to_string()) } {
|
|
||||||
Text { content == "B" };
|
|
||||||
};
|
|
||||||
ListMarker { numbered == true, kind == MarkerKind::Close };
|
|
||||||
ListEntry { numbering == vec![(false, 2)] } {
|
|
||||||
Text { content == "Back" };
|
|
||||||
};
|
|
||||||
ListMarker { numbered == true, kind == MarkerKind::Open };
|
|
||||||
ListMarker { numbered == false, kind == MarkerKind::Open };
|
|
||||||
ListEntry { numbering == vec![(false, 3), (true, 1), (false, 1)] } {
|
|
||||||
Text { content == "More nested" };
|
|
||||||
};
|
|
||||||
ListMarker { numbered == false, kind == MarkerKind::Close };
|
|
||||||
ListMarker { numbered == true, kind == MarkerKind::Close };
|
|
||||||
ListMarker { numbered == false, kind == MarkerKind::Close };
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl RawRule {
|
||||||
Regex::new(r"\{\?(?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)(\?\}))?")
|
Regex::new(r"\{\?(?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)(\?\}))?")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
],
|
],
|
||||||
properties: PropertyParser { properties: props },
|
properties: PropertyParser{ properties: props },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,18 +268,15 @@ impl RegexRule for RawRule {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::compiler::compiler::Target;
|
use crate::compiler::compiler::Target;
|
||||||
use crate::elements::paragraph::Paragraph;
|
|
||||||
use crate::elements::text::Text;
|
|
||||||
use crate::parser::langparser::LangParser;
|
use crate::parser::langparser::LangParser;
|
||||||
use crate::parser::source::SourceFile;
|
use crate::parser::source::SourceFile;
|
||||||
use crate::validate_document;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parser() {
|
fn raw_tests() {
|
||||||
let source = Rc::new(SourceFile::with_content(
|
let source = Rc::new(SourceFile::with_content(
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
r#"
|
r#"
|
||||||
Break{?[kind=block] Raw?}NewParagraph{?<b>?}
|
Break{?[kind=block]<RAW>?}NewParagraph
|
||||||
"#
|
"#
|
||||||
.to_string(),
|
.to_string(),
|
||||||
None,
|
None,
|
||||||
|
@ -288,13 +285,13 @@ Break{?[kind=block] Raw?}NewParagraph{?<b>?}
|
||||||
let compiler = Compiler::new(Target::HTML, None);
|
let compiler = Compiler::new(Target::HTML, None);
|
||||||
let doc = parser.parse(source, None);
|
let doc = parser.parse(source, None);
|
||||||
|
|
||||||
validate_document!(doc.content().borrow(), 0,
|
let borrow = doc.content().borrow();
|
||||||
Paragraph;
|
let found = borrow
|
||||||
Raw { kind == ElemKind::Block, content == "Raw" };
|
.iter()
|
||||||
Paragraph {
|
.filter_map(|e| e.downcast_ref::<Raw>())
|
||||||
Text;
|
.collect::<Vec<_>>();
|
||||||
Raw { kind == ElemKind::Inline, content == "<b>" };
|
|
||||||
};
|
assert_eq!(found[0].compile(&compiler, &*doc), Ok("<RAW>".to_string()));
|
||||||
);
|
//assert_eq!(found[1].compile(&compiler, &*doc), Ok("<RAW>".to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue