diff --git a/crates/workshop-rs/src/parser.rs b/crates/workshop-rs/src/parser.rs index 7793a25..6b8e82c 100644 --- a/crates/workshop-rs/src/parser.rs +++ b/crates/workshop-rs/src/parser.rs @@ -1976,7 +1976,7 @@ impl Parser<'_> { )?; let player = self.value()?; self.expect(TokenKind::Comma, "expected ',' after loop player")?; - let (name, _, _) = self.phrase()?; + let (name, target_start, target_end) = self.phrase()?; let variable = self.player_by_name(&name)?; self.expect(TokenKind::Comma, "expected ',' after loop variable")?; let start_value = self.value()?; @@ -2004,6 +2004,7 @@ impl Parser<'_> { step, body, span: Some(Span::new(self.file(), start, end_span.1)), + target_span: Some(Span::new(self.file(), target_start, target_end)), }; Ok(self.target.actions.push(action)) } diff --git a/crates/workshop-rs/src/wir/dump.rs b/crates/workshop-rs/src/wir/dump.rs index 15fc2c1..27dd016 100644 --- a/crates/workshop-rs/src/wir/dump.rs +++ b/crates/workshop-rs/src/wir/dump.rs @@ -295,6 +295,7 @@ fn render_action(program: &Program, id: super::ActionId, out: &mut String, level step, body, span, + .. } => { out.push_str(&format!("{}forPlayerVariable ", indent(level))); render_value(program, *player, out); diff --git a/crates/workshop-rs/src/wir/mod.rs b/crates/workshop-rs/src/wir/mod.rs index c7afd56..531dda9 100644 --- a/crates/workshop-rs/src/wir/mod.rs +++ b/crates/workshop-rs/src/wir/mod.rs @@ -396,6 +396,8 @@ pub enum Action { step: ValueId, body: Vec, span: Option, + /// The exact span of the loop variable identifier. + target_span: Option, }, /// Any other action call with side effects. Call { diff --git a/crates/workshop-rs/tests/action_layout.rs b/crates/workshop-rs/tests/action_layout.rs index 2a078ed..1e967d4 100644 --- a/crates/workshop-rs/tests/action_layout.rs +++ b/crates/workshop-rs/tests/action_layout.rs @@ -68,6 +68,7 @@ fn program_with_structured_actions() -> (wir::Program, Vec) { step: number, body: vec![nested_if_body], span: None, + target_span: None, }); let trailing_leaf = leaf(&mut program, number); let actions = vec![ diff --git a/crates/workshop-rs/tests/parser.rs b/crates/workshop-rs/tests/parser.rs index 419ed4c..449e0e6 100644 --- a/crates/workshop-rs/tests/parser.rs +++ b/crates/workshop-rs/tests/parser.rs @@ -390,6 +390,36 @@ fn spans_are_preserved() { assert!(action.span().is_some()); } +#[test] +fn player_variable_loop_preserves_target_span() { + let text = r#"variables { + player: + 0: I +} + +rule ("r") { + event { + Ongoing - Global; + } + actions { + For Player Variable(Host Player, I, 0, 3, 1); + End; + } +} +"#; + let program = parser::parse(text, &catalog(), &Locale::new("en-US")).unwrap(); + let rule = program.rules.iter().next().unwrap(); + let action = program.actions.get(rule.actions[0]).unwrap(); + let wir::Action::ForPlayerVariable { target_span, .. } = action else { + panic!("expected For Player Variable"); + }; + let target_span = target_span.expect("player loop target span"); + assert_eq!(target_span.start.line, 11); + assert_eq!(target_span.start.col, 42); + assert_eq!(target_span.end.line, 11); + assert_eq!(target_span.end.col, 43); +} + #[test] fn malformed_input_is_reported_as_malformed() { // A rule-final If without `End;` is the oracle's valid spelling; an If