rustyphoenixlecture 1.3.0

This project aims to provide a simple a powerfull lecture compilation to generate html web sites
/***************************************
	Auteur : Pierre Aubert
	Mail : pierre.aubert@lapp.in2p3.fr
	Licence : CeCILL-C
****************************************/

use std::collections::HashMap;
use crate::phighlighter::{
	pactionhighlight::PActionHighLight, pfileparser::PFileParserIter, phighlighter::PHighlighterGetUntilReplace, phighlither_configuration::{
		PHighlighterGetUntil,
		PHighlighterSequence,
	}
};

///Iterator on a PNodeHighlight for the highlighting
/// This idea here is the walk on the node as usual by respect to the next avaliable token character but also to keep the available oneof and check if they are followed by a required token
/// t -> o -> k -> e -> n (ok to match token)
/// oneof(alphabet + !) -> ! (=> this is a rust macro an no more a possible token)
#[derive(Debug,Clone)]
struct PNodeHighlightIter<'a>{
	///Current node of the token parsing
	p_current_node: &'a PNodeHighLight,
}

impl<'a> PNodeHighlightIter<'a> {
	///Contructor of a PNodeHighlightIter
	/// # Parameters
	/// - `node_highlight` : PNodeHighLight to start the parsing with
	fn new(node_highlight: &'a PNodeHighLight) -> Self{
		PNodeHighlightIter{
			p_current_node: node_highlight,
		}
	}
	///Iterate on the next character to parse
	/// # Parameters
	/// - `current_char` : current char to parse
	/// # Returns
	/// True if the iterator is still on a node which matches the given character, false otherwise
	fn next(&mut self, current_char: u8) -> bool{
		if self.p_current_node.is_oneof() {
			let str_char = String::from(current_char as char);
			if self.p_current_node.p_pattern.contains(&str_char) {
				return true;	//If the current node contains the str_char, it lives
			}
		}
		//We have to iterate on the next possible PNodeHighLight to update the p_current_node
		match self.p_current_node.p_map_sequence.get(&current_char) {
			Some(child) => {	//We found a match, so we jump on the next PNodeHighLight
				self.p_current_node = child;
				//And we have to update the available oneof which are compatible with the new char
				true
			},
			None => {
				//There is no token available anymore
				false
			}
		}
	}
}

///Multiple Iterator on a all PNodeHighlight which are compatible with all the previous characters for the highlighting
/// This idea here is the walk on the nodes which match the current character and keep them until they don't match anymore (especially for oneof)
/// If we parse token!
/// t -> o -> k -> e -> n (ok to match token but ! is not in the available characters)
/// oneof(alphabet + ::) -> (=> this is not a Rust namespace because we expect : and we get !)
/// oneof(alphabet + !) -> ! (=> this is a rust macro an no more a possible token)
#[derive(Debug,Clone)]
struct PVecNodeHighlightIter<'a>{
	///Vector of possible node which are still compatible with the parsed character
	p_vec_possible_node: Vec<PNodeHighlightIter<'a>>,
}

impl<'a> PVecNodeHighlightIter<'a> {
	///Contructor of a PVecNodeHighlightIter
	/// # Parameters
	/// - `node_highlight` : PNodeHighLight to start the parsing with
	fn new(node_highlight: &'a PNodeHighLight) -> Self{
		let mut other = PVecNodeHighlightIter{
			p_vec_possible_node: vec![],
		};
		other.p_vec_possible_node.push(PNodeHighlightIter::new(node_highlight));
		//We add all other nodes for oneof
		for oneof_node in node_highlight.p_vec_oneof.iter(){
			other.p_vec_possible_node.push(PNodeHighlightIter::new(oneof_node));
		}
		return other;
	}
	///Iterate all PNodeHighlightIter on the next character to parse
	/// # Parameters
	/// - `current_char` : current char to parse
	/// # Returns
	/// Action index to be performed, or None if we have to wait
	fn next(&mut self, current_char: u8) -> Option<usize>{
		//Let's start to filter the oneof which are still relevant for this parsing
		let mut vec_remaning_oneof: Vec<PNodeHighlightIter<'a>> = vec![];
		let mut vec_available_action_index: Vec<usize> = vec![];
		for node in self.p_vec_possible_node.iter_mut() {
			if node.next(current_char) {
				vec_remaning_oneof.push(node.clone());
				//If we keep the node, we have to add its oneof
				for child_oneof in node.p_current_node.p_vec_oneof.iter() {
					vec_remaning_oneof.push(PNodeHighlightIter::new(child_oneof));
				}
			}else{
				//If the node does not match anymore, maybe it has an action to perform
				match node.p_current_node.p_action_index {
					Some(action_index) => vec_available_action_index.push(action_index),
					None => {}
				}
			}
		}
		//If we have nothing more to parse
		if vec_remaning_oneof.is_empty() {
			//Hopefully we have an action to perform
			return match vec_available_action_index.first() {
				Some(first_action) => Some(*first_action),
				None => Some(0)
			};
		}
		//Let' update the vector of remaning node for the next iteration
		self.p_vec_possible_node = vec_remaning_oneof;
		return None;
	}
	
