Compare commits

...

2 Commits

Author SHA1 Message Date
7933d76a92 Add decorator 2026-02-19 18:23:20 +08:00
ba08f8b9f7 Update gitignore 2026-02-19 18:22:39 +08:00
18 changed files with 500 additions and 86 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[env]
RUST_LOG="magma=debug"

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
**/target **/target
.idea .idea
Cargo.lock

200
Cargo.lock generated
View File

@@ -17,6 +17,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.21" version = "0.6.21"
@@ -67,6 +76,12 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "anyhow"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.76" version = "0.3.76"
@@ -97,12 +112,24 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
@@ -140,7 +167,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -201,6 +228,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.7" version = "0.1.7"
@@ -242,6 +278,29 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "env_filter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -268,6 +327,16 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -335,6 +404,30 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "jiff"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde_core",
]
[[package]]
name = "jiff-static"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -366,6 +459,12 @@ dependencies = [
"scopeguard", "scopeguard",
] ]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]] [[package]]
name = "magma" name = "magma"
version = "0.1.0" version = "0.1.0"
@@ -374,6 +473,8 @@ dependencies = [
"clap_mangen", "clap_mangen",
"color-eyre", "color-eyre",
"dirs", "dirs",
"env_logger",
"log",
"magma-command", "magma-command",
"pest", "pest",
"pest_derive", "pest_derive",
@@ -388,6 +489,8 @@ name = "magma-command"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"color-eyre", "color-eyre",
"log",
"quartz_nbt",
"serde", "serde",
"serde_json", "serde_json",
] ]
@@ -405,6 +508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [ dependencies = [
"adler2", "adler2",
"simd-adler32",
] ]
[[package]] [[package]]
@@ -506,7 +610,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -525,6 +629,21 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "portable-atomic-util"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@@ -534,6 +653,31 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quartz_nbt"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf389329ba2dad9c6d898b7955a64e58c89dd52d04f4e2753b9d86eb5f49821"
dependencies = [
"anyhow",
"byteorder",
"cesu8",
"flate2",
"quartz_nbt_macros",
"serde",
]
[[package]]
name = "quartz_nbt_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "289baa0c8a4d1f840d2de528a7f8c29e0e9af48b3018172b3edad4f716e8daed"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.44" version = "1.0.44"
@@ -563,6 +707,35 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[package]] [[package]]
name = "roff" name = "roff"
version = "0.2.2" version = "0.2.2"
@@ -618,7 +791,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -673,6 +846,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simd-adler32"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.1" version = "1.15.1"
@@ -695,6 +874,17 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.114" version = "2.0.114"
@@ -723,7 +913,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -760,7 +950,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]

View File

@@ -15,12 +15,15 @@ tokio = { version = "1.49.0", features = ["full"] }
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"
env_logger = { version = "0.11.8", features = ["kv"] }
magma-command = { path = "./magma-command" } magma-command = { path = "./magma-command" }
color-eyre.workspace = true color-eyre.workspace = true
serde.workspace = true serde.workspace = true
log.workspace = true
[workspace.dependencies] [workspace.dependencies]
color-eyre = "0.6.5" color-eyre = "0.6.5"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
log = { version = "0.4.29", features = ["std"] }

View File

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

View File

@@ -5,6 +5,8 @@ edition = "2024"
[dependencies] [dependencies]
serde_json = "1.0.149" serde_json = "1.0.149"
quartz_nbt = { version = "0.2.9", features = ["serde"] }
serde.workspace = true serde.workspace = true
color-eyre.workspace = true color-eyre.workspace = true
log.workspace = true

View File

@@ -1,3 +1,6 @@
use std::error::Error;
use std::fmt::{Display};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum CommandError { pub enum CommandError {
EmptyCommand, EmptyCommand,
@@ -19,7 +22,7 @@ pub enum CommandError {
}, },
} }
impl std::fmt::Display for CommandError { impl Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
Self::EmptyCommand => write!(f, "Empty command"), Self::EmptyCommand => write!(f, "Empty command"),
@@ -45,4 +48,4 @@ impl std::fmt::Display for CommandError {
} }
} }
impl std::error::Error for CommandError {} impl Error for CommandError {}

