mod charclasses; use serde_json; use std::collections::HashMap; use charclasses::*; struct CallExpression { callee: Option>, arguments: Vec>, } enum Expression { Root(), Argument(u64), Get(Box, Box), Attribute(Box, String), Call(Box, Box), Int(u64), } struct IfSubElement { branches: Vec, conditions: Vec } struct ForSubElement { iterable: Expression, hold_key: bool, hold_value: bool, core: Element, /* Either "\n", " " or "" */ join: String, } enum SubElement{ Static(String), /* ======== Other are dynamic ======== */ If(IfSubElement), /* Both for {{}} and {[]} */ InsertExpr(Expression), For(ForSubElement), Let(Expression, Element), } struct Element { argc: usize, sub_elements: Vec } enum Plemege { Element(Element), Package(Box>), } pub enum FileParsingErrorKind { expected_pack_opening_or_element_opening_or_pack_ending, expected_pack_opening_or_element_opening_or_eof, unmatched_pack_ending_tag, expected_pack_name, illegal_pack_name, pack_member_name_already_occupied, expected_pack_opening_tag_end, expected_element_name, illegal_element_name, expected_argument_name_or_eldef_opening_tag_end, illegal_argument_name, repeated_argument_name, expected_command_name, incorrect_block_ending_tag_expected_normal, expected_write_tag_end_after_expression, expected_roughinsert_tag_end_after_expression, illegal_command_name, expected_cmd_tag_end, } use FileParsingErrorKind::*; pub struct FileParsingError { kind: FileParsingErrorKind, p1: usize, p2: usize, } impl FileParsingError { fn new(kind: FileParsingErrorKind, p1: usize, p2: usize) -> Self { Self{kind, p1, p2} } } struct Parser<'a> { text: &'a str, p: usize } impl Parser { fn here(&self)->Option { self.text[self.p..].chars().next() } fn is_ahead(&self, substr: &[u8])->bool { self.text[self.p..].starts_with(substr) } fn advance(&mut self) { self.p += self.text[self.p..].char_indices().next().unwrap().0; } fn skip_whitespace(&mut self) { loop { match self.here() { Some(ch ) => if !is_whitespace(ch) { break } else { self.advance(); } None => break } } } fn skip_normal_word(&mut self){ loop { match self.here() { Some(ch ) => if !is_normal_word_constituent(ch) { break } else { self.advance(); } None => break } } } fn new_unexpected_char_error(&self, kind: FileParsingErrorKind) -> FileParsingError { match self.text[self.p..].char_indices().next() { Some((off, _)) => FileParsingError::new(kind, self.p, self.p + off), None => FileParsingError::new(kind, self.p, self.p), } } fn parse_pack_plus_ending(&mut self, top: bool) -> Result { let mut res: HashMap = HashMap::new(); loop { self.skip_whitespace(); if self.p == self.text.len() { return if top { Ok(Plemege::Package(Box::new(res))) } else { Err(self.new_unexpected_char_error(expected_pack_opening_or_element_opening_or_pack_ending)) } } if self.is_ahead(&[b'{', b'$', b'}']) { if top { return Err(FileParsingError::new(unmatched_pack_ending_tag, self.p, self.p + 3)) } else { self.p += 3; return Ok(res); } } else if self.is_ahead(&[b'{', b'$']) { self.p += 2; self.skip_whitespace(); let p1 = self.p; self.skip_normal_word(); if self.p == p1 { return Err(self.new_error(expected_pack_name)) } let child_name: &str = &self.text[p1..self.p]; if !is_illegal_name(child_name) { return Err(FileParsingError::new(illegal_pack_name, p1, self.p)) } if let Some(_) = res.get(child_name) { return Err(FileParsingError::new(pack_member_name_already_occupied, p1, self.p)) } self.skip_normal_word(); if !self.is_ahead(&[b'$', b'}']) { return Err(self.new_unexpected_char_error(expected_pack_opening_tag_end)) } self.p += 2; res.insert(String::from(child_name), self.parse_pack_plus_ending(false)); } else if self.is_ahead(&[b'{', b'@']) { self.p += 2; self.skip_whitespace(); let p1 = self.p; self.skip_normal_word(); if p1 == self.p { return Err(FileParsingError::new(expected_element_name, p1, self.p)) } let child_name = &self.text[p1..self.p]; if is_illegal_name(child_name) { return Err(FileParsingError::new(illegal_element_name, p1, self.p)) } if let Some(_) = res.get(child_name) { return Err(FileParsingError::new(pack_member_name_already_occupied, p1, self.p)) } let mut arg_names: Vec<&str> = Vec::new(); loop { self.skip_whitespace(); if self.is_ahead(&[b'@', b'}']) { self.p += 2; break } let p1 = self.p; self.skip_normal_word(); if p1 == self.p { return Err(FileParsingError::new(expected_argument_name_or_eldef_opening_tag_end, p1, self.p)) } let arg_name: &str = &self.text[p1..self.p]; if is_illegal_name(arg_name) { return Err(FileParsingError::new(illegal_argument_name, p1, self.p)) } if arg_names.iter().any(|b: &str| b == arg_name) { return Err(FileParsingError::new(repeated_argument_name, p1, self.p)) } arg_names.push(arg_name); } let (child_el, end_cmd): (Element, ReasonOfElementEnd) = self.parse_element_plus_ending(arg_names)?; if end_cmd.cmd != BlockEndingCmdTag::NORMAL { return Err(FileParsingError::new(incorrect_block_ending_tag_expected_normal, end_cmd.p1, self.p)) } res.insert(child_name, child_el); } else { self.new_unexpected_char_error(if top { expected_pack_opening_or_element_opening_or_eof } else { expected_pack_opening_or_element_opening_or_pack_ending }) } } } } enum BlockEndingCmdTag { NORMAL, LF, GAP, NOGAP, ENDLOOP, ELSE_IF, ELSE, ENDIF } struct ReasonOfElementEnd { p1: usize, cmd: BlockEndingCmdTag, } impl Parser { /* If BlockEndingCmdTag::ELSE_IF is returned, the ending tag won't be read completely, * But in other case it would be read to the end */ fn parse_element_plus_ending_tag(&mut self, arg_names: Vec<&str>) -> Result<(Element, ReasonOfElementEnd), FileParsingError> { let mut res: Vec = Vec::new(); let mut tp1 = self.p; let fin_static = || { if tp1 < self.p { res.push(SubElement::Static(String::from(&self.text[tp1..self.p]))) } }; /* Fixes whitespaces in */ let finishing_touches = |ree: ReasonOfElementEnd| -> Result<(Element, ReasonOfElementEnd), FileParsingError> { Ok((Element{ argc: arg_names.count(), sub_elements: res }, ree)) }; loop { if self.is_ahead(&[b'{', b'{']) { fin_static(); self.p += 2; let (expr, tt) = self.parse_expression()?; if tt != ExpressionEndingTagEnd::Write { return Err(FileParsingError::new(expected_write_tag_end_after_expression, self.p - 2, self.p)) } res.push(SubElement::InsertExpr( Expression::Call( Box::new(Expression::Attribute( Box::new(Expression::Root()), "sanitize" )), expr) )); tp1 = self.p; } else if self.is_ahead(&[b'{', b'[']) { fin_static(); self.p += 2; let (expr, tt) = self.parse_expression()?; if tt != ExpressionEndingTagEnd::RoughInsert { return Err(FileParsingError::new(expected_roughinsert_tag_end_after_expression, self.p - 2, self.p)) } res.push(SubElement::InsertExpr(expr)); } else if self.is_ahead(&[b'{', b'%', b'}']) { fin_static(); self.p += 3; return finishing_touches(ReasonOfElementEnd{p1: self.p - 3, cmd: BlockEndingCmdTag::NORMAL}); } else if self.is_ahead(&[b'{', b'%']) { fin_static(); /* Might be needed if this is the ENDING cmd tag */ let p1 = self.p; self.p += 2; self.skip_whitespace(); let pb = self.p; self.skip_normal_word(); if pb == self.p { return Err(self.new_unexpected_char_error(expected_command_name)) } let cmd = &self.text[pb..self.p]; /* Read space + expect %} and do finishing_touches */ let just_one_thing = |cmd: BlockEndingCmdTag| -> Result<(Element, ReasonOfElementEnd), FileParsingError> { self.skip_whitespace(); if !self.is_ahead(&[b'%', b'}']) { return self.new_unexpected_char_error(expected_cmd_tag_end); } self.p += 2; finishing_touches(ReasonOfElementEnd{p1, cmd}) }; match cmd { "lf" => return just_one_thing(BlockEndingCmdTag::LF), "gap" => return just_one_thing(BlockEndingCmdTag::GAP), "nogap" => return just_one_thing(BlockEndingCmdTag::NOGAP), "else" => { self.skip_whitespace(); let ps = self.p; self.skip_normal_word(); if ps == self.p { return just_one_thing(BlockEndingCmdTag::ELSE) } else if self.text[ps..self.p] != "if" { return Err(FileParsingError::new(illegal_command_name, pb, self.p)) } return finishing_touches(ReasonOfElementEnd{p1, cmd: BlockEndingCmdTag::ELSE_IF}) } "endif" => return just_one_thing(BlockEndingCmdTag::ENDIF), "endloop" => return just_one_thing(BlockEndingCmdTag::ENDLOOP), "for" => res.push(self.parse_let(&arg_names)?), "if" => res.push(self.parse_if(&arg_names)?), "let" => res.push(self.parse_let(&arg_names)?), _ => return Err(FileParsingError::new(illegal_command_name, pb, self.p)), } } else { self.advance(); } } } /* It turned out to be so complex I put it in a separate function. * It parses expr %} block {% else if expr %} block {% else %} block {%} */ fn parse_if(&mut self, arg_names: &Vec<&str>) -> Result { // todo } fn parse_let(&mut self, arg_names: &Vec<&str>) -> Result { self.skip_whitespace(); let p1 = self.p; self.skip_normal_word(); if p1 == self.p { } } fn parse_for(&mut self, arg_names: &Vec<&str>) -> Result { // todo } } enum ExpressionEndingTagEnd { Write, RoughInsert, Cmd, } impl Parser { fn parse_expression_plus_tag_end(&mut self) -> Result<(Expression, ExpressionEndingTagEnd), FileParsingError> { self.skip_whitespace(); return Err(self.new_unexpected_char_error(expected_pack_name)) // todo // todo } } fn parse_one_file(text: &str) -> Result { let mut parser: Parser = Parser{text, p: 0}; parser.parse_pack_plus_ending(true) } #[cfg(test)] mod tests{ use super::*; #[test] fn t1 () { } }