use std::collections::HashMap;
use super::lex::{Node, Token};
use super::{Parsed, Parser, SetDef, check_name, check_sprite_name, duplicate};
use crate::lint::Kind;
use crate::{ErrorCode, Group, Position, Sprite};
pub(crate) struct GroupDef<'a> {
pub name: Token<'a>,
pub members: Vec<Token<'a>>,
}
impl<'a> Parser<'a, '_> {
pub(super) fn group(&mut self, node: &Node<'a>) -> Parsed<()> {
let tokens = &node.line.tokens;
let name = *tokens
.get(1)
.ok_or_else(|| tokens[0].end_error("Expected a name for the group".to_owned()))?;
check_name(name)?;
let mut members: Vec<Token<'a>> = Vec::new();
let mut seen: HashMap<&str, Position> = HashMap::new();
let mut lint = None;
for &token in &tokens[2..] {
match token.text.strip_prefix("lint=") {
Some(value) if lint.is_none() => {
lint = Some(Token {
text: value,
pos: token.at("lint=".len()),
});
}
_ if lint.is_some() => {
return Err(token.error(
ErrorCode::Syntax,
format!("Unexpected `{}` after `lint=`", token.text),
));
}
_ => {
check_sprite_name(token)?;
if let Some(first) = seen.get(token.text) {
return Err(token.error(
ErrorCode::DuplicateName,
format!(
"Sprite `{}` is in group `{}` already, at column {}",
token.text, name.text, first.column
),
));
}
seen.insert(token.text, token.pos);
members.push(token);
}
}
}
if members.is_empty() {
return Err(name.end_error(format!(
"Group `{}` has no sprites; write them after its name, such as `group items \
sword shield`",
name.text
)));
}
if let Some(child) = node.children.first() {
return Err(child.line.first().error(
ErrorCode::Syntax,
"Unexpected indented line under a group; its sprites follow its name",
));
}
self.lint_option(Kind::Group, name, lint)?;
self.groups.push(GroupDef { name, members });
Ok(())
}
pub(super) fn resolve_groups(&mut self, sprites: &[Sprite]) -> Vec<Group> {
let mut seen: HashMap<&str, Position> = HashMap::new();
let mut groups = Vec::new();
for def in &self.groups {
if let Some(&first) = seen.get(def.name.text) {
self.errors.push(duplicate(def.name, "Group", first));
continue;
}
seen.insert(def.name.text, def.name.pos);
let mut complete = true;
for member in &def.members {
if sprites.iter().any(|s| s.name == member.text) {
continue;
}
complete = false;
if !self.sprites.iter().any(|d| d.name.text == member.text) {
self.errors.push(member.error(
ErrorCode::UnknownName,
format!("Unknown sprite `{}`", member.text),
));
}
}
if complete {
groups.push(Group {
name: def.name.text.to_owned(),
members: def.members.iter().map(|m| m.text.to_owned()).collect(),
});
}
}
groups
}
pub(super) fn group_sets(&self) -> Vec<SetDef<'a>> {
self.groups
.iter()
.map(|g| {
let members = g
.members
.iter()
.map(|m| crate::lint::Member {
name: m.text.to_owned(),
pos: m.pos,
sources: vec![m.text.to_owned()],
})
.collect();
(Kind::Group, g.name, members)
})
.collect()
}
}