use crate::parser::lex::Token;
use log::{debug, error};
use std::{path::PathBuf, process::exit};
const OPTION_NAMES: [&str; 8] = [
"active_directory",
"case_type",
"case_abbreviations",
"case_exceptions",
"entry_name",
"preprocess_text",
"preprocess_pretty",
"preserve_folders",
];
#[derive(Debug, Clone)]
pub struct RecipeOptions {
pub active_directory: PathBuf,
pub case_type: CaseType,
pub case_abbreviations: bool,
pub case_exceptions: Vec<String>,
pub entry_name: String,
pub preprocess_text: bool,
pub preprocess_pretty: bool,
pub preserve_folders: bool,
pub cc: Option<String>,
pub cflags: Option<String>,
}
#[derive(Debug, Clone)]
pub enum CaseType {
Pascal,
Camel,
Snake,
Kebab,
}
#[derive(Debug, Clone, Copy)]
pub enum ScriptType {
LocalScript,
ServerScript,
ModuleScript,
}
#[derive(Debug, Clone)]
pub enum ChildType {
WaitChild,
FindChild,
CreateChild,
Service,
}
#[derive(Debug, Clone)]
pub struct RecipeAssociation {
pub path: PathBuf,
pub script_type: ScriptType,
}
#[derive(Debug, Clone)]
pub struct RecipePath {
pub path: String,
pub child_type: ChildType,
}
#[derive(Debug, Clone)]
pub struct RecipeEntry {
pub path: PathBuf,
pub target: Vec<RecipePath>,
}
#[derive(Debug, Clone)]
pub struct Recipe {
pub options: RecipeOptions,
pub associations: Vec<RecipeAssociation>,
pub entries: Vec<RecipeEntry>,
}
macro_rules! expect {
($iterator:ident, $token:pat) => {
match $iterator.next() {
Some($token) => {}
Some(token) => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected token {:?} in vakefile", token);
exit(1);
}
_ => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected end of file in vakefile");
exit(1);
}
}
};
}
macro_rules! expect_value {
($iterator:ident, $token_type:ident) => {
match $iterator.next() {
Some(Token::$token_type(name)) => Some(name),
Some(token) => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected token {:?} in vakefile", token);
exit(1);
}
_ => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected end of file in vakefile");
exit(1);
}
}
.unwrap()
};
}
macro_rules! expect_boolean {
($iterator:ident) => {
match $iterator.next() {
Some(Token::True) => true,
Some(Token::False) => false,
Some(token) => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected token {:?} in vakefile", token);
exit(1);
}
_ => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected end of file in vakefile");
exit(1);
}
}
};
}
pub fn init(tokens: Vec<Token>) -> Recipe {
debug!("Parsing vakefile with {} tokens", tokens.len());
let mut iterator = tokens.into_iter().peekable();
let default_options = RecipeOptions {
active_directory: PathBuf::from("src"),
case_type: CaseType::Pascal,
case_abbreviations: false,
case_exceptions: Vec::new(),
entry_name: String::from("main.lua"),
preprocess_text: true,
preprocess_pretty: false,
preserve_folders: false,
cc: Option::None,
cflags: Option::None,
};
let mut recipe = Recipe {
options: default_options,
associations: Vec::new(),
entries: Vec::new(),
};
while let Some(token) = iterator.next() {
match token {
Token::Colon => {
let option = expect_value!(iterator, Identifier);
if !OPTION_NAMES.contains(&option.as_str()) {
error!("Aborted due to malformed vakefile body");
error!("Unexpected option {:?} in vakefile", option);
exit(1);
}
expect!(iterator, Token::Equal);
handle_option(&mut recipe, option, &mut iterator);
}
Token::Identifier(name) => {
let path = build_path(name, &mut iterator);
match iterator.peek() {
Some(Token::Arrow) => handle_entry(&mut recipe, path, &mut iterator),
Some(Token::DoubleColon) => {
handle_association(&mut recipe, path, &mut iterator)
}
_ => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected token {:?} in vakefile", iterator.peek());
exit(1);
}
}
}
Token::Slash | Token::Dot => {
let path = build_path(String::new(), &mut iterator);
match iterator.peek() {
Some(Token::Arrow) => handle_entry(&mut recipe, path, &mut iterator),
Some(Token::DoubleColon) => {
handle_association(&mut recipe, path, &mut iterator)
}
_ => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected token {:?} in vakefile", iterator.peek());
exit(1);
}
}
}
_ => {
error!("Aborted due to malformed vakefile body");
error!("Unexpected token {:?} in vakefile", token);
exit(1);
}
}
}
return recipe;
}
fn handle_option(
recipe: &mut Recipe,
option: String,
iterator: &mut std::iter::Peekable<std::vec::IntoIter<Token>>,
) {
debug!("Parsing option {}", option);
match option.as_str() {
"active_directory" => {
let value = expect_value!(iterator, String);
recipe.options.active_directory = PathBuf::from(value);
}
"case_type" => {
let value = expect_value!(iterator, Identifier);
match value.as_str() {
"pascal" => recipe.options.case_type = CaseType::Pascal,
"camel" => recipe.options.case_type = CaseType::Camel,
"snake" => recipe.options.case_type = CaseType::Snake,
"kebab" => recipe.options.case_type = CaseType::Kebab,
_ => {
error!("Aborted due to malformed vakefile options");
error!("Unexpected case type {:?} in vakefile", value);
exit(1);
}
}
}
"case_abbreviations" => {
let value = expect_boolean!(iterator);
recipe.options.case_abbreviations = value;
}
"case_exceptions" => {
let mut exceptions = Vec::new();
expect!(iterator, Token::LeftBracket);
loop {
match iterator.peek() {
Some(Token::String(name)) => {
exceptions.push(name.clone());
iterator.next();
}
_ => {
expect!(iterator, Token::RightBracket);
break;
}
}
match iterator.peek() {
Some(Token::Comma) => {
iterator.next();
}
Some(Token::RightBracket) => {
iterator.next();
break;
}
_ => {
error!("Aborted due to malformed vakefile options");
error!("Unexpected token {:?} in vakefile", iterator.peek());
exit(1);
}
}
}
recipe.options.case_exceptions = exceptions;
}
"entry_name" => {
let value = expect_value!(iterator, String);
recipe.options.entry_name = value;
}
"preprocess_text" => {
let value = expect_boolean!(iterator);
recipe.options.preprocess_text = value;
}
"preprocess_pretty" => {
let value = expect_boolean!(iterator);
recipe.options.preprocess_pretty = value;
}
"preserve_folders" => {
let value = expect_boolean!(iterator);
recipe.options.preserve_folders = value;
}
"cc" => {
let value = expect_value!(iterator, String);
recipe.options.cc = Some(value);
}
"cflags" => {
let value = expect_value!(iterator, String);
recipe.options.cflags = Some(value);
}
_ => {
error!("Aborted due to malformed vakefile options");
error!("Unexpected option {:?} in vakefile", option);
exit(1);
}
}
}
fn handle_association(
recipe: &mut Recipe,
path: PathBuf,
iterator: &mut std::iter::Peekable<std::vec::IntoIter<Token>>,
) {
iterator.next();
let association = RecipeAssociation {
path: path,
script_type: match expect_value!(iterator, Keyword).as_str() {
"LocalScript" => ScriptType::LocalScript,
"ServerScript" => ScriptType::ServerScript,
"ModuleScript" => ScriptType::ModuleScript,
_ => {
error!("Aborted due to malformed vakefile association");
error!("Unexpected script type {:?} in vakefile", iterator.peek());
exit(1);
}
},
};
recipe.associations.push(association);
}
fn handle_entry(
recipe: &mut Recipe,
path: PathBuf,
iterator: &mut std::iter::Peekable<std::vec::IntoIter<Token>>,
) {
iterator.next();
let entry = RecipeEntry {
path: path,
target: build_tree(iterator),
};
recipe.entries.push(entry);
}
fn build_path(
name: String,
iterator: &mut std::iter::Peekable<std::vec::IntoIter<Token>>,
) -> PathBuf {
let mut path = PathBuf::new();
path.push(name);
loop {
match iterator.peek() {
Some(Token::Arrow) | Some(Token::DoubleColon) => break,
Some(Token::Dot) | Some(Token::Slash) => {
iterator.next();
}
Some(Token::Identifier(name)) => {
path.push(name);
iterator.next();
}
_ => {
error!("Aborted due to malformed vakefile path");
error!("Unexpected token {:?} in vakefile", iterator.peek());
exit(1);
}
}
}
return path;
}
fn build_tree(iterator: &mut std::iter::Peekable<std::vec::IntoIter<Token>>) -> Vec<RecipePath> {
let mut tree: Vec<RecipePath> = vec![RecipePath {
path: expect_value!(iterator, Keyword),
child_type: ChildType::Service,
}];
loop {
match iterator.peek() {
Some(Token::Colon) => {
iterator.next();
tree.push(RecipePath {
path: expect_value!(iterator, Identifier),
child_type: ChildType::WaitChild,
})
}
Some(Token::Bang) => {
iterator.next();
tree.push(RecipePath {
path: expect_value!(iterator, Identifier),
child_type: ChildType::CreateChild,
})
}
Some(Token::Dot) => {
iterator.next();
tree.push(RecipePath {
path: expect_value!(iterator, Identifier),
child_type: ChildType::FindChild,
})
}
_ => break,
}
}
return tree;
}