in cli/src/generate/properties.rs [245:403]
fn populate_state(&mut self, item_set: ItemSet<'a>, state_id: StateId) {
let is_start_state = state_id == 0;
let mut transitions: HashMap<PropertyTransitionJSON, u32> = HashMap::new();
let mut selector_matches = Vec::new();
// First, compute all of the possible state transition conditions for
// this state, and all of the rules that are currently matching.
for item in item_set.0.iter().chain(self.start_item_set.0.iter()) {
// If this item has more elements remaining in its selector, then
// add a state transition based on the next step.
if let Some(step) = item.next_step() {
transitions
.entry(PropertyTransitionJSON {
kind: step.kind.clone(),
field: step.field.clone(),
named: step.is_named,
index: step.child_index,
text: step.text_pattern.clone(),
state_id: 0,
})
.and_modify(|rule_id| {
if item.rule_id > *rule_id {
*rule_id = item.rule_id;
}
})
.or_insert(item.rule_id);
}
// If the item has matched its entire selector, then the item's
// properties are applicable to this state.
else {
selector_matches.push(SelectorMatch {
rule_id: item.rule_id,
specificity: selector_specificity(item.selector),
});
}
}
// Compute the merged properties that apply in the current state.
// Sort the matching property sets by ascending specificity and by
// their order in the sheet. This way, more specific selectors and later
// rules will override less specific selectors and earlier rules.
let mut properties = PropertySet::new();
selector_matches.sort_unstable_by(|a, b| {
(a.specificity.cmp(&b.specificity)).then_with(|| a.rule_id.cmp(&b.rule_id))
});
selector_matches.dedup();
for selector_match in selector_matches {
let rule = &self.rules[selector_match.rule_id as usize];
for (property, value) in &rule.properties {
properties.insert(property.clone(), value.clone());
}
}
self.output.states[state_id].property_set_id = self.add_property_set(properties);
// If there are multiple transitions that could *both* match (e.g. one based on a
// a node type and one based on a field name), then create an additional transition
// for the intersection of the two.
let mut i = 0;
let mut transition_list = transitions.into_iter().collect::<Vec<_>>();
while i < transition_list.len() {
for j in 0..i {
if let Some(intersection) =
self.intersect_transitions(&transition_list[j].0, &transition_list[i].0)
{
transition_list.push((
intersection,
u32::max(transition_list[i].1, transition_list[j].1),
));
}
}
i += 1;
}
// Ensure that for a given node type, more specific transitions are tried
// first, and in the event of a tie, transitions corresponding to later rules
// in the cascade are tried first. Also, sort the non-intersecting transitions
// by name to guarantee a deterministic order.
transition_list.sort_by(|a, b| {
(transition_specificity(&b.0).cmp(&transition_specificity(&a.0)))
.then_with(|| b.1.cmp(&a.1))
.then_with(|| a.0.kind.cmp(&b.0.kind))
.then_with(|| a.0.named.cmp(&b.0.named))
.then_with(|| a.0.field.cmp(&b.0.field))
});
// For eacy possible state transition, compute the set of items in that transition's
// destination state.
i = 0;
while i < transition_list.len() {
let transition = &mut transition_list[i].0;
let transition_is_leaf = transition.named == Some(false)
|| transition
.kind
.as_ref()
.map_or(false, |kind| self.token_names.contains(kind));
let mut next_item_set = ItemSet::new();
let mut transition_differs_from_start_state = false;
for item in item_set.0.iter().chain(self.start_item_set.0.iter()) {
if let Some(next_step) = item.next_step() {
// If the next step of the item's selector satisfies this transition,
// advance the item to the next part of its selector and add the
// resulting item to this transition's destination state.
if step_matches_transition(next_step, transition) {
let next_item = Item {
rule_id: item.rule_id,
selector: item.selector,
step_id: item.step_id + 1,
};
if !transition_is_leaf || next_item.is_done() {
next_item_set.insert(next_item);
if item.step_id > 0 {
transition_differs_from_start_state = true;
}
}
}
// If the next step of the item is not an immediate child, then
// include this item in this transition's destination state, because
// the next step of the item might match a descendant node.
if !transition_is_leaf && !next_step.is_immediate && item.step_id > 0 {
next_item_set.insert(*item);
transition_differs_from_start_state = true;
}
}
}
if (is_start_state || transition_differs_from_start_state)
&& !next_item_set.0.is_empty()
{
transition.state_id = self.add_state(next_item_set);
if is_start_state || !self.output.states[0].transitions.contains(&transition) {
i += 1;
continue;
}
}
transition_list.remove(i);
}
self.output.states[state_id]
.transitions
.extend(transition_list.into_iter().map(|i| i.0));
// Compute the default successor item set - the item set that
// we should advance to if the next element doesn't match any
// of the next elements in the item set's selectors.
let mut default_next_item_set = ItemSet::new();
for item in &item_set.0 {
let next_step = item.selector.0.get(item.step_id as usize);
if let Some(step) = next_step {
if !step.is_immediate {
default_next_item_set.insert(*item);
}
}
}
self.output.states[state_id].default_next_state_id = self.add_state(default_next_item_set);
self.item_set_list.push(item_set);
}