use super::{macros::Macro, Compiler};
use crate::{
ast::{AssignmentOp, Node},
compiler::if_while::{IfStatement, WhileLoop},
};
use pest::{iterators::Pairs, Parser};
use std::collections::HashMap;
#[derive(Parser)]
#[grammar = "databind.pest"]
pub(crate) struct DatabindParser;
pub type ParseResult<T> = Result<T, pest::error::Error<Rule>>;
impl Compiler {
pub fn parse(
raw_file: &str,
subfolder: &str,
macros: &mut HashMap<String, Macro>,
) -> ParseResult<Vec<Node>> {
let tokens = DatabindParser::parse(Rule::file, &raw_file)?
.next()
.unwrap();
Compiler::parse_tokens(&mut tokens.into_inner(), macros, subfolder)
}
pub(crate) fn parse_tokens(
tokens: &mut Pairs<Rule>,
macros: &mut HashMap<String, Macro>,
subfolder: &str,
) -> ParseResult<Vec<Node>> {
let mut ast = vec![];
macro_rules! percent_escape {
($str: expr) => {
if let Some(stripped) = $str.strip_prefix("%") {
stripped.into()
} else {
$str.into()
}
};
}
macro_rules! unwrap_name {
($inner: expr) => {{
let as_str = $inner.next().unwrap().as_str();
percent_escape!(as_str)
}};
}
macro_rules! fix_escapes {
($str: expr) => {
$str.replace("\\\\", "\\")
.replace("\\/", "/")
.replace("\\\"", "\"")
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
};
}
for token in tokens {
match token.as_rule() {
Rule::new_var => {
let mut inner = token.into_inner();
let name = unwrap_name!(inner);
let value: i32 = inner.next().unwrap().as_str().parse().unwrap();
ast.push(Node::NewVar { name, value });
}
Rule::set_var => {
let mut inner = token.into_inner();
let name = unwrap_name!(inner);
let operator = match inner.next().unwrap().as_str() {
"=" => AssignmentOp::Set,
"+=" => AssignmentOp::Add,
"-=" => AssignmentOp::Subtract,
_ => unimplemented!(),
};
let value: i32 = inner.next().unwrap().as_str().parse().unwrap();
ast.push(Node::SetVar {
name,
operator,
value,
});
}
Rule::test_var => {
let mut inner = token.into_inner();
let name = unwrap_name!(inner);
let test = unwrap_name!(inner);
ast.push(Node::TestVar { name, test });
}
Rule::delete_var => {
let mut inner = token.into_inner();
let name = unwrap_name!(inner);
ast.push(Node::DeleteVar(name));
}
Rule::new_obj => {
let mut inner = token.into_inner();
let name = unwrap_name!(inner);
let objective = unwrap_name!(inner);
ast.push(Node::NewObjective { name, objective });
}
Rule::set_obj => {
let mut inner = token.into_inner();
let target = unwrap_name!(inner);
let name = unwrap_name!(inner);
let operator = match inner.next().unwrap().as_str() {
"=" => AssignmentOp::Set,
"+=" => AssignmentOp::Add,
"-=" => AssignmentOp::Subtract,
_ => unimplemented!(),
};
let value: i32 = inner.next().unwrap().as_str().parse().unwrap();
ast.push(Node::SetObjective {
target,
name,
operator,
value,
});
}
Rule::get_var => {
let mut inner = token.into_inner();
let name = unwrap_name!(inner);
ast.push(Node::GetVar(name));
}
Rule::sbop | Rule::mc_command => {
let rule = token.as_rule();
let mut inner = token.into_inner();
let name = if let Rule::mc_command = rule {
unwrap_name!(inner)
} else {
"scoreboard".into()
};
let args = {
let mut args = if let Rule::sbop = rule {
vec![
Node::CommandArg("players".into()),
Node::CommandArg("operation".into()),
]
} else {
vec![]
};
args.append(&mut Compiler::parse_tokens(&mut inner, macros, subfolder)?);
args
};
ast.push(Node::MinecraftCommand { name, args });
}
Rule::command_arg => {
let as_str = token.as_str();
ast.push(Node::CommandArg(if as_str == "%=" {
as_str.into()
} else {
percent_escape!(as_str)
}));
}
Rule::function => {
let mut inner = token.into_inner();
let name = unwrap_name!(inner);
let contents = Compiler::parse_tokens(&mut inner, macros, subfolder)?;
ast.push(Node::Function { name, contents });
}
Rule::tag => {
let mut inner = token.into_inner();
ast.push(Node::Tag(unwrap_name!(inner)));
}
Rule::call_function => {
let mut inner = token.into_inner();
ast.push(Node::CallFunction(unwrap_name!(inner)));
}
Rule::if_statement => {
let mut inner = token.into_inner();
let condition = Compiler::parse_tokens(
&mut inner.next().unwrap().into_inner(),
macros,
subfolder,
)?;
let if_block = Compiler::parse_tokens(
&mut inner.next().unwrap().into_inner(),
macros,
subfolder,
)?;
let else_block = if let Some(tokens) = inner.next() {
Some(Compiler::parse_tokens(
&mut tokens.into_inner(),
macros,
subfolder,
)?)
} else {
None
};
let if_statement = IfStatement {
condition,
if_block,
else_block,
};
ast.append(&mut Compiler::convert_if(&if_statement, subfolder));
}
Rule::while_loop => {
let mut inner = token.into_inner();
let condition = Compiler::parse_tokens(
&mut inner.next().unwrap().into_inner(),
macros,
subfolder,
)?;
let contents = Compiler::parse_tokens(
&mut inner.next().unwrap().into_inner(),
macros,
subfolder,
)?;
let while_loop = WhileLoop {
condition,
contents,
};
ast.append(&mut Compiler::convert_while(&while_loop, subfolder));
}
Rule::macro_def => {
let mut inner = token.into_inner();
let name = unwrap_name!(inner);
let args = inner
.next()
.unwrap()
.into_inner()
.map(|x| x.as_str().into())
.collect();
let contents = unwrap_name!(inner);
macros.insert(name, Macro { args, contents });
}
Rule::macro_call => {
let mut inner = token.into_inner();
let name: String = unwrap_name!(inner);
let args: Vec<String> = inner
.map(|x| fix_escapes!(x.into_inner().as_str()).into())
.collect();
let macro_def = macros
.get(&name)
.clone()
.expect(&format!("No macro definition found for call of {}", name))
.clone();
let mut expanded = macro_def.expand_to_ast(&args, macros, subfolder)?;
ast.append(&mut expanded);
}
Rule::trustme => {
let mut inner = token.into_inner();
let content = unwrap_name!(inner);
ast.push(Node::TrustMe(content));
}
Rule::EOI => break,
_ => todo!(),
}
}
Ok(ast)
}
}