use crate::output::emitter::*;
impl EmitContext<'_> {
pub(crate) fn action(
&mut self,
id: wir::ActionId,
level: usize,
rule_final: bool,
) -> Result<()> {
let Some(action) = self.program.actions.get(id) else {
return Err(WorkshopError::malformed(
format!("dangling action {id}"),
None,
));
};
match action {
wir::Action::SetGlobalVariable {
variable, value, ..
} => {
let name = self.global_name(*variable)?;
let value_text = self.value_text(*value)?;
let keyword = self.spelling(Kind::Structural, "setGlobalVariable")?;
self.line(level, &format!("{keyword}({name}, {value_text});"))?;
}
wir::Action::ModifyGlobalVariable {
variable,
op,
value,
..
} => {
let name = self.global_name(*variable)?;
let op = self.modify_op_spelling(*op)?;
let value_text = self.value_text(*value)?;
let keyword = self.spelling(Kind::Structural, "modifyGlobalVariable")?;
self.line(level, &format!("{keyword}({name}, {op}, {value_text});"))?;
}
wir::Action::SetPlayerVariable {
player,
variable,
value,
..
} => {
let player_text = self.value_text(*player)?;
let name = self.player_name(*variable)?;
let value_text = self.value_text(*value)?;
let keyword = self.spelling(Kind::Structural, "setPlayerVariable")?;
self.line(
level,
&format!("{keyword}({player_text}, {name}, {value_text});"),
)?;
}
wir::Action::ModifyPlayerVariable {
player,
variable,
op,
value,
..
} => {
let player_text = self.value_text(*player)?;
let name = self.player_name(*variable)?;
let op = self.modify_op_spelling(*op)?;
let value_text = self.value_text(*value)?;
let keyword = self.spelling(Kind::Structural, "modifyPlayerVariable")?;
self.line(
level,
&format!("{keyword}({player_text}, {name}, {op}, {value_text});"),
)?;
}
wir::Action::CallSubroutine { subroutine, .. } => {
let name = self
.program
.subroutines
.get(*subroutine)
.map(|s| s.name.as_str())
.ok_or_else(|| {
WorkshopError::unknown(
"subroutine",
format!("<{subroutine}>"),
self.locale.clone(),
None,
)
.with_candidates(crate::core::suggest::nearest(
&subroutine.to_string(),
self.program.subroutines.iter().map(|s| s.name.as_str()),
crate::core::suggest::CANDIDATE_LIMIT,
))
})?;
let keyword = self.spelling(Kind::Structural, "callSubroutine")?;
self.line(level, &format!("{keyword}({name});"))?;
}
wir::Action::If {
branches,
else_body,
..
} => {
for (index, branch) in branches.iter().enumerate() {
let condition = self.value_text(branch.condition)?;
let keyword =
self.spelling(Kind::Structural, if index == 0 { "if" } else { "elseIf" })?;
self.line(level, &format!("{keyword}({condition});"))?;
for action in &branch.body {
self.action(*action, level + 1, false)?;
}
}
if let Some(else_body) = else_body {
let keyword = self.spelling(Kind::Structural, "else")?;
self.line(level, &format!("{keyword};"))?;
for action in else_body {
self.action(*action, level + 1, false)?;
}
}
if !rule_final {
let keyword = self.spelling(Kind::Structural, "end")?;
self.line(level, &format!("{keyword};"))?;
}
}
wir::Action::While {
condition, body, ..
} => {
let text = self.value_text(*condition)?;
let keyword = self.spelling(Kind::Structural, "while")?;
self.line(level, &format!("{keyword}({text});"))?;
for action in body {
self.action(*action, level + 1, false)?;
}
let end = self.spelling(Kind::Structural, "end")?;
self.line(level, &format!("{end};"))?;
}
wir::Action::ForGlobalVariable {
variable,
start,
stop,
step,
body,
..
} => {
let name = self.global_name(*variable)?;
let mut start_text = String::new();
let mut stop_text = String::new();
let mut step_text = String::new();
self.value(*start, &mut start_text)?;
self.value(*stop, &mut stop_text)?;
self.value(*step, &mut step_text)?;
let keyword = self.spelling(Kind::Structural, "forGlobalVariable")?;
self.line(
level,
&format!("{keyword}({name}, {start_text}, {stop_text}, {step_text});"),
)?;
for action in body {
self.action(*action, level + 1, false)?;
}
let end = self.spelling(Kind::Structural, "end")?;
self.line(level, &format!("{end};"))?;
}
wir::Action::ForPlayerVariable {
player,
variable,
start,
stop,
step,
body,
..
} => {
let keyword = self.structural("forPlayerVariable")?;
let mut player_text = String::new();
let mut start_text = String::new();
let mut stop_text = String::new();
let mut step_text = String::new();
self.value(*player, &mut player_text)?;
self.value(*start, &mut start_text)?;
self.value(*stop, &mut stop_text)?;
self.value(*step, &mut step_text)?;
let name = self.player_name(*variable)?;
self.line(
level,
&format!(
"{keyword}({player_text}, {name}, {start_text}, {stop_text}, {step_text});"
),
)?;
for action in body {
self.action(*action, level + 1, false)?;
}
let end = self.spelling(Kind::Structural, "end")?;
self.line(level, &format!("{end};"))?;
}
wir::Action::AssignMember {
target, op, value, ..
} => {
let mut target_text = String::new();
let mut value_text = String::new();
self.value(*target, &mut target_text)?;
self.value(*value, &mut value_text)?;
let operator = match op {
None => "=".to_string(),
Some(op) => {
let token = match op {
wir::ModifyOp::Add => "+",
wir::ModifyOp::Subtract => "-",
wir::ModifyOp::Multiply => "*",
wir::ModifyOp::Divide => "/",
wir::ModifyOp::Modulo => "%",
wir::ModifyOp::Min => "min",
wir::ModifyOp::Max => "max",
_ => {
return Err(WorkshopError::unsupported(
format!("unsupported member assignment operator {op:?}"),
None,
));
}
};
format!("{token}=")
}
};
self.line(level, &format!("{target_text} {operator} {value_text};"))?;
}
wir::Action::Disabled { action, .. } => {
let start = self.out.len();
self.action(*action, level, rule_final)?;
let modifier = format!("{} ", self.structural("disabled")?);
self.out.insert_str(start + level * 4, &modifier);
}
wir::Action::Call { name, args, .. } => {
if matches!(name.as_str(), "chaseAtRate" | "chaseOverTime") {
let player_var = args.first().and_then(|id| {
self.program
.values
.get(*id)
.and_then(|node| match &node.value {
wir::Value::PlayerVariable { player, variable } => {
Some((*player, *variable))
}
_ => None,
})
});
let spelling = if let Some((player, variable)) = player_var {
let id = if name == "chaseAtRate" {
"chasePlayerVariableAtRate"
} else {
"chasePlayerVariableOverTime"
};
let spelling = self.spelling(Kind::Action, id)?;
let text = self.value_text(player)?;
let mut parts = vec![text, self.player_name(variable)?.to_string()];
for arg in args.iter().skip(1) {
let part = self.value_text(*arg)?;
parts.push(part);
}
return self.line(level, &format!("{spelling}({});", parts.join(", ")));
} else {
self.spelling(Kind::Action, name)?
};
let mut args_text = String::new();
self.args(args, &mut args_text)?;
return self.line(level, &format!("{spelling}({args_text});"));
}
if name == "stopChasingPlayerVariable" {
let Some((player, variable)) = args.first().and_then(|id| {
self.program
.values
.get(*id)
.and_then(|node| match &node.value {
wir::Value::PlayerVariable { player, variable } => {
Some((*player, *variable))
}
_ => None,
})
}) else {
return Err(WorkshopError::malformed(
"Stop Chasing Player Variable requires a player variable",
None,
));
};
let spelling = self.spelling(Kind::Action, name)?;
let player_text = self.value_text(player)?;
return self.line(
level,
&format!(
"{spelling}({player_text}, {});",
self.player_name(variable)?
),
);
}
let canonical = match name.as_str() {
"createBeam" => Some("createBeamEffect"),
_ => None,
};
let spelling = if let Some(canonical) = canonical {
self.spelling(Kind::Action, canonical)?
} else {
self.spelling(Kind::Action, name)?
};
if args.is_empty() {
self.line(level, &format!("{spelling};"))?;
} else {
let player_pair_merged = matches!(
name.as_str(),
"setPlayerVariableAtIndex" | "modifyPlayerVariableAtIndex"
) && args
.first()
.and_then(|id| self.program.values.get(*id))
.is_some_and(|node| {
matches!(node.value, wir::Value::PlayerVariable { .. })
});
let mut args_text = String::new();
for (index, arg) in args.iter().enumerate() {
if index > 0 {
args_text.push_str(", ");
}
if index == 0
&& player_pair_merged
&& let Some(wir::Value::PlayerVariable { player, variable }) =
self.program.values.get(*arg).map(|node| &node.value)
{
self.emit_variable_pair(*player, *variable, &mut args_text)?;
continue;
}
let variable_position = match name.as_str() {
"setGlobalVariableAtIndex" | "modifyGlobalVariableAtIndex" => {
index == 0
}
"setPlayerVariableAtIndex" | "modifyPlayerVariableAtIndex" => {
index == 1 && !player_pair_merged
}
_ => false,
};
if variable_position && let Some(node) = self.program.values.get(*arg) {
match &node.value {
wir::Value::GlobalVariable(variable) => {
args_text.push_str(self.global_name(*variable)?);
continue;
}
wir::Value::PlayerVariable { variable, .. } => {
args_text.push_str(self.player_name(*variable)?);
continue;
}
_ => {}
}
}
let operation_name = if matches!(
name.as_str(),
"modifyGlobalVariableAtIndex" | "modifyPlayerVariableAtIndex"
) && index == 2
{
self.program
.values
.get(*arg)
.and_then(|node| match &node.value {
wir::Value::Call { name, args } if args.is_empty() => {
Some(name.clone())
}
_ => None,
})
} else {
None
};
if let Some(operation_name) = operation_name
&& self
.catalog
.entry(Kind::Operator, &operation_name)
.is_some()
{
args_text.push_str(self.spelling(Kind::Operator, &operation_name)?);
continue;
}
self.value(*arg, &mut args_text)?;
}
self.line(level, &format!("{spelling}({args_text});"))?;
}
}
}
Ok(())
}
fn emit_variable_pair(
&mut self,
player: wir::ValueId,
variable: wir::PlayerVarId,
out: &mut String,
) -> Result<()> {
self.value(player, out)?;
out.push_str(", ");
out.push_str(self.player_name(variable)?);
Ok(())
}
pub(crate) fn args(&mut self, args: &[wir::ValueId], out: &mut String) -> Result<()> {
for (index, arg) in args.iter().enumerate() {
if index > 0 {
out.push_str(", ");
}
self.value(*arg, out)?;
}
Ok(())
}
}