1use crate::condition::{Condition, Constraint, Interval};
5use crate::diagnostic::{Sources, Span};
6use crate::vocabulary::{EventId, VarId, Variable, Vocabulary};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct ExpId(pub u32);
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct FeatureId(pub u32);
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct StoryId(pub u32);
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct SectionId(pub u32);
19
20#[derive(Clone, Copy, Debug, PartialEq)]
23pub enum Shift {
24 By(f64),
25 Same,
26}
27
28impl Shift {
29 pub fn apply(self, var: &Variable, before: Option<&Constraint>) -> Constraint {
31 let before = before.copied().unwrap_or_else(|| var.full());
32 match (self, before, var.full()) {
33 (Shift::By(d), Constraint::Range(r), Constraint::Range(full)) => {
34 Constraint::Range(Interval { lo: r.lo + d, hi: r.hi + d, ..r }.intersect(&full))
35 }
36 _ => before,
37 }
38 }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum Keyword {
43 Given,
44 When,
45 Then,
46 And,
47 But,
48}
49
50impl Keyword {
51 pub fn as_str(self) -> &'static str {
52 match self {
53 Keyword::Given => "Given",
54 Keyword::When => "When",
55 Keyword::Then => "Then",
56 Keyword::And => "And",
57 Keyword::But => "But",
58 }
59 }
60}
61
62#[derive(Clone, Debug)]
65pub struct StepText {
66 pub keyword: Keyword,
67 pub text: String,
68 pub span: Span,
69 pub meaning: Option<Said>,
70}
71
72#[derive(Clone, Copy, Debug)]
74pub enum Said {
75 Fact(VarId, Constraint),
76 Event(EventId),
77 Phrase(PhraseId),
78 Shift(VarId, Shift),
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub struct PhraseId(pub u32);
83
84#[derive(Clone, Debug)]
88pub struct Phrase {
89 pub id: PhraseId,
90 pub name: String,
91 pub key: String,
92 pub say: Option<String>,
95 pub span: Span,
96 pub body: PhraseBody,
97}
98
99#[derive(Clone, Debug)]
100pub enum PhraseBody {
101 Setup(Vec<Fact>),
103 Actions(Vec<Trigger>),
105}
106
107#[derive(Clone, Debug)]
109pub struct Fact {
110 pub var: VarId,
111 pub constraint: Constraint,
112 pub span: Span,
113}
114
115#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
118pub struct Trigger {
119 pub event: Option<EventId>,
120 pub changes: Condition,
121}
122
123impl Trigger {
124 pub fn is_none(&self) -> bool {
126 self.event.is_none() && self.changes.is_unconstrained()
127 }
128
129 pub fn compatible(&self, o: &Trigger) -> bool {
131 self.event == o.event && self.changes.overlaps(&o.changes)
132 }
133
134 pub fn fires(&self, o: &Trigger) -> bool {
136 !o.is_none() && self.event == o.event && self.changes.implies(&o.changes)
137 }
138
139 pub fn may_fire(&self, o: &Trigger) -> bool {
142 !o.is_none() && self.compatible(o) && o.changes.vars().all(|v| self.changes.get(v).is_some())
143 }
144}
145
146#[derive(Clone, Debug)]
148pub struct Expectation {
149 pub id: ExpId,
150 pub feature: FeatureId,
151 pub name: String,
152 pub slug: String,
153 pub description: Vec<String>,
154 pub span: Span,
155 pub given: Condition,
156 pub trigger: Trigger,
157 pub then: Condition,
158 pub shifts: Vec<(VarId, Shift)>,
160 pub overrides: Vec<ExpId>,
162 pub steps: Vec<StepText>,
163 pub hidden: bool,
165}
166
167impl Expectation {
168 pub fn is_invariant(&self) -> bool {
169 self.trigger.is_none()
170 }
171
172 pub fn effect(&self, vocab: &Vocabulary, before: &Condition) -> Condition {
174 let mut out = self.then.clone();
175 for &(v, shift) in &self.shifts {
176 let var = vocab.var(v);
177 out.set(var, shift.apply(var, before.get(v)));
178 }
179 out
180 }
181
182 pub fn outcome(&self, vocab: &Vocabulary) -> Condition {
185 if self.is_invariant() {
186 self.given.and(&self.then)
187 } else {
188 let before = self.given.overlay(&self.trigger.changes);
189 before.overlay(&self.effect(vocab, &before))
190 }
191 }
192
193 pub fn sets(&self) -> impl Iterator<Item = VarId> + '_ {
195 self.then.vars().chain(self.shifts.iter().map(|(v, _)| *v))
196 }
197
198 pub fn when_steps(&self) -> impl Iterator<Item = &StepText> {
199 let mut section = Keyword::Given;
200 self.steps.iter().filter(move |s| {
201 if !matches!(s.keyword, Keyword::And | Keyword::But) {
202 section = s.keyword;
203 }
204 section == Keyword::When
205 })
206 }
207}
208
209#[derive(Clone, Debug)]
212pub struct Section {
213 pub id: SectionId,
214 pub parent: Option<SectionId>,
215 pub name: String,
216 pub slug: String,
218 pub path: String,
220 pub description: Vec<String>,
221 pub setup: Condition,
223 pub setup_steps: Vec<StepText>,
224 pub start: Condition,
226 pub span: Option<Span>,
227 pub children: Vec<SectionId>,
228 pub features: Vec<FeatureId>,
229 pub stories: Vec<StoryId>,
230}
231
232#[derive(Clone, Debug)]
233pub struct Feature {
234 pub id: FeatureId,
235 pub section: SectionId,
236 pub name: String,
237 pub slug: String,
238 pub description: Vec<String>,
239 pub span: Span,
240 pub expectations: Vec<ExpId>,
241}
242
243#[derive(Clone, Debug)]
245pub struct Story {
246 pub id: StoryId,
247 pub section: SectionId,
248 pub name: String,
249 pub slug: String,
250 pub narrative: Vec<String>,
252 pub description: Vec<String>,
254 pub span: Span,
255 pub scenarios: Vec<Scenario>,
256}
257
258#[derive(Clone, Debug)]
259pub struct Scenario {
260 pub name: String,
261 pub slug: String,
262 pub description: Vec<String>,
263 pub span: Span,
264 pub given: Condition,
266 pub given_steps: Vec<StepText>,
267 pub steps: Vec<StoryStep>,
268}
269
270#[derive(Clone, Debug)]
272pub struct StoryStep {
273 pub action: Trigger,
274 pub checks: Vec<Fact>,
275 pub steps: Vec<StepText>,
276 pub span: Span,
277 pub group: usize,
280 pub written: Vec<StepText>,
283}
284
285#[derive(Clone, Debug, Default)]
288pub struct Suite {
289 pub name: String,
290 pub vocabulary: Vocabulary,
291 pub background: Condition,
293 pub background_steps: Vec<StepText>,
294 pub sections: Vec<Section>,
295 pub features: Vec<Feature>,
296 pub expectations: Vec<Expectation>,
297 pub stories: Vec<Story>,
298 pub glossary: Vec<Term>,
300 pub sources: Sources,
301 pub platforms: Vec<String>,
303 pub platform: Option<String>,
306}
307
308#[derive(Clone, Debug)]
310pub struct Term {
311 pub name: String,
312 pub slug: String,
313 pub description: Vec<String>,
314 pub span: Span,
315}
316
317impl Suite {
318 pub fn exp(&self, id: ExpId) -> &Expectation {
319 &self.expectations[id.0 as usize]
320 }
321
322 pub fn feature(&self, id: FeatureId) -> &Feature {
323 &self.features[id.0 as usize]
324 }
325
326 pub fn story(&self, id: StoryId) -> &Story {
327 &self.stories[id.0 as usize]
328 }
329
330 pub fn section(&self, id: SectionId) -> &Section {
331 &self.sections[id.0 as usize]
332 }
333
334 pub fn chain(&self, id: SectionId) -> Vec<&Section> {
336 let mut out = vec![self.section(id)];
337 while let Some(p) = out.last().unwrap().parent {
338 out.push(self.section(p));
339 }
340 out.reverse();
341 out
342 }
343
344 pub fn section_of(&self, e: ExpId) -> &Section {
346 self.section(self.feature(self.exp(e).feature).section)
347 }
348
349 pub fn subtree(&self, id: SectionId) -> Vec<SectionId> {
351 let mut out = vec![id];
352 let mut i = 0;
353 while i < out.len() {
354 out.extend(self.section(out[i]).children.iter().copied());
355 i += 1;
356 }
357 out
358 }
359
360 pub fn describe(&self, c: &Condition) -> String {
361 self.vocabulary.describe(c)
362 }
363
364 pub fn describe_trigger(&self, t: &Trigger) -> String {
366 let mut parts = vec![];
367 if !t.changes.is_unconstrained() {
368 parts.push(self.vocabulary.describe(&t.changes));
369 }
370 if let Some(e) = t.event {
371 parts.push(self.vocabulary.event(e).text.clone());
372 }
373 if parts.is_empty() { "always".into() } else { parts.join(" and ") }
374 }
375
376 pub fn transitions(&self) -> impl Iterator<Item = &Expectation> {
378 self.expectations.iter().filter(|e| !e.is_invariant())
379 }
380
381 pub fn invariants(&self) -> impl Iterator<Item = &Expectation> {
382 self.expectations.iter().filter(|e| e.is_invariant())
383 }
384}