Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ libquickjs-ng-sys = "0.13.0"
serde = "1"
serde_json = "1"
sha2 = "0.10"
workshop-rs = "=0.8.0"
workshop-rs = "=0.9.1"

[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "deny"
Expand Down
43 changes: 1 addition & 42 deletions crates/opy-rs/src/compiler/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4695,7 +4695,7 @@ impl<'a> Lowering<'a> {
)
})?
};
let mut args = self.normalize_catalog_argument_domains(catalog_id, args);
let mut args = self.normalize_contextual_arguments(catalog_id, args);
self.apply_replacements(catalog_id, &mut args, span);
self.optimize_wait_duration(catalog_id, &mut args, span);
Ok(self.push_call_action_with_spans(catalog_id, &args, spans))
Expand Down Expand Up @@ -4729,47 +4729,6 @@ impl<'a> Lowering<'a> {
}
}

fn normalize_catalog_argument_domains(
&mut self,
catalog_id: &str,
mut args: Vec<ValueId>,
) -> Vec<ValueId> {
if self
.compiler
.catalog
.entry(Kind::Action, catalog_id)
.is_none()
{
return args;
}
let mut index = 0;
while index < args.len() {
args[index] = self.normalize_contextual_argument(catalog_id, index, args[index]);
let Some(domain) = self
.compiler
.catalog
.entry(Kind::Action, catalog_id)
.and_then(|entry| entry.param_domain(index))
.map(str::to_string)
else {
index += 1;
continue;
};
let Some(Value::Enum { value_type, value }) = self.values.get(args[index]) else {
index += 1;
continue;
};
if value_type == "Team" && domain == "Color" {
args[index] = self.push_value(Value::Enum {
value_type: domain,
value: value.clone(),
});
}
index += 1;
}
args
}

fn lower_hud_text(
&mut self,
args: &[Expr],
Expand Down
44 changes: 34 additions & 10 deletions crates/opy-rs/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1824,16 +1824,40 @@ rule "assignments":
.emitted
.contains("Modify Player Variable(Event Player, p1, Multiply, 2);")
);
assert!(
artifact
.emitted
.contains("Set Player Variable At Index((Event Player).p2, 2, 7);")
);
assert!(
artifact
.emitted
.contains("Modify Player Variable At Index((Event Player).p2, 0, Subtract, 3);")
);
assert!(artifact.wir.rules[1].actions.iter().any(|action| matches!(
action,
workshop_rs::Action::Call { name, args }
if name == "setPlayerVariableAtIndex"
&& matches!(
args.as_slice(),
[
workshop_rs::Value::PlayerVariable { player, variable },
workshop_rs::Value::Number(index),
workshop_rs::Value::Number(value),
] if matches!(player.as_ref(), workshop_rs::Value::EventPlayer)
&& variable == "p2"
&& *index == 2.0
&& *value == 7.0
)
)));
assert!(artifact.wir.rules[1].actions.iter().any(|action| matches!(
action,
workshop_rs::Action::Call { name, args }
if name == "modifyPlayerVariableAtIndex"
&& matches!(
args.as_slice(),
[
workshop_rs::Value::PlayerVariable { player, variable },
workshop_rs::Value::Number(index),
workshop_rs::Value::Call { name: operation, .. },
workshop_rs::Value::Number(value),
] if matches!(player.as_ref(), workshop_rs::Value::EventPlayer)
&& variable == "p2"
&& *index == 0.0
&& operation == "subtract"
&& *value == 3.0
)
)));

let rule = artifact.wir.rules.get(1).unwrap();
assert!(matches!(
Expand Down
43 changes: 35 additions & 8 deletions crates/opy-rs/src/compiler/tests/callable_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::path::Path;

use crate::Compiler;
use workshop_rs::{Action, Value};

#[test]
fn translation_calls_use_the_existing_translation_helper() {
Expand Down Expand Up @@ -267,19 +268,45 @@ rule "event values":
result = eventWasEnvironment
result = eventWasHealthPack
"#;
let emitted = Compiler::new()
let artifact = Compiler::new()
.unwrap()
.compile_source_artifact(source, "event-values.opy", Path::new("."))
.expect("bare event values and the hero setting must compile")
.emitted;
.expect("bare event values and the hero setting must compile");

assert!(
artifact
.wir
.rules
.iter()
.flat_map(|rule| &rule.actions)
.any(|action| {
matches!(
action,
Action::SetGlobalVariable {
variable,
value: Value::Call { name, .. },
} if variable == "heroSetting" && name == "createWorkshopSettingHero"
)
})
);
let rule = artifact
.wir
.rules
.iter()
.find(|rule| rule.name == "event values")
.expect("event rule must lower");
for expected in [
"Workshop Setting Hero(Custom String(\"cat\"), Custom String(\"hero\"), Ana, 0)",
"Set Global Variable(result, Event Direction);",
"Set Global Variable(result, Event Was Environment);",
"Set Global Variable(result, Event Was Health Pack);",
"eventDirection",
"eventWasEnvironment",
"eventWasHealthPack",
] {
assert!(emitted.contains(expected), "missing {expected}\n{emitted}");
assert!(rule.actions.iter().any(|action| matches!(
action,
Action::SetGlobalVariable {
variable,
value: Value::Call { name, .. },
} if variable == "result" && name == expected
)));
}
}

Expand Down
45 changes: 43 additions & 2 deletions crates/opy-rs/src/compiler/tests/catalog_lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@ use std::path::{Path, PathBuf};
use crate::Compiler;
use workshop_rs::catalog::{Catalog, Locale};
use workshop_rs::roundtrip::equivalent;
use workshop_rs::{Action, Value};

fn collect_calls<'a>(value: &'a Value, name: &str, calls: &mut Vec<&'a [Value]>) {
match value {
Value::Call {
name: call_name,
args,
} => {
if call_name == name {
calls.push(args);
}
for argument in args {
collect_calls(argument, name, calls);
}
}
Value::Array(values) => {
for value in values {
collect_calls(value, name, calls);
}
}
Value::Vector { x, y, z } => {
collect_calls(x, name, calls);
collect_calls(y, name, calls);
collect_calls(z, name, calls);
}
Value::PlayerVariable { player, .. } => collect_calls(player, name, calls),
_ => {}
}
}

fn fixture_dir(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
Expand Down Expand Up @@ -219,8 +248,20 @@ fn pinned_texture_members_lower_with_texture_tag_setup() {
.emitted
.contains("Create Dummy Bot(All Heroes, If-Then-Else(")
);
assert!(artifact.emitted.contains("Number Of Slots(Team(Team 1))"));
assert!(artifact.emitted.contains("Number Of Slots(Team(Team 2))"));
let mut slot_queries = Vec::new();
for action in artifact.wir.rules.iter().flat_map(|rule| &rule.actions) {
if let Action::Call { args, .. } = action {
for value in args {
collect_calls(value, "getNumberOfSlots", &mut slot_queries);
}
}
}
for team in ["TEAM_1", "TEAM_2"] {
assert!(slot_queries.iter().any(|args| matches!(
*args,
[Value::Enum { value_type, value }] if value_type == "Team" && value == team
)));
}
assert!(
artifact
.emitted
Expand Down
72 changes: 62 additions & 10 deletions crates/opy-rs/src/compiler/tests/indexed_assignments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
use crate::Compiler;
use workshop_rs::catalog::{Catalog, Locale};
use workshop_rs::roundtrip::equivalent;
use workshop_rs::{Action, Value};

fn fixture_dir(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
Expand Down Expand Up @@ -79,9 +80,26 @@ rule "delete nested player value":
"#;
let hir = crate::compile(source, "nested-player-delete.opy", Path::new(".")).unwrap();
let artifact = Compiler::new().unwrap().compile_hir(&hir).unwrap();
assert!(artifact.emitted.contains(
"Modify Player Variable At Index((Event Player).values, index, Remove From Array By Index, 0);"
));
let Action::Call { name, args } = &artifact.wir.rules[0].actions[0] else {
panic!("nested deletion must lower to an indexed player-variable action");
};
assert_eq!(name, "modifyPlayerVariableAtIndex");
let [
Value::PlayerVariable { player, variable },
Value::GlobalVariable(outer_index),
Value::Call {
name: operation, ..
},
Value::Number(inner_index),
] = args.as_slice()
else {
panic!("nested deletion must preserve its variable, indices, and operation");
};
assert!(matches!(player.as_ref(), Value::EventPlayer));
assert_eq!(variable, "values");
assert_eq!(outer_index, "index");
assert_eq!(operation, "removeFromArrayByIndex");
assert_eq!(*inner_index, 0.0);
}

#[test]
Expand Down Expand Up @@ -129,13 +147,47 @@ rule "indexed receiver mutations":
let hir = crate::compile(source, "indexed-receiver-mutations.opy", Path::new(".")).unwrap();
let artifact = Compiler::new().unwrap().compile_hir(&hir).unwrap();

assert!(artifact.emitted.contains(
"Modify Global Variable At Index(values, Slot Of(Event Player), Append To Array, Current Map);"
));
assert!(artifact.emitted.contains(
"Modify Player Variable At Index((Event Player).slots, Slot Of(Event Player), Remove From Array, Hero Of(Event Player));"
));
assert_eq!(artifact.emitted.matches("Slot Of(Event Player)").count(), 2);
let calls = artifact
.wir
.rules
.iter()
.flat_map(|rule| rule.actions.iter())
.filter_map(|action| match action {
Action::Call { name, args } => Some((name.as_str(), args.as_slice())),
_ => None,
})
.collect::<Vec<_>>();
assert!(calls.iter().any(|(name, args)| {
*name == "modifyGlobalVariableAtIndex"
&& matches!(
*args,
[
Value::GlobalVariable(variable),
Value::Call { name: index, .. },
Value::Call { name: operation, .. },
Value::Call { name: value, .. },
] if variable == "values"
&& index == "getSlot"
&& operation == "appendToArray"
&& value == "currentMap"
)
}));
assert!(calls.iter().any(|(name, args)| {
*name == "modifyPlayerVariableAtIndex"
&& matches!(
*args,
[
Value::PlayerVariable { player, variable },
Value::Call { name: index, .. },
Value::Call { name: operation, .. },
Value::Call { name: value, .. },
] if matches!(player.as_ref(), Value::EventPlayer)
&& variable == "slots"
&& index == "getSlot"
&& operation == "removeFromArray"
&& value == "getHero"
)
}));

assert_eq!(artifact.wir.action_span(0, 0).unwrap().start.line, 7);
assert_eq!(artifact.wir.action_span(0, 1).unwrap().start.line, 8);
Expand Down
30 changes: 19 additions & 11 deletions crates/opy-rs/src/compiler/tests/numeric_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,36 +180,44 @@ fn find_initializer(program: &crate::hir::Program) -> &Expr {
#[test]
fn omitted_setting_sort_order_lowers_to_zero() {
let compiler = Compiler::new().expect("released Workshop contract must load");
for (call, expected) in [
for (call, function) in [
(
"createWorkshopSettingBool(\"C\", \"N\", true)",
"Workshop Setting Toggle(Custom String(\"C\"), Custom String(\"N\"), True, 0)",
"workshopSettingToggle",
),
(
"createWorkshopSettingInt(\"C\", \"N\", 1, 0, 10)",
"Workshop Setting Integer(Custom String(\"C\"), Custom String(\"N\"), 1, 0, 10, 0)",
"workshopSettingInteger",
),
(
"createWorkshopSettingEnum(\"C\", \"N\", 0, [\"a\", \"b\"])",
"Workshop Setting Combo(Custom String(\"C\"), Custom String(\"N\"), 0, Array(Custom String(\"a\"), Custom String(\"b\")), 0)",
"workshopSettingCombo",
),
(
"createWorkshopSettingHero(\"C\", \"N\", Hero.ANA)",
"Workshop Setting Hero(Custom String(\"C\"), Custom String(\"N\"), Ana, 0)",
"createWorkshopSettingHero",
),
(
"createWorkshopSetting(int[0:10], \"C\", \"N\", 1)",
"Workshop Setting Integer(Custom String(\"C\"), Custom String(\"N\"), 1, 0, 10, 0)",
"workshopSettingInteger",
),
] {
let source = format!("globalvar g\n\nrule \"p\":\n @Event global\n g = {call}\n");
let artifact = compiler
.compile_source_with_locale(&source, "sort.opy", Path::new("."), &Locale::new("en-US"))
.unwrap_or_else(|error| panic!("{call} must compile: {error}"));
assert!(
artifact.emitted.contains(expected),
"{call} emitted:\n{}",
artifact.emitted
);
let setting = artifact
.wir
.rules
.iter()
.flat_map(|rule| rule.actions.iter())
.flat_map(action_values)
.find_map(|value| find_call(value, function))
.unwrap_or_else(|| panic!("{call} must lower to its Workshop setting call"));
let Value::Call { name, args } = setting else {
unreachable!("the search above only returns call values");
};
assert_eq!(name, function);
assert!(matches!(args.last(), Some(Value::Number(sort_order)) if *sort_order == 0.0));
}
}
Loading
Loading