View File

@@ -17,13 +17,10 @@ impl MinecraftCommandValidator {
pub fn validate(&self, command: &str) -> Result<ValidationResult, CommandError> { pub fn validate(&self, command: &str) -> Result<ValidationResult, CommandError> {
let tokens = self.tokenize(command); let tokens = self.tokenize(command);
if tokens.is_empty() { if tokens.is_empty() {
return Err(CommandError::EmptyCommand); return Err(CommandError::EmptyCommand);
} }
let result = self.validate_tokens(&tokens, &self.root, 0, Vec::new())?; let result = self.validate_tokens(&tokens, &self.root, 0, Vec::new())?;
Ok(result) Ok(result)
} }
@@ -87,20 +84,7 @@ impl MinecraftCommandValidator {
let current_token = &tokens[index]; let current_token = &tokens[index];
let children = node.children(); let children = node.children();
if children.is_empty() && node.redirects().is_empty() {
return if node.is_executable() {
Err(CommandError::TooManyArguments {
path: path.join(" "),
})
} else {
Err(CommandError::IncompleteCommand {
path: path.join(" "),
suggestions: self.get_suggestions_from_node(node),
})
}
}
if !node.redirects().is_empty() { if !node.redirects().is_empty() {
let nodes = self.get_redirects(&node.redirects()); let nodes = self.get_redirects(&node.redirects());
for node in nodes { for node in nodes {
@@ -113,6 +97,19 @@ impl MinecraftCommandValidator {
to: node.redirects().to_vec(), to: node.redirects().to_vec(),
}) })
} }
if children.is_empty() {
return if node.is_executable() {
Err(CommandError::TooManyArguments {
path: path.join(" "),
})
} else {
Err(CommandError::IncompleteCommand {
path: path.join(" "),
suggestions: self.get_suggestions_from_node(node),
})
}
}
for child in children { for child in children {
if let CommandNode::Literal { name, .. } = child && name == current_token { if let CommandNode::Literal { name, .. } = child && name == current_token {
@@ -210,8 +207,15 @@ impl MinecraftCommandValidator {
} }
} }
ParserType::String { .. } => Ok(()), ParserType::String { .. } => Ok(()),
ParserType::Message | ParserType::Component => Ok(()), ParserType::Message => Ok(()),
_ => Ok(()), ParserType::Component => match quartz_nbt::snbt::parse(value) {
Ok(_) => Ok(()),
Err(e) => Err(format!("Invalid component: {}", e)),
},
_ => {
log::warn!("Unknown parser type: {:?}, for '{}'", parser_type, value);
Ok(())
}
} }
} }

View File

@@ -0,0 +1,9 @@
pub struct PackMcMeta {
min_format: [u8; 2],
max_format: [u8; 2],
support_version: [u8; 2]
}
pub struct FunctionTag {
value: Vec<String>
}

View File

@@ -1,8 +1,10 @@
mod enums; mod enums;
mod parser; mod parser;
mod mcmeta;
pub use enums::*; pub use enums::*;
pub use parser::*; pub use parser::*;
pub use mcmeta::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@@ -18,6 +18,7 @@ impl ProjectCompiler {
pub async fn run(self, path: &PathBuf) -> color_eyre::Result<()> { pub async fn run(self, path: &PathBuf) -> color_eyre::Result<()> {
let _ = self.compiler.compile(path).await?; let _ = self.compiler.compile(path).await?;
Ok(()) Ok(())
} }
} }

View File

@@ -1,9 +1,15 @@
use std::cmp::PartialEq;
use std::path::PathBuf; use std::path::PathBuf;
use color_eyre::eyre::eyre;
use color_eyre::Help;
use pest::error::ErrorVariant;
use pest::iterators::{Pair, Pairs}; use pest::iterators::{Pair, Pairs};
use pest::Parser; use pest::Parser;
use magma_command::error::CommandError;
use magma_command::MinecraftCommandValidator; use magma_command::MinecraftCommandValidator;
use crate::helpers;
use crate::parser::{MagmaParser, Rule}; use crate::parser::{MagmaParser, Rule};
use crate::types::{MagmaProjectConfig, McFunctionFile}; use crate::types::{Decorator, MagmaProjectConfig, McFunctionFile, EXCLUSIVE_DECORATORS};
pub(crate) struct MagmaCompiler { pub(crate) struct MagmaCompiler {
out_functions: Vec<McFunctionFile>, out_functions: Vec<McFunctionFile>,
@@ -25,56 +31,125 @@ impl MagmaCompiler {
let main_file_path = path.join(&self.config.entrypoint); let main_file_path = path.join(&self.config.entrypoint);
let main_file_content = tokio::fs::read_to_string(&main_file_path).await?; let main_file_content = tokio::fs::read_to_string(&main_file_path).await?;
let parse_result = MagmaParser::parse(Rule::program, &main_file_content)?; let parse_result = MagmaParser::parse(Rule::program, &main_file_content)?;
let mut errors = Vec::new();
for pair in parse_result { for pair in parse_result {
self.parse(pair)?; log::debug!("Parsing pair:\n{}", helpers::format_pair(pair.clone(), 4, true));
let Err(e) = self.parse(pair) else {
continue;
};
errors.push(e);
} }
if errors.len() == 1 {
return Err(eyre!(errors[0].to_string()));
} else if !errors.is_empty() {
let mut report = eyre!("Multiple parsing errors:");
for e in errors {
report = report.section(e);
}
return Err(report);
}
Ok(self.out_functions) Ok(self.out_functions)
} }
fn parse(&mut self, pair: Pair<Rule>) -> color_eyre::Result<()> { fn parse(&mut self, pair: Pair<Rule>) -> Result<Vec<McFunctionFile>, pest::error::Error<Rule>> {
let mut out_functions = Vec::new();
match pair.as_rule() { match pair.as_rule() {
Rule::functionExpression => { Rule::functionExpression => {
let mut pairs = pair.into_inner(); let pairs = pair.into_inner();
let identifier = pairs.next().unwrap(); let identifier = pairs.find_first_tagged("name").unwrap();
let args = pairs.find_first_tagged("args");
let statements = pairs.find_first_tagged("block").unwrap().into_inner();
let decorators = if let Some(decorators) = pairs.find_first_tagged("decorators") {
match self.parse_decorators(decorators) {
Ok(decorators) => decorators,
Err(error) => return Err(error)
}
} else { Vec::with_capacity(0) };
let args = if let Some(rule) = pairs.peek() let function_file = self.parse_function(&decorators, None, identifier, args, statements)?;
&& rule.as_rule() == Rule::functionArgs out_functions.push(function_file);
{ pairs.next() }
else { None };
let statements = if let Some(rule) = pairs.peek()
&& rule.as_rule() == Rule::block
{ expand_next_rule(pairs) }
else { None };
self.parse_function(None, identifier, args, statements)?;
} }
_ => {} _ => {}
} }
Ok(()) Ok(out_functions)
}
fn parse_decorators(&self, decorators: Pair<Rule>) -> Result<Vec<Decorator>, pest::error::Error<Rule>> {
let rules = decorators.into_inner();
let mut parsed_decorators = Vec::<(Decorator, pest::Span)>::with_capacity(rules.len());
for rule in rules {
let rule = rule.into_inner().find_first_tagged("value").unwrap();
let span = rule.as_span();
let value = rule.as_str();
let decorator_result = Decorator::try_from(value);
let decorator = match decorator_result {
Ok(value) => value,
Err(e) => {
let error = pest::error::Error::<Rule>::new_from_span(ErrorVariant::CustomError {
message: e
}, rule.as_span());
return Err(error);
}
};
if parsed_decorators.iter().any(|(d, _)| d == &decorator) {
let error = pest::error::Error::<Rule>::new_from_span(
ErrorVariant::CustomError {
message: format!(
"Decorator `{}` already exists", &decorator
),
},
span,
);
return Err(error);
}
for (existing, _) in &parsed_decorators {
if Self::is_exclusive_pair(existing, &decorator) {
let error = pest::error::Error::<Rule>::new_from_span(
ErrorVariant::CustomError {
message: format!(
"Decorator `{}` conflicts with `{}`", decorator, existing
),
},
span,
);
return Err(error);
}
}
parsed_decorators.push((decorator, span))
}
Ok(
parsed_decorators.into_iter()
.map(|(decorator, _)| decorator)
.collect::<Vec<_>>()
)
}
fn is_exclusive_pair(a: &Decorator, b: &Decorator) -> bool {
EXCLUSIVE_DECORATORS.iter().any(|pair| {
(pair[0].eq(a) && pair[1].eq(b)) || (pair[0].eq(b) && pair[1].eq(a))
})
} }
fn parse_function( fn parse_function(
&self, &self,
decorators: &[Decorator],
namespace: Option<String>, namespace: Option<String>,
identifier: Pair<Rule>, identifier: Pair<Rule>,
args: Option<Pair<Rule>>, args: Option<Pair<Rule>>,
statements: Option<Pairs<Rule>> statements: Pairs<Rule>
) -> color_eyre::Result<McFunctionFile> { ) -> Result<McFunctionFile, pest::error::Error<Rule>> {
let mut file = McFunctionFile::new(namespace, identifier.as_str().to_string()); log::debug!("Compiling '{}'", identifier.as_str());
let Some(statements) = statements else { let mut file = McFunctionFile::new(namespace, identifier.as_str().to_string(), decorators);
return Ok(file);
};
for statement in statements { for statement in statements {
let statements = self.parse_statement(statement)?; let statements = self.parse_statement(statement)?;
file.add_lines(statements); file.add_lines(statements);
} }
println!("{}", file.lines().join("\n")); log::debug!("Compile Result:\n{}", file.lines().join("\n"));
println!();
Ok(file) Ok(file)
} }
fn parse_statement(&self, statement: Pair<Rule>) -> color_eyre::Result<Vec<String>> { fn parse_statement(&self, statement: Pair<Rule>) -> Result<Vec<String>, pest::error::Error<Rule>> {
let statement = statement.into_inner().next().unwrap(); let statement = statement.into_inner().next().unwrap();
let mut lines = Vec::new(); let mut lines = Vec::new();
match statement.as_rule() { match statement.as_rule() {
@@ -88,37 +163,29 @@ impl MagmaCompiler {
lines.push(self.parse_command(command)?); lines.push(self.parse_command(command)?);
} }
} }
_ => { _ => {}
println!("{:?}", statement);
}
} }
Ok(lines) Ok(lines)
} }
fn parse_command(&self, command: Pair<Rule>) -> color_eyre::Result<String> { fn parse_command(&self, command: Pair<Rule>) -> Result<String, pest::error::Error<Rule>> {
let mut primitives = Vec::<String>::new(); let mut primitives = Vec::<String>::new();
let span = command.as_span();
for primitive in command.into_inner() { for primitive in command.into_inner() {
match primitive.as_rule() { match primitive.as_rule() {
Rule::string => { Rule::string => {
let string = unbox_string(primitive); let string = helpers::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(" ");
self.command_validator.validate(&unboxed_command)?; if let Err(e) = self.command_validator.validate(&unboxed_command) {
return Err(pest::error::Error::<Rule>::new_from_span(ErrorVariant::CustomError {
message: e.to_string()
}, span));
}
Ok(unboxed_command) Ok(unboxed_command)
} }
} }
fn expand_next_rule(mut rule: Pairs<Rule>) -> Option<Pairs<Rule>> {
if let Some(inner) = rule.next() {
Some(inner.into_inner())
} else { None }
}
fn unbox_string(string: Pair<Rule>) -> String {
let str = string.as_str();
str[1..str.len()-1].to_string()
}

View File

@@ -8,15 +8,23 @@ fileScopeNamespaceExpression = {
namespaceExpression = { namespaceExpression = {
"namespace" ~ namespaceIdentifier ~ "{" ~ functionExpression* ~ "}" "namespace" ~ namespaceIdentifier ~ "{" ~ functionExpression* ~ "}"
} }
includeExpression = {
"include" ~ string ~ ";"
}
decorators = {
decoratorExpression*
}
decoratorExpression = {
"@" ~ #value = identifier ~ NEWLINE?
}
// function
functionExpression = { functionExpression = {
"function" ~ identifier ~ "(" ~ functionArgs? ~ ")" ~ block #decorators = decorators? ~ "function" ~ #name = identifier ~ "(" ~ #args = functionArgs? ~ ")" ~ #block = block
} }
functionArgs = { functionArgs = {
identifier ~ ("," ~ identifier)* identifier ~ ("," ~ identifier)*
} }
includeExpression = {
"include" ~ string ~ ";"
}
// rules // rules
block = { "{" ~ statements* ~ "}" } block = { "{" ~ statements* ~ "}" }
@@ -25,7 +33,7 @@ namespaceIdentifier = @{ identifier ~ ("." ~ identifier)* }
identifier = @{ (ASCII_ALPHA) ~ (ASCII_ALPHANUMERIC | "_")* } identifier = @{ (ASCII_ALPHA) ~ (ASCII_ALPHANUMERIC | "_")* }
number = @{ "-"? ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? } number = @{ "-"? ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }
string = @{ "\"" ~ ( escapeCharacters | !"\"" ~ ANY )* ~ "\"" } string = @{ "\"" ~ ( escapeCharacters | !"\"" ~ ANY )* ~ "\"" }
statements = { commandStatement | returnStatement | ifStatement } statements = { commandStatement | returnStatement | ifStatement | ifCommandStatement | assignmentStatement }
// statements // statements
ifStatement = { ifStatement = {
@@ -38,18 +46,24 @@ elseStatement = {
"else" ~ block "else" ~ block
} }
ifCommandStatement = {
"if" ~ "command" ~ command ~ block ~ elseIfCommandStatement* ~ elseStatement?
}
elseIfCommandStatement = {
"else" ~ "if" ~ "command" ~ command ~ block
}
returnStatement = { "return" ~ value ~ ";" } returnStatement = { "return" ~ value ~ ";" }
assignmentStatement = { identifier ~ "=" ~ binaryExpression ~ ";" } assignmentStatement = { identifier ~ "=" ~ binaryExpression ~ ";" }
commandStatement = _{ commandLine | commandBlock } commandStatement = _{ commandLine | commandBlock }
// mc commands // mc commands
commandLine = { "command" ~ command } commandLine = { "command" ~ command ~ ";" }
commandBlock = { "command" ~ "{" ~ command* ~ "}" } commandBlock = { "command" ~ "{" ~ (command ~ ";")* ~ "}" }
command = { commandName ~ mcArg+ ~ ";" } command = { mcArg+ }
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)+ }
// Binary operations with precedence (using PEG climbing) // Binary operations with precedence (using PEG climbing)
@@ -86,7 +100,7 @@ OR = { "||" }
AND = { "&&" } AND = { "&&" }
// constants // constants
escapeCharacters = @{ "\\" ~ ("\"" | "\\" | "n" | "r" | "t") } escapeCharacters = @{ "\\" ~ ANY }
WHITESPACE = _{ " " | "\t" | "\r" | "\n" } WHITESPACE = _{ " " | "\t" | "\r" | "\n" }
COMMENT = _{ multiLineComment | singleLineComment } COMMENT = _{ multiLineComment | singleLineComment }
multiLineComment = { "/*" ~ (!"*/" ~ ANY)* ~ "*/" } multiLineComment = { "/*" ~ (!"*/" ~ ANY)* ~ "*/" }

View File

@@ -1,4 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use pest::iterators::{Pair, Pairs};
use crate::parser::Rule;
pub const FILE_EXTENSION: &str = "mg"; pub const FILE_EXTENSION: &str = "mg";
@@ -14,4 +16,72 @@ pub(crate) fn expand_tilde(path: PathBuf) -> color_eyre::Result<PathBuf> {
return Ok(home.join(relative)); return Ok(home.join(relative));
} }
Ok(path) Ok(path)
}
pub(crate) fn expand_next_rule(mut rule: Pairs<Rule>) -> Option<Pairs<Rule>> {
if let Some(inner) = rule.next() {
Some(inner.into_inner())
} else { None }
}
pub(crate) fn unbox_string(string: Pair<Rule>) -> String {
let str = string.as_str();
str[1..str.len()-1].to_string()
}
pub(crate) fn format_pair(pair: Pair<Rule>, indent_level: usize, is_newline: bool) -> String {
let indent = if is_newline {
" ".repeat(indent_level)
} else {
String::new()
};
let children: Vec<_> = pair.clone().into_inner().collect();
let len = children.len();
let children: Vec<_> = children
.into_iter()
.map(|pair| {
format_pair(
pair,
if len > 1 {
indent_level + 1
} else {
indent_level
},
len > 1,
)
})
.collect();
let dash = if is_newline { "- " } else { "" };
let pair_tag = match pair.as_node_tag() {
Some(tag) => format!("(#{}) ", tag),
None => String::new(),
};
match len {
0 => format!(
"{}{}{}{:?}: {:?}",
indent,
dash,
pair_tag,
pair.as_rule(),
pair.as_span().as_str()
),
1 => format!(
"{}{}{}{:?} > {}",
indent,
dash,
pair_tag,
pair.as_rule(),
children[0]
),
_ => format!(
"{}{}{}{:?}\n{}",
indent,
dash,
pair_tag,
pair.as_rule(),
children.join("\n")
),
}
} }

View File

@@ -8,6 +8,8 @@ mod compiler;
#[tokio::main] #[tokio::main]
async fn main() -> color_eyre::Result<()> { async fn main() -> color_eyre::Result<()> {
env_logger::init();
color_eyre::install()?;
cli::Cli::parse().run().await?; cli::Cli::parse().run().await?;
Ok(()) Ok(())
} }

38
src/types/decorator.rs Normal file
View File

@@ -0,0 +1,38 @@
use std::fmt::{Display, Formatter};
pub const EXCLUSIVE_DECORATORS: [[Decorator; 2]; 1] = [
[Decorator::Load, Decorator::Tick]
];
#[derive(Clone, Debug, PartialEq)]
pub enum Decorator {
Tick, Load
}
impl TryFrom<&str> for Decorator {
type Error = String;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"tick" => Ok(Decorator::Tick),
"load" => Ok(Decorator::Load),
_ => Err(String::from("Invalid decorator type")),
}
}
}
impl Into<String> for &Decorator {
fn into(self) -> String {
match self {
Decorator::Tick => "tick".to_string(),
Decorator::Load => "load".to_string(),
}
}
}
impl Display for Decorator {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let value: String = self.into();
write!(f, "{}", value)
}
}

View File

@@ -1,16 +1,20 @@
use crate::types::decorator::Decorator;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct McFunctionFile { pub(crate) struct McFunctionFile {
content: Vec<String>, content: Vec<String>,
namespace: Option<String>, namespace: Option<String>,
name: String name: String,
attributes: Vec<Decorator>
} }
impl McFunctionFile { impl McFunctionFile {
pub fn new(namespace: Option<String>, name: String) -> Self { pub fn new(namespace: Option<String>, name: String, attributes: &[Decorator]) -> Self {
Self { Self {
content: Vec::new(), content: Vec::new(),
namespace, namespace,
name name,
attributes: attributes.to_vec()
} }
} }

View File

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