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
2 changes: 1 addition & 1 deletion crates/workshop-rs-cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ fn locales_lists_declared_locales_with_coverage() {
assert_eq!(lines.len(), 2);
for (line, (locale, expected)) in lines
.iter()
.zip([("en-us", None), ("zh-cn", Some(("1240", "1259")))])
.zip([("en-us", None), ("zh-cn", Some(("1242", "1261")))])
{
let (reported_locale, coverage) = line.split_once(' ').expect("locale coverage line");
let (mapped, total) = coverage.split_once('/').expect("mapped/total coverage");
Expand Down
16 changes: 15 additions & 1 deletion crates/workshop-rs/src/catalog/data/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -3992,7 +3992,7 @@
]
}
],
"digest": "174a4ebc3f1250485ea55605ebb885016d84897368f65f1ad3818f9789728060",
"digest": "e3a0c15bd30fd59ec933114bccce5feaeb5c8a77bb34497b185e5a36dab41d34",
"enums": [
{
"domain": "Impulse",
Expand Down Expand Up @@ -9658,6 +9658,20 @@
},
"id": "modulo"
},
{
"aliases": {
"en-US": "Min",
"zh-CN": "较小"
},
"id": "min"
},
{
"aliases": {
"en-US": "Max",
"zh-CN": "较大"
},
"id": "max"
},
{
"aliases": {
"en-US": "Raise To Power",
Expand Down
2 changes: 2 additions & 0 deletions crates/workshop-rs/src/element_count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,8 @@ fn is_canonical_helper(name: &str) -> bool {
| "multiply"
| "divide"
| "modulo"
| "min"
| "max"
| "raiseToPower"
| "appendToArray"
| "removeFromArray"
Expand Down
15 changes: 3 additions & 12 deletions crates/workshop-rs/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,8 @@ impl Emitter<'_> {
wir::ModifyOp::Multiply => "*",
wir::ModifyOp::Divide => "/",
wir::ModifyOp::Modulo => "%",
wir::ModifyOp::Min => "min",
wir::ModifyOp::Max => "max",
_ => {
return Err(WorkshopError::Unsupported {
message: format!(
Expand Down Expand Up @@ -1462,18 +1464,7 @@ impl Emitter<'_> {
/// The localized spelling of a modify operator, resolved through the
/// catalog (fallback-aware).
fn modify_op_spelling(&mut self, op: wir::ModifyOp) -> Result<String> {
let id = match op {
wir::ModifyOp::Add => "add",
wir::ModifyOp::Subtract => "subtract",
wir::ModifyOp::Multiply => "multiply",
wir::ModifyOp::Divide => "divide",
wir::ModifyOp::Modulo => "modulo",
wir::ModifyOp::RaiseToPower => "raiseToPower",
wir::ModifyOp::AppendToArray => "appendToArray",
wir::ModifyOp::RemoveFromArray => "removeFromArray",
wir::ModifyOp::RemoveFromArrayByIndex => "removeFromArrayByIndex",
};
self.spelling(Kind::Operator, id)
self.spelling(Kind::Operator, op.catalog_id())
}

/// The localized spelling of a canonical builtin id, resolving through
Expand Down
151 changes: 115 additions & 36 deletions crates/workshop-rs/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1376,7 +1376,7 @@ impl Parser<'_> {
TokenKind::RBracket,
"expected ']' after global variable index",
)?;
let _operator = self.assignment_operator().ok_or_else(|| {
let operator = self.assignment_operator()?.ok_or_else(|| {
self.malformed(
"expected assignment after global variable index",
self.peek().as_ref().unwrap_or(self.eof()),
Expand All @@ -1389,13 +1389,11 @@ impl Parser<'_> {
));
let value = self.value()?;
self.expect(TokenKind::Semi, "expected ';' after indexed assignment")?;
return Ok(Some(self.target.actions.push(Action::Call {
name: "setGlobalVariableAtIndex".to_string(),
args: vec![target, index, value],
span: Some(Span::new(self.file(), start, self.previous_span().1)),
})));
return Ok(Some(self.indexed_assignment_action(
true, target, index, operator, value, start,
)));
}
let Some(operator) = self.assignment_operator() else {
let Some(operator) = self.assignment_operator()? else {
return self.member_assignment_action(saved, start);
};
let variable = self.global_by_name(&name)?;
Expand Down Expand Up @@ -1463,7 +1461,7 @@ impl Parser<'_> {
TokenKind::RBracket,
"expected ']' after player variable index",
)?;
let _operator = self.assignment_operator().ok_or_else(|| {
let operator = self.assignment_operator()?.ok_or_else(|| {
self.malformed(
"expected assignment after player variable index",
self.peek().as_ref().unwrap_or(self.eof()),
Expand All @@ -1478,13 +1476,16 @@ impl Parser<'_> {
},
Some(Span::new(self.file(), target_start, target_end)),
));
return Ok(Some(self.target.actions.push(Action::Call {
name: "setPlayerVariableAtIndex".to_string(),
args: vec![variable_value, index, value],
span: Some(Span::new(self.file(), start, self.previous_span().1)),
})));
return Ok(Some(self.indexed_assignment_action(
false,
variable_value,
index,
operator,
value,
start,
)));
}
let Some(operator) = self.assignment_operator() else {
let Some(operator) = self.assignment_operator()? else {
return self.member_assignment_action(saved, start);
};
let value = self.value()?;
Expand Down Expand Up @@ -1520,7 +1521,7 @@ impl Parser<'_> {
return Ok(None);
}
let target = self.value()?;
let Some(operator) = self.assignment_operator() else {
let Some(operator) = self.assignment_operator()? else {
self.pos = saved;
return Ok(None);
};
Expand All @@ -1538,29 +1539,103 @@ impl Parser<'_> {
})))
}

fn assignment_operator(&mut self) -> Option<AssignmentOperator> {
let operator = match self.peek()?.kind {
TokenKind::Op(operator) => operator,
_ => return None,
fn indexed_assignment_action(
&mut self,
global: bool,
variable: wir::ValueId,
index: wir::ValueId,
operator: AssignmentOperator,
value: wir::ValueId,
start: Position,
) -> wir::ActionId {
let (name, args) = match operator {
AssignmentOperator::Set => (
if global {
"setGlobalVariableAtIndex"
} else {
"setPlayerVariableAtIndex"
},
vec![variable, index, value],
),
AssignmentOperator::Modify(op) => (
if global {
"modifyGlobalVariableAtIndex"
} else {
"modifyPlayerVariableAtIndex"
},
vec![
variable,
index,
self.target.values.push(ValueNode::new(
Value::Call {
name: op.catalog_id().to_string(),
args: Vec::new(),
},
None,
)),
value,
],
),
};
self.target.actions.push(Action::Call {
name: name.to_string(),
args,
span: Some(Span::new(self.file(), start, self.previous_span().1)),
})
}

fn assignment_operator(&mut self) -> Result<Option<AssignmentOperator>> {
let Some(token) = self.peek() else {
return Ok(None);
};
if let TokenKind::Word(word) = &token.kind {
let op = match word.as_str() {
"min" => ModifyOp::Min,
"max" => ModifyOp::Max,
_ => {
if matches!(self.peek_at(1).map(|token| token.kind), Some(TokenKind::Op(equal)) if equal == "=")
{
return Err(WorkshopError::Unsupported {
message: format!("unsupported assignment operator '{word}='"),
span: Some(Span::new(
self.file(),
token.start,
self.peek_at(1).unwrap().end,
)),
});
}
return Ok(None);
}
};
if !matches!(self.peek_at(1).map(|token| token.kind), Some(TokenKind::Op(equal)) if equal == "=")
{
return Ok(None);
}
self.pos += 2;
return Ok(Some(AssignmentOperator::Modify(op)));
}
let operator = match &token.kind {
TokenKind::Op(operator) => operator.clone(),
_ => return Ok(None),
};
if operator == "=" {
self.pos += 1;
return Some(AssignmentOperator::Set);
return Ok(Some(AssignmentOperator::Set));
}
let op = match operator.as_str() {
"+" => ModifyOp::Add,
"-" => ModifyOp::Subtract,
"*" => ModifyOp::Multiply,
"/" => ModifyOp::Divide,
"%" => ModifyOp::Modulo,
_ => return None,
_ => return Ok(None),
};
if !matches!(self.peek_at(1).map(|token| token.kind), Some(TokenKind::Op(equal)) if equal == "=")
{
return None;
return Ok(None);
}
self.pos += 2;
Some(AssignmentOperator::Modify(op))
Ok(Some(AssignmentOperator::Modify(op)))
}

fn opaque_action(&mut self) -> Result<wir::ActionId> {
Expand Down Expand Up @@ -2015,6 +2090,8 @@ impl Parser<'_> {
"multiply" => ModifyOp::Multiply,
"divide" => ModifyOp::Divide,
"modulo" => ModifyOp::Modulo,
"min" => ModifyOp::Min,
"max" => ModifyOp::Max,
"raiseToPower" => ModifyOp::RaiseToPower,
"appendToArray" => ModifyOp::AppendToArray,
"removeFromArray" | "removeFromArrayByValue" => ModifyOp::RemoveFromArray,
Expand Down Expand Up @@ -2911,17 +2988,7 @@ impl Parser<'_> {
is_operator
} {
let operator = self.modify_op()?;
let name = match operator {
ModifyOp::Add => "add",
ModifyOp::Subtract => "subtract",
ModifyOp::Multiply => "multiply",
ModifyOp::Divide => "divide",
ModifyOp::Modulo => "modulo",
ModifyOp::RaiseToPower => "raiseToPower",
ModifyOp::AppendToArray => "appendToArray",
ModifyOp::RemoveFromArray => "removeFromArray",
ModifyOp::RemoveFromArrayByIndex => "removeFromArrayByIndex",
};
let name = operator.catalog_id();
args.push(self.target.values.push(ValueNode::new(
Value::Call {
name: name.to_string(),
Expand Down Expand Up @@ -3104,10 +3171,16 @@ impl Parser<'_> {
}

fn line_has_assignment(&self) -> bool {
self.tokens[self.pos..]
let tokens: Vec<_> = self.tokens[self.pos..]
.iter()
.take_while(|token| !matches!(token.kind, TokenKind::Semi | TokenKind::RBrace))
.any(|token| matches!(&token.kind, TokenKind::Op(op) if matches!(op.as_str(), "=" | "+=" | "-=" | "*=" | "/=" | "%=")))
.collect();
tokens.iter().any(|token| {
matches!(&token.kind, TokenKind::Op(op) if matches!(op.as_str(), "=" | "+=" | "-=" | "*=" | "/=" | "%="))
}) || tokens.windows(2).any(|window| {
matches!(&window[0].kind, TokenKind::Word(_))
&& matches!(&window[1].kind, TokenKind::Op(op) if op == "=")
})
}

fn push_bool(&mut self, value: bool, start: Position, end: Position) -> wir::ValueId {
Expand Down Expand Up @@ -3204,6 +3277,12 @@ impl Parser<'_> {
end: word_end,
..
} => {
if matches!(
self.peek_at(1).map(|token| token.kind),
Some(TokenKind::Op(equal)) if equal == "="
) {
break;
}
words.push(word.clone());
end = word_end;
self.pos += 1;
Expand Down
2 changes: 2 additions & 0 deletions crates/workshop-rs/src/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ fn inspect_value(
| "multiply"
| "divide"
| "modulo"
| "min"
| "max"
| "raiseToPower"
| "appendToArray"
| "removeFromArray"
Expand Down
4 changes: 4 additions & 0 deletions crates/workshop-rs/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ fn validate_value(
| "multiply"
| "divide"
| "modulo"
| "min"
| "max"
| "raiseToPower"
| "appendToArray"
| "removeFromArray"
Expand Down Expand Up @@ -476,6 +478,8 @@ fn value_matches_single_type(catalog: &Catalog, value: &wir::Value, expected: &s
| "multiply"
| "divide"
| "modulo"
| "min"
| "max"
| "raiseToPower"
| "appendToArray"
| "removeFromArray"
Expand Down
2 changes: 2 additions & 0 deletions crates/workshop-rs/src/wir/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ fn render_action(program: &Program, id: super::ActionId, out: &mut String, level
Some(ModifyOp::Multiply) => " *= ",
Some(ModifyOp::Divide) => " /= ",
Some(ModifyOp::Modulo) => " %= ",
Some(ModifyOp::Min) => " min= ",
Some(ModifyOp::Max) => " max= ",
Some(_) => " <unsupported> ",
});
render_value(program, *value, out);
Expand Down
21 changes: 21 additions & 0 deletions crates/workshop-rs/src/wir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ pub enum ModifyOp {
Multiply,
Divide,
Modulo,
Min,
Max,
RaiseToPower,
AppendToArray,
RemoveFromArray,
Expand All @@ -465,10 +467,29 @@ impl ModifyOp {
ModifyOp::Multiply => "Multiply",
ModifyOp::Divide => "Divide",
ModifyOp::Modulo => "Modulo",
ModifyOp::Min => "Min",
ModifyOp::Max => "Max",
ModifyOp::RaiseToPower => "RaiseToPower",
ModifyOp::AppendToArray => "AppendToArray",
ModifyOp::RemoveFromArray => "RemoveFromArray",
ModifyOp::RemoveFromArrayByIndex => "RemoveFromArrayByIndex",
}
}

/// The canonical catalog identity for this modification operation.
pub fn catalog_id(self) -> &'static str {
match self {
ModifyOp::Add => "add",
ModifyOp::Subtract => "subtract",
ModifyOp::Multiply => "multiply",
ModifyOp::Divide => "divide",
ModifyOp::Modulo => "modulo",
ModifyOp::Min => "min",
ModifyOp::Max => "max",
ModifyOp::RaiseToPower => "raiseToPower",
ModifyOp::AppendToArray => "appendToArray",
ModifyOp::RemoveFromArray => "removeFromArray",
ModifyOp::RemoveFromArrayByIndex => "removeFromArrayByIndex",
}
}
}
Loading
Loading