Add some tests
This commit is contained in:
parent
2617cbe507
commit
5ccd8048c2
8 changed files with 579 additions and 354 deletions
|
@ -6,7 +6,7 @@ use mlua::{Function, Lua};
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
use syntect::{easy::HighlightLines, highlighting::ThemeSet, parsing::SyntaxSet};
|
use syntect::{easy::HighlightLines, highlighting::ThemeSet, parsing::SyntaxSet};
|
||||||
|
|
||||||
use crate::{cache::cache::{Cached, CachedError}, compiler::compiler::{Compiler, Target}, document::{document::Document, element::{ElemKind, Element}}, parser::{parser::Parser, rule::RegexRule, source::{Source, Token}, util::{self, Property, PropertyParser}}};
|
use crate::{cache::cache::{Cached, CachedError}, compiler::compiler::{Compiler, Target}, document::{document::Document, element::{ElemKind, Element}}, parser::{parser::Parser, rule::RegexRule, source::{Source, Token}, util::{self, Property, PropertyMapError, PropertyParser}}};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
@ -341,18 +341,32 @@ impl RegexRule for CodeRule
|
||||||
|prop, value| value.parse::<usize>().map_err(|e| (prop, e)))
|
|prop, value| value.parse::<usize>().map_err(|e| (prop, e)))
|
||||||
{
|
{
|
||||||
Ok((_prop, offset)) => offset,
|
Ok((_prop, offset)) => offset,
|
||||||
Err((prop, e)) => {
|
Err(e) => match e {
|
||||||
reports.push(
|
PropertyMapError::ParseError((prop, err)) => {
|
||||||
Report::build(ReportKind::Error, token.source(), token.start())
|
reports.push(
|
||||||
.with_message("Invalid Code Property")
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
.with_label(
|
.with_message("Invalid Code Property")
|
||||||
Label::new((token.source().clone(), token.start()+1..token.end()))
|
.with_label(
|
||||||
.with_message(format!("Property `line_offset: {}` cannot be converted: {}",
|
Label::new((token.source().clone(), token.start()+1..token.end()))
|
||||||
prop.fg(parser.colors().info),
|
.with_message(format!("Property `line_offset: {}` cannot be converted: {}",
|
||||||
e.fg(parser.colors().error)))
|
prop.fg(parser.colors().info),
|
||||||
.with_color(parser.colors().warning))
|
err.fg(parser.colors().error)))
|
||||||
.finish());
|
.with_color(parser.colors().warning))
|
||||||
return reports;
|
.finish());
|
||||||
|
return reports;
|
||||||
|
},
|
||||||
|
PropertyMapError::NotFoundError(err) => {
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
|
.with_message("Invalid Code Property")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), token.start()+1..token.end()))
|
||||||
|
.with_message(format!("Property `{}` doesn't exist",
|
||||||
|
err.fg(parser.colors().info)))
|
||||||
|
.with_color(parser.colors().warning))
|
||||||
|
.finish());
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use mlua::{Error::BadArgument, Function, Lua};
|
use mlua::{Error::BadArgument, Function, Lua};
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
use crate::{compiler::compiler::Compiler, document::{document::Document, element::{ElemKind, Element}}, lua::kernel::CTX, parser::{parser::Parser, rule::RegexRule, source::{Source, Token}, util::{self, Property, PropertyParser}}};
|
use crate::{compiler::compiler::Compiler, document::{document::Document, element::{ElemKind, Element}}, lua::kernel::CTX, parser::{parser::Parser, rule::RegexRule, source::{Source, Token}, util::{self, Property, PropertyMapError, PropertyParser}}};
|
||||||
use ariadne::{Fmt, Label, Report, ReportKind};
|
use ariadne::{Fmt, Label, Report, ReportKind};
|
||||||
use std::{collections::HashMap, ops::Range, rc::Rc, str::FromStr, sync::Arc};
|
use std::{collections::HashMap, ops::Range, rc::Rc, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
|
@ -138,18 +138,32 @@ impl RegexRule for RawRule
|
||||||
|prop, value| ElemKind::from_str(value.as_str()).map_err(|e| (prop, e)))
|
|prop, value| ElemKind::from_str(value.as_str()).map_err(|e| (prop, e)))
|
||||||
{
|
{
|
||||||
Ok((_prop, kind)) => kind,
|
Ok((_prop, kind)) => kind,
|
||||||
Err((prop, e)) => {
|
Err(e) => match e {
|
||||||
reports.push(
|
PropertyMapError::ParseError((prop, err)) => {
|
||||||
Report::build(ReportKind::Error, token.source(), token.start())
|
reports.push(
|
||||||
.with_message("Invalid Raw Code Property")
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
.with_label(
|
.with_message("Invalid Raw Code Property")
|
||||||
Label::new((token.source().clone(), token.range.clone()))
|
.with_label(
|
||||||
.with_message(format!("Property `kind: {}` cannot be converted: {}",
|
Label::new((token.source().clone(), token.range.clone()))
|
||||||
prop.fg(parser.colors().info),
|
.with_message(format!("Property `kind: {}` cannot be converted: {}",
|
||||||
e.fg(parser.colors().error)))
|
prop.fg(parser.colors().info),
|
||||||
.with_color(parser.colors().warning))
|
err.fg(parser.colors().error)))
|
||||||
.finish());
|
.with_color(parser.colors().warning))
|
||||||
return reports;
|
.finish());
|
||||||
|
return reports;
|
||||||
|
},
|
||||||
|
PropertyMapError::NotFoundError(err) => {
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
|
.with_message("Invalid Code Property")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), token.start()+1..token.end()))
|
||||||
|
.with_message(format!("Property `{}` doesn't exist",
|
||||||
|
err.fg(parser.colors().info)))
|
||||||
|
.with_color(parser.colors().warning))
|
||||||
|
.finish());
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::rc::Rc;
|
use std::{cell::{RefCell, RefMut}, collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use crate::parser::source::{Cursor, Source};
|
use crate::{document::{document::Document, element::Element}, lua::kernel::{Kernel, KernelHolder}, parser::{parser::{Parser, ReportColors}, rule::Rule, source::{Cursor, Source}, state::StateHolder}};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LineCursor
|
pub struct LineCursor
|
||||||
|
@ -56,26 +56,6 @@ impl LineCursor
|
||||||
//eprintln!("({}, {c:#?}) ({} {})", self.pos, self.line, self.line_pos);
|
//eprintln!("({}, {c:#?}) ({} {})", self.pos, self.line, self.line_pos);
|
||||||
prev = Some(c);
|
prev = Some(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
self.source.content()
|
|
||||||
.as_str()[start..pos+1]
|
|
||||||
.char_indices()
|
|
||||||
.for_each(|(at, c)| {
|
|
||||||
self.pos = at+start;
|
|
||||||
|
|
||||||
if c == '\n'
|
|
||||||
{
|
|
||||||
self.line += 1;
|
|
||||||
self.line_pos = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.line_pos += c.len_utf8();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
else if pos < self.pos
|
else if pos < self.pos
|
||||||
{
|
{
|
||||||
|
@ -114,3 +94,53 @@ impl From<&LineCursor> for Cursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LsParser
|
||||||
|
{
|
||||||
|
rules: Vec<Box<dyn Rule>>,
|
||||||
|
colors: ReportColors,
|
||||||
|
|
||||||
|
// Parser state
|
||||||
|
pub state: RefCell<StateHolder>,
|
||||||
|
pub kernels: RefCell<HashMap<String, Kernel>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for LsParser
|
||||||
|
{
|
||||||
|
fn colors(&self) -> &ReportColors { &self.colors }
|
||||||
|
fn rules(&self) -> &Vec<Box<dyn Rule>> { &self.rules }
|
||||||
|
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>> { &mut self.rules }
|
||||||
|
|
||||||
|
fn state(&self) -> std::cell::Ref<'_, StateHolder> { self.state.borrow() }
|
||||||
|
fn state_mut(&self) -> std::cell::RefMut<'_, StateHolder> { self.state.borrow_mut() }
|
||||||
|
|
||||||
|
fn push<'a>(&self, doc: &dyn Document, elem: Box<dyn Element>) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<'a>(&self, source: Rc<dyn Source>, parent: Option<&'a dyn Document<'a>>) -> Box<dyn Document<'a>+'a> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_into<'a>(&self, source: Rc<dyn Source>, document: &'a dyn Document<'a>) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KernelHolder for LsParser
|
||||||
|
{
|
||||||
|
fn get_kernel(&self, name: &str)
|
||||||
|
-> Option<RefMut<'_, Kernel>> {
|
||||||
|
RefMut::filter_map(self.kernels.borrow_mut(),
|
||||||
|
|map| map.get_mut(name)).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_kernel(&self, name: String, kernel: Kernel)
|
||||||
|
-> RefMut<'_, Kernel> {
|
||||||
|
//TODO do not get
|
||||||
|
self.kernels.borrow_mut()
|
||||||
|
.insert(name.clone(), kernel);
|
||||||
|
self.get_kernel(name.as_str()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ pub fn provide(semantic_tokens: &mut Vec<SemanticToken>, cursor: &mut LineCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn semantic_token_from_document(document: &Document) -> Vec<SemanticToken>
|
pub fn semantic_token_from_document(document: &dyn Document) -> Vec<SemanticToken>
|
||||||
{
|
{
|
||||||
let mut semantic_tokens = vec![];
|
let mut semantic_tokens = vec![];
|
||||||
|
|
||||||
|
|
|
@ -101,32 +101,7 @@ impl Parser for LangParser
|
||||||
fn colors(&self) -> &ReportColors { &self.colors }
|
fn colors(&self) -> &ReportColors { &self.colors }
|
||||||
|
|
||||||
fn rules(&self) -> &Vec<Box<dyn Rule>> { &self.rules }
|
fn rules(&self) -> &Vec<Box<dyn Rule>> { &self.rules }
|
||||||
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>)
|
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>> { &mut self.rules }
|
||||||
{
|
|
||||||
// Error on duplicate rule
|
|
||||||
let rule_name = (*rule).name();
|
|
||||||
self.rules.iter().for_each(|rule| {
|
|
||||||
if (*rule).name() != rule_name { return; }
|
|
||||||
|
|
||||||
panic!("Attempted to introduce duplicate rule: `{rule_name}`");
|
|
||||||
});
|
|
||||||
|
|
||||||
match after
|
|
||||||
{
|
|
||||||
Some(name) => {
|
|
||||||
let before = self.rules.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_pos, r)| (r).name() == name);
|
|
||||||
|
|
||||||
match before
|
|
||||||
{
|
|
||||||
Some((pos, _)) => self.rules.insert(pos+1, rule),
|
|
||||||
_ => panic!("Unable to find rule named `{name}`, to insert rule `{}` after it", rule.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => self.rules.push(rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn state(&self) -> std::cell::Ref<'_, StateHolder> { self.state.borrow() }
|
fn state(&self) -> std::cell::Ref<'_, StateHolder> { self.state.borrow() }
|
||||||
fn state_mut(&self) -> std::cell::RefMut<'_, StateHolder> { self.state.borrow_mut() }
|
fn state_mut(&self) -> std::cell::RefMut<'_, StateHolder> { self.state.borrow_mut() }
|
||||||
|
|
|
@ -48,7 +48,39 @@ pub trait Parser: KernelHolder
|
||||||
fn colors(&self) -> &ReportColors;
|
fn colors(&self) -> &ReportColors;
|
||||||
|
|
||||||
fn rules(&self) -> &Vec<Box<dyn Rule>>;
|
fn rules(&self) -> &Vec<Box<dyn Rule>>;
|
||||||
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>);
|
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>>;
|
||||||
|
|
||||||
|
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>) -> Result<(), String>
|
||||||
|
{
|
||||||
|
// Error on duplicate rule
|
||||||
|
let rule_name = (*rule).name();
|
||||||
|
if let Err(e) = self.rules().iter().try_for_each(|rule| {
|
||||||
|
if (*rule).name() != rule_name { return Ok(()); }
|
||||||
|
|
||||||
|
return Err(format!("Attempted to introduce duplicate rule: `{rule_name}`"));
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
match after
|
||||||
|
{
|
||||||
|
Some(name) => {
|
||||||
|
let before = self.rules().iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_pos, r)| (r).name() == name);
|
||||||
|
|
||||||
|
match before
|
||||||
|
{
|
||||||
|
Some((pos, _)) => self.rules_mut().insert(pos+1, rule),
|
||||||
|
_ => return Err(format!("Unable to find rule named `{name}`, to insert rule `{}` after it", rule.name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self.rules_mut().push(rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn state(&self) -> Ref<'_, StateHolder>;
|
fn state(&self) -> Ref<'_, StateHolder>;
|
||||||
fn state_mut(&self) -> RefMut<'_, StateHolder>;
|
fn state_mut(&self) -> RefMut<'_, StateHolder>;
|
||||||
|
|
|
@ -2,90 +2,88 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{document::{document::{Document, DocumentAccessors}, element::ElemKind}, elements::paragraph::Paragraph};
|
use crate::{
|
||||||
|
document::{
|
||||||
|
document::{Document, DocumentAccessors},
|
||||||
|
element::ElemKind,
|
||||||
|
},
|
||||||
|
elements::paragraph::Paragraph,
|
||||||
|
};
|
||||||
|
|
||||||
/// Processes text for escape characters and paragraphing
|
/// Processes text for escape characters and paragraphing
|
||||||
pub fn process_text(document: &dyn Document, content: &str) -> String
|
pub fn process_text(document: &dyn Document, content: &str) -> String {
|
||||||
{
|
let mut escaped = false;
|
||||||
let mut escaped = false;
|
let mut newlines = 0usize; // Consecutive newlines
|
||||||
let mut newlines = 0usize; // Consecutive newlines
|
//println!("Processing: [{content}]");
|
||||||
//println!("Processing: [{content}]");
|
let processed = content
|
||||||
let processed = content
|
.graphemes(true)
|
||||||
.grapheme_indices(true)
|
.fold((String::new(), None), |(mut out, prev), g| {
|
||||||
.fold((String::new(), None),
|
if newlines != 0 && g != "\n" {
|
||||||
|(mut out, prev), (_pos, g)| {
|
newlines = 0;
|
||||||
if newlines != 0 && g != "\n"
|
|
||||||
{
|
|
||||||
newlines = 0;
|
|
||||||
|
|
||||||
// Add a whitespace if necessary
|
// Add a whitespace if necessary
|
||||||
match out.chars().last()
|
match out.chars().last() {
|
||||||
{
|
Some(c) => {
|
||||||
Some(c) => {
|
// NOTE: \n is considered whitespace, so previous codepoint can be \n
|
||||||
// NOTE: \n is considered whitespace, so previous codepoint can be \n
|
// (Which can only be done by escaping it)
|
||||||
// (Which can only be done by escaping it)
|
if !c.is_whitespace() || c == '\n' {
|
||||||
if !c.is_whitespace() || c == '\n'
|
out += " ";
|
||||||
{
|
}
|
||||||
out += " ";
|
}
|
||||||
}
|
None => {
|
||||||
}
|
if document
|
||||||
None => {
|
.last_element::<Paragraph>()
|
||||||
if document.last_element::<Paragraph>()
|
.and_then(|par| {
|
||||||
.and_then(|par| par.find_back(|e| e.kind() != ElemKind::Invisible)
|
par.find_back(|e| e.kind() != ElemKind::Invisible)
|
||||||
.and_then(|e| Some(e.kind() == ElemKind::Inline)))
|
.and_then(|e| Some(e.kind() == ElemKind::Inline))
|
||||||
.unwrap_or(false)
|
})
|
||||||
{
|
.unwrap_or(false)
|
||||||
out += " ";
|
{
|
||||||
}
|
out += " ";
|
||||||
} // Don't output anything
|
}
|
||||||
}
|
} // Don't output anything
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Output grapheme literally when escaped
|
// Output grapheme literally when escaped
|
||||||
if escaped
|
if escaped {
|
||||||
{
|
escaped = false;
|
||||||
escaped = false;
|
return (out + g, Some(g));
|
||||||
return (out + g, Some(g));
|
}
|
||||||
}
|
// Increment newlines counter
|
||||||
// Increment newlines counter
|
else if g == "\n" {
|
||||||
else if g == "\n"
|
newlines += 1;
|
||||||
{
|
return (out, Some(g));
|
||||||
newlines += 1;
|
}
|
||||||
return (out, Some(g));
|
// Determine if escaped
|
||||||
}
|
else if g == "\\" {
|
||||||
// Determine if escaped
|
escaped = !escaped;
|
||||||
else if g == "\\"
|
return (out, Some(g));
|
||||||
{
|
}
|
||||||
escaped = !escaped;
|
// Whitespaces
|
||||||
return (out, Some(g));
|
else if g.chars().count() == 1 && g.chars().last().unwrap().is_whitespace() {
|
||||||
}
|
// Content begins with whitespace
|
||||||
// Whitespaces
|
if prev.is_none() {
|
||||||
else if g.chars().count() == 1 && g.chars().last().unwrap().is_whitespace()
|
if document.last_element::<Paragraph>().is_some() {
|
||||||
{
|
return (out + g, Some(g));
|
||||||
// Content begins with whitespace
|
} else {
|
||||||
if prev.is_none()
|
return (out, Some(g));
|
||||||
{
|
}
|
||||||
if document.last_element::<Paragraph>().is_some()
|
}
|
||||||
{
|
// Consecutive whitespaces are converted to a single whitespace
|
||||||
return (out+g, Some(g));
|
else if prev.unwrap().chars().count() == 1
|
||||||
}
|
&& prev.unwrap().chars().last().unwrap().is_whitespace()
|
||||||
else
|
{
|
||||||
{
|
return (out, Some(g));
|
||||||
return (out, Some(g));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Consecutive whitespaces are converted to a single whitespace
|
|
||||||
else if prev.unwrap().chars().count() == 1 &&
|
|
||||||
prev.unwrap().chars().last().unwrap().is_whitespace()
|
|
||||||
{
|
|
||||||
return (out, Some(g));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (out + g, Some(g));
|
return (out + g, Some(g));
|
||||||
}).0.to_string();
|
})
|
||||||
|
.0
|
||||||
|
.to_string();
|
||||||
|
|
||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processed a string and escapes a single token out of it
|
/// Processed a string and escapes a single token out of it
|
||||||
|
@ -94,104 +92,124 @@ pub fn process_text(document: &dyn Document, content: &str) -> String
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// assert_eq!(process_escaped('\\', "%", "escaped: \\%, also escaped: \\\\\\%, untouched: \\a"),
|
/// assert_eq!(process_escaped('\\', "%", "escaped: \\%, also escaped: \\\\\\%, untouched: \\a"),
|
||||||
/// "escaped: %, also escaped: \\%, untouched \\a");
|
/// "escaped: %, also escaped: \\%, untouched: \\a");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn process_escaped<S: AsRef<str>>(escape: char, token: &'static str, content: S) -> String
|
pub fn process_escaped<S: AsRef<str>>(escape: char, token: &'static str, content: S) -> String {
|
||||||
{
|
let mut processed = String::new();
|
||||||
let mut processed = String::new();
|
let mut escaped = 0;
|
||||||
let mut escaped = 0;
|
let mut token_it = token.chars().peekable();
|
||||||
let mut token_it = token.chars().peekable();
|
for c in content
|
||||||
for c in content.as_ref().chars()
|
.as_ref()
|
||||||
.as_str()
|
.chars()
|
||||||
.trim_start()
|
.as_str()
|
||||||
.trim_end()
|
.trim_start()
|
||||||
.chars()
|
.trim_end()
|
||||||
{
|
.chars()
|
||||||
if c == escape
|
{
|
||||||
{
|
if c == escape {
|
||||||
escaped += 1;
|
escaped += 1;
|
||||||
}
|
} else if escaped % 2 == 1 && token_it.peek().map_or(false, |p| *p == c) {
|
||||||
else if escaped % 2 == 1 && token_it.peek().map_or(false, |p| *p == c)
|
let _ = token_it.next();
|
||||||
{
|
if token_it.peek() == None {
|
||||||
let _ = token_it.next();
|
(0..(escaped / 2)).for_each(|_| processed.push(escape));
|
||||||
if token_it.peek() == None
|
escaped = 0;
|
||||||
{
|
token_it = token.chars().peekable();
|
||||||
(0..((escaped-1)/2))
|
processed.push_str(token);
|
||||||
.for_each(|_| processed.push(escape));
|
}
|
||||||
escaped = 0;
|
} else {
|
||||||
token_it = token.chars().peekable();
|
if escaped != 0 {
|
||||||
processed.push_str(token);
|
// Add untouched escapes
|
||||||
}
|
(0..escaped).for_each(|_| processed.push('\\'));
|
||||||
}
|
token_it = token.chars().peekable();
|
||||||
else
|
escaped = 0;
|
||||||
{
|
}
|
||||||
if escaped != 0
|
processed.push(c);
|
||||||
{
|
}
|
||||||
// Add untouched escapes
|
}
|
||||||
(0..escaped).for_each(|_| processed.push('\\'));
|
// Add trailing escapes
|
||||||
token_it = token.chars().peekable();
|
(0..escaped).for_each(|_| processed.push('\\'));
|
||||||
escaped = 0;
|
|
||||||
}
|
|
||||||
processed.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add trailing escapes
|
|
||||||
(0..escaped).for_each(|_| processed.push('\\'));
|
|
||||||
|
|
||||||
processed
|
processed
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Property
|
pub struct Property {
|
||||||
{
|
required: bool,
|
||||||
required: bool,
|
description: String,
|
||||||
description: String,
|
default: Option<String>,
|
||||||
default: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Property {
|
impl Property {
|
||||||
pub fn new(required: bool, description: String, default: Option<String>) -> Self {
|
pub fn new(required: bool, description: String, default: Option<String>) -> Self {
|
||||||
Self { required, description, default }
|
Self {
|
||||||
|
required,
|
||||||
|
description,
|
||||||
|
default,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for Property
|
impl core::fmt::Display for Property {
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.default.as_ref()
|
match self.default.as_ref() {
|
||||||
{
|
None => write!(
|
||||||
None => write!(f, "{} {}",
|
f,
|
||||||
["[Opt]", "[Req]"][self.required as usize],
|
"{} {}",
|
||||||
self.description),
|
["[Opt]", "[Req]"][self.required as usize],
|
||||||
Some(default) => write!(f, "{} {} (Deafult: {})",
|
self.description
|
||||||
["[Opt]", "[Req]"][self.required as usize],
|
),
|
||||||
self.description,
|
Some(default) => write!(
|
||||||
default)
|
f,
|
||||||
}
|
"{} {} (Deafult: {})",
|
||||||
|
["[Opt]", "[Req]"][self.required as usize],
|
||||||
|
self.description,
|
||||||
|
default
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PropertyMap<'a>
|
pub enum PropertyMapError<E> {
|
||||||
{
|
ParseError(E),
|
||||||
pub(crate) properties: HashMap<String, (&'a Property, String)>
|
NotFoundError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PropertyMap<'a> {
|
||||||
|
pub(crate) properties: HashMap<String, (&'a Property, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PropertyMap<'a> {
|
impl<'a> PropertyMap<'a> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { properties: HashMap::new() }
|
Self {
|
||||||
|
properties: HashMap::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<T, Error, F: FnOnce(&'a Property, &String) -> Result<T, Error>>(&self, name: &str, f: F)
|
pub fn get<T, Error, F: FnOnce(&'a Property, &String) -> Result<T, Error>>(
|
||||||
-> Result<(&'a Property, T), Error> {
|
&self,
|
||||||
let (prop, value) = self.properties.get(name).unwrap();
|
name: &str,
|
||||||
|
f: F,
|
||||||
|
) -> Result<(&'a Property, T), PropertyMapError<Error>> {
|
||||||
|
let (prop, value) = match self.properties.get(name) {
|
||||||
|
Some(found) => found,
|
||||||
|
None => {
|
||||||
|
return Err(PropertyMapError::NotFoundError(format!(
|
||||||
|
"Property `{name}` not found"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
f(prop, value).and_then(|value| Ok((*prop, value)))
|
match f(prop, value) {
|
||||||
}
|
Ok(parsed) => Ok((*prop, parsed)),
|
||||||
|
Err(err) => Err(PropertyMapError::ParseError(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PropertyParser {
|
pub struct PropertyParser {
|
||||||
properties: HashMap<String, Property>,
|
properties: HashMap<String, Property>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PropertyParser {
|
impl PropertyParser {
|
||||||
|
@ -199,63 +217,60 @@ impl PropertyParser {
|
||||||
Self { properties }
|
Self { properties }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to build a default propertymap
|
/// Attempts to build a default propertymap
|
||||||
///
|
///
|
||||||
/// Returns an error if at least one [`Property`] is required and doesn't provide a default
|
/// Returns an error if at least one [`Property`] is required and doesn't provide a default
|
||||||
pub fn default(&self) -> Result<PropertyMap<'_>, String> {
|
pub fn default(&self) -> Result<PropertyMap<'_>, String> {
|
||||||
let mut properties = PropertyMap::new();
|
let mut properties = PropertyMap::new();
|
||||||
|
|
||||||
for (name, prop) in &self.properties
|
for (name, prop) in &self.properties {
|
||||||
{
|
match (prop.required, prop.default.as_ref()) {
|
||||||
match (prop.required, prop.default.as_ref())
|
(true, None) => return Err(format!("Missing property `{name}` {prop}")),
|
||||||
{
|
(false, None) => {}
|
||||||
(true, None) => return Err(format!("Missing property `{name}` {prop}")),
|
(_, Some(default)) => {
|
||||||
(false, None) => {},
|
properties
|
||||||
(_, Some(default)) => {
|
.properties
|
||||||
properties.properties.insert(
|
.insert(name.clone(), (prop, default.clone()));
|
||||||
name.clone(),
|
}
|
||||||
(prop, default.clone())
|
}
|
||||||
);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(properties)
|
Ok(properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses properties string "prop1=value1, prop2 = val\,2" -> {prop1: value1, prop2: val,2}
|
/// Parses properties string "prop1=value1, prop2 = val\,2" -> {prop1: value1, prop2: val,2}
|
||||||
///
|
///
|
||||||
/// # Key-value pair
|
/// # Key-value pair
|
||||||
///
|
///
|
||||||
/// Property names/values are separated by a single '=' that cannot be escaped.
|
/// Property names/values are separated by a single '=' that cannot be escaped.
|
||||||
/// Therefore names cannot contain the '=' character.
|
/// Therefore names cannot contain the '=' character.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// let properties = HashMap::new();
|
/// let mut properties = HashMap::new();
|
||||||
/// properties.insert("width", Property::new(true, "Width of the element in em", None));
|
/// properties.insert("width".to_string(),
|
||||||
///
|
/// Property::new(true, "Width of the element in em".to_string(), None));
|
||||||
/// let parser = PropertyParser::new(properties);
|
///
|
||||||
/// let pm = parser.parse("width=15").unwrap();
|
/// let parser = PropertyParser::new(properties);
|
||||||
///
|
/// let pm = parser.parse("width=15").unwrap();
|
||||||
/// assert!(pm.get("width", |_, val| val.parse::<i32>()) == Ok(15));
|
///
|
||||||
/// ```
|
/// assert_eq!(pm.get("width", |_, s| s.parse::<i32>()).unwrap().1, 15);
|
||||||
/// # Return value
|
/// ```
|
||||||
///
|
/// # Return value
|
||||||
/// Returns the parsed property map, or an error if either:
|
///
|
||||||
/// * A required property is missing
|
/// Returns the parsed property map, or an error if either:
|
||||||
/// * An unknown property is present
|
/// * A required property is missing
|
||||||
/// * A duplicate property is present
|
/// * An unknown property is present
|
||||||
///
|
/// * A duplicate property is present
|
||||||
/// Note: Only ',' inside values can be escaped, other '\' are treated literally
|
///
|
||||||
pub fn parse(&self, content: &str) -> Result<PropertyMap<'_>, String> {
|
/// Note: Only ',' inside values can be escaped, other '\' are treated literally
|
||||||
let mut properties = PropertyMap::new();
|
pub fn parse(&self, content: &str) -> Result<PropertyMap<'_>, String> {
|
||||||
let mut try_insert = |name: &String, value: &String|
|
let mut properties = PropertyMap::new();
|
||||||
-> Result<(), String> {
|
let mut try_insert = |name: &String, value: &String| -> Result<(), String> {
|
||||||
let trimmed_name = name.trim_end().trim_start();
|
let trimmed_name = name.trim_end().trim_start();
|
||||||
let trimmed_value = value.trim_end().trim_start();
|
let trimmed_value = value.trim_end().trim_start();
|
||||||
let prop = match self.properties.get(trimmed_name)
|
let prop = match self.properties.get(trimmed_name)
|
||||||
{
|
{
|
||||||
None => return Err(format!("Unknown property name: `{trimmed_name}` (with value: `{trimmed_value}`). Valid properties are:\n{}",
|
None => return Err(format!("Unknown property name: `{trimmed_name}` (with value: `{trimmed_value}`). Valid properties are:\n{}",
|
||||||
self.properties.iter().fold(String::new(),
|
self.properties.iter().fold(String::new(),
|
||||||
|
@ -263,81 +278,226 @@ impl PropertyParser {
|
||||||
Some(prop) => prop
|
Some(prop) => prop
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((_, previous)) = properties.properties.insert(
|
if let Some((_, previous)) = properties
|
||||||
trimmed_name.to_string(),
|
.properties
|
||||||
(prop, trimmed_value.to_string()))
|
.insert(trimmed_name.to_string(), (prop, trimmed_value.to_string()))
|
||||||
{
|
{
|
||||||
return Err(format!("Duplicate property `{trimmed_name}`, previous value: `{previous}` current value: `{trimmed_value}`"))
|
return Err(format!("Duplicate property `{trimmed_name}`, previous value: `{previous}` current value: `{trimmed_value}`"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut in_name = true;
|
let mut in_name = true;
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
let mut value = String::new();
|
let mut value = String::new();
|
||||||
let mut escaped = 0usize;
|
let mut escaped = 0usize;
|
||||||
for c in content.chars()
|
for c in content.chars() {
|
||||||
{
|
if c == '\\' {
|
||||||
if c == '\\'
|
escaped += 1;
|
||||||
{
|
} else if c == '=' && in_name {
|
||||||
escaped += 1;
|
in_name = false;
|
||||||
}
|
(0..escaped).for_each(|_| name.push('\\'));
|
||||||
else if c == '=' && in_name
|
escaped = 0;
|
||||||
{
|
} else if c == ',' && !in_name {
|
||||||
in_name = false;
|
if escaped % 2 == 0
|
||||||
(0..escaped).for_each(|_| name.push('\\'));
|
// Not escaped
|
||||||
escaped = 0;
|
{
|
||||||
}
|
(0..escaped / 2).for_each(|_| value.push('\\'));
|
||||||
else if c == ',' && !in_name
|
escaped = 0;
|
||||||
{
|
in_name = true;
|
||||||
if escaped % 2 == 0 // Not escaped
|
|
||||||
{
|
|
||||||
(0..escaped/2).for_each(|_| value.push('\\'));
|
|
||||||
escaped = 0;
|
|
||||||
in_name = true;
|
|
||||||
|
|
||||||
if let Err(e) = try_insert(&name, &value) {
|
if let Err(e) = try_insert(&name, &value) {
|
||||||
return Err(e)
|
return Err(e);
|
||||||
}
|
}
|
||||||
name.clear();
|
name.clear();
|
||||||
value.clear();
|
value.clear();
|
||||||
}
|
} else {
|
||||||
else
|
(0..(escaped - 1) / 2).for_each(|_| value.push('\\'));
|
||||||
{
|
value.push(',');
|
||||||
(0..(escaped-1)/2).for_each(|_| value.push('\\'));
|
escaped = 0;
|
||||||
value.push(',');
|
}
|
||||||
escaped = 0;
|
} else {
|
||||||
}
|
if in_name {
|
||||||
}
|
(0..escaped).for_each(|_| name.push('\\'));
|
||||||
else
|
name.push(c)
|
||||||
{
|
} else {
|
||||||
if in_name {
|
(0..escaped).for_each(|_| value.push('\\'));
|
||||||
(0..escaped).for_each(|_| name.push('\\'));
|
value.push(c)
|
||||||
name.push(c)
|
}
|
||||||
}
|
escaped = 0;
|
||||||
else {
|
}
|
||||||
(0..escaped).for_each(|_| value.push('\\'));
|
}
|
||||||
value.push(c)
|
if !in_name && value.trim_end().trim_start().is_empty() {
|
||||||
}
|
return Err("Expected a value after last `=`".to_string());
|
||||||
escaped = 0;
|
} else if name.is_empty() || value.is_empty() {
|
||||||
}
|
return Err("Expected non empty property list.".to_string());
|
||||||
}
|
}
|
||||||
if !in_name && value.trim_end().trim_start().is_empty()
|
|
||||||
{
|
|
||||||
return Err("Expected a value after last `=`".to_string())
|
|
||||||
}
|
|
||||||
else if name.is_empty() || value.is_empty()
|
|
||||||
{
|
|
||||||
return Err("Expected non empty property list.".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = try_insert(&name, &value) {
|
if let Err(e) = try_insert(&name, &value) {
|
||||||
return Err(e)
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Missing properties
|
if let Err(e) = self.properties.iter().try_for_each(|(key, prop)| {
|
||||||
|
if !properties.properties.contains_key(key) {
|
||||||
Ok(properties)
|
if let Some(default) = &prop.default {
|
||||||
}
|
properties
|
||||||
|
.properties
|
||||||
|
.insert(key.clone(), (prop, default.clone()));
|
||||||
|
} else if prop.required {
|
||||||
|
return Err(format!("Missing required property: {prop}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}) {
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
Ok(properties)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
document::langdocument::LangDocument,
|
||||||
|
elements::{comment::Comment, style::Style, text::Text},
|
||||||
|
parser::source::{SourceFile, Token},
|
||||||
|
};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn process_text_tests() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let doc = LangDocument::new(source.clone(), None);
|
||||||
|
|
||||||
|
assert_eq!(process_text(&doc, "a\nb"), "a b");
|
||||||
|
assert_eq!(process_text(&doc, "a\n\nb"), "a b"); // Should never happen but why not
|
||||||
|
assert_eq!(process_text(&doc, "a\\b"), "ab");
|
||||||
|
assert_eq!(process_text(&doc, "a\\\nb"), "a\nb");
|
||||||
|
assert_eq!(process_text(&doc, "a\\\\b"), "a\\b");
|
||||||
|
assert_eq!(process_text(&doc, "a\\\\\nb"), "a\\ b");
|
||||||
|
assert_eq!(process_text(&doc, "\na"), "a");
|
||||||
|
|
||||||
|
let tok = Token::new(0..0, source);
|
||||||
|
doc.push(Box::new(Paragraph::new(tok.clone())));
|
||||||
|
|
||||||
|
// A space is appended as previous element is inline
|
||||||
|
(&doc as &dyn Document)
|
||||||
|
.last_element_mut::<Paragraph>()
|
||||||
|
.unwrap()
|
||||||
|
.push(Box::new(Text::new(tok.clone(), "TEXT".to_string())));
|
||||||
|
assert_eq!(process_text(&doc, "\na"), " a");
|
||||||
|
|
||||||
|
(&doc as &dyn Document)
|
||||||
|
.last_element_mut::<Paragraph>()
|
||||||
|
.unwrap()
|
||||||
|
.push(Box::new(Style::new(tok.clone(), 0, false)));
|
||||||
|
assert_eq!(process_text(&doc, "\na"), " a");
|
||||||
|
|
||||||
|
// Comments are ignored (kind => Invisible)
|
||||||
|
(&doc as &dyn Document)
|
||||||
|
.last_element_mut::<Paragraph>()
|
||||||
|
.unwrap()
|
||||||
|
.push(Box::new(Comment::new(tok.clone(), "COMMENT".to_string())));
|
||||||
|
assert_eq!(process_text(&doc, "\na"), " a");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn process_escaped_tests() {
|
||||||
|
assert_eq!(
|
||||||
|
process_escaped(
|
||||||
|
'\\',
|
||||||
|
"%",
|
||||||
|
"escaped: \\%, also escaped: \\\\\\%, untouched: \\a"
|
||||||
|
),
|
||||||
|
"escaped: %, also escaped: \\%, untouched: \\a"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
process_escaped('"', "><)))°>", "Escaped fish: \"><)))°>"),
|
||||||
|
"Escaped fish: ><)))°>".to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
process_escaped('\\', "]", "Escaped \\]"),
|
||||||
|
"Escaped ]".to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
process_escaped('\\', "]", "Unescaped \\\\]"),
|
||||||
|
"Unescaped \\\\]".to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
process_escaped('\\', "]", "Escaped \\\\\\]"),
|
||||||
|
"Escaped \\]".to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
process_escaped('\\', "]", "Unescaped \\\\\\\\]"),
|
||||||
|
"Unescaped \\\\\\\\]".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn property_parser_tests() {
|
||||||
|
let mut properties = HashMap::new();
|
||||||
|
properties.insert(
|
||||||
|
"width".to_string(),
|
||||||
|
Property::new(true, "Width of the element in em".to_string(), None),
|
||||||
|
);
|
||||||
|
properties.insert(
|
||||||
|
"length".to_string(),
|
||||||
|
Property::new(false, "Length in cm".to_string(), None),
|
||||||
|
);
|
||||||
|
properties.insert(
|
||||||
|
"angle".to_string(),
|
||||||
|
Property::new(
|
||||||
|
true,
|
||||||
|
"Angle in degrees".to_string(),
|
||||||
|
Some("180".to_string()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
properties.insert(
|
||||||
|
"weight".to_string(),
|
||||||
|
Property::new(false, "Weight in %".to_string(), Some("0.42".to_string())),
|
||||||
|
);
|
||||||
|
|
||||||
|
let parser = PropertyParser::new(properties);
|
||||||
|
let pm = parser.parse("width=15,length=-10").unwrap();
|
||||||
|
|
||||||
|
// Ok
|
||||||
|
assert_eq!(pm.get("width", |_, s| s.parse::<i32>()).unwrap().1, 15);
|
||||||
|
assert_eq!(pm.get("length", |_, s| s.parse::<i32>()).unwrap().1, -10);
|
||||||
|
assert_eq!(pm.get("angle", |_, s| s.parse::<f64>()).unwrap().1, 180f64);
|
||||||
|
assert_eq!(pm.get("angle", |_, s| s.parse::<i32>()).unwrap().1, 180);
|
||||||
|
assert_eq!(
|
||||||
|
pm.get("weight", |_, s| s.parse::<f32>()).unwrap().1,
|
||||||
|
0.42f32
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
pm.get("weight", |_, s| s.parse::<f64>()).unwrap().1,
|
||||||
|
0.42f64
|
||||||
|
);
|
||||||
|
|
||||||
|
// Error
|
||||||
|
assert!(pm.get("length", |_, s| s.parse::<u32>()).is_err());
|
||||||
|
assert!(pm.get("height", |_, s| s.parse::<f64>()).is_err());
|
||||||
|
|
||||||
|
// Missing property
|
||||||
|
assert!(parser.parse("length=15").is_err());
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
assert!(parser.parse("width=15").is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
parser
|
||||||
|
.parse("width=0,weight=0.15")
|
||||||
|
.unwrap()
|
||||||
|
.get("weight", |_, s| s.parse::<f32>())
|
||||||
|
.unwrap()
|
||||||
|
.1,
|
||||||
|
0.15f32
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,9 +51,9 @@ impl Backend {
|
||||||
let parser = LangParser::default();
|
let parser = LangParser::default();
|
||||||
let doc = parser.parse(Rc::new(source), None);
|
let doc = parser.parse(Rc::new(source), None);
|
||||||
|
|
||||||
let semantic_tokens = semantic_token_from_document(&doc);
|
//let semantic_tokens = semantic_token_from_document(&doc);
|
||||||
self.semantic_token_map
|
//self.semantic_token_map
|
||||||
.insert(params.uri.to_string(), semantic_tokens);
|
// .insert(params.uri.to_string(), semantic_tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue