pub mod command;
use crate::generator::parser::command::{
argument::{
minecraft::{MinecraftEntityAnchor, MinecraftMessage, MinecraftScoreHolder, MinecraftTime},
Argument,
},
resource_location::{ResourceLocation, ResourceLocationRef},
CommandParser, CommandParserError, CommandParserResult, ParsedNode,
};
use log::debug;
use std::{
collections::{BTreeMap, BTreeSet},
convert::TryFrom,
usize,
};
use self::command::argument::minecraft::{
entity::{MinecraftEntity, MinecraftSelector, MinecraftSelectorType},
MinecraftFunction, MinecraftObjective,
};
#[derive(Clone, Debug, PartialEq)]
pub enum Line {
Empty,
Comment,
FunctionCall {
column_index: usize,
name: ResourceLocation,
anchor: Option<MinecraftEntityAnchor>,
selectors: BTreeMap<usize, SelectorValue>,
objectives: BTreeSet<String>,
},
OptionalSelectorCommand {
missing_selector: usize,
selectors: BTreeMap<usize, SelectorValue>,
objectives: BTreeSet<String>,
},
Schedule {
schedule_start: usize,
function: ResourceLocation,
operation: ScheduleOperation,
selectors: BTreeMap<usize, SelectorValue>,
objectives: BTreeSet<String>,
},
OtherCommand {
selectors: BTreeMap<usize, SelectorValue>,
objectives: BTreeSet<String>,
},
}
impl Line {
pub fn objectives(&self) -> Option<&BTreeSet<String>> {
match self {
Line::FunctionCall { objectives, .. }
| Line::OptionalSelectorCommand { objectives, .. }
| Line::Schedule { objectives, .. }
| Line::OtherCommand { objectives, .. } => Some(objectives),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ScheduleOperation {
Append { time: MinecraftTime },
Clear,
Replace { time: MinecraftTime },
}
pub fn parse_line(parser: &CommandParser, line: &str) -> Line {
let (line, error) = parse_line_internal(parser, line);
if let Some(error) = error {
debug!("Failed to parse command: {}", error);
}
line
}
fn parse_line_internal<'l>(
parser: &'l CommandParser,
line: &'l str,
) -> (Line, Option<CommandParserError<'l>>) {
let line = line.trim();
if line.starts_with('#') {
(Line::Comment, None)
} else if line.is_empty() {
(Line::Empty, None)
} else {
parse_command(parser, line)
}
}
fn parse_command<'l>(
parser: &'l CommandParser,
command: &'l str,
) -> (Line, Option<CommandParserError<'l>>) {
let CommandParserResult {
parsed_nodes,
error,
} = parser.parse(command);
let mut nodes = parsed_nodes.as_slice();
let mut selectors = BTreeMap::new();
let mut objectives = BTreeSet::new();
let mut maybe_anchor: Option<MinecraftEntityAnchor> = None;
while let [_, tail @ ..] = nodes {
match nodes {
[ParsedNode::Argument {
argument: Argument::MinecraftEntity(entity),
index,
..
}, ..] => {
selectors.insert(*index, SelectorValue::from(entity));
}
[ParsedNode::Argument {
argument: Argument::MinecraftScoreHolder(MinecraftScoreHolder::Selector(selector)),
index,
..
}, ..] => {
selectors.insert(*index, SelectorValue::from(selector));
}
[ParsedNode::Argument {
argument:
Argument::MinecraftMessage(MinecraftMessage {
selectors: message_selectors,
..
}),
index,
..
}, ..] => {
selectors.extend(
message_selectors.iter().map(|(selector, start, _end)| {
(index + start, SelectorValue::from(selector))
}),
);
}
[ParsedNode::Argument {
argument: Argument::MinecraftObjective(MinecraftObjective(objective)),
..
}, ..]
| [ParsedNode::Literal {
literal: "scoreboard",
..
}, ParsedNode::Literal {
literal: "objectives",
..
}, ParsedNode::Literal { literal: "add", .. }, ParsedNode::Argument {
argument: Argument::BrigadierString(objective),
..
}, ..] => {
objectives.insert(objective.to_string());
}
[ParsedNode::Literal {
literal: "execute", ..
}
| ParsedNode::Redirect("execute"), ParsedNode::Literal {
literal: "anchored",
..
}, ParsedNode::Argument {
argument: Argument::MinecraftEntityAnchor(anchor),
..
}, ..] => {
maybe_anchor = Some(*anchor);
}
_ => {}
}
nodes = tail;
}
if error.is_none() {
if let Some((column_index, name)) = as_function_call(&parsed_nodes) {
return (
Line::FunctionCall {
column_index,
name,
anchor: maybe_anchor,
selectors,
objectives,
},
None,
);
}
if let Some((schedule_start, function, operation)) = as_schedule(&parsed_nodes) {
return (
Line::Schedule {
schedule_start,
function: function.to_owned(),
operation,
selectors,
objectives,
},
None,
);
}
if let Some(missing_selector) = find_missing_selector(&parsed_nodes) {
return (
Line::OptionalSelectorCommand {
missing_selector,
selectors,
objectives,
},
None,
);
}
}
(
Line::OtherCommand {
selectors,
objectives,
},
error,
)
}
fn as_function_call(nodes: &[ParsedNode]) -> Option<(usize, ResourceLocation)> {
if let [.., ParsedNode::Literal {
literal: "function",
index,
..
}, ParsedNode::Argument {
argument: Argument::MinecraftFunction(MinecraftFunction(function)),
..
}] = nodes
{
Some((*index, function.to_owned()))
} else {
None
}
}
fn as_schedule(mut nodes: &[ParsedNode]) -> Option<(usize, ResourceLocation, ScheduleOperation)> {
while let [_, tail @ ..] = nodes {
match nodes {
[ParsedNode::Literal {
literal: "schedule",
index,
..
}, ParsedNode::Literal {
literal: "function",
..
}, ParsedNode::Argument {
argument: Argument::MinecraftFunction(MinecraftFunction(function)),
..
}, ParsedNode::Argument {
argument: Argument::MinecraftTime(time),
..
}, tail @ ..] => {
let op = match tail {
[ParsedNode::Literal {
literal: "append", ..
}] => Some(ScheduleOperation::Append { time: time.clone() }),
[]
| [ParsedNode::Literal {
literal: "replace", ..
}] => Some(ScheduleOperation::Replace { time: time.clone() }),
_ => None,
};
if let Some(op) = op {
return Some((*index, function.to_owned(), op));
}
}
[ParsedNode::Literal {
literal: "schedule",
index,
..
}, ParsedNode::Literal {
literal: "clear", ..
}, ParsedNode::Argument {
argument: Argument::BrigadierString(string),
..
}] => {
if let Ok(function) = ResourceLocationRef::try_from(*string) {
return Some((*index, function.to_owned(), ScheduleOperation::Clear));
}
}
_ => {}
}
nodes = tail;
}
None
}
fn find_missing_selector(tail: &[ParsedNode]) -> Option<usize> {
match tail {
[.., ParsedNode::Literal {
literal: kill @ "kill",
index,
}] => Some(index + kill.len()),
[.., ParsedNode::Literal {
literal: "team", ..
}, ParsedNode::Literal {
literal: "join", ..
}, ParsedNode::Argument {
argument: Argument::MinecraftTeam(..),
index,
len,
..
}] => Some(index + len),
[.., ParsedNode::Redirect("teleport")
| ParsedNode::Literal {
literal: "teleport",
..
}, ParsedNode::Argument {
name: "destination",
argument: Argument::MinecraftEntity(..),
index,
..
}
| ParsedNode::Argument {
name: "location",
argument: Argument::MinecraftVec3(..),
index,
..
}] => Some(index - 1),
_ => None,
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SelectorValue {
pub exclude_minect_cursor: bool,
}
impl SelectorValue {
fn entity_exclude_minect_cursor(value: &MinecraftEntity<'_>) -> bool {
match value {
MinecraftEntity::Selector(selector) => Self::selector_exclude_minect_cursor(selector),
MinecraftEntity::PlayerNameOrUuid(..) => false,
}
}
fn selector_exclude_minect_cursor(value: &MinecraftSelector<'_>) -> bool {
value.selector_type != MinecraftSelectorType::S
&& !value.tags.iter().any(|it| it.string == "minect_cursor")
}
pub(crate) fn empty_self() -> SelectorValue {
Self {
exclude_minect_cursor: false,
}
}
}
impl Default for SelectorValue {
fn default() -> Self {
Self {
exclude_minect_cursor: true,
}
}
}
impl From<&MinecraftSelector<'_>> for SelectorValue {
fn from(value: &MinecraftSelector<'_>) -> Self {
Self {
exclude_minect_cursor: Self::selector_exclude_minect_cursor(value),
}
}
}
impl From<&MinecraftEntity<'_>> for SelectorValue {
fn from(value: &MinecraftEntity<'_>) -> Self {
Self {
exclude_minect_cursor: Self::entity_exclude_minect_cursor(value),
}
}
}
#[cfg(test)]
mod tests;