924 lines
37 KiB
Rust
924 lines
37 KiB
Rust
use std::collections::HashMap;
|
|
use crate::charclasses::{is_special_name, is_digit, is_lnspace,
|
|
is_normal_word_constituent, is_whitespace};
|
|
use std::fmt::{self, Display, Formatter};
|
|
use std::error::Error;
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub struct NewLambdaExpression {
|
|
pub local_var_array: Vec<usize>,
|
|
/* We store all expression trees in giant vector in MTGOTT object.
|
|
* We fill this global (to all the files) vector at compile time, and it can be used
|
|
* to access expressions (parsed source code) from any dynamically created lambda */
|
|
pub expr_id: usize,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub enum Expression {
|
|
Root,
|
|
Local(usize),
|
|
Attribute(Box<Expression>, String),
|
|
Get(Box<Expression>, Box<Expression>),
|
|
Call(Box<Expression>, Box<Expression>),
|
|
Lambda(NewLambdaExpression),
|
|
Int(u64),
|
|
Str(String),
|
|
Arr(Vec<Expression>),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub struct IfSubElement {
|
|
pub branches: Vec<Element>,
|
|
pub conditions: Vec<Expression>,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub struct ForSubElement {
|
|
pub iterable: Expression,
|
|
pub core: Element,
|
|
// Either "\n", " " or ""
|
|
pub separator: String,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub enum SubElement {
|
|
Static(String),
|
|
// ======== Other are dynamic ========
|
|
If(IfSubElement),
|
|
// Both for {{}} and {[]}
|
|
InsertExpr(Expression),
|
|
For(ForSubElement),
|
|
Let(Expression, Element),
|
|
}
|
|
|
|
pub type Element = Vec<SubElement>;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum Plemege {
|
|
Expression(Expression),
|
|
/* .0 is argc, .1 is the actual body
|
|
* I could compile Element into Expression "on-the-run", but I consider this
|
|
* bad for performance + not that good for improving loc count */
|
|
Element(usize, Element),
|
|
Package(HashMap<String, Plemege>),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
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_tag_end,
|
|
expected_pack_opening_tag_end_or_assignment_operator,
|
|
expected_element_name,
|
|
incorrect_block_ending_tag_expected_end_element,
|
|
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,
|
|
expected_variable_name,
|
|
illegal_variable_name,
|
|
expected_assignment_operator,
|
|
expected_cmd_tag_end_after_expression,
|
|
expected_comma_or_colon,
|
|
expected_colon,
|
|
forloop_key_and_val_cant_have_same_name,
|
|
incorrect_block_ending_tag_expected_normal_or_lf_gap_nogap_or_forloop,
|
|
incorrect_block_ending_tag_expected_normal_or_endif_or_else_or_else_if,
|
|
incorrect_block_ending_tag_expected_normal_or_endif,
|
|
expected_nonempty_expression,
|
|
expected_closing_round_bracket,
|
|
cant_start_word_immediately_after_digit,
|
|
integer_parsing_error,
|
|
expected_attribute_name_after_dot,
|
|
illegal_attribute_name,
|
|
illegal_lambda_argument_name,
|
|
expected_closing_square_bracket,
|
|
unmatched_element_ending_tag,
|
|
unmatched_magic_block_ending_tag,
|
|
recursion_limit_exceeded,
|
|
leave_space_between_dollar_argument_and_other_arguments,
|
|
cant_use_dollar_in_expression_of_element_without_dollar_argument,
|
|
illegal_dollar_level_attribute_name,
|
|
expected_round_brackets_open_or_dollar_or_word_or_int_or_string_or_square_bracket_open,
|
|
unmatched_double_quotes,
|
|
unmatched_closing_square_bracket,
|
|
}
|
|
|
|
use self::FileParsingErrorKind::*;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct FileParsingError {
|
|
pub kind: FileParsingErrorKind,
|
|
pub p1: usize,
|
|
pub p2: usize,
|
|
}
|
|
|
|
impl FileParsingError {
|
|
fn new(kind: FileParsingErrorKind, p1: usize, p2: usize) -> Self {
|
|
Self{kind, p1, p2}
|
|
}
|
|
}
|
|
|
|
impl Display for FileParsingErrorKind {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{:?}", self)
|
|
}
|
|
}
|
|
|
|
impl Display for FileParsingError {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
write!(f, "FileParsingError: {} at positions {} to {}", self.kind, self.p1, self.p2)
|
|
}
|
|
}
|
|
|
|
impl Error for FileParsingError {}
|
|
|
|
struct Parser<'a> {
|
|
text: &'a str,
|
|
p: usize,
|
|
/* We can use `this` keyword that refers to current file
|
|
* (current file can be either package or element), it does not matter, this_meaning contains expression
|
|
* that extracts object representing this file from root.
|
|
* Here we abuse the fact that Parser corresponds to one file */
|
|
this_meaning: Expression,
|
|
source_expr: &'a mut Vec<Expression>
|
|
}
|
|
|
|
impl<'a> Parser<'a> {
|
|
fn here(&self)->Option<char> {
|
|
self.text[self.p..].chars().next()
|
|
}
|
|
|
|
fn is_ahead(&self, substr: &str)->bool {
|
|
self.text[self.p..].starts_with(substr)
|
|
}
|
|
|
|
fn is_char_ahead(&self, ch: char) -> bool {
|
|
match self.here() {
|
|
Some(cha) => cha == ch,
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
fn is_digit_ahead(&self) -> bool {
|
|
match self.here() {
|
|
Some(ch) => is_digit(ch),
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
fn is_word_ahead(&self) -> bool {
|
|
match self.here() {
|
|
Some(ch) => is_normal_word_constituent(ch),
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
fn advance(&mut self) {
|
|
self.p += self.text[self.p..].chars().next().unwrap().len_utf8();
|
|
}
|
|
|
|
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 next_p(&self) -> usize {
|
|
match self.text[self.p..].char_indices().next() {
|
|
Some((off, _)) => self.p + off,
|
|
None => self.p,
|
|
}
|
|
}
|
|
|
|
fn new_unexpected_char_error(&self, kind: FileParsingErrorKind) -> FileParsingError {
|
|
FileParsingError::new(kind, self.p, self.next_p())
|
|
}
|
|
|
|
fn parse_pack_plus_ending(
|
|
&mut self, top: bool, recc: u32
|
|
) -> Result<Plemege, FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
let mut res: HashMap<String, Plemege> = HashMap::new();
|
|
loop {
|
|
self.skip_whitespace();
|
|
if self.p == self.text.len() {
|
|
return if top {
|
|
Ok(Plemege::Package(res))
|
|
} else {
|
|
Err(self.new_unexpected_char_error(expected_pack_opening_or_element_opening_or_pack_ending))
|
|
}
|
|
}
|
|
if self.is_ahead("{$}") {
|
|
return if top {
|
|
Err(FileParsingError::new(unmatched_pack_ending_tag, self.p, self.p + 3))
|
|
} else {
|
|
self.p += 3;
|
|
Ok(Plemege::Package(res))
|
|
};
|
|
} else if self.is_ahead("{$") {
|
|
self.p += 2;
|
|
self.skip_whitespace();
|
|
let p1 = self.p;
|
|
self.skip_normal_word();
|
|
if self.p == p1 {
|
|
return Err(self.new_unexpected_char_error(expected_pack_name))
|
|
}
|
|
let child_name: &str = &self.text[p1..self.p];
|
|
if is_special_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_whitespace();
|
|
|
|
if self.is_ahead("$}"){
|
|
self.p += 2;
|
|
res.insert(String::from(child_name), self.parse_pack_plus_ending(false, recc - 1)?);
|
|
} else if self.is_char_ahead('=') {
|
|
self.p += 1;
|
|
self.skip_whitespace();
|
|
res.insert(String::from(child_name), Plemege::Expression(
|
|
self.parse_expression(&vec![], &mut vec![],recc - 1)?
|
|
));
|
|
if !self.is_ahead("$}"){
|
|
return Err(self.new_unexpected_char_error(expected_pack_tag_end))
|
|
}
|
|
self.p += 2;
|
|
} else {
|
|
return Err(self.new_unexpected_char_error(expected_pack_opening_tag_end_or_assignment_operator))
|
|
}
|
|
} else if self.is_ahead("{@") {
|
|
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_special_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();
|
|
/* Some functions have $ as their first argument, we will let them have it */
|
|
self.skip_whitespace();
|
|
if self.is_char_ahead('$') {
|
|
arg_names.push("$");
|
|
self.p += 1;
|
|
if self.is_word_ahead() {
|
|
return Err(FileParsingError::new(leave_space_between_dollar_argument_and_other_arguments,
|
|
self.p - 1, self.next_p()));
|
|
}
|
|
}
|
|
|
|
loop {
|
|
self.skip_whitespace();
|
|
if self.is_ahead("@}") {
|
|
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_special_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_tag(
|
|
&arg_names, recc - 1)?;
|
|
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(arg_names.len(), child_el));
|
|
} else {
|
|
return Err(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 BlockEndingTag {
|
|
EOF,
|
|
/* 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
|
|
}
|
|
|
|
struct ReasonOfElementEnd {
|
|
p1: usize,
|
|
cmd: BlockEndingTag,
|
|
}
|
|
|
|
fn fix_whitespaces_in_element(subels: &mut Vec<SubElement>) {
|
|
let n = subels.len();
|
|
if n > 0 {
|
|
match &mut subels[0] {
|
|
SubElement::Static(org) => {
|
|
let mut ta = 0;
|
|
for p in 0..org.len(){
|
|
if !is_whitespace(org.as_bytes()[p] as char) {
|
|
ta = p; break
|
|
}
|
|
if org.as_bytes()[p] == b'\n' {
|
|
ta = p + 1;
|
|
}
|
|
}
|
|
*org = String::from(&org[ta..]);
|
|
},
|
|
_ => {},
|
|
}
|
|
match &mut subels[n - 1] {
|
|
SubElement::Static(org) => {
|
|
while let Some(ch) = org.chars().last() {
|
|
if is_whitespace(ch) { org.pop(); } else { break }
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
}
|
|
let mut min_offset = usize::MAX;
|
|
for i in 0..subels.len() {
|
|
match &mut subels[i] {
|
|
SubElement::Static(org) => {
|
|
let mut seen_online = i > 0;
|
|
let mut line_bg: usize = 0;
|
|
for p in 0..org.len() {
|
|
let ch = org.as_bytes()[p] as char;
|
|
if !is_whitespace(ch) {
|
|
if !seen_online {
|
|
seen_online = true;
|
|
min_offset = std::cmp::min(min_offset, p - line_bg)
|
|
}
|
|
} else if ch == '\n' {
|
|
line_bg = p + 1;
|
|
seen_online = false;
|
|
}
|
|
}
|
|
/* This won't cause issues on the .last() because we previously rstripped the last part */
|
|
if !seen_online{
|
|
min_offset = std::cmp::min(min_offset, org.len() - line_bg)
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
}
|
|
for i in 0..subels.len() {
|
|
match &mut subels[i] {
|
|
SubElement::Static(org) => {
|
|
let mut res: Vec<u8> = Vec::new();
|
|
let mut should_ignore_gap = i > 0;
|
|
let mut line_bg: usize = 0;
|
|
for p in 0..org.len() {
|
|
let ch = org.as_bytes()[p];
|
|
if ch == b'\n' {
|
|
line_bg = p + 1;
|
|
should_ignore_gap = false;
|
|
/* We handle trailing whitespaces case here */
|
|
while let Some(&ch) = res.last() {
|
|
if is_lnspace(ch as char) { res.pop(); } else { break }
|
|
}
|
|
} else if p - line_bg < min_offset && !should_ignore_gap{
|
|
continue
|
|
}
|
|
res.push(ch);
|
|
}
|
|
*org = String::from_utf8(res).unwrap();
|
|
},
|
|
_ => {},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Parser<'a> {
|
|
/* 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>, recc: u32
|
|
) -> Result<(Element, ReasonOfElementEnd), FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
let mut res: Vec<SubElement> = Vec::new();
|
|
let mut tp1 = self.p;
|
|
|
|
let fin_static = |p: &Parser, tp1: usize, res: &mut Vec<SubElement>| {
|
|
res.push(SubElement::Static(String::from(&p.text[tp1..p.p])))
|
|
};
|
|
|
|
/* Fixes whitespaces in static sub-elements */
|
|
let finishing_touches = |ree: ReasonOfElementEnd, mut res: Vec<SubElement>|
|
|
-> Result<(Element, ReasonOfElementEnd), FileParsingError> {
|
|
fix_whitespaces_in_element(&mut res);
|
|
Ok((res, ree))
|
|
};
|
|
|
|
loop {
|
|
if self.p == self.text.len() {
|
|
fin_static(self, tp1, &mut res);
|
|
return finishing_touches(ReasonOfElementEnd{p1: self.p, cmd: BlockEndingTag::EOF}, res);
|
|
} else if self.is_ahead("{{") {
|
|
fin_static(self, tp1, &mut res);
|
|
self.p += 2;
|
|
if !self.is_ahead("}}") {
|
|
let expr: Expression = self.parse_expression(
|
|
arg_names, &mut (0..arg_names.len()).collect(), recc)?;
|
|
res.push(SubElement::InsertExpr(
|
|
Expression::Call(
|
|
Box::new(Expression::Attribute(Box::new(Expression::Root), "sanitize".into())),
|
|
Box::new(expr))
|
|
));
|
|
if !self.is_ahead("}}") {
|
|
return Err(FileParsingError::new(expected_write_tag_end_after_expression, self.p - 2, self.p));
|
|
}
|
|
}
|
|
self.p += 2;
|
|
tp1 = self.p;
|
|
} else if self.is_ahead("{[") {
|
|
fin_static(self, tp1, &mut res);
|
|
self.p += 2;
|
|
let expr: Expression = self.parse_expression(
|
|
arg_names, &mut (0..arg_names.len()).collect(), recc)?;
|
|
res.push(SubElement::InsertExpr(expr));
|
|
if !self.is_ahead("]}") {
|
|
return Err(FileParsingError::new(expected_write_tag_end_after_expression, self.p - 2, self.p));
|
|
}
|
|
self.p += 2;
|
|
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: BlockEndingTag::NORMAL}, res);
|
|
} else if self.is_ahead("{%") {
|
|
fin_static(self, tp1, &mut res);
|
|
/* 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 = |
|
|
pelf: &mut Parser, cmd: BlockEndingTag, res: Vec<SubElement>
|
|
| -> Result<(Element, ReasonOfElementEnd), FileParsingError> {
|
|
pelf.skip_whitespace();
|
|
if !pelf.is_ahead("%}") {
|
|
return Err(pelf.new_unexpected_char_error(expected_cmd_tag_end));
|
|
}
|
|
pelf.p += 2;
|
|
finishing_touches(ReasonOfElementEnd{p1, cmd}, res)
|
|
};
|
|
|
|
match cmd {
|
|
"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, 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: BlockEndingTag::ELSE_IF}, 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, recc - 1)?),
|
|
"if" => res.push(self.parse_if(arg_names, recc - 1)?),
|
|
"let" => res.push(self.parse_let(arg_names, recc - 1)?),
|
|
_ => return Err(FileParsingError::new(illegal_command_name, pb, self.p)),
|
|
}
|
|
tp1 = self.p;
|
|
} else {
|
|
self.advance();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_expression_at_cmd_tag_end(
|
|
&mut self, arg_names: &Vec<&str>, recc: u32
|
|
) -> Result<Expression, FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
let expr: Expression = self.parse_expression(arg_names, &mut (0..arg_names.len()).collect(), recc - 1)?;
|
|
if !self.is_ahead("%}"){
|
|
return Err(self.new_unexpected_char_error(expected_cmd_tag_end_after_expression));
|
|
}
|
|
self.p += 2;
|
|
Ok(expr)
|
|
}
|
|
|
|
/*
|
|
* It parses expr %} block {% else if expr %} block {% else %} block {%} */
|
|
fn parse_if(
|
|
&mut self, arg_names: &Vec<&str>, recc: u32
|
|
) -> Result<SubElement, FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
let mut conditions: Vec<Expression> = Vec::new();
|
|
let mut blocks: Vec<Element> = Vec::new();
|
|
loop {
|
|
let expr = self.parse_expression_at_cmd_tag_end(arg_names, recc - 1)?;
|
|
let (inner_block, ending_tag) = self.parse_element_plus_ending_tag(arg_names, recc - 1)?;
|
|
conditions.push(expr);
|
|
match ending_tag.cmd {
|
|
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, BlockEndingTag::ELSE) {
|
|
let (else_block, the_end) = self.parse_element_plus_ending_tag(arg_names, recc - 1)?;
|
|
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, BlockEndingTag::NORMAL | BlockEndingTag::ENDIF) {
|
|
break
|
|
}
|
|
}
|
|
Ok(SubElement::If(IfSubElement{branches: blocks, conditions}))
|
|
}
|
|
|
|
fn parse_let(&mut self, arg_names: &Vec<&str>, recc: u32) -> Result<SubElement, FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
self.skip_whitespace();
|
|
let p1 = self.p;
|
|
self.skip_normal_word();
|
|
if p1 == self.p {
|
|
// Ironically, these symbols are actually constants
|
|
return Err(FileParsingError::new(expected_variable_name, p1, self.p));
|
|
}
|
|
let new_variable_name = &self.text[p1..self.p];
|
|
if is_special_name(new_variable_name){
|
|
return Err(FileParsingError::new(illegal_variable_name, p1, self.p));
|
|
}
|
|
self.skip_whitespace();
|
|
if !self.is_char_ahead('=') {
|
|
return Err(self.new_unexpected_char_error(expected_assignment_operator));
|
|
}
|
|
self.p += 1;
|
|
let expr = self.parse_expression_at_cmd_tag_end(arg_names, recc - 1)?;
|
|
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, recc - 1)?;
|
|
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))
|
|
}
|
|
|
|
fn parse_for_new_variable(&mut self) -> Result<(&'a str, usize), FileParsingError> {
|
|
self.skip_whitespace();
|
|
let t1 = self.p;
|
|
self.skip_normal_word();
|
|
if t1 == self.p {
|
|
return Err(self.new_unexpected_char_error(expected_variable_name));
|
|
}
|
|
let name = &self.text[t1..self.p];
|
|
if is_special_name(name) {
|
|
return Err(FileParsingError::new(illegal_variable_name, t1, self.p));
|
|
}
|
|
Ok((name, t1))
|
|
}
|
|
|
|
fn parse_for(&mut self, arg_names: &Vec<&str>, recc: u32) -> Result<SubElement, FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
let mut arg_names_extended = arg_names.clone();
|
|
|
|
let (name1, _) = self.parse_for_new_variable()?;
|
|
arg_names_extended.push(name1);
|
|
|
|
self.skip_whitespace();
|
|
let mut name2= if self.is_char_ahead(',') {
|
|
self.p += 1;
|
|
let (name, pt2) = self.parse_for_new_variable()?;
|
|
if name == name1 {
|
|
return Err(FileParsingError::new(forloop_key_and_val_cant_have_same_name, pt2, self.p))
|
|
}
|
|
name
|
|
} else { "" };
|
|
arg_names_extended.push(name2);
|
|
|
|
if !self.is_char_ahead(':'){
|
|
return Err(self.new_unexpected_char_error(
|
|
if name2.len() > 0 { expected_colon } else { expected_comma_or_colon }
|
|
));
|
|
}
|
|
self.p += 1;
|
|
|
|
let expr = self.parse_expression_at_cmd_tag_end(arg_names, recc - 1)?;
|
|
let (inner_block, ending) = self.parse_element_plus_ending_tag(
|
|
&arg_names_extended, recc - 1)?;
|
|
let separator: String = String::from(match ending.cmd {
|
|
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)),
|
|
});
|
|
Ok(SubElement::For(ForSubElement{iterable: expr, core: inner_block, separator}))
|
|
}
|
|
|
|
/* Checks for ]} }} and %}. May actually return NoneOfThose */
|
|
fn is_tag_end_ahead(&self) -> bool {
|
|
["]}", "}}", "%}", "$}"].iter().any(|s| self.is_ahead(s))
|
|
}
|
|
|
|
fn fn_parse_expression_l2_trail(
|
|
&mut self, arg_names: &Vec<&str>, mut from: Expression,
|
|
used_local_consts: &mut Vec<usize>, recc: u32
|
|
) -> Result<Expression, FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
loop {
|
|
self.skip_whitespace();
|
|
from = if self.is_char_ahead('.') {
|
|
self.p += 1;
|
|
self.skip_whitespace();
|
|
let attrp1 = self.p;
|
|
self.skip_normal_word();
|
|
if attrp1 == self.p {
|
|
return Err(self.new_unexpected_char_error(expected_attribute_name_after_dot));
|
|
}
|
|
let attr_name = &self.text[attrp1..self.p];
|
|
if is_special_name(attr_name) {
|
|
return Err(FileParsingError::new(illegal_attribute_name, attrp1, self.p));
|
|
}
|
|
Expression::Attribute(Box::new(from), String::from(attr_name))
|
|
} else if self.is_char_ahead('[') {
|
|
self.p += 1;
|
|
let sub_expr = self.parse_expression(arg_names, used_local_consts, recc - 1)?;
|
|
self.skip_whitespace();
|
|
if !self.is_char_ahead(']') {
|
|
return Err(self.new_unexpected_char_error(expected_closing_square_bracket))
|
|
}
|
|
self.p += 1;
|
|
Expression::Get(Box::new(from), Box::new(sub_expr))
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
Ok(from)
|
|
}
|
|
|
|
/* Suppose we use some toplevel name in expression. There is a chance it is a local constant.
|
|
* If so, we search for its index I in arg_names and if name_in_expr belongs to arg_names
|
|
* we return the index of number I in used_local_consts (pushing I to the right if needed).
|
|
* If name_in_expr is not a local constant, we return None */
|
|
fn expression_parsing_include_local_const(
|
|
arg_names: &Vec<&str>, used_local_consts: &mut Vec<usize>, name_in_expr: &str
|
|
) -> Option<usize> {
|
|
match arg_names.iter().rposition(|&n| n == name_in_expr) {
|
|
Some(i) => {
|
|
let lci = match used_local_consts.iter().position(|iig| *iig == i) {
|
|
Some(lci) => lci,
|
|
None => {used_local_consts.push(i); used_local_consts.len() - 1},
|
|
};
|
|
Some(lci)
|
|
}
|
|
None => None
|
|
}
|
|
}
|
|
|
|
/* l1 expression = l2 space l2 space ... space l2 */
|
|
fn parse_expression_l2(
|
|
&mut self, arg_names: &Vec<&str>,
|
|
used_local_consts: &mut Vec<usize>, recc: u32
|
|
) -> Result<Option<Expression>, FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
self.skip_whitespace();
|
|
if self.is_tag_end_ahead() {
|
|
Ok(None)
|
|
} else if self.is_char_ahead('(') {
|
|
self.p += 1;
|
|
let expr = self.parse_expression(arg_names, used_local_consts, recc - 1)?;
|
|
self.skip_whitespace();
|
|
if !self.is_char_ahead(')') {
|
|
return Err(self.new_unexpected_char_error(expected_closing_round_bracket))
|
|
}
|
|
self.p += 1;
|
|
Ok(Some(expr))
|
|
} else if self.is_digit_ahead() {
|
|
let p1 = self.p;
|
|
loop {
|
|
if self.is_digit_ahead() {
|
|
self.p += 1;
|
|
} else if self.is_word_ahead() {
|
|
return Err(self.new_unexpected_char_error(cant_start_word_immediately_after_digit))
|
|
} else {
|
|
return match self.text[p1..self.p].parse::<u64>() {
|
|
Ok(v) => Ok(Some(Expression::Int(v))),
|
|
Err(_) => Err(FileParsingError::new(integer_parsing_error, p1, self.p)),
|
|
};
|
|
}
|
|
}
|
|
} else if self.is_word_ahead() {
|
|
let p1 = self.p;
|
|
self.skip_normal_word();
|
|
let toplevel_name = &self.text[p1..self.p];
|
|
let p2 = self.p;
|
|
self.skip_whitespace();
|
|
if self.is_char_ahead(':') {
|
|
if is_special_name(toplevel_name) {
|
|
return Err(FileParsingError::new(illegal_lambda_argument_name, p1, p2))
|
|
}
|
|
self.p += 1;
|
|
let argument_iig = arg_names.len();
|
|
let mut arg_names_extended = arg_names.clone();
|
|
arg_names_extended.push(toplevel_name);
|
|
let mut used_by_lambda: Vec<usize> = Vec::new();
|
|
let lambda_body = self.parse_expression(
|
|
&arg_names_extended, &mut used_by_lambda, recc - 1)?;
|
|
for iig in &used_by_lambda {
|
|
if !used_local_consts.contains(iig) && *iig != argument_iig {
|
|
used_local_consts.push(*iig);
|
|
}
|
|
}
|
|
let expr_id = self.source_expr.len();
|
|
self.source_expr.push(lambda_body);
|
|
Ok(Some(Expression::Lambda(NewLambdaExpression{
|
|
local_var_array: used_by_lambda.iter().map( |iig| -> usize {
|
|
if *iig == argument_iig {
|
|
usize::MAX // We assume usize::MAX never appears as an actual index in used_local_consts
|
|
} else {
|
|
used_local_consts.iter().position(|iig_of_this| iig_of_this == iig).unwrap()
|
|
}
|
|
}).collect(), expr_id})))
|
|
} else if toplevel_name == "this" {
|
|
Ok(Some(self.fn_parse_expression_l2_trail(
|
|
arg_names, self.this_meaning.clone(), used_local_consts, recc - 1)?))
|
|
} else {
|
|
let bg: Expression = match Self::expression_parsing_include_local_const(
|
|
arg_names, used_local_consts, toplevel_name
|
|
) {
|
|
Some(n) => Expression::Local(n),
|
|
None => Expression::Attribute(Box::new(Expression::Root), toplevel_name.into()),
|
|
};
|
|
Ok(Some(self.fn_parse_expression_l2_trail(arg_names, bg, used_local_consts, recc - 1)?))
|
|
}
|
|
} else if self.is_char_ahead('$') {
|
|
self.p += 1;
|
|
let bg_dollar: Expression = match Self::expression_parsing_include_local_const(
|
|
arg_names, used_local_consts, "$"
|
|
) {
|
|
Some(n) => Expression::Local(n),
|
|
None => return Err(self.new_unexpected_char_error(cant_use_dollar_in_expression_of_element_without_dollar_argument)),
|
|
};
|
|
let bg: Expression = if self.is_word_ahead() {
|
|
let p1 = self.p;
|
|
self.skip_normal_word();
|
|
let dollar_level_name = &self.text[p1..self.p];
|
|
if is_special_name(dollar_level_name) {
|
|
return Err(FileParsingError::new(illegal_dollar_level_attribute_name, p1, self.p));
|
|
}
|
|
Expression::Attribute(Box::new(bg_dollar), dollar_level_name.into())
|
|
} else { bg_dollar };
|
|
Ok(Some(self.fn_parse_expression_l2_trail(arg_names, bg, used_local_consts, recc - 1)?))
|
|
} else if self.is_char_ahead('"'){
|
|
self.p += 1;
|
|
let sb = self.p;
|
|
loop {
|
|
match self.here() {
|
|
Some(ch ) => if ch == '"' { break } else { self.advance(); }
|
|
None => return Err(self.new_unexpected_char_error(unmatched_double_quotes))
|
|
}
|
|
}
|
|
let se = self.p;
|
|
self.p += 1;
|
|
Ok(Some(Expression::Str(String::from(&self.text[sb..se]))))
|
|
} else if self.is_char_ahead('['){
|
|
self.p += 1;
|
|
self.skip_whitespace();
|
|
let mut res: Vec<Expression> = Vec::new();
|
|
loop {
|
|
if self.is_char_ahead(']') {
|
|
self.p += 1;
|
|
break;
|
|
} else {
|
|
res.push(self.parse_expression(arg_names, used_local_consts, recc - 1)?);
|
|
if self.is_char_ahead(',') {
|
|
self.p += 1;
|
|
self.skip_whitespace();
|
|
} else if self.is_char_ahead(']') {
|
|
self.p += 1;
|
|
break
|
|
} else {
|
|
return Err(self.new_unexpected_char_error(unmatched_closing_square_bracket))
|
|
}
|
|
}
|
|
}
|
|
Ok(Some(Expression::Arr(res)))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn parse_expression(
|
|
&mut self, arg_names: &Vec<&str>,
|
|
used_local_consts: &mut Vec<usize>, recc: u32
|
|
) -> Result<Expression, FileParsingError> {
|
|
self.check_recursion_limit(recc)?;
|
|
let mut e1: Expression = self.parse_expression_l2(arg_names, used_local_consts, recc - 1)?
|
|
.ok_or(self.new_unexpected_char_error(expected_round_brackets_open_or_dollar_or_word_or_int_or_string_or_square_bracket_open))?;
|
|
loop {
|
|
let arg_expr: Option<Expression> = self.parse_expression_l2(arg_names, used_local_consts,recc - 1)?;
|
|
if matches!(arg_expr, None) { break }
|
|
e1 = Expression::Call(Box::new(e1), Box::new(arg_expr.unwrap()));
|
|
}
|
|
Ok(e1)
|
|
}
|
|
|
|
fn check_recursion_limit(&self, recc: u32) -> Result<(), FileParsingError> {
|
|
if recc == 0 { Err(self.new_unexpected_char_error(recursion_limit_exceeded)) } else { Ok(() )}
|
|
}
|
|
}
|
|
|
|
/* file_path is a path without extension and separated by '/' */
|
|
pub fn make_expression_for_this(file_path: &str) -> Expression {
|
|
file_path.split("/").fold(Expression::Root, |b, s| {
|
|
Expression::Attribute(Box::new(b), String::from(s))
|
|
})
|
|
}
|
|
|
|
/* Parses a file treating it like a package (where other packages and elements could be located)
|
|
* file_path is a path of file, separated by '/' and without mtgott extension */
|
|
pub fn parse_one_file_packed(
|
|
text: &str, file_path: &str, source_expr: &mut Vec<Expression>,
|
|
) -> Result<Plemege, FileParsingError> {
|
|
let mut parser: Parser = Parser{text, p: 0, this_meaning: make_expression_for_this(file_path), source_expr};
|
|
parser.parse_pack_plus_ending(true, 150)
|
|
}
|
|
|
|
pub fn parse_one_file_simplified(
|
|
text: &str, file_path: &str, source_expr: &mut Vec<Expression>,
|
|
) -> Result<Plemege, FileParsingError> {
|
|
let mut parser: Parser = Parser{text, p: 0, this_meaning: make_expression_for_this(file_path), source_expr};
|
|
let (el, tt): (Element, ReasonOfElementEnd) = parser.parse_element_plus_ending_tag(&vec!["$"], 150)?;
|
|
match tt.cmd {
|
|
BlockEndingTag::EOF => Ok(Plemege::Element(1, el)),
|
|
BlockEndingTag::END_ELEMENT => Err(FileParsingError::new(unmatched_element_ending_tag, tt.p1, parser.p)),
|
|
_ => Err(FileParsingError::new(unmatched_magic_block_ending_tag, tt.p1, parser.p)),
|
|
}
|
|
}
|