use std::ops::Index;
use std::{fmt, rc::Rc};
use itertools::Itertools;
use rustc_hash::FxHashSet;
use crate::{block_id::BlockId, item_id::ItemId, type_context::TypeContext};
use super::{
json_format::{FormattedText, JsonFormatComponent},
minecraft_utils::{ScoreboardComparison, ScoreboardOperation, ScoreboardValue},
ObjectRef,
};
#[derive(Debug)]
pub struct Function {
pub id: BlockId,
pub(crate) nodes: Vec<Node>,
pub(crate) return_value: ObjectRef,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FastStore {
pub id: ItemId,
pub value: ScoreboardValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FastStoreFromResult {
pub id: ItemId,
pub command: Box<Node>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BinaryOperation {
pub id: ItemId,
pub lhs: ScoreboardValue,
pub rhs: ScoreboardValue,
pub operation: ScoreboardOperation,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Call {
pub id: BlockId,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Condition {
Compare {
lhs: ScoreboardValue,
rhs: ScoreboardValue,
comparison: ScoreboardComparison,
},
And(Vec<Condition>),
Or(Vec<Condition>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchKind {
PosBranch,
NegBranch,
Both,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Branch {
pub condition: Condition,
pub pos_branch: Box<Node>,
pub neg_branch: Box<Node>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecuteRawComponent {
String(Rc<str>),
ScoreboardValue(ScoreboardValue),
Node(Node),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecuteRaw(pub Vec<ExecuteRawComponent>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WriteMessage {
pub target: WriteTarget,
pub message: FormattedText,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteTarget {
Chat,
Actionbar,
Title,
Subtitle,
}
impl fmt::Display for WriteTarget {
#[allow(clippy::use_debug)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node {
FastStore(FastStore),
FastStoreFromResult(FastStoreFromResult),
BinaryOperation(BinaryOperation),
Call(Call),
Condition(Condition),
Branch(Branch),
Execute(ExecuteRaw),
Write(WriteMessage),
Nop,
}
pub enum VariableAccess<'a> {
Read(&'a ScoreboardValue),
Write(&'a ItemId, Option<i32>),
ReadWrite(&'a ScoreboardValue),
}
pub enum VariableAccessMut<'a> {
Read(&'a mut ScoreboardValue),
Write(&'a mut ItemId, Option<i32>),
ReadWrite(&'a mut ScoreboardValue),
}
macro_rules! make_access_visitor {
(node, $self:ident, $visitor:ident, $fn_name:ident, $VariableAccess:ident) => {
match $self {
Node::BinaryOperation(BinaryOperation {
id,
lhs,
rhs,
operation: _,
}) => {
$visitor($VariableAccess::Write(id, None));
$visitor($VariableAccess::Read(lhs));
$visitor($VariableAccess::Read(rhs));
}
Node::Branch(Branch {
condition,
pos_branch,
neg_branch,
}) => {
pos_branch.$fn_name($visitor);
neg_branch.$fn_name($visitor);
condition.$fn_name($visitor);
}
Node::Call(Call { id: _ }) => {}
Node::Condition(condition) => {
condition.$fn_name($visitor);
}
Node::Execute(ExecuteRaw(components)) => {
for component in components {
match component {
ExecuteRawComponent::ScoreboardValue(value) => {
$visitor($VariableAccess::ReadWrite(value));
}
ExecuteRawComponent::Node(node) => node.$fn_name($visitor),
ExecuteRawComponent::String(_) => {}
}
}
}
Node::FastStore(FastStore { id, value }) => {
let static_val = if let ScoreboardValue::Static(value) = value {
Some(*value)
} else {
None
};
$visitor($VariableAccess::Write(id, static_val));
$visitor($VariableAccess::Read(value));
}
Node::FastStoreFromResult(FastStoreFromResult { id, command }) => {
$visitor($VariableAccess::Write(id, None));
command.$fn_name($visitor);
}
Node::Nop => {}
Node::Write(WriteMessage {
target: _,
message: FormattedText { components },
}) => {
for component in components {
match component {
JsonFormatComponent::Score(value) => {
$visitor($VariableAccess::Read(value));
}
JsonFormatComponent::RawText(_) | JsonFormatComponent::Function(_) => {}
}
}
}
}
};
(condition, $self:ident, $visitor:ident, $fn_name:ident, $VariableAccess:ident) => {
match $self {
Condition::Compare {
lhs,
rhs,
comparison: _,
} => {
$visitor($VariableAccess::Read(lhs));
$visitor($VariableAccess::Read(rhs));
}
Condition::And(conditions) => {
for condition in conditions {
condition.$fn_name($visitor);
}
}
Condition::Or(conditions) => {
for condition in conditions {
condition.$fn_name($visitor);
}
}
}
};
}
impl Function {
pub fn nodes(&self) -> &[Node] {
self.nodes.as_slice()
}
pub fn calls_function(&self, function_id: &BlockId) -> bool {
for node in self.nodes() {
let mut contains_call = false;
node.scan(&mut |inner_node| match inner_node {
Node::Call(Call { id }) if id == function_id => {
contains_call = true;
}
_ => {}
});
if contains_call {
return true;
}
}
false
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
#[doc(hidden)]
pub fn _dummy(id: BlockId, nodes: Vec<Node>) -> Self {
Function {
id,
nodes,
return_value: TypeContext::default().null(),
}
}
}
impl Condition {
pub fn not(&self) -> Condition {
match self {
Condition::Compare {
comparison,
lhs,
rhs,
} => Condition::Compare {
comparison: comparison.invert(),
lhs: *lhs,
rhs: *rhs,
},
Condition::And(conditions) => {
Condition::Or(conditions.iter().map(Condition::not).collect())
}
Condition::Or(conditions) => {
Condition::And(conditions.iter().map(Condition::not).collect())
}
}
}
pub fn is_effect_free(&self) -> bool {
match self {
Condition::And(nested) | Condition::Or(nested) => {
nested.iter().all(Condition::is_effect_free)
}
Condition::Compare { .. } => true,
}
}
pub fn is_simple(&self) -> bool {
matches!(self, Condition::Compare { .. })
}
pub fn variable_reads(&self) -> FxHashSet<ItemId> {
let mut reads = FxHashSet::default();
self.variable_accesses(&mut |access| {
if let VariableAccess::Read(var) | VariableAccess::ReadWrite(var) = access {
if let ScoreboardValue::Scoreboard(item_id) = var {
reads.insert(*item_id);
}
}
});
reads
}
pub fn variable_accesses<F: FnMut(VariableAccess)>(&self, visitor: &mut F) {
make_access_visitor!(condition, self, visitor, variable_accesses, VariableAccess);
}
pub fn variable_accesses_mut<F: FnMut(VariableAccessMut)>(&mut self, visitor: &mut F) {
make_access_visitor!(
condition,
self,
visitor,
variable_accesses_mut,
VariableAccessMut
);
}
}
impl fmt::Display for Condition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Condition::Compare {
lhs,
rhs,
comparison,
} => write!(f, "{lhs} {} {rhs}", comparison.str_value()),
Condition::And(parts) => {
let nested = parts.iter().map(Condition::to_string).join(" and ");
write!(f, "({nested})")
}
Condition::Or(parts) => {
let nested = parts.iter().map(Condition::to_string).join(" or ");
write!(f, "({nested})")
}
}
}
}
impl Node {
pub fn scan<F>(&self, func: &mut F)
where
F: FnMut(&Node),
{
func(self);
match self {
Node::Branch(branch) => {
branch.pos_branch.scan(func);
branch.neg_branch.scan(func);
}
Node::FastStoreFromResult(FastStoreFromResult { command, .. }) => command.scan(func),
Node::Execute(ExecuteRaw(components)) => {
for component in components {
match component {
ExecuteRawComponent::Node(node) => {
node.scan(func);
}
ExecuteRawComponent::ScoreboardValue(_)
| ExecuteRawComponent::String(_) => {}
}
}
}
_ => {}
}
}
pub fn scan_mut<F>(&mut self, func: &mut F)
where
F: FnMut(&mut Node),
{
func(self);
match self {
Node::Branch(branch) => {
branch.pos_branch.scan_mut(func);
branch.neg_branch.scan_mut(func);
}
Node::FastStoreFromResult(FastStoreFromResult { command, .. }) => {
command.scan_mut(func);
}
Node::Execute(ExecuteRaw(components)) => {
for component in components {
match component {
ExecuteRawComponent::Node(node) => {
node.scan_mut(func);
}
ExecuteRawComponent::ScoreboardValue(_)
| ExecuteRawComponent::String(_) => {}
}
}
}
_ => {}
}
}
pub fn is_effect_free<'a, 'b: 'a, T>(&'a self, function_map: &'b T) -> bool
where
T: Index<&'a BlockId, Output = Function>,
{
match self {
Node::Branch(branch) => {
branch.condition.is_effect_free()
&& branch.pos_branch.is_effect_free(function_map)
&& branch.neg_branch.is_effect_free(function_map)
}
Node::Call(Call { id }) => function_map[id]
.nodes
.iter()
.all(|node| node.is_effect_free(function_map)),
Node::Condition(condition) => condition.is_effect_free(),
Node::BinaryOperation(_)
| Node::Execute(_)
| Node::FastStore(_)
| Node::FastStoreFromResult(_)
| Node::Write(_) => false,
Node::Nop => true,
}
}
pub fn has_call(&self) -> bool {
let mut has_call = false;
self.scan(&mut |node| {
if matches!(node, Node::Call(_)) {
has_call = true;
}
});
has_call
}
pub fn variable_accesses<F: FnMut(VariableAccess)>(&self, visitor: &mut F) {
make_access_visitor!(node, self, visitor, variable_accesses, VariableAccess);
}
pub fn variable_accesses_mut<F: FnMut(VariableAccessMut)>(&mut self, visitor: &mut F) {
make_access_visitor!(
node,
self,
visitor,
variable_accesses_mut,
VariableAccessMut
);
}
pub fn writes_to(&self, item_id: ItemId) -> bool {
let mut found_write = false;
self.variable_accesses(&mut |access| match access {
VariableAccess::Write(id, _)
| VariableAccess::ReadWrite(ScoreboardValue::Scoreboard(id))
if *id == item_id =>
{
found_write = true;
}
_ => {}
});
found_write
}
pub fn reads_from(&self, item_id: ItemId) -> bool {
let mut has_read = false;
self.variable_accesses(&mut |access| match access {
VariableAccess::Read(ScoreboardValue::Scoreboard(id))
| VariableAccess::ReadWrite(ScoreboardValue::Scoreboard(id))
if *id == item_id =>
{
has_read = true;
}
_ => {}
});
has_read
}
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let id = self.id.0;
let content = self.nodes.iter().join("\n");
write!(f, "Function {id}:\n{content}\n")
}
}
impl fmt::Display for Node {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Node::BinaryOperation(binop) => write!(
f,
"{} = {} {} {}",
binop.id,
binop.lhs,
binop.operation.str_value(),
binop.rhs
),
Node::Branch(branch) => write!(
f,
"if ({}):\n\t{}\n\t{}",
branch.condition, branch.pos_branch, branch.neg_branch
),
Node::Call(call) => write!(f, "call {}", call.id.0),
Node::Condition(condition) => condition.fmt(f),
Node::Execute(ExecuteRaw(components)) => {
for component in components {
match component {
ExecuteRawComponent::ScoreboardValue(
value @ ScoreboardValue::Scoreboard { .. },
) => write!(f, "${value}")?,
ExecuteRawComponent::ScoreboardValue(ScoreboardValue::Static(value)) => {
write!(f, "{value}")?;
}
ExecuteRawComponent::Node(node) => write!(f, "${{{node}}}")?,
ExecuteRawComponent::String(string) => f.write_str(string)?,
}
}
Ok(())
}
Node::FastStore(FastStore { id, value }) => write!(f, "{id} = {value}"),
Node::FastStoreFromResult(FastStoreFromResult { id, command }) => {
write!(f, "{id} = {command}")
}
Node::Write(WriteMessage { target, message }) => {
write!(f, "write {target}: {message}")
}
Node::Nop => f.write_str("Nop"),
}
}
}