	///Do the highlight of string with a parser
	/// # Parameters
	/// - `body` : output of the highlighting
	/// - `current_match_token` : current matched token when walking on the tree of PNodeHighLight children
	/// - `it` : iterator on the parser to use
	/// - `vec_action` : vector of action to be performed by the highlighter
	/// - `token_charset` : charset which contains all characters which composed a token
	fn highlight(&mut self, body: &mut String, current_match_token: &mut String, it: &mut PFileParserIter, vec_action: &Vec<PActionHighLight>,
		token_charset: &String)
	{
		let is_prev_char_in_token_charset: bool = it.is_prev_char_in_charset(token_charset);
		//If we are not at the end of the file we parse
		while !it.is_end_of_file() {
			match it.get_current_char() {
				//If we have a current char
				Some(current_char) => {
					match self.next(current_char) {
						Some(action_index) => {
							highlight_run_action(action_index, current_char, body, current_match_token, it, vec_action, token_charset, is_prev_char_in_token_charset);
							break;	//We finised the parsing of the current token / sequence / text
						},
						None => {	//We are not at the end of the parsing yet
							it.next();	//We get the next char
							*current_match_token += &String::from(current_char as char);	//We update the curently parsed token
						}
					}
				},
				None => {}	//This is the end of parsing
			};
		}
	}
}


///Run the given action_index
/// # Parameters
/// - `action_index` : index of the action to be performed
/// - `body` : output of the highlighting
/// - `current_match_token` : current matched token when walking on the tree of PNodeHighLight children
/// - `it` : iterator on the parser to use
/// - `vec_action` : vector of action to be performed by the highlighter
/// - `token_charset` : charset which contains all characters which composed a token
/// - `is_prev_char_in_token_charset` : true if the previous char of the whole sequence was in the token charset
fn highlight_run_action(action_index: usize, current_char: u8, body: &mut String, current_match_token: &mut String, it: &mut PFileParserIter, vec_action: &Vec<PActionHighLight>, token_charset: &String, is_prev_char_in_token_charset: bool){
	// println!("PNodeHighlight::run_action : current_char = '{}', current_match_token = '{}'", String::from(current_char as char), current_match_token);
	match vec_action.get(action_index) {
		Some(action) => {
			if current_match_token.len() == 0 {	//They were no previous match
				*body += &String::from(current_char as char);
				it.next();	//We get the next char
			}else{
				//Let's performed the associated action
				*body += &action.run(it, &current_match_token, token_charset, is_prev_char_in_token_charset);
				//The current char does not match here, but maybe at the begining of a new token
				*current_match_token = String::from("");	//We found a token, so we reset the matched token
			}
		},
		None => panic!("highlight_run_action : no action {} to be performed at {}", action_index, it.get_location())
	}
}

///Create the vector which tells which step could have an action because is it not followed by a token, and the ones wihout action because its needs a last token to be parse to validate it
/// # Parameters
/// - `sequence` : sequence of parsing steps (token or oneof)
/// # Returns
/// Corresponding vector which tells which step could have an action because is it not followed by a token, and the ones wihout action because its needs a last token to be parse to validate it
fn create_vec_is_required(sequence: &PHighlighterSequence) -> Vec<bool>{
	let mut is_last_required: bool = false;
	let mut vec_is_required: Vec<bool> = vec![];
	for step in sequence.vec_step.iter().rev() {
		match &step.token {
			Some(_) => {
				vec_is_required.push(is_last_required);
				is_last_required = true;
			},
			None => match &step.keyword {
				Some(_) => {
					vec_is_required.push(is_last_required);
					is_last_required = true;
				},
				None => match &step.oneof {
					Some(_) => {
						vec_is_required.push(is_last_required);
					},
					None => {}
				}
			}
		}
	}
	vec_is_required.reverse();
	return vec_is_required;
}

///Part of the highlighting tree
#[derive(Debug,Default,Clone)]
pub struct PNodeHighLight{
	///Index of the corresponding action to perform once none p_map_sequence or p_vec_oneof have a match (there will be a lot of action dupplication so we diminish the needed memory by factorizing it in the PHighLighter::p_map_action)
	p_action_index: Option<usize>,
	///Pattern or sequence of char to be tested by the node
	p_pattern: String,
	///Vector of node which match a sequence
	p_map_sequence: HashMap<u8, PNodeHighLight>,
	///Vector of node which match a oneof (one char match in all chars of the String p_pattern)
	p_vec_oneof: Vec<PNodeHighLight>,
}

impl PNodeHighLight {
	///Constructor of a PNodeHighLight
	/// # Parameters
	/// - `action_index` : index of the action to perform if no more match is found or None, if other required token have to be parsed after
	/// - `pattern` : pattern for 'OneOf' search
	/// # Returns
	/// Initialised PNodeHighLight
	pub fn new(action_index: &Option<usize>, pattern: &String) -> Self{
		PNodeHighLight{
			p_action_index: action_index.clone(),
			p_pattern: pattern.clone(),
			p_map_sequence: HashMap::default(),
			p_vec_oneof: vec![]
		}
	}
	///Say if the current node is a oneof
	/// # Returns
	/// True if the current node is a oneof
	pub fn is_oneof(&self) -> bool{
		return !self.p_pattern.is_empty();
	}
	
	///Add a token in the PNodeHighLight
	/// # Parameters
	/// - `token` : token to be parsed
	/// - `action_index` : index of the action to perform if there is no more match or None, if other required token have to be parsed after
	pub fn add_token(&mut self, token: &String, action_index: &Option<usize>){
		let vec_byte: Vec<u8> = Vec::from(token.clone().into_bytes());
		self.add_vec_u8(&vec_byte, action_index);
	}
	
	///Add a token in the PNodeHighLight
	/// # Parameters
	/// - `token` : token to be parsed
	/// - `action_index` : index of the action to perform if there is no more match or None, if other required token have to be parsed after
	pub fn add_vec_u8(&mut self, token: &Vec<u8>, action_index: &Option<usize>){
		//Here we need to walk on the vector of u8 and add all the necessary nodes for the parsing
		// All new nodes have the action 0 except the last one which has action action_index
		let mut last_node = self;
		for ch in token.iter() {
			let map_sequence = &mut last_node.p_map_sequence;
			//If the node does not exist, we create it
			if !map_sequence.contains_key(ch) {
				let node: PNodeHighLight = Default::default();
				map_sequence.insert(*ch, node);
			}
			//Then we get the next node, and it always exist
			last_node = map_sequence.get_mut(ch).unwrap();
			// last_node = match map_sequence.get_mut(ch) {
				// Some(node) => node,
				// None => &mut tmp_node
			// };
		}
		last_node.p_action_index = action_index.clone();
	}
	///Add a getuntil in the PNodeHighLight
	/// # Parameters
	/// - `getuntil` : getuntil configuration to be used
	/// - `action_index` : index of the action to perform if there is no more match or None, if other required token have to be parsed after
	pub fn add_getuntil(&mut self, getuntil: &PHighlighterGetUntil, action_index: usize){
		let vec_byte: Vec<u8> = Vec::from(getuntil.start_token.clone().into_bytes());
		self.add_vec_u8(&vec_byte, &Some(action_index));
	}
	///Add a getuntilreplace in the PNodeHighLight
	/// # Parameters
	/// - `getuntilreplace` : getuntilreplace configuration to be used
	/// - `action_index` : index of the action to perform if there is no more match or None, if other required token have to be parsed after
	pub fn add_getuntilreplace(&mut self, getuntilreplace: &PHighlighterGetUntilReplace, action_index: usize){
		let vec_byte: Vec<u8> = Vec::from(getuntilreplace.start_token.clone().into_bytes());
		self.add_vec_u8(&vec_byte, &Some(action_index));
	}
	///Add a sequence in the PNodeHighligth
	/// # Parameters
	/// - `sequence` : sequence configuration to be used
	/// - `action_index` : index of the action to perform if there is no more match or None, if other required token have to be parsed after
	pub fn add_sequence(&mut self, sequence: &PHighlighterSequence, action_index: &Option<usize>){
		//TODO : here if the sequence is followed by a required token, it has no action
		let vec_is_required: Vec<bool> = create_vec_is_required(sequence);
		
		//Let's add all steps of the sequence
		let mut last_node = self;
		for (step, is_last_required) in sequence.vec_step.iter().zip(vec_is_required.iter()){
			//Depending if the step is a match or a getStrComposedOf
			// There is no need for an optionnal attribute because we can end after each step
			match &step.token {
				Some(token) => {
					//We have to add the token in the node before calling the other steps
					// So we just change the last_node to be used, and where we will call the oneof at some point
					let vec_byte: Vec<u8> = Vec::from(token.clone().into_bytes());
					last_node = last_node.get_child_mut(&vec_byte);
					if *is_last_required {
						last_node.p_action_index = None;	//No action because we have to wait until the last node is parsed
					}else{
						last_node.p_action_index = action_index.clone();
					}
				},
				None => match &step.keyword {
					Some(keyword) => {
						let vec_byte: Vec<u8> = Vec::from(keyword.clone().into_bytes());
						last_node = last_node.get_child_mut(&vec_byte);
						if *is_last_required {
							last_node.p_action_index = None;	//No action because we have to wait until the last node is parsed
						}else{
							last_node.p_action_index = action_index.clone();
						}
					},
					None => match &step.oneof {
						Some(oneof) => {
							//We have to add a get_str_of in the current node
							// First, we create the node
							let node_action: Option<usize> = if *is_last_required {
								None	//No action because we have to wait until the last node is parsed
							}else{
								action_index.clone()
							};
							let oneof_node = PNodeHighLight::new(&node_action, oneof);
							last_node.p_vec_oneof.push(oneof_node);
							//And we add the node into the current one
							last_node = last_node.p_vec_oneof.last_mut().unwrap();
						},
						None => {}
					}
				}
			};
		}
		// last_node.p_action_index = action_index;
	}
	///Get a mutable child at the given token
	/// # Parameters
	/// - `token` : token to be parsed
	/// # Returns
	/// Mutable reference to the correspondign child (it will be created if it does not exist)
	fn get_child_mut(&mut self, token: &Vec<u8>) -> &mut PNodeHighLight{
		let mut last_node = self;
		for ch in token.iter() {
			let map_sequence = &mut last_node.p_map_sequence;
			//If the node does not exist, we create it
			if !map_sequence.contains_key(ch) {
				let node: PNodeHighLight = Default::default();
				map_sequence.insert(*ch, node);
			}
			//Then we get the next node, and it always exist
			last_node = map_sequence.get_mut(ch).unwrap();
			// last_node = match map_sequence.get_mut(ch) {
				// Some(node) => node,
				// None => &mut tmp_node
			// };
		}
		return last_node;
	}
	///Do the highlight on a PNodeHighLight
	/// # Parameters
	/// - `body` : output of the highlighting
	/// - `current_match_token` : current matched token when walking on the tree of PNodeHighLight children
	/// - `it` : iterator on the parser to use
	/// - `vec_action` : vector of action to be performed by the highlighter
	/// - `token_charset` : charset which contains all characters which composed a token
	pub fn highlight(&self, body: &mut String, current_match_token: &mut String, it: &mut PFileParserIter,
		vec_action: &Vec<PActionHighLight>, token_charset: &String)
	{
		let mut vec_node_iter = PVecNodeHighlightIter::new(self);
		vec_node_iter.highlight(body, current_match_token, it, vec_action, token_charset);
		return;
	}
}