From 5ccd8048c2a6f99b5d00b0c576c6791704bdd336 Mon Sep 17 00:00:00 2001
From: ef3d0c3e <ef3d0c3e@pundalik.org>
Date: Wed, 24 Jul 2024 09:05:57 +0200
Subject: [PATCH] Add some tests

---
 src/elements/code.rs     |  40 ++-
 src/elements/raw.rs      |  40 ++-
 src/lsp/parser.rs        |  74 ++--
 src/lsp/semantic.rs      |   2 +-
 src/parser/langparser.rs |  27 +-
 src/parser/parser.rs     |  34 +-
 src/parser/util.rs       | 710 ++++++++++++++++++++++++---------------
 src/server.rs            |   6 +-
 8 files changed, 579 insertions(+), 354 deletions(-)

diff --git a/src/elements/code.rs b/src/elements/code.rs
index 5921a53..164f109 100644
--- a/src/elements/code.rs
+++ b/src/elements/code.rs
@@ -6,7 +6,7 @@ use mlua::{Function, Lua};
 use regex::{Captures, Regex};
 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;
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -341,18 +341,32 @@ impl RegexRule for CodeRule
 				|prop, value| value.parse::<usize>().map_err(|e| (prop, e)))
 			{
 				Ok((_prop, offset)) => offset,
-				Err((prop, e)) => {
-					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 `line_offset: {}` cannot be converted: {}",
-									prop.fg(parser.colors().info),
-									e.fg(parser.colors().error)))
-							.with_color(parser.colors().warning))
-						.finish());
-						return reports;
+				Err(e) => match e {
+					PropertyMapError::ParseError((prop, 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 `line_offset: {}` cannot be converted: {}",
+										prop.fg(parser.colors().info),
+										err.fg(parser.colors().error)))
+								.with_color(parser.colors().warning))
+							.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;
+					}
 				}
 			};
 
diff --git a/src/elements/raw.rs b/src/elements/raw.rs
index 3f51085..ccaab1f 100644
--- a/src/elements/raw.rs
+++ b/src/elements/raw.rs
@@ -1,6 +1,6 @@
 use mlua::{Error::BadArgument, Function, Lua};
 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 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)))
 		{
 			Ok((_prop, kind)) => kind,
-			Err((prop, e)) => {
-				reports.push(
-					Report::build(ReportKind::Error, token.source(), token.start())
-					.with_message("Invalid Raw Code Property")
-					.with_label(
-						Label::new((token.source().clone(), token.range.clone()))
-						.with_message(format!("Property `kind: {}` cannot be converted: {}",
-								prop.fg(parser.colors().info),
-								e.fg(parser.colors().error)))
-						.with_color(parser.colors().warning))
-					.finish());
-					return reports;
+			Err(e) => match e {
+				PropertyMapError::ParseError((prop, err)) => {
+					reports.push(
+						Report::build(ReportKind::Error, token.source(), token.start())
+						.with_message("Invalid Raw Code Property")
+						.with_label(
+							Label::new((token.source().clone(), token.range.clone()))
+							.with_message(format!("Property `kind: {}` cannot be converted: {}",
+									prop.fg(parser.colors().info),
+									err.fg(parser.colors().error)))
+							.with_color(parser.colors().warning))
+						.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;
+				}
 			}
 		};
 
diff --git a/src/lsp/parser.rs b/src/lsp/parser.rs
index 320fd9b..9f517b3 100644
--- a/src/lsp/parser.rs
+++ b/src/lsp/parser.rs
@@ -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)]
 pub struct LineCursor
@@ -56,26 +56,6 @@ impl LineCursor
 				//eprintln!("({}, {c:#?}) ({} {})", self.pos, self.line, self.line_pos);
 				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
 		{
@@ -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()
+    }
+}
diff --git a/src/lsp/semantic.rs b/src/lsp/semantic.rs
index cebcf68..de00f56 100644
--- a/src/lsp/semantic.rs
+++ b/src/lsp/semantic.rs
@@ -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![];
 
diff --git a/src/parser/langparser.rs b/src/parser/langparser.rs
index 80f000f..2221ce7 100644
--- a/src/parser/langparser.rs
+++ b/src/parser/langparser.rs
@@ -101,32 +101,7 @@ impl Parser for LangParser
 	fn colors(&self) -> &ReportColors { &self.colors }
 
 	fn rules(&self) -> &Vec<Box<dyn Rule>> { &self.rules }
-	fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>)
-	{
-		// 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 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() }
diff --git a/src/parser/parser.rs b/src/parser/parser.rs
index be104a1..037e9b6 100644
--- a/src/parser/parser.rs
+++ b/src/parser/parser.rs
@@ -48,7 +48,39 @@ pub trait Parser: KernelHolder
 	fn colors(&self) -> &ReportColors;
 
 	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_mut(&self) -> RefMut<'_, StateHolder>;
diff --git a/src/parser/util.rs b/src/parser/util.rs
index 219ef37..6a2a679 100644
--- a/src/parser/util.rs
+++ b/src/parser/util.rs
@@ -2,90 +2,88 @@ use std::collections::HashMap;
 
 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
-pub fn process_text(document: &dyn Document, content: &str) -> String
-{
-	let mut escaped = false;
-	let mut newlines = 0usize; // Consecutive newlines
-	//println!("Processing: [{content}]");
-	let processed = content
-		.grapheme_indices(true)
-		.fold((String::new(), None),
-		|(mut out, prev), (_pos, g)| {
-		if newlines != 0 && g != "\n"
-		{
-			newlines = 0;
+pub fn process_text(document: &dyn Document, content: &str) -> String {
+    let mut escaped = false;
+    let mut newlines = 0usize; // Consecutive newlines
+                               //println!("Processing: [{content}]");
+    let processed = content
+        .graphemes(true)
+        .fold((String::new(), None), |(mut out, prev), g| {
+            if newlines != 0 && g != "\n" {
+                newlines = 0;
 
-			// Add a whitespace if necessary
-			match out.chars().last()
-			{
-				Some(c) => {
-					// NOTE: \n is considered whitespace, so previous codepoint can be \n
-					// (Which can only be done by escaping it)
-					if !c.is_whitespace() || c == '\n'
-					{
-						out += " ";
-					}
-				}
-				None => {
-					if document.last_element::<Paragraph>()
-						.and_then(|par| par.find_back(|e| e.kind() != ElemKind::Invisible)
-						.and_then(|e| Some(e.kind() == ElemKind::Inline)))
-							.unwrap_or(false)
-					{
-						out += " ";
-					}
-				} // Don't output anything
-			}
-		}
+                // Add a whitespace if necessary
+                match out.chars().last() {
+                    Some(c) => {
+                        // NOTE: \n is considered whitespace, so previous codepoint can be \n
+                        // (Which can only be done by escaping it)
+                        if !c.is_whitespace() || c == '\n' {
+                            out += " ";
+                        }
+                    }
+                    None => {
+                        if document
+                            .last_element::<Paragraph>()
+                            .and_then(|par| {
+                                par.find_back(|e| e.kind() != ElemKind::Invisible)
+                                    .and_then(|e| Some(e.kind() == ElemKind::Inline))
+                            })
+                            .unwrap_or(false)
+                        {
+                            out += " ";
+                        }
+                    } // Don't output anything
+                }
+            }
 
-		// Output grapheme literally when escaped
-		if escaped
-		{
-			escaped = false;
-			return (out + g, Some(g));
-		}
-		// Increment newlines counter
-		else if g == "\n"
-		{
-			newlines += 1;
-			return (out, Some(g));
-		}
-		// Determine if escaped
-		else if g == "\\"
-		{
-			escaped = !escaped;
-			return (out, Some(g));
-		}
-		// Whitespaces
-		else if g.chars().count() == 1 && g.chars().last().unwrap().is_whitespace()
-		{
-			// Content begins with whitespace
-			if prev.is_none()
-			{
-				if document.last_element::<Paragraph>().is_some()
-				{
-					return (out+g, Some(g));
-				}
-				else
-				{
-					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));
-			}
-		}
+            // Output grapheme literally when escaped
+            if escaped {
+                escaped = false;
+                return (out + g, Some(g));
+            }
+            // Increment newlines counter
+            else if g == "\n" {
+                newlines += 1;
+                return (out, Some(g));
+            }
+            // Determine if escaped
+            else if g == "\\" {
+                escaped = !escaped;
+                return (out, Some(g));
+            }
+            // Whitespaces
+            else if g.chars().count() == 1 && g.chars().last().unwrap().is_whitespace() {
+                // Content begins with whitespace
+                if prev.is_none() {
+                    if document.last_element::<Paragraph>().is_some() {
+                        return (out + g, Some(g));
+                    } else {
+                        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));
-	}).0.to_string();
+            return (out + g, Some(g));
+        })
+        .0
+        .to_string();
 
-	return processed;
+    return processed;
 }
 
 /// 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
 /// ```
 /// 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
-{
-	let mut processed = String::new();
-	let mut escaped = 0;
-	let mut token_it = token.chars().peekable();
-	for c in content.as_ref().chars()
-		.as_str()
-			.trim_start()
-			.trim_end()
-			.chars()
-			{
-				if c == escape
-				{
-					escaped += 1;
-				}
-				else if escaped % 2 == 1 && token_it.peek().map_or(false, |p| *p == c)
-				{
-					let _ = token_it.next();
-					if token_it.peek() == None
-					{
-						(0..((escaped-1)/2))
-							.for_each(|_| processed.push(escape));
-						escaped = 0;
-						token_it = token.chars().peekable();
-						processed.push_str(token);
-					}
-				}
-				else
-				{
-					if escaped != 0
-					{
-						// Add untouched escapes
-						(0..escaped).for_each(|_| processed.push('\\'));
-						token_it = token.chars().peekable();
-						escaped = 0;
-					}
-					processed.push(c);
-				}
-			}
-	// Add trailing escapes
-	(0..escaped).for_each(|_| processed.push('\\'));
+pub fn process_escaped<S: AsRef<str>>(escape: char, token: &'static str, content: S) -> String {
+    let mut processed = String::new();
+    let mut escaped = 0;
+    let mut token_it = token.chars().peekable();
+    for c in content
+        .as_ref()
+        .chars()
+        .as_str()
+        .trim_start()
+        .trim_end()
+        .chars()
+    {
+        if c == escape {
+            escaped += 1;
+        } else if escaped % 2 == 1 && token_it.peek().map_or(false, |p| *p == c) {
+            let _ = token_it.next();
+            if token_it.peek() == None {
+                (0..(escaped / 2)).for_each(|_| processed.push(escape));
+                escaped = 0;
+                token_it = token.chars().peekable();
+                processed.push_str(token);
+            }
+        } else {
+            if escaped != 0 {
+                // Add untouched escapes
+                (0..escaped).for_each(|_| processed.push('\\'));
+                token_it = token.chars().peekable();
+                escaped = 0;
+            }
+            processed.push(c);
+        }
+    }
+    // Add trailing escapes
+    (0..escaped).for_each(|_| processed.push('\\'));
 
-	processed
+    processed
 }
 
 #[derive(Debug)]
-pub struct Property
-{
-	required: bool,
-	description: String,
-	default: Option<String>,
+pub struct Property {
+    required: bool,
+    description: String,
+    default: Option<String>,
 }
 
 impl Property {
     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 {
-		match self.default.as_ref()
-		{
-			None => write!(f, "{} {}", 
-				["[Opt]", "[Req]"][self.required as usize],
-				self.description),
-			Some(default) => write!(f, "{} {} (Deafult: {})",
-				["[Opt]", "[Req]"][self.required as usize],
-				self.description,
-				default)
-		}
+        match self.default.as_ref() {
+            None => write!(
+                f,
+                "{} {}",
+                ["[Opt]", "[Req]"][self.required as usize],
+                self.description
+            ),
+            Some(default) => write!(
+                f,
+                "{} {} (Deafult: {})",
+                ["[Opt]", "[Req]"][self.required as usize],
+                self.description,
+                default
+            ),
+        }
     }
 }
 
 #[derive(Debug)]
-pub struct PropertyMap<'a>
-{
-	pub(crate) properties: HashMap<String, (&'a Property, String)>
+pub enum PropertyMapError<E> {
+    ParseError(E),
+    NotFoundError(String),
+}
+
+#[derive(Debug)]
+pub struct PropertyMap<'a> {
+    pub(crate) properties: HashMap<String, (&'a Property, String)>,
 }
 
 impl<'a> PropertyMap<'a> {
     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)
-		-> Result<(&'a Property, T), Error> {
-		let (prop, value) = self.properties.get(name).unwrap();
+    pub fn get<T, Error, F: FnOnce(&'a Property, &String) -> Result<T, Error>>(
+        &self,
+        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 {
-	properties: HashMap<String, Property>,
+    properties: HashMap<String, Property>,
 }
 
 impl PropertyParser {
@@ -199,63 +217,60 @@ impl PropertyParser {
         Self { properties }
     }
 
-	/// Attempts to build a default propertymap
-	///
-	/// Returns an error if at least one [`Property`] is required and doesn't provide a default
-	pub fn default(&self) -> Result<PropertyMap<'_>, String> {
-		let mut properties = PropertyMap::new();
+    /// Attempts to build a default propertymap
+    ///
+    /// Returns an error if at least one [`Property`] is required and doesn't provide a default
+    pub fn default(&self) -> Result<PropertyMap<'_>, String> {
+        let mut properties = PropertyMap::new();
 
-		for (name, prop) in &self.properties
-		{
-			match (prop.required, prop.default.as_ref())
-			{
-    			(true, None) => return Err(format!("Missing property `{name}` {prop}")),
-				(false, None) => {},
-    			(_, Some(default)) => {
-						properties.properties.insert(
-						name.clone(),
-						(prop, default.clone())
-					);
-				}
-			}
-		}
+        for (name, prop) in &self.properties {
+            match (prop.required, prop.default.as_ref()) {
+                (true, None) => return Err(format!("Missing property `{name}` {prop}")),
+                (false, None) => {}
+                (_, Some(default)) => {
+                    properties
+                        .properties
+                        .insert(name.clone(), (prop, default.clone()));
+                }
+            }
+        }
 
-		Ok(properties)
-	}
+        Ok(properties)
+    }
 
-	/// Parses properties string "prop1=value1, prop2 = val\,2" -> {prop1: value1, prop2: val,2}
-	///
-	/// # Key-value pair
-	///
-	/// Property names/values are separated by a single '=' that cannot be escaped.
-	/// Therefore names cannot contain the '=' character.
-	///
-	/// # Example
-	///
-	/// ```
-	/// let properties = HashMap::new();
-	/// properties.insert("width", Property::new(true, "Width of the element in em", None));
-	///
-	/// let parser = PropertyParser::new(properties);
-	/// let pm = parser.parse("width=15").unwrap();
-	///
-	/// assert!(pm.get("width", |_, val| val.parse::<i32>()) == Ok(15));
-	/// ```
-	/// # Return value
-	///
-	/// Returns the parsed property map, or an error if either:
-	///  * A required property is missing
-	///  * 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> {
-		let mut properties = PropertyMap::new();
-		let mut try_insert = |name: &String, value: &String|
-			-> Result<(), String> {
-			let trimmed_name = name.trim_end().trim_start();
-			let trimmed_value = value.trim_end().trim_start();
-			let prop = match self.properties.get(trimmed_name)
+    /// Parses properties string "prop1=value1, prop2 = val\,2" -> {prop1: value1, prop2: val,2}
+    ///
+    /// # Key-value pair
+    ///
+    /// Property names/values are separated by a single '=' that cannot be escaped.
+    /// Therefore names cannot contain the '=' character.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// let mut properties = HashMap::new();
+    /// 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();
+    ///
+    /// assert_eq!(pm.get("width", |_, s| s.parse::<i32>()).unwrap().1, 15);
+    /// ```
+    /// # Return value
+    ///
+    /// Returns the parsed property map, or an error if either:
+    ///  * A required property is missing
+    ///  * 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> {
+        let mut properties = PropertyMap::new();
+        let mut try_insert = |name: &String, value: &String| -> Result<(), String> {
+            let trimmed_name = name.trim_end().trim_start();
+            let trimmed_value = value.trim_end().trim_start();
+            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{}",
 					self.properties.iter().fold(String::new(),
@@ -263,81 +278,226 @@ impl PropertyParser {
 				Some(prop) => prop
 			};
 
-			if let Some((_, previous)) = properties.properties.insert(
-				trimmed_name.to_string(),
-				(prop, trimmed_value.to_string()))
-			{
-				return Err(format!("Duplicate property `{trimmed_name}`, previous value: `{previous}` current value: `{trimmed_value}`"))
-			}
+            if let Some((_, previous)) = properties
+                .properties
+                .insert(trimmed_name.to_string(), (prop, trimmed_value.to_string()))
+            {
+                return Err(format!("Duplicate property `{trimmed_name}`, previous value: `{previous}` current value: `{trimmed_value}`"));
+            }
 
-			Ok(())
-		};
+            Ok(())
+        };
 
-		let mut in_name = true;
-		let mut name = String::new();
-		let mut value = String::new();
-		let mut escaped = 0usize;
-		for c in content.chars()
-		{
-			if c == '\\'
-			{
-				escaped += 1;
-			}
-			else if c == '=' && in_name
-			{
-				in_name = false;
-				(0..escaped).for_each(|_| name.push('\\'));
-				escaped = 0;
-			}
-			else if c == ',' && !in_name
-			{
-				if escaped % 2 == 0 // Not escaped
-				{
-					(0..escaped/2).for_each(|_| value.push('\\'));
-					escaped = 0;
-					in_name = true;
+        let mut in_name = true;
+        let mut name = String::new();
+        let mut value = String::new();
+        let mut escaped = 0usize;
+        for c in content.chars() {
+            if c == '\\' {
+                escaped += 1;
+            } else if c == '=' && in_name {
+                in_name = false;
+                (0..escaped).for_each(|_| name.push('\\'));
+                escaped = 0;
+            } else if c == ',' && !in_name {
+                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) {
-						return Err(e)
-					}
-					name.clear();
-					value.clear();
-				}
-				else
-				{
-					(0..(escaped-1)/2).for_each(|_| value.push('\\'));
-					value.push(',');
-					escaped = 0;
-				}
-			}
-			else
-			{
-				if in_name {
-					(0..escaped).for_each(|_| name.push('\\'));
-					name.push(c)
-				}
-				else {
-					(0..escaped).for_each(|_| value.push('\\'));
-					value.push(c)
-				}
-				escaped = 0;
-			}
-		}
-		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) {
+                        return Err(e);
+                    }
+                    name.clear();
+                    value.clear();
+                } else {
+                    (0..(escaped - 1) / 2).for_each(|_| value.push('\\'));
+                    value.push(',');
+                    escaped = 0;
+                }
+            } else {
+                if in_name {
+                    (0..escaped).for_each(|_| name.push('\\'));
+                    name.push(c)
+                } else {
+                    (0..escaped).for_each(|_| value.push('\\'));
+                    value.push(c)
+                }
+                escaped = 0;
+            }
+        }
+        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) {
-			return Err(e)
-		}
+        if let Err(e) = try_insert(&name, &value) {
+            return Err(e);
+        }
 
-		// TODO: Missing properties
-
-		Ok(properties)
-	}
+        if let Err(e) = self.properties.iter().try_for_each(|(key, prop)| {
+            if !properties.properties.contains_key(key) {
+                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
+        );
+    }
 }
diff --git a/src/server.rs b/src/server.rs
index b73e404..2e26c32 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -51,9 +51,9 @@ impl Backend {
 		let parser = LangParser::default();
 		let doc = parser.parse(Rc::new(source), None);
 		
-		let semantic_tokens = semantic_token_from_document(&doc);
-        self.semantic_token_map
-            .insert(params.uri.to_string(), semantic_tokens);
+		//let semantic_tokens = semantic_token_from_document(&doc);
+        //self.semantic_token_map
+        //    .insert(params.uri.to_string(), semantic_tokens);
 	}
 }