diff --git a/.gitignore b/.gitignore index 3a8cabc..3728341 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/target +**/target .idea diff --git a/Cargo.lock b/Cargo.lock index 0eb3f42..af5ded9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,15 +374,24 @@ dependencies = [ "clap_mangen", "color-eyre", "dirs", + "magma-command", "pest", "pest_derive", "semver", "serde", - "serde_json", "tokio", "toml", ] +[[package]] +name = "magma-command" +version = "0.1.0" +dependencies = [ + "color-eyre", + "serde", + "serde_json", +] + [[package]] name = "memchr" version = "2.7.6" diff --git a/Cargo.toml b/Cargo.toml index 7864994..d53217c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,15 +3,24 @@ name = "magma" version = "0.1.0" edition = "2024" +[workspace] +members = [ "magma-command" ] + [dependencies] clap = { version = "4.5.54", features = ["cargo", "derive"] } clap_mangen = "0.2.31" pest = { version = "2.8.5", features = ["pretty-print"] } pest_derive = { version = "2.8.5", features = ["grammar-extras"] } -color-eyre = "0.6.5" tokio = { version = "1.49.0", features = ["full"] } -serde = { version = "1.0.228", features = ["derive"] } toml = "0.9.11" semver = { version = "1.0.27", features = ["serde"] } dirs = "6.0.0" -serde_json = "1.0.149" + +magma-command = { path = "./magma-command" } + +color-eyre.workspace = true +serde.workspace = true + +[workspace.dependencies] +color-eyre = "0.6.5" +serde = { version = "1.0.228", features = ["derive"] } \ No newline at end of file diff --git a/assets/commands.json b/assets/commands.json index 65243d0..ccc4c25 100644 --- a/assets/commands.json +++ b/assets/commands.json @@ -8141,7 +8141,7 @@ "type": "literal", "name": "run", "executable": false, - "redirects": [], + "redirects": [""], "children": [] }, { diff --git a/magma-command/Cargo.lock b/magma-command/Cargo.lock new file mode 100644 index 0000000..c28606a --- /dev/null +++ b/magma-command/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "magma-command" +version = "0.1.0" diff --git a/magma-command/Cargo.toml b/magma-command/Cargo.toml new file mode 100644 index 0000000..686af77 --- /dev/null +++ b/magma-command/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "magma-command" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde_json = "1.0.149" + +serde.workspace = true +color-eyre.workspace = true diff --git a/magma-command/src/error.rs b/magma-command/src/error.rs new file mode 100644 index 0000000..b2033e7 --- /dev/null +++ b/magma-command/src/error.rs @@ -0,0 +1,48 @@ +#[derive(Debug, Clone)] +pub enum CommandError { + EmptyCommand, + IncompleteCommand { + path: String, + suggestions: Vec, + }, + TooManyArguments { + path: String, + }, + InvalidArgument { + path: String, + argument: String, + expected: Vec, + }, + InvalidRedirect { + from: String, + to: Vec, + }, +} + +impl std::fmt::Display for CommandError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::EmptyCommand => write!(f, "Empty command"), + Self::IncompleteCommand { path, suggestions } => { + write!(f, "Incomplete command: {}\nExpected one of: {}", path, suggestions.join(", ")) + } + Self::TooManyArguments { path } => { + write!(f, "Too many arguments for command: {}", path) + } + Self::InvalidArgument { path, argument, expected } => { + write!( + f, + "Invalid argument '{}' for command: {}\nExpected one of: {}", + argument, + path, + expected.join(", ") + ) + }, + Self::InvalidRedirect { from, to } => { + write!(f, "Invalid redirect from '{}' to '{}'", from, to.join(", ")) + } + } + } +} + +impl std::error::Error for CommandError {} \ No newline at end of file diff --git a/src/helpers/command_validator.rs b/magma-command/src/lib.rs similarity index 53% rename from src/helpers/command_validator.rs rename to magma-command/src/lib.rs index ea3e9af..0ffba9d 100644 --- a/src/helpers/command_validator.rs +++ b/magma-command/src/lib.rs @@ -1,5 +1,8 @@ +pub mod types; +pub mod error; -use crate::types::mc_command::{CommandNode, CommandTree, ParserType}; +use types::{CommandNode, CommandTree, ParserType, StringKind}; +use error::CommandError; pub struct MinecraftCommandValidator { root: CommandNode, @@ -65,15 +68,14 @@ impl MinecraftCommandValidator { tokens: &[String], node: &CommandNode, index: usize, - path: Vec, + mut path: Vec, ) -> Result { - // If we've consumed all tokens if index >= tokens.len() { return if node.is_executable() { Ok(ValidationResult { valid: true, path, - suggestions: vec![], + suggestions: self.get_suggestions_from_node(node), }) } else { Err(CommandError::IncompleteCommand { @@ -83,60 +85,80 @@ impl MinecraftCommandValidator { }; } - let token = &tokens[index]; + let current_token = &tokens[index]; let children = node.children(); - if children.is_empty() { - if node.is_executable() { - return Err(CommandError::TooManyArguments { + if children.is_empty() && node.redirects().is_empty() { + return if node.is_executable() { + Err(CommandError::TooManyArguments { path: path.join(" "), - }); + }) } else { - return Err(CommandError::IncompleteCommand { + Err(CommandError::IncompleteCommand { path: path.join(" "), - suggestions: vec![], - }); + suggestions: self.get_suggestions_from_node(node), + }) } } - // Try literal match first - for child in children { - if let CommandNode::Literal { name, .. } = child { - if name == token { - let mut new_path = path.clone(); - new_path.push(token.clone()); - return self.validate_tokens(tokens, child, index + 1, new_path); + if !node.redirects().is_empty() { + let nodes = self.get_redirects(&node.redirects()); + for node in nodes { + if let Ok(result) = self.validate_tokens(tokens, node, index, path.clone()) { + return Ok(result); } } + return Err(CommandError::InvalidRedirect { + from: path.join(" "), + to: node.redirects().to_vec(), + }) } - // Try argument parsers for child in children { - if let CommandNode::Argument { name, parser, .. } = child { - let parser_type = ParserType::from_parser_info(parser); - - // Check if this is a greedy parser (includes Message, Component, and greedy strings) - if self.is_greedy_parser(&parser_type) { - // Greedy parser consumes all remaining tokens - let remaining = tokens[index..].join(" "); - if self.validate_argument(&remaining, &parser_type).is_ok() { - let mut new_path = path.clone(); - new_path.push(format!("<{}>", name)); - // Jump to the end since greedy consumed everything - return self.validate_tokens(tokens, child, tokens.len(), new_path); - } - } else if self.validate_argument(token, &parser_type).is_ok() { - let mut new_path = path.clone(); - new_path.push(format!("<{}>", name)); - return self.validate_tokens(tokens, child, index + 1, new_path); - } + if let CommandNode::Literal { name, .. } = child && name == current_token { + path.push(current_token.clone()); + return self.validate_tokens(tokens, child, index + 1, path); } } - - // No match found + self.parse_args(node, tokens, index, path) + } + + /// an empty string value will redirect to all children + fn get_redirects(&self, name: &[String]) -> Vec<&CommandNode> { + self.root.children().iter() + .filter( + |child| + name.contains(child.name()) || name.contains(&String::from("")) + ) + .collect() + } + + fn parse_args( + &self, + node: &CommandNode, + tokens: &[String], + index: usize, + mut path: Vec + ) -> Result { + let token = &tokens[index]; + for child in node.children() { + let CommandNode::Argument { name, parser, .. } = child else { + continue; + }; + let parser_type = ParserType::from_parser_info(parser); + if self.is_greedy_parser(&parser_type) + && self.validate_argument(&tokens[index..].join(" "), &parser_type).is_ok() + { + path.push(format!("<{}>", name)); + return self.validate_tokens(tokens, child, tokens.len(), path); + } else if self.validate_argument(token, &parser_type).is_ok() { + path.push(format!("<{}>", name)); + return self.validate_tokens(tokens, child, index + 1, path); + } + } Err(CommandError::InvalidArgument { path: path.join(" "), - argument: token.clone(), + argument: token.to_string(), expected: self.get_suggestions_from_node(node), }) } @@ -144,7 +166,7 @@ impl MinecraftCommandValidator { fn is_greedy_parser(&self, parser_type: &ParserType) -> bool { matches!( parser_type, - ParserType::String { kind: crate::types::mc_command::StringKind::GreedyPhrase } + ParserType::String { kind: StringKind::GreedyPhrase } | ParserType::Message | ParserType::Component ) @@ -161,43 +183,35 @@ impl MinecraftCommandValidator { } ParserType::Integer { min, max } => { let num: i32 = value.parse().map_err(|_| "Invalid integer")?; - if let Some(min) = min { - if (num as f64) < *min { - return Err(format!("Value must be >= {}", min)); - } + if let Some(min) = min && (num as f64) < *min { + return Err(format!("Value must be >= {}", min)); } - if let Some(max) = max { - if (num as f64) > *max { - return Err(format!("Value must be <= {}", max)); - } + if let Some(max) = max && (num as f64) > *max { + return Err(format!("Value must be <= {}", max)); } Ok(()) } ParserType::Float { min, max } | ParserType::Double { min, max } => { let num: f64 = value.parse().map_err(|_| "Invalid number")?; - if let Some(min) = min { - if num < *min { - return Err(format!("Value must be >= {}", min)); - } + if let Some(min) = min && num < *min { + return Err(format!("Value must be >= {}", min)); } - if let Some(max) = max { - if num > *max { - return Err(format!("Value must be <= {}", max)); - } + if let Some(max) = max && num > *max { + return Err(format!("Value must be <= {}", max)); } Ok(()) } - ParserType::String { .. } => Ok(()), ParserType::Entity { .. } => { - if value.starts_with('@') || value.starts_with('"') { + //TODO: have to check amount + if value.starts_with('@') { Ok(()) } else { Err("Invalid entity selector".to_string()) } } - // Message and Component parsers accept any text + ParserType::String { .. } => Ok(()), ParserType::Message | ParserType::Component => Ok(()), - _ => todo!(), + _ => Ok(()), } } @@ -225,46 +239,4 @@ pub struct ValidationResult { pub valid: bool, pub path: Vec, pub suggestions: Vec, -} - -#[derive(Debug, Clone)] -pub enum CommandError { - EmptyCommand, - IncompleteCommand { - path: String, - suggestions: Vec, - }, - TooManyArguments { - path: String, - }, - InvalidArgument { - path: String, - argument: String, - expected: Vec, - }, -} - -impl std::fmt::Display for CommandError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::EmptyCommand => write!(f, "Empty command"), - Self::IncompleteCommand { path, suggestions } => { - write!(f, "Incomplete command: {}\nExpected one of: {}", path, suggestions.join(", ")) - } - Self::TooManyArguments { path } => { - write!(f, "Too many arguments for command: {}", path) - } - Self::InvalidArgument { path, argument, expected } => { - write!( - f, - "Invalid argument '{}' for command: {}\nExpected one of: {}", - argument, - path, - expected.join(", ") - ) - } - } - } -} - -impl std::error::Error for CommandError {} \ No newline at end of file +} \ No newline at end of file diff --git a/magma-command/src/types/enums.rs b/magma-command/src/types/enums.rs new file mode 100644 index 0000000..af6c647 --- /dev/null +++ b/magma-command/src/types/enums.rs @@ -0,0 +1,75 @@ +#[derive(Debug, Clone)] +pub enum ParserType { + Bool, + Double { min: Option, max: Option }, + Float { min: Option, max: Option }, + Integer { min: Option, max: Option }, + Long { min: Option, max: Option }, + String { kind: StringKind }, + Entity { amount: EntityAmount, entity_type: EntityType }, + ScoreHolder { amount: ScoreHolderAmount }, + GameProfile, + BlockPos, + ColumnPos, + Vec3, + Vec2, + BlockState, + BlockPredicate, + ItemStack, + ItemPredicate, + Color, + Component, + Message, + Nbt, + NbtTag, + NbtPath, + Objective, + ObjectiveCriteria, + Operation, + Particle, + Angle, + Rotation, + ScoreboardSlot, + Swizzle, + Team, + ItemSlot, + ResourceLocation { registry: Option }, + Function, + EntityAnchor, + IntRange, + FloatRange, + Dimension, + Gamemode, + Time, + ResourceOrTag { registry: Option }, + Resource { registry: Option }, + TemplateMirror, + TemplateRotation, + Uuid, + Unknown(String), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StringKind { + SingleWord, + QuotablePhrase, + GreedyPhrase, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EntityAmount { + Single, + Multiple, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EntityType { + Players, + Entities, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScoreHolderAmount { + Single, + Multiple, +} \ No newline at end of file diff --git a/magma-command/src/types/mod.rs b/magma-command/src/types/mod.rs new file mode 100644 index 0000000..0b45f7f --- /dev/null +++ b/magma-command/src/types/mod.rs @@ -0,0 +1,86 @@ +mod enums; +mod parser; + +pub use enums::*; +pub use parser::*; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CommandTree { + pub root: CommandNode, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum CommandNode { + Root { + name: String, + #[serde(default)] + executable: bool, + #[serde(default)] + redirects: Vec, + #[serde(default)] + children: Vec, + }, + Literal { + name: String, + #[serde(default)] + executable: bool, + #[serde(default)] + redirects: Vec, + #[serde(default)] + children: Vec, + }, + Argument { + name: String, + #[serde(default)] + executable: bool, + #[serde(default)] + redirects: Vec, + #[serde(default)] + children: Vec, + parser: ParserInfo, + }, +} + +impl CommandNode { + pub fn name(&self) -> &String { + match self { + CommandNode::Root { name, .. } => name, + CommandNode::Literal { name, .. } => name, + CommandNode::Argument { name, .. } => name, + } + } + + pub fn is_executable(&self) -> bool { + match self { + CommandNode::Root { executable, .. } => *executable, + CommandNode::Literal { executable, .. } => *executable, + CommandNode::Argument { executable, .. } => *executable, + } + } + + pub fn children(&self) -> &[CommandNode] { + match self { + CommandNode::Root { children, .. } => children, + CommandNode::Literal { children, .. } => children, + CommandNode::Argument { children, .. } => children, + } + } + + pub fn redirects(&self) -> &[String] { + match self { + CommandNode::Root { redirects, .. } => redirects, + CommandNode::Literal { redirects, .. } => redirects, + CommandNode::Argument { redirects, .. } => redirects, + } + } + + pub fn parser(&self) -> Option<&ParserInfo> { + match self { + CommandNode::Argument { parser, .. } => Some(parser), + _ => None, + } + } +} diff --git a/magma-command/src/types/parser.rs b/magma-command/src/types/parser.rs new file mode 100644 index 0000000..5f6939f --- /dev/null +++ b/magma-command/src/types/parser.rs @@ -0,0 +1,67 @@ +use serde::{Deserialize, Serialize}; +use crate::types::{EntityAmount, EntityType, ParserType, ScoreHolderAmount, StringKind}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ParserInfo { + pub parser: String, + pub modifier: Option, +} + +impl ParserType { + pub fn from_parser_info(info: &ParserInfo) -> Self { + match info.parser.as_str() { + "brigadier:bool" => ParserType::Bool, + "brigadier:double" => ParserType::Double { min: None, max: None }, + "brigadier:float" => ParserType::Float { min: None, max: None }, + "brigadier:integer" => ParserType::Integer { min: None, max: None }, + "brigadier:long" => ParserType::Long { min: None, max: None }, + "brigadier:string" => ParserType::String { kind: StringKind::SingleWord }, + "minecraft:entity" => ParserType::Entity { + amount: EntityAmount::Multiple, + entity_type: EntityType::Entities, + }, + "minecraft:score_holder" => ParserType::ScoreHolder { + amount: ScoreHolderAmount::Multiple, + }, + "minecraft:game_profile" => ParserType::GameProfile, + "minecraft:block_pos" => ParserType::BlockPos, + "minecraft:column_pos" => ParserType::ColumnPos, + "minecraft:vec3" => ParserType::Vec3, + "minecraft:vec2" => ParserType::Vec2, + "minecraft:block_state" => ParserType::BlockState, + "minecraft:block_predicate" => ParserType::BlockPredicate, + "minecraft:item_stack" => ParserType::ItemStack, + "minecraft:item_predicate" => ParserType::ItemPredicate, + "minecraft:color" => ParserType::Color, + "minecraft:component" => ParserType::Component, + "minecraft:message" => ParserType::Message, + "minecraft:nbt_compound_tag" => ParserType::Nbt, + "minecraft:nbt_tag" => ParserType::NbtTag, + "minecraft:nbt_path" => ParserType::NbtPath, + "minecraft:objective" => ParserType::Objective, + "minecraft:objective_criteria" => ParserType::ObjectiveCriteria, + "minecraft:operation" => ParserType::Operation, + "minecraft:particle" => ParserType::Particle, + "minecraft:angle" => ParserType::Angle, + "minecraft:rotation" => ParserType::Rotation, + "minecraft:scoreboard_slot" => ParserType::ScoreboardSlot, + "minecraft:swizzle" => ParserType::Swizzle, + "minecraft:team" => ParserType::Team, + "minecraft:item_slot" => ParserType::ItemSlot, + "minecraft:resource_location" => ParserType::ResourceLocation { registry: None }, + "minecraft:function" => ParserType::Function, + "minecraft:entity_anchor" => ParserType::EntityAnchor, + "minecraft:int_range" => ParserType::IntRange, + "minecraft:float_range" => ParserType::FloatRange, + "minecraft:dimension" => ParserType::Dimension, + "minecraft:gamemode" => ParserType::Gamemode, + "minecraft:time" => ParserType::Time, + "minecraft:resource_or_tag" => ParserType::ResourceOrTag { registry: None }, + "minecraft:resource" => ParserType::Resource { registry: None }, + "minecraft:template_mirror" => ParserType::TemplateMirror, + "minecraft:template_rotation" => ParserType::TemplateRotation, + "minecraft:uuid" => ParserType::Uuid, + _ => ParserType::Unknown(info.parser.clone()), + } + } +} \ No newline at end of file diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 20fc269..dd96c2b 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use pest::iterators::{Pair, Pairs}; use pest::Parser; -use crate::helpers::MinecraftCommandValidator; +use magma_command::MinecraftCommandValidator; use crate::parser::{MagmaParser, Rule}; use crate::types::{MagmaProjectConfig, McFunctionFile}; @@ -36,15 +36,17 @@ impl MagmaCompiler { Rule::functionExpression => { let mut pairs = pair.into_inner(); let identifier = pairs.next().unwrap(); + let args = if let Some(rule) = pairs.peek() && rule.as_rule() == Rule::functionArgs - { - Some(pairs.next().unwrap()) - } else { None }; + { pairs.next() } + else { None }; + let statements = if let Some(rule) = pairs.peek() && rule.as_rule() == Rule::block - { unbox_rule(pairs.next().unwrap()) } + { expand_next_rule(pairs) } else { None }; + self.parse_function(None, identifier, args, statements)?; } _ => {} @@ -64,25 +66,33 @@ impl MagmaCompiler { return Ok(file); }; for statement in statements { - let Some(statement) = self.parse_statement(statement)? else { - continue; - }; - file.add_line(statement); + let statements = self.parse_statement(statement)?; + file.add_lines(statements); } println!("{}", file.lines().join("\n")); + println!(); Ok(file) } - fn parse_statement(&self, statement: Pair) -> color_eyre::Result> { + fn parse_statement(&self, statement: Pair) -> color_eyre::Result> { + let statement = statement.into_inner().next().unwrap(); + let mut lines = Vec::new(); match statement.as_rule() { Rule::commandLine => { let command = statement.into_inner().next().unwrap(); - Ok(Some(self.parse_command(command)?)) + lines.push(self.parse_command(command)?); + }, + Rule::commandBlock => { + let commands = statement.into_inner(); + for command in commands { + lines.push(self.parse_command(command)?); + } } _ => { - Ok(None) + println!("{:?}", statement); } } + Ok(lines) } fn parse_command(&self, command: Pair) -> color_eyre::Result { @@ -93,7 +103,7 @@ impl MagmaCompiler { let string = unbox_string(primitive); primitives.push(string); } - _ => primitives.push(primitive.as_str().to_string()) + _ => { primitives.push(primitive.as_str().to_string())} } } let unboxed_command = primitives.join(" "); @@ -102,9 +112,8 @@ impl MagmaCompiler { } } -fn unbox_rule(rule: Pair) -> Option> { - let mut block = rule.into_inner(); - if let Some(inner) = block.next() { +fn expand_next_rule(mut rule: Pairs) -> Option> { + if let Some(inner) = rule.next() { Some(inner.into_inner()) } else { None } } diff --git a/src/grammar.pest b/src/grammar.pest index c5e831b..0f3539b 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -47,7 +47,7 @@ commandLine = { "command" ~ command } commandBlock = { "command" ~ "{" ~ command* ~ "}" } command = { commandName ~ mcArg+ ~ ";" } -commandName = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } +commandName = @{ ASCII_ALPHA+ } mcArg = _{ nbtBlock | string | mcPrimitive } nbtBlock = { "{" ~ (nbtBlock | string | !("}" | "{") ~ ANY)* ~ "}" } mcPrimitive = @{ (!(";" | "{" | "}" | "\"" | WHITESPACE) ~ ANY)+ } diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 836ad5b..4e50ec7 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1,6 +1,3 @@ -mod command_validator; - -pub use command_validator::*; use std::path::PathBuf; pub const FILE_EXTENSION: &str = "mg"; diff --git a/src/types/mc_command.rs b/src/types/mc_command.rs deleted file mode 100644 index 466d7eb..0000000 --- a/src/types/mc_command.rs +++ /dev/null @@ -1,221 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct CommandTree { - pub root: CommandNode, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum CommandNode { - Root { - name: String, - #[serde(default)] - executable: bool, - #[serde(default)] - redirects: Vec, - #[serde(default)] - children: Vec, - }, - Literal { - name: String, - #[serde(default)] - executable: bool, - #[serde(default)] - redirects: Vec, - #[serde(default)] - children: Vec, - }, - Argument { - name: String, - #[serde(default)] - executable: bool, - #[serde(default)] - redirects: Vec, - #[serde(default)] - children: Vec, - parser: ParserInfo, - }, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ParserInfo { - pub parser: String, - pub modifier: Option, -} - -impl CommandNode { - pub fn name(&self) -> &str { - match self { - CommandNode::Root { name, .. } => name, - CommandNode::Literal { name, .. } => name, - CommandNode::Argument { name, .. } => name, - } - } - - pub fn is_executable(&self) -> bool { - match self { - CommandNode::Root { executable, .. } => *executable, - CommandNode::Literal { executable, .. } => *executable, - CommandNode::Argument { executable, .. } => *executable, - } - } - - pub fn children(&self) -> &[CommandNode] { - match self { - CommandNode::Root { children, .. } => children, - CommandNode::Literal { children, .. } => children, - CommandNode::Argument { children, .. } => children, - } - } - - pub fn redirects(&self) -> &[String] { - match self { - CommandNode::Root { redirects, .. } => redirects, - CommandNode::Literal { redirects, .. } => redirects, - CommandNode::Argument { redirects, .. } => redirects, - } - } - - pub fn parser(&self) -> Option<&ParserInfo> { - match self { - CommandNode::Argument { parser, .. } => Some(parser), - _ => None, - } - } -} - -#[derive(Debug, Clone)] -pub enum ParserType { - Bool, - Double { min: Option, max: Option }, - Float { min: Option, max: Option }, - Integer { min: Option, max: Option }, - Long { min: Option, max: Option }, - String { kind: StringKind }, - Entity { amount: EntityAmount, entity_type: EntityType }, - ScoreHolder { amount: ScoreHolderAmount }, - GameProfile, - BlockPos, - ColumnPos, - Vec3, - Vec2, - BlockState, - BlockPredicate, - ItemStack, - ItemPredicate, - Color, - Component, - Message, - Nbt, - NbtTag, - NbtPath, - Objective, - ObjectiveCriteria, - Operation, - Particle, - Angle, - Rotation, - ScoreboardSlot, - Swizzle, - Team, - ItemSlot, - ResourceLocation { registry: Option }, - Function, - EntityAnchor, - IntRange, - FloatRange, - Dimension, - Gamemode, - Time, - ResourceOrTag { registry: Option }, - Resource { registry: Option }, - TemplateMirror, - TemplateRotation, - Uuid, - Unknown(String), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StringKind { - SingleWord, - QuotablePhrase, - GreedyPhrase, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EntityAmount { - Single, - Multiple, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EntityType { - Players, - Entities, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ScoreHolderAmount { - Single, - Multiple, -} - -impl ParserType { - pub fn from_parser_info(info: &ParserInfo) -> Self { - match info.parser.as_str() { - "brigadier:bool" => ParserType::Bool, - "brigadier:double" => ParserType::Double { min: None, max: None }, - "brigadier:float" => ParserType::Float { min: None, max: None }, - "brigadier:integer" => ParserType::Integer { min: None, max: None }, - "brigadier:long" => ParserType::Long { min: None, max: None }, - "brigadier:string" => ParserType::String { kind: StringKind::SingleWord }, - "minecraft:entity" => ParserType::Entity { - amount: EntityAmount::Multiple, - entity_type: EntityType::Entities, - }, - "minecraft:score_holder" => ParserType::ScoreHolder { - amount: ScoreHolderAmount::Multiple, - }, - "minecraft:game_profile" => ParserType::GameProfile, - "minecraft:block_pos" => ParserType::BlockPos, - "minecraft:column_pos" => ParserType::ColumnPos, - "minecraft:vec3" => ParserType::Vec3, - "minecraft:vec2" => ParserType::Vec2, - "minecraft:block_state" => ParserType::BlockState, - "minecraft:block_predicate" => ParserType::BlockPredicate, - "minecraft:item_stack" => ParserType::ItemStack, - "minecraft:item_predicate" => ParserType::ItemPredicate, - "minecraft:color" => ParserType::Color, - "minecraft:component" => ParserType::Component, - "minecraft:message" => ParserType::Message, - "minecraft:nbt_compound_tag" => ParserType::Nbt, - "minecraft:nbt_tag" => ParserType::NbtTag, - "minecraft:nbt_path" => ParserType::NbtPath, - "minecraft:objective" => ParserType::Objective, - "minecraft:objective_criteria" => ParserType::ObjectiveCriteria, - "minecraft:operation" => ParserType::Operation, - "minecraft:particle" => ParserType::Particle, - "minecraft:angle" => ParserType::Angle, - "minecraft:rotation" => ParserType::Rotation, - "minecraft:scoreboard_slot" => ParserType::ScoreboardSlot, - "minecraft:swizzle" => ParserType::Swizzle, - "minecraft:team" => ParserType::Team, - "minecraft:item_slot" => ParserType::ItemSlot, - "minecraft:resource_location" => ParserType::ResourceLocation { registry: None }, - "minecraft:function" => ParserType::Function, - "minecraft:entity_anchor" => ParserType::EntityAnchor, - "minecraft:int_range" => ParserType::IntRange, - "minecraft:float_range" => ParserType::FloatRange, - "minecraft:dimension" => ParserType::Dimension, - "minecraft:gamemode" => ParserType::Gamemode, - "minecraft:time" => ParserType::Time, - "minecraft:resource_or_tag" => ParserType::ResourceOrTag { registry: None }, - "minecraft:resource" => ParserType::Resource { registry: None }, - "minecraft:template_mirror" => ParserType::TemplateMirror, - "minecraft:template_rotation" => ParserType::TemplateRotation, - "minecraft:uuid" => ParserType::Uuid, - _ => ParserType::Unknown(info.parser.clone()), - } - } -} \ No newline at end of file diff --git a/src/types/mcfunction_file.rs b/src/types/mcfunction_file.rs index 2c305eb..512d474 100644 --- a/src/types/mcfunction_file.rs +++ b/src/types/mcfunction_file.rs @@ -18,6 +18,10 @@ impl McFunctionFile { self.content.push(line); } + pub fn add_lines(&mut self, lines: Vec) { + self.content.extend(lines); + } + pub fn lines(&self) -> &Vec { &self.content } diff --git a/src/types/mod.rs b/src/types/mod.rs index 865680b..7f73038 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,6 +1,5 @@ mod magma_project_config; mod mcfunction_file; -pub(crate) mod mc_command; pub use magma_project_config::*; pub(crate) use mcfunction_file::*; \ No newline at end of file