2025-04-04 19:45:42 +03:00
|
|
|
mod charclasses;
|
|
|
|
|
|
|
|
|
|
use serde_json;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use charclasses::*;
|
|
|
|
|
|
|
|
|
|
struct CallExpression {
|
|
|
|
|
callee: Option<Box<Expression>>,
|
|
|
|
|
arguments: Vec<Box<Expression>>,
|
2025-04-01 20:10:28 +03:00
|
|
|
}
|
2025-04-04 19:45:42 +03:00
|
|
|
|
|
|
|
|
enum Expression {
|
|
|
|
|
Root(),
|
|
|
|
|
Argument(u64),
|
|
|
|
|
Get(Box<Expression>, Box<Expression>),
|
|
|
|
|
Attribute(Box<Expression>, String),
|
|
|
|
|
Call(Box<Expression>, Box<Expression>),
|
|
|
|
|
Int(u64),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct IfSubElement {
|
|
|
|
|
branches: Vec<Element>,
|
|
|
|
|
conditions: Vec<Expression>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<SubElement>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Plemege {
|
|
|
|
|
Element(Element),
|
|
|
|
|
Package(Box<HashMap<String, Plemege>>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<char> {
|
|
|
|
|
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<Plemege::Package, FileParsingError> {
|
|
|
|
|
let mut res: HashMap<String, Plemege> = 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<SubElement> = 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<SubElement::If, FileParsingError> {
|
|
|
|
|
// todo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_let(&mut self, arg_names: &Vec<&str>) -> Result<SubElement::Let, FileParsingError> {
|
|
|
|
|
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<SubElement::For, FileParsingError> {
|
|
|
|
|
// 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<Plemege::Package, FileParsingError> {
|
|
|
|
|
let mut parser: Parser = Parser{text, p: 0};
|
|
|
|
|
parser.parse_pack_plus_ending(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests{
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn t1 () {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|