use std::collections::HashSet;
use crate::lang_types::*;
static mut COMPILER_ARRAY: Option<Compiler> = None;
pub(crate) struct Compiler {
pub allow_action: bool,
pub allow_rule: bool,
pub scripts: Vec<String>,
pub active_script: usize,
pub global_variables: HashSet<&'static str>,
pub player_variables: HashSet<&'static str>,
pub subroutines: HashSet<&'static str>,
}
impl Compiler {
pub fn new() -> Compiler {
Compiler {
allow_action: false,
allow_rule: false,
scripts: Vec::new(),
active_script: 0,
global_variables: HashSet::new(),
player_variables: HashSet::new(),
subroutines: HashSet::new(),
}
}
pub fn push_action(&mut self, value: String) {
if !self.allow_action {
panic!("Action is not allowed here!");
}
let script = &mut self.scripts[self.active_script];
script.push_str(&format!("\t\t{};\n", value));
}
}
pub(crate) fn get_compiler() -> &'static mut Compiler {
unsafe {
if COMPILER_ARRAY.is_none() {
COMPILER_ARRAY = Some(Compiler::new());
}
COMPILER_ARRAY.as_mut().unwrap()
}
}
pub struct Script {
id: usize,
name: String,
}
#[doc(hidden)]
pub struct SlotLiteral(u8);
#[allow(non_snake_case)]
pub fn Slot(slot: u8) -> SlotLiteral {
if slot >= 12 {
panic!("Slot {} is out of range!", slot);
}
SlotLiteral(slot)
}
impl Cast<Self> for SlotLiteral {
fn cast(self) -> Self {
self
}
}
impl Compile for SlotLiteral {
fn from_string(_: String) -> Self {
unimplemented!()
}
fn compile(&self) -> &str {
unimplemented!()
}
fn take(self) -> String {
format!("Slot {}", self.0)
}
}
pub struct Everyone;
#[doc(hidden)]
pub trait PlayerSelector {
fn compile(&self) -> String;
}
impl PlayerSelector for HeroLiteral {
fn compile(&self) -> String {
Compile::compile(self).to_string()
}
}
impl PlayerSelector for SlotLiteral {
fn compile(&self) -> String {
Compile::compile(self).to_string()
}
}
impl PlayerSelector for Everyone {
fn compile(&self) -> String {
"All".to_string()
}
}
pub enum Event {
OngoingGlobal(Box<dyn PlayerSelector>),
OngoingPlayer(TeamLiteral, Box<dyn PlayerSelector>),
PlayerEarnedElimination(TeamLiteral, Box<dyn PlayerSelector>),
PlayerDealtFinalBlow(TeamLiteral, Box<dyn PlayerSelector>),
PlayerDealtDamage(TeamLiteral, Box<dyn PlayerSelector>),
PlayerTookDamage(TeamLiteral, Box<dyn PlayerSelector>),
PlayerDied(TeamLiteral, Box<dyn PlayerSelector>),
PlayerDealtHealing(TeamLiteral, Box<dyn PlayerSelector>),
PlayerReceivedHealing(TeamLiteral, Box<dyn PlayerSelector>),
PlayerJoined(TeamLiteral, Box<dyn PlayerSelector>),
PlayerLeft(TeamLiteral, Box<dyn PlayerSelector>),
SubroutineBody(Subroutine, Box<dyn PlayerSelector>),
PlayerDealtKnockback(TeamLiteral, Box<dyn PlayerSelector>),
PlayerReceivedKnockback(TeamLiteral, Box<dyn PlayerSelector>),
}
#[allow(non_snake_case)]
pub fn OngoingGlobal() -> Event {
Event::OngoingGlobal(Box::new(Everyone))
}
#[allow(non_snake_case)]
pub fn OngoingPlayer<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::OngoingPlayer(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerEarnedElimination<S: PlayerSelector + 'static>(
team: TeamLiteral,
selector: S,
) -> Event {
Event::PlayerEarnedElimination(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerDealtFinalBlow<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerDealtFinalBlow(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerDealtDamage<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerDealtDamage(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerTookDamage<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerTookDamage(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerDied<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerDied(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerDealtHealing<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerDealtHealing(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerReceivedHealing<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerReceivedHealing(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerJoined<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerJoined(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerLeft<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerLeft(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn SubroutineBody(subroutine: Subroutine) -> Event {
Event::SubroutineBody(subroutine, Box::new(Everyone))
}
#[allow(non_snake_case)]
pub fn PlayerDealtKnockback<S: PlayerSelector + 'static>(team: TeamLiteral, selector: S) -> Event {
Event::PlayerDealtKnockback(team, Box::new(selector))
}
#[allow(non_snake_case)]
pub fn PlayerReceivedKnockback<S: PlayerSelector + 'static>(
team: TeamLiteral,
selector: S,
) -> Event {
Event::PlayerReceivedKnockback(team, Box::new(selector))
}
impl Event {
pub(crate) fn take(self) -> Vec<String> {
match self {
Event::OngoingGlobal(_) => vec!["Ongoing - Global".to_string()],
Event::OngoingPlayer(t, s) => {
vec!["Ongoing - Each Player".to_string(), t.take(), s.compile()]
}
Event::PlayerEarnedElimination(t, s) => {
vec![
"Player Earned Elimination".to_string(),
t.take(),
s.compile(),
]
}
Event::PlayerDealtFinalBlow(t, s) => {
vec!["Player Dealt Final Blow".to_string(), t.take(), s.compile()]
}
Event::PlayerDealtDamage(t, s) => {
vec!["Player Dealt Damage".to_string(), t.take(), s.compile()]
}
Event::PlayerTookDamage(t, s) => {
vec!["Player Took Damage".to_string(), t.take(), s.compile()]
}
Event::PlayerDied(t, s) => vec!["Player Died".to_string(), t.take(), s.compile()],
Event::PlayerDealtHealing(t, s) => {
vec!["Player Dealt Healing".to_string(), t.take(), s.compile()]
}
Event::PlayerReceivedHealing(t, s) => {
vec!["Player Received Healing".to_string(), t.take(), s.compile()]
}
Event::PlayerJoined(t, s) => {
vec!["Player Joined Match".to_string(), t.take(), s.compile()]
}
Event::PlayerLeft(t, s) => {
vec!["Player Left Match".to_string(), t.take(), s.compile()]
}
Event::SubroutineBody(s, _) => vec!["Subroutine".to_string(), s.take()],
Event::PlayerDealtKnockback(t, s) => {
vec!["Player Dealt Knockback".to_string(), t.take(), s.compile()]
}
Event::PlayerReceivedKnockback(t, s) => {
vec![
"Player Received Knockback".to_string(),
t.take(),
s.compile(),
]
}
}
}
}
pub struct Conditions(Vec<Boolean>);
pub fn conditions<const C: usize>(conditions: [Boolean; C]) -> Conditions {
Conditions(conditions.into())
}
pub struct Rule {
pub name: &'static str,
pub event: Event,
pub conditions: Conditions,
pub actions: fn(),
}
impl Script {
pub fn new_unnamed() -> Self {
let mut compiler = get_compiler();
if compiler.scripts.is_empty() {
compiler.allow_rule = true;
}
let id = compiler.scripts.len();
compiler.scripts.push(String::new());
compiler.active_script = id;
Script {
id,
name: format!("unnamed script {}", id + 1),
}
}
pub fn new(name: &str) -> Self {
let mut compiler = get_compiler();
if compiler.scripts.is_empty() {
compiler.allow_rule = true;
}
let id = compiler.scripts.len();
compiler.scripts.push(String::new());
compiler.active_script = id;
Script {
id,
name: name.to_string(),
}
}
pub fn add(&self, rule: Rule) {
let mut compiler = get_compiler();
if !compiler.allow_rule {
panic!("Invalid attempt to add rule");
}
compiler.allow_rule = false;
compiler.active_script = self.id;
let output = &mut compiler.scripts[compiler.active_script];
output.push_str(&format!("rule(\"{}\")\n{{\n\tevent\n\t{{\n", rule.name));
let event_body = rule.event.take();
for param in event_body {
output.push_str(&format!("\t\t{param};\n"));
}
output.push_str("\t}\n");
if !rule.conditions.0.is_empty() {
output.push_str("\tconditions\n\t{\n");
for condition in rule.conditions.0 {
output.push_str(&format!("\t\t{} == True;\n", condition.take()));
}
output.push_str("\t}\n");
}
output.push_str("\tactions\n\t{\n");
compiler.allow_action = true;
(rule.actions)();
compiler.allow_action = false;
output.push_str("\t}\n}\n");
compiler.allow_rule = true;
}
pub fn generate(&self) -> String {
let compiler = get_compiler();
let rules = compiler.scripts[self.id].as_str();
let mut variables = String::new();
if !compiler.global_variables.is_empty() || !compiler.player_variables.is_empty() {
variables.push_str("variables\n{\n");
if !compiler.global_variables.is_empty() {
variables.push_str("\tglobal:\n");
for (i, v) in compiler.global_variables.iter().enumerate() {
variables.push_str(&format!("\t\t{i}: {v}\n"));
}
}
if !compiler.player_variables.is_empty() {
variables.push_str("\tplayer:\n");
for (i, v) in compiler.player_variables.iter().enumerate() {
variables.push_str(&format!("\t\t{i}: {v}\n"));
}
}
variables.push_str("}\n\n");
}
let mut subroutines = String::new();
if !compiler.subroutines.is_empty() {
subroutines.push_str("subroutines\n{\n");
for (i, v) in compiler.subroutines.iter().enumerate() {
subroutines.push_str(&format!("\t{i}: {v}\n"));
}
subroutines.push_str("}\n\n");
}
let name = &self.name;
let date = chrono::Utc::now()
.format("%Y-%m-%d %H:%M:%S UTC")
.to_string();
format!("// Generated by [{name}] at {date}\n\n{variables}{subroutines}{rules}")
}
}
#[doc(hidden)]
pub fn __register_global_variable__(name: &'static str) {
get_compiler().global_variables.insert(name);
}
#[doc(hidden)]
pub fn __register_player_variable__(name: &'static str) {
get_compiler().player_variables.insert(name);
}
#[doc(hidden)]
pub fn __register_subroutine__(name: &'static str) {
get_compiler().subroutines.insert(name);
}
#[macro_export]
macro_rules! bind_player_variables {
($($name:ident : $type:ty ;)*) => {
mod player_variables {
use super::ExprType::{self, *};
#[allow(non_camel_case_types)]
pub trait PlayerVariableHolder {
$(
fn $name(self) -> Variable<$type>;
)*
}
impl PlayerVariableHolder for Player {
$(
fn $name(self) -> Variable<$type> {
let name = stringify!($name);
super::__register_player_variable__(name);
Variable::__new__(Some(self), name)
}
)*
}
}
pub use player_variables::PlayerVariableHolder;
};
}
#[macro_export]
macro_rules! bind_global_variables {
($name:ident : $type:ident) => {
mod global_variables {
use super::ExprType::{self, *};
#[allow(non_camel_case_types)]
pub trait GlobalVariableHolder {
fn $name() -> Variable<$type>;
}
impl GlobalVariableHolder for Global {
fn $name() -> Variable<$type> {
let name = stringify!($name);
super::__register_global_variable__(name);
Variable::__new__(None, name)
}
}
}
pub use global_variables::GlobalVariableHolder;
};
}
#[macro_export]
macro_rules! bind_subroutines {
($($name:ident ;)*) => {
#[allow(non_camel_case_types)]
pub trait SubroutineHolder {
$(
fn $name() -> Subroutine;
)*
}
impl SubroutineHolder for Subroutine {
$(
fn $name() -> Subroutine {
let name = stringify!($name);
__register_subroutine__(name);
Subroutine::__new__(name)
}
)*
}
};
}
pub trait WriteToFile {
fn write_to_file(&self, path: &str);
}
impl WriteToFile for String {
fn write_to_file(&self, path: &str) {
std::fs::write(path, self)
.expect(format!("Failed to write contents to file {}", path).as_str());
}
}
pub trait CopyToClipboard {
fn copy_to_clipboard(&self);
}
impl CopyToClipboard for String {
fn copy_to_clipboard(&self) {
let mut ctx: clipboard::ClipboardContext =
clipboard::ClipboardProvider::new().expect("Failed to access clipboard");
clipboard::ClipboardProvider::set_contents(&mut ctx, self.clone())
.expect("Failed to copy to clipboard");
}
}