implemented command

refactor command to a new crate
This commit is contained in:
2026-02-08 17:34:52 +08:00
parent bfcd414a14
commit 900cd48509
17 changed files with 424 additions and 353 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
/target **/target
.idea .idea

11
Cargo.lock generated
View File

@@ -374,15 +374,24 @@ dependencies = [
"clap_mangen", "clap_mangen",
"color-eyre", "color-eyre",
"dirs", "dirs",
"magma-command",
"pest", "pest",
"pest_derive", "pest_derive",
"semver", "semver",
"serde", "serde",
"serde_json",
"tokio", "tokio",
"toml", "toml",
] ]
[[package]]
name = "magma-command"
version = "0.1.0"
dependencies = [
"color-eyre",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.6" version = "2.7.6"

View File

@@ -3,15 +3,24 @@ name = "magma"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[workspace]
members = [ "magma-command" ]
[dependencies] [dependencies]
clap = { version = "4.5.54", features = ["cargo", "derive"] } clap = { version = "4.5.54", features = ["cargo", "derive"] }
clap_mangen = "0.2.31" clap_mangen = "0.2.31"
pest = { version = "2.8.5", features = ["pretty-print"] } pest = { version = "2.8.5", features = ["pretty-print"] }
pest_derive = { version = "2.8.5", features = ["grammar-extras"] } pest_derive = { version = "2.8.5", features = ["grammar-extras"] }
color-eyre = "0.6.5"
tokio = { version = "1.49.0", features = ["full"] } tokio = { version = "1.49.0", features = ["full"] }
serde = { version = "1.0.228", features = ["derive"] }
toml = "0.9.11" toml = "0.9.11"
semver = { version = "1.0.27", features = ["serde"] } semver = { version = "1.0.27", features = ["serde"] }
dirs = "6.0.0" 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"] }

View File

@@ -8141,7 +8141,7 @@
"type": "literal", "type": "literal",
"name": "run", "name": "run",
"executable": false, "executable": false,
"redirects": [], "redirects": [""],
"children": [] "children": []
}, },
{ {

7
magma-command/Cargo.lock generated Normal file
View File

@@ -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"

10
magma-command/Cargo.toml Normal file
View File

@@ -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

View File

@@ -0,0 +1,48 @@
#[derive(Debug, Clone)]
pub enum CommandError {
EmptyCommand,
IncompleteCommand {
path: String,
suggestions: Vec<String>,
},
TooManyArguments {
path: String,
},
InvalidArgument {
path: String,
argument: String,
expected: Vec<String>,
},
InvalidRedirect {
from: String,
to: Vec<String>,
},
}
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 {}

View File

@@ -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 { pub struct MinecraftCommandValidator {
root: CommandNode, root: CommandNode,
@@ -65,15 +68,14 @@ impl MinecraftCommandValidator {
tokens: &[String], tokens: &[String],
node: &CommandNode, node: &CommandNode,
index: usize, index: usize,
path: Vec<String>, mut path: Vec<String>,
) -> Result<ValidationResult, CommandError> { ) -> Result<ValidationResult, CommandError> {
// If we've consumed all tokens
if index >= tokens.len() { if index >= tokens.len() {
return if node.is_executable() { return if node.is_executable() {
Ok(ValidationResult { Ok(ValidationResult {
valid: true, valid: true,
path, path,
suggestions: vec![], suggestions: self.get_suggestions_from_node(node),
}) })
} else { } else {
Err(CommandError::IncompleteCommand { Err(CommandError::IncompleteCommand {
@@ -83,60 +85,80 @@ impl MinecraftCommandValidator {
}; };
} }
let token = &tokens[index]; let current_token = &tokens[index];
let children = node.children(); let children = node.children();
if children.is_empty() { if children.is_empty() && node.redirects().is_empty() {
if node.is_executable() { return if node.is_executable() {
return Err(CommandError::TooManyArguments { Err(CommandError::TooManyArguments {
path: path.join(" "), path: path.join(" "),
}); })
} else { } else {
return Err(CommandError::IncompleteCommand { Err(CommandError::IncompleteCommand {
path: path.join(" "), path: path.join(" "),
suggestions: vec![], suggestions: self.get_suggestions_from_node(node),
}); })
} }
} }
// Try literal match first if !node.redirects().is_empty() {
for child in children { let nodes = self.get_redirects(&node.redirects());
if let CommandNode::Literal { name, .. } = child { for node in nodes {
if name == token { if let Ok(result) = self.validate_tokens(tokens, node, index, path.clone()) {
let mut new_path = path.clone(); return Ok(result);
new_path.push(token.clone());
return self.validate_tokens(tokens, child, index + 1, new_path);
} }
} }
return Err(CommandError::InvalidRedirect {
from: path.join(" "),
to: node.redirects().to_vec(),
})
} }
// Try argument parsers
for child in children { for child in children {
if let CommandNode::Argument { name, parser, .. } = child { if let CommandNode::Literal { name, .. } = child && name == current_token {
let parser_type = ParserType::from_parser_info(parser); path.push(current_token.clone());
return self.validate_tokens(tokens, child, index + 1, path);
// 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);
}
} }
} }
self.parse_args(node, tokens, index, path)
// No match found }
/// 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<String>
) -> Result<ValidationResult, CommandError> {
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 { Err(CommandError::InvalidArgument {
path: path.join(" "), path: path.join(" "),
argument: token.clone(), argument: token.to_string(),
expected: self.get_suggestions_from_node(node), expected: self.get_suggestions_from_node(node),
}) })
} }
@@ -144,7 +166,7 @@ impl MinecraftCommandValidator {
fn is_greedy_parser(&self, parser_type: &ParserType) -> bool { fn is_greedy_parser(&self, parser_type: &ParserType) -> bool {
matches!( matches!(
parser_type, parser_type,
ParserType::String { kind: crate::types::mc_command::StringKind::GreedyPhrase } ParserType::String { kind: StringKind::GreedyPhrase }
| ParserType::Message | ParserType::Message
| ParserType::Component | ParserType::Component
) )
@@ -161,43 +183,35 @@ impl MinecraftCommandValidator {
} }
ParserType::Integer { min, max } => { ParserType::Integer { min, max } => {
let num: i32 = value.parse().map_err(|_| "Invalid integer")?; let num: i32 = value.parse().map_err(|_| "Invalid integer")?;
if let Some(min) = min { if let Some(min) = min && (num as f64) < *min {
if (num as f64) < *min { return Err(format!("Value must be >= {}", min));
return Err(format!("Value must be >= {}", min));
}
} }
if let Some(max) = max { if let Some(max) = max && (num as f64) > *max {
if (num as f64) > *max { return Err(format!("Value must be <= {}", max));
return Err(format!("Value must be <= {}", max));
}
} }
Ok(()) Ok(())
} }
ParserType::Float { min, max } | ParserType::Double { min, max } => { ParserType::Float { min, max } | ParserType::Double { min, max } => {
let num: f64 = value.parse().map_err(|_| "Invalid number")?; let num: f64 = value.parse().map_err(|_| "Invalid number")?;
if let Some(min) = min { if let Some(min) = min && num < *min {
if num < *min { return Err(format!("Value must be >= {}", min));
return Err(format!("Value must be >= {}", min));
}
} }
if let Some(max) = max { if let Some(max) = max && num > *max {
if num > *max { return Err(format!("Value must be <= {}", max));
return Err(format!("Value must be <= {}", max));
}
} }
Ok(()) Ok(())
} }
ParserType::String { .. } => Ok(()),
ParserType::Entity { .. } => { ParserType::Entity { .. } => {
if value.starts_with('@') || value.starts_with('"') { //TODO: have to check amount
if value.starts_with('@') {
Ok(()) Ok(())
} else { } else {
Err("Invalid entity selector".to_string()) Err("Invalid entity selector".to_string())
} }
} }
// Message and Component parsers accept any text ParserType::String { .. } => Ok(()),
ParserType::Message | ParserType::Component => Ok(()), ParserType::Message | ParserType::Component => Ok(()),
_ => todo!(), _ => Ok(()),
} }
} }
@@ -225,46 +239,4 @@ pub struct ValidationResult {
pub valid: bool, pub valid: bool,
pub path: Vec<String>, pub path: Vec<String>,
pub suggestions: Vec<String>, pub suggestions: Vec<String>,
} }
#[derive(Debug, Clone)]
pub enum CommandError {
EmptyCommand,
IncompleteCommand {
path: String,
suggestions: Vec<String>,
},
TooManyArguments {
path: String,
},
InvalidArgument {
path: String,
argument: String,
expected: Vec<String>,
},
}
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 {}

View File

@@ -0,0 +1,75 @@
#[derive(Debug, Clone)]
pub enum ParserType {
Bool,
Double { min: Option<f64>, max: Option<f64> },
Float { min: Option<f64>, max: Option<f64> },
Integer { min: Option<f64>, max: Option<f64> },
Long { min: Option<f64>, max: Option<f64> },
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<String> },
Function,
EntityAnchor,
IntRange,
FloatRange,
Dimension,
Gamemode,
Time,
ResourceOrTag { registry: Option<String> },
Resource { registry: Option<String> },
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,
}

View File

@@ -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<String>,
#[serde(default)]
children: Vec<CommandNode>,
},
Literal {
name: String,
#[serde(default)]
executable: bool,
#[serde(default)]
redirects: Vec<String>,
#[serde(default)]
children: Vec<CommandNode>,
},
Argument {
name: String,
#[serde(default)]
executable: bool,
#[serde(default)]
redirects: Vec<String>,
#[serde(default)]
children: Vec<CommandNode>,
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,
}
}
}

View File

@@ -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<serde_json::Value>,
}
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()),
}
}
}

View File

@@ -1,7 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use pest::iterators::{Pair, Pairs}; use pest::iterators::{Pair, Pairs};
use pest::Parser; use pest::Parser;
use crate::helpers::MinecraftCommandValidator; use magma_command::MinecraftCommandValidator;
use crate::parser::{MagmaParser, Rule}; use crate::parser::{MagmaParser, Rule};
use crate::types::{MagmaProjectConfig, McFunctionFile}; use crate::types::{MagmaProjectConfig, McFunctionFile};
@@ -36,15 +36,17 @@ impl MagmaCompiler {
Rule::functionExpression => { Rule::functionExpression => {
let mut pairs = pair.into_inner(); let mut pairs = pair.into_inner();
let identifier = pairs.next().unwrap(); let identifier = pairs.next().unwrap();
let args = if let Some(rule) = pairs.peek() let args = if let Some(rule) = pairs.peek()
&& rule.as_rule() == Rule::functionArgs && rule.as_rule() == Rule::functionArgs
{ { pairs.next() }
Some(pairs.next().unwrap()) else { None };
} else { None };
let statements = if let Some(rule) = pairs.peek() let statements = if let Some(rule) = pairs.peek()
&& rule.as_rule() == Rule::block && rule.as_rule() == Rule::block
{ unbox_rule(pairs.next().unwrap()) } { expand_next_rule(pairs) }
else { None }; else { None };
self.parse_function(None, identifier, args, statements)?; self.parse_function(None, identifier, args, statements)?;
} }
_ => {} _ => {}
@@ -64,25 +66,33 @@ impl MagmaCompiler {
return Ok(file); return Ok(file);
}; };
for statement in statements { for statement in statements {
let Some(statement) = self.parse_statement(statement)? else { let statements = self.parse_statement(statement)?;
continue; file.add_lines(statements);
};
file.add_line(statement);
} }
println!("{}", file.lines().join("\n")); println!("{}", file.lines().join("\n"));
println!();
Ok(file) Ok(file)
} }
fn parse_statement(&self, statement: Pair<Rule>) -> color_eyre::Result<Option<String>> { fn parse_statement(&self, statement: Pair<Rule>) -> color_eyre::Result<Vec<String>> {
let statement = statement.into_inner().next().unwrap();
let mut lines = Vec::new();
match statement.as_rule() { match statement.as_rule() {
Rule::commandLine => { Rule::commandLine => {
let command = statement.into_inner().next().unwrap(); 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<Rule>) -> color_eyre::Result<String> { fn parse_command(&self, command: Pair<Rule>) -> color_eyre::Result<String> {
@@ -93,7 +103,7 @@ impl MagmaCompiler {
let string = unbox_string(primitive); let string = unbox_string(primitive);
primitives.push(string); primitives.push(string);
} }
_ => primitives.push(primitive.as_str().to_string()) _ => { primitives.push(primitive.as_str().to_string())}
} }
} }
let unboxed_command = primitives.join(" "); let unboxed_command = primitives.join(" ");
@@ -102,9 +112,8 @@ impl MagmaCompiler {
} }
} }
fn unbox_rule(rule: Pair<Rule>) -> Option<Pairs<Rule>> { fn expand_next_rule(mut rule: Pairs<Rule>) -> Option<Pairs<Rule>> {
let mut block = rule.into_inner(); if let Some(inner) = rule.next() {
if let Some(inner) = block.next() {
Some(inner.into_inner()) Some(inner.into_inner())
} else { None } } else { None }
} }

View File

@@ -47,7 +47,7 @@ commandLine = { "command" ~ command }
commandBlock = { "command" ~ "{" ~ command* ~ "}" } commandBlock = { "command" ~ "{" ~ command* ~ "}" }
command = { commandName ~ mcArg+ ~ ";" } command = { commandName ~ mcArg+ ~ ";" }
commandName = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } commandName = @{ ASCII_ALPHA+ }
mcArg = _{ nbtBlock | string | mcPrimitive } mcArg = _{ nbtBlock | string | mcPrimitive }
nbtBlock = { "{" ~ (nbtBlock | string | !("}" | "{") ~ ANY)* ~ "}" } nbtBlock = { "{" ~ (nbtBlock | string | !("}" | "{") ~ ANY)* ~ "}" }
mcPrimitive = @{ (!(";" | "{" | "}" | "\"" | WHITESPACE) ~ ANY)+ } mcPrimitive = @{ (!(";" | "{" | "}" | "\"" | WHITESPACE) ~ ANY)+ }

View File

@@ -1,6 +1,3 @@
mod command_validator;
pub use command_validator::*;
use std::path::PathBuf; use std::path::PathBuf;
pub const FILE_EXTENSION: &str = "mg"; pub const FILE_EXTENSION: &str = "mg";

View File

@@ -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<String>,
#[serde(default)]
children: Vec<CommandNode>,
},
Literal {
name: String,
#[serde(default)]
executable: bool,
#[serde(default)]
redirects: Vec<String>,
#[serde(default)]
children: Vec<CommandNode>,
},
Argument {
name: String,
#[serde(default)]
executable: bool,
#[serde(default)]
redirects: Vec<String>,
#[serde(default)]
children: Vec<CommandNode>,
parser: ParserInfo,
},
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ParserInfo {
pub parser: String,
pub modifier: Option<serde_json::Value>,
}
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<f64>, max: Option<f64> },
Float { min: Option<f64>, max: Option<f64> },
Integer { min: Option<f64>, max: Option<f64> },
Long { min: Option<f64>, max: Option<f64> },
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<String> },
Function,
EntityAnchor,
IntRange,
FloatRange,
Dimension,
Gamemode,
Time,
ResourceOrTag { registry: Option<String> },
Resource { registry: Option<String> },
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()),
}
}
}

View File

@@ -18,6 +18,10 @@ impl McFunctionFile {
self.content.push(line); self.content.push(line);
} }
pub fn add_lines(&mut self, lines: Vec<String>) {
self.content.extend(lines);
}
pub fn lines(&self) -> &Vec<String> { pub fn lines(&self) -> &Vec<String> {
&self.content &self.content
} }

View File

@@ -1,6 +1,5 @@
mod magma_project_config; mod magma_project_config;
mod mcfunction_file; mod mcfunction_file;
pub(crate) mod mc_command;
pub use magma_project_config::*; pub use magma_project_config::*;
pub(crate) use mcfunction_file::*; pub(crate) use mcfunction_file::*;