From 00b00cf26a6df1fea79e4c59a8c96213f9825b33 Mon Sep 17 00:00:00 2001 From: Andreev Gregory Date: Sat, 5 Apr 2025 23:22:07 +0300 Subject: [PATCH] Daily update. One passing test :) --- src/mtgott/charclasses.rs | 4 +- src/mtgott/parser.rs | 98 +++++++++++++++++++++++---------------- tests/parsing_test.rs | 23 +++++++-- 3 files changed, 79 insertions(+), 46 deletions(-) diff --git a/src/mtgott/charclasses.rs b/src/mtgott/charclasses.rs index 1ddac41..0275db6 100644 --- a/src/mtgott/charclasses.rs +++ b/src/mtgott/charclasses.rs @@ -1,11 +1,11 @@ use std::ops::RangeBounds; pub fn is_lnspace(ch: char) -> bool { - ch == '\t' && ch == ' ' && ch == '\r' + ch == '\t' || ch == ' ' || ch == '\r' } pub fn is_whitespace(ch: char) -> bool { - is_lnspace(ch) && ch == '\n' + is_lnspace(ch) || ch == '\n' } pub fn is_digit(ch: char) -> bool { diff --git a/src/mtgott/parser.rs b/src/mtgott/parser.rs index 5b3f4e3..baaef67 100644 --- a/src/mtgott/parser.rs +++ b/src/mtgott/parser.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use crate::mtgott::charclasses::*; +#[derive(Debug, PartialEq, Eq)] pub enum Expression { Root, Argument(u64), @@ -11,38 +12,44 @@ pub enum Expression { None, } +#[derive(Debug, PartialEq, Eq)] pub struct IfSubElement { - branches: Vec, - conditions: Vec + pub branches: Vec, + pub conditions: Vec, } +#[derive(Debug, PartialEq, Eq)] pub struct ForSubElement { - iterable: Expression, - core: Element, - /* Either "\n", " " or "" */ - join: String, + pub iterable: Expression, + pub core: Element, + // Either "\n", " " or "" + pub join: String, } -pub enum SubElement{ +#[derive(Debug, PartialEq, Eq)] +pub enum SubElement { Static(String), - /* ======== Other are dynamic ======== */ + // ======== Other are dynamic ======== If(IfSubElement), - /* Both for {{}} and {[]} */ + // Both for {{}} and {[]} InsertExpr(Expression), For(ForSubElement), Let(Expression, Element), } +#[derive(Debug, PartialEq, Eq)] pub struct Element { - argc: usize, - sub_elements: Vec + pub argc: usize, + pub sub_elements: Vec, } +#[derive(Debug, PartialEq, Eq)] pub enum Plemege { Element(Element), - Package(Box>), + Package(HashMap), } +#[derive(Debug, PartialEq, Eq)] pub enum FileParsingErrorKind { expected_pack_opening_or_element_opening_or_pack_ending, expected_pack_opening_or_element_opening_or_eof, @@ -52,6 +59,7 @@ pub enum FileParsingErrorKind { pack_member_name_already_occupied, expected_pack_opening_tag_end, expected_element_name, + incorrect_block_ending_tag_expected_end_element, illegal_element_name, expected_argument_name_or_eldef_opening_tag_end, illegal_argument_name, @@ -87,10 +95,11 @@ pub enum FileParsingErrorKind { use FileParsingErrorKind::*; use crate::mtgott::charclasses::{is_bad_name, is_digit, is_illegal_name, is_lnspace, is_normal_word_constituent, is_whitespace}; +#[derive(Debug, PartialEq, Eq)] pub struct FileParsingError { - kind: FileParsingErrorKind, - p1: usize, - p2: usize, + pub kind: FileParsingErrorKind, + pub p1: usize, + pub p2: usize, } impl FileParsingError { @@ -135,7 +144,7 @@ impl<'a> Parser<'a> { } fn advance(&mut self) { - self.p += self.text[self.p..].char_indices().next().unwrap().0; + self.p += self.text[self.p..].chars().next().unwrap().len_utf8(); } fn skip_whitespace(&mut self) { @@ -173,7 +182,7 @@ impl<'a> Parser<'a> { self.skip_whitespace(); if self.p == self.text.len() { return if top { - Ok(Plemege::Package(Box::new(res))) + Ok(Plemege::Package(res)) } else { Err(self.new_unexpected_char_error(expected_pack_opening_or_element_opening_or_pack_ending)) } @@ -183,7 +192,7 @@ impl<'a> Parser<'a> { Err(FileParsingError::new(unmatched_pack_ending_tag, self.p, self.p + 3)) } else { self.p += 3; - Ok(Plemege::Package(Box::new(res))) + Ok(Plemege::Package(res)) }; } else if self.is_ahead("{$") { self.p += 2; @@ -243,8 +252,8 @@ impl<'a> Parser<'a> { arg_names.push(arg_name); } let (child_el, end_cmd): (Element, ReasonOfElementEnd) = self.parse_element_plus_ending_tag(&arg_names)?; - if !matches!(end_cmd.cmd, BlockEndingCmdTag::NORMAL) { - return Err(FileParsingError::new(incorrect_block_ending_tag_expected_normal, end_cmd.p1, self.p)) + if !matches!(end_cmd.cmd, BlockEndingTag::END_ELEMENT) { + return Err(FileParsingError::new(incorrect_block_ending_tag_expected_end_element, end_cmd.p1, self.p)) } res.insert(String::from(child_name), Plemege::Element(child_el)); } else { @@ -258,12 +267,17 @@ impl<'a> Parser<'a> { } } -enum BlockEndingCmdTag { +enum BlockEndingTag { + /* This is {@} */ + END_ELEMENT, + /* These tags are command tags */ + /* {%} */ NORMAL, LF, GAP, NOGAP, ENDLOOP, + /* Incomplete tag (ony `{% else if` is included */ ELSE_IF, ELSE, ENDIF @@ -271,7 +285,7 @@ enum BlockEndingCmdTag { struct ReasonOfElementEnd { p1: usize, - cmd: BlockEndingCmdTag, + cmd: BlockEndingTag, } fn fix_whitespaces_in_element(subels: &mut Vec) { @@ -404,10 +418,14 @@ impl<'a> Parser<'a> { res.push(SubElement::InsertExpr(expr)); } tp1 = self.p; + } else if self.is_ahead("{@}") { + fin_static(self, tp1, &mut res); + self.p += 3; + return finishing_touches(ReasonOfElementEnd{p1: self.p - 3, cmd: BlockEndingTag::END_ELEMENT}, res); } else if self.is_ahead("{%}") { fin_static(self, tp1, &mut res); self.p += 3; - return finishing_touches(ReasonOfElementEnd{p1: self.p - 3, cmd: BlockEndingCmdTag::NORMAL}, res); + return finishing_touches(ReasonOfElementEnd{p1: self.p - 3, cmd: BlockEndingTag::NORMAL}, res); } else if self.is_ahead("{%") { fin_static(self, tp1, &mut res); /* Might be needed if this is the ENDING cmd tag */ @@ -422,7 +440,7 @@ impl<'a> Parser<'a> { let cmd = &self.text[pb..self.p]; /* Read space + expect %} and do finishing_touches */ - let just_one_thing = |pelf: &mut Parser, cmd: BlockEndingCmdTag, res: Vec| -> Result<(Element, ReasonOfElementEnd), FileParsingError> { + let just_one_thing = |pelf: &mut Parser, cmd: BlockEndingTag, res: Vec| -> Result<(Element, ReasonOfElementEnd), FileParsingError> { pelf.skip_whitespace(); if !pelf.is_ahead("%}") { return Err(pelf.new_unexpected_char_error(expected_cmd_tag_end)); @@ -432,22 +450,22 @@ impl<'a> Parser<'a> { }; match cmd { - "lf" => return just_one_thing(self, BlockEndingCmdTag::LF, res), - "gap" => return just_one_thing(self, BlockEndingCmdTag::GAP, res), - "nogap" => return just_one_thing(self, BlockEndingCmdTag::NOGAP, res), + "lf" => return just_one_thing(self, BlockEndingTag::LF, res), + "gap" => return just_one_thing(self, BlockEndingTag::GAP, res), + "nogap" => return just_one_thing(self, BlockEndingTag::NOGAP, res), "else" => { self.skip_whitespace(); let ps = self.p; self.skip_normal_word(); if ps == self.p { - return just_one_thing(self, BlockEndingCmdTag::ELSE, res) + return just_one_thing(self, BlockEndingTag::ELSE, res) } 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}, res); + return finishing_touches(ReasonOfElementEnd{p1, cmd: BlockEndingTag::ELSE_IF}, res); } - "endif" => return just_one_thing(self, BlockEndingCmdTag::ENDIF, res), - "endloop" => return just_one_thing(self, BlockEndingCmdTag::ENDLOOP, res), + "endif" => return just_one_thing(self, BlockEndingTag::ENDIF, res), + "endloop" => return just_one_thing(self, BlockEndingTag::ENDLOOP, res), "for" => res.push(self.parse_let(arg_names)?), "if" => res.push(self.parse_if(arg_names)?), "let" => res.push(self.parse_let(arg_names)?), @@ -482,19 +500,19 @@ impl<'a> Parser<'a> { let (inner_block, ending_tag) = self.parse_element_plus_ending_tag(arg_names)?; conditions.push(expr); match ending_tag.cmd { - BlockEndingCmdTag::ELSE | BlockEndingCmdTag::NORMAL | BlockEndingCmdTag::ENDIF | - BlockEndingCmdTag::ELSE_IF => blocks.push(inner_block), + BlockEndingTag::ELSE | BlockEndingTag::NORMAL | BlockEndingTag::ENDIF | + BlockEndingTag::ELSE_IF => blocks.push(inner_block), _ => return Err(FileParsingError::new( incorrect_block_ending_tag_expected_normal_or_endif_or_else_or_else_if, ending_tag.p1, self.p)), } - if matches!(ending_tag.cmd, BlockEndingCmdTag::ELSE) { + if matches!(ending_tag.cmd, BlockEndingTag::ELSE) { let (else_block, the_end) = self.parse_element_plus_ending_tag(arg_names)?; - if !matches!(the_end.cmd, BlockEndingCmdTag::NORMAL | BlockEndingCmdTag::ENDIF){ + if !matches!(the_end.cmd, BlockEndingTag::NORMAL | BlockEndingTag::ENDIF){ return Err(FileParsingError::new(incorrect_block_ending_tag_expected_normal_or_endif, the_end.p1, self.p)); } blocks.push(else_block); break - } else if matches!(ending_tag.cmd, BlockEndingCmdTag::NORMAL | BlockEndingCmdTag::ENDIF) { + } else if matches!(ending_tag.cmd, BlockEndingTag::NORMAL | BlockEndingTag::ENDIF) { break } } @@ -522,7 +540,7 @@ impl<'a> Parser<'a> { let mut arg_names_extended = arg_names.clone(); arg_names_extended.push(new_variable_name); let (inner_block, ending) = self.parse_element_plus_ending_tag(&arg_names_extended)?; - if !matches!(ending.cmd, BlockEndingCmdTag::NORMAL) { + if !matches!(ending.cmd, BlockEndingTag::NORMAL) { return Err(FileParsingError::new(incorrect_block_ending_tag_expected_normal, ending.p1, self.p)); } Ok(SubElement::Let(expr, inner_block)) @@ -569,9 +587,9 @@ impl<'a> Parser<'a> { let expr = self.parse_expression_at_cmd_tag_end(arg_names)?; let (inner_block, ending) = self.parse_element_plus_ending_tag(&arg_names_extended)?; let separator: String = String::from(match ending.cmd { - BlockEndingCmdTag::NOGAP => "", - BlockEndingCmdTag::GAP => " ", - BlockEndingCmdTag::NORMAL | BlockEndingCmdTag::LF | BlockEndingCmdTag::ENDLOOP => "\n", + BlockEndingTag::NOGAP => "", + BlockEndingTag::GAP => " ", + BlockEndingTag::NORMAL | BlockEndingTag::LF | BlockEndingTag::ENDLOOP => "\n", _ => return Err(FileParsingError::new( incorrect_block_ending_tag_expected_normal_or_lf_gap_nogap_or_forloop, ending.p1, self.p)), }); diff --git a/tests/parsing_test.rs b/tests/parsing_test.rs index ab23ca9..b3afdd3 100644 --- a/tests/parsing_test.rs +++ b/tests/parsing_test.rs @@ -1,4 +1,5 @@ use yyyi_ru::mtgott::parser::*; +use std::collections::HashMap; fn generate_strings( prefix: &mut String, @@ -20,15 +21,29 @@ fn generate_strings( #[test] fn test_parse_file_with_all_combinations() { let alphabet = [' ', '{', '%', '}', '$', 'a']; - let target_length = 3; + let target_length = 5 ; generate_strings(&mut String::new(), target_length, &alphabet, &mut |s| { println!("Parsing {s}"); parse_one_file(&s); + parse_one_file((String::from("{% as s e1e %} adasd {%}") + s.as_str()).as_str()); + parse_one_file((String::from("{% as s e1e %} a{[111 . 2332]]dasd {%} {% as s e1e %} adas {{}}d {%} ") + s.as_str()).as_str()); }); } -#[test] -fn t1(){ +macro_rules! string_map { + ($($key:expr => $val:expr),* $(,)?) => {{ + let mut map = HashMap::new(); + $( map.insert($key.to_string(), $val); )* + map + }}; +} -} \ No newline at end of file + +#[test] +fn resulting_package() { + assert_eq!(parse_one_file("{@ x @}{@}"), + Ok(Plemege::Package(string_map! { + "x" => Plemege::Element(Element{argc: 0, sub_elements: vec![]}) + }))) +}