diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..59b3dac --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUST_LOG="magma=debug" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index af5ded9..4bf34f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "anstream" version = "0.6.21" @@ -67,6 +76,12 @@ dependencies = [ "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]] name = "backtrace" version = "0.3.76" @@ -97,12 +112,24 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -140,7 +167,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -201,6 +228,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -242,6 +278,29 @@ dependencies = [ "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]] name = "equivalent" version = "1.0.2" @@ -268,6 +327,16 @@ dependencies = [ "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]] name = "generic-array" version = "0.14.7" @@ -335,6 +404,30 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "lazy_static" version = "1.5.0" @@ -366,6 +459,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "magma" version = "0.1.0" @@ -374,6 +473,8 @@ dependencies = [ "clap_mangen", "color-eyre", "dirs", + "env_logger", + "log", "magma-command", "pest", "pest_derive", @@ -388,6 +489,8 @@ name = "magma-command" version = "0.1.0" dependencies = [ "color-eyre", + "log", + "quartz_nbt", "serde", "serde_json", ] @@ -405,6 +508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -506,7 +610,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -525,6 +629,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "proc-macro2" version = "1.0.106" @@ -534,6 +653,31 @@ dependencies = [ "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]] name = "quote" version = "1.0.44" @@ -563,6 +707,35 @@ dependencies = [ "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]] name = "roff" version = "0.2.2" @@ -618,7 +791,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -673,6 +846,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "smallvec" version = "1.15.1" @@ -695,6 +874,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "syn" version = "2.0.114" @@ -723,7 +913,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -760,7 +950,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d53217c..f0e743a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,15 @@ tokio = { version = "1.49.0", features = ["full"] } toml = "0.9.11" semver = { version = "1.0.27", features = ["serde"] } dirs = "6.0.0" +env_logger = { version = "0.11.8", features = ["kv"] } magma-command = { path = "./magma-command" } color-eyre.workspace = true serde.workspace = true +log.workspace = true [workspace.dependencies] color-eyre = "0.6.5" -serde = { version = "1.0.228", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0.228", features = ["derive"] } +log = { version = "0.4.29", features = ["std"] } diff --git a/assets/commands.json b/assets/commands.json index ccc4c25..59448d4 100644 --- a/assets/commands.json +++ b/assets/commands.json @@ -14849,7 +14849,7 @@ "type": "literal", "name": "run", "executable": false, - "redirects": [], + "redirects": [""], "children": [] }, { diff --git a/magma-command/Cargo.toml b/magma-command/Cargo.toml index 686af77..facf0c2 100644 --- a/magma-command/Cargo.toml +++ b/magma-command/Cargo.toml @@ -5,6 +5,8 @@ edition = "2024" [dependencies] serde_json = "1.0.149" +quartz_nbt = { version = "0.2.9", features = ["serde"] } serde.workspace = true color-eyre.workspace = true +log.workspace = true diff --git a/magma-command/src/error.rs b/magma-command/src/error.rs index b2033e7..ed24ed3 100644 --- a/magma-command/src/error.rs +++ b/magma-command/src/error.rs @@ -1,3 +1,6 @@ +use std::error::Error; +use std::fmt::{Display}; + #[derive(Debug, Clone)] pub enum CommandError { 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 { match self { Self::EmptyCommand => write!(f, "Empty command"), @@ -45,4 +48,4 @@ impl std::fmt::Display for CommandError { } } -impl std::error::Error for CommandError {} \ No newline at end of file +impl Error for CommandError {} \ No newline at end of file diff --git a/magma-command/src/lib.rs b/magma-command/src/lib.rs index 0ffba9d..ee4d700 100644 --- a/magma-command/src/lib.rs +++ b/magma-command/src/lib.rs @@ -17,13 +17,10 @@ impl MinecraftCommandValidator { pub fn validate(&self, command: &str) -> Result { let tokens = self.tokenize(command); - if tokens.is_empty() { return Err(CommandError::EmptyCommand); } - let result = self.validate_tokens(&tokens, &self.root, 0, Vec::new())?; - Ok(result) } @@ -87,20 +84,7 @@ impl MinecraftCommandValidator { let current_token = &tokens[index]; 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() { let nodes = self.get_redirects(&node.redirects()); for node in nodes { @@ -113,6 +97,19 @@ impl MinecraftCommandValidator { 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 { if let CommandNode::Literal { name, .. } = child && name == current_token { @@ -210,8 +207,15 @@ impl MinecraftCommandValidator { } } ParserType::String { .. } => Ok(()), - ParserType::Message | ParserType::Component => Ok(()), - _ => Ok(()), + ParserType::Message => 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(()) + } } } diff --git a/magma-command/src/types/mcmeta.rs b/magma-command/src/types/mcmeta.rs new file mode 100644 index 0000000..232ba31 --- /dev/null +++ b/magma-command/src/types/mcmeta.rs @@ -0,0 +1,9 @@ +pub struct PackMcMeta { + min_format: [u8; 2], + max_format: [u8; 2], + support_version: [u8; 2] +} + +pub struct FunctionTag { + value: Vec +} \ No newline at end of file diff --git a/magma-command/src/types/mod.rs b/magma-command/src/types/mod.rs index 0b45f7f..c43115c 100644 --- a/magma-command/src/types/mod.rs +++ b/magma-command/src/types/mod.rs @@ -1,8 +1,10 @@ mod enums; mod parser; +mod mcmeta; pub use enums::*; pub use parser::*; +pub use mcmeta::*; use serde::{Deserialize, Serialize}; diff --git a/src/cli/project_compiler.rs b/src/cli/project_compiler.rs index 6132a28..1b84f88 100644 --- a/src/cli/project_compiler.rs +++ b/src/cli/project_compiler.rs @@ -18,6 +18,7 @@ impl ProjectCompiler { pub async fn run(self, path: &PathBuf) -> color_eyre::Result<()> { let _ = self.compiler.compile(path).await?; + Ok(()) } } \ No newline at end of file diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index dd96c2b..3546052 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,9 +1,15 @@ +use std::cmp::PartialEq; use std::path::PathBuf; +use color_eyre::eyre::eyre; +use color_eyre::Help; +use pest::error::ErrorVariant; use pest::iterators::{Pair, Pairs}; use pest::Parser; +use magma_command::error::CommandError; use magma_command::MinecraftCommandValidator; +use crate::helpers; use crate::parser::{MagmaParser, Rule}; -use crate::types::{MagmaProjectConfig, McFunctionFile}; +use crate::types::{Decorator, MagmaProjectConfig, McFunctionFile, EXCLUSIVE_DECORATORS}; pub(crate) struct MagmaCompiler { out_functions: Vec, @@ -25,56 +31,125 @@ impl MagmaCompiler { let main_file_path = path.join(&self.config.entrypoint); let main_file_content = tokio::fs::read_to_string(&main_file_path).await?; let parse_result = MagmaParser::parse(Rule::program, &main_file_content)?; + let mut errors = Vec::new(); 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) } - fn parse(&mut self, pair: Pair) -> color_eyre::Result<()> { + fn parse(&mut self, pair: Pair) -> Result, pest::error::Error> { + let mut out_functions = Vec::new(); match pair.as_rule() { Rule::functionExpression => { - let mut pairs = pair.into_inner(); - let identifier = pairs.next().unwrap(); + let pairs = pair.into_inner(); + 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() - && rule.as_rule() == Rule::functionArgs - { 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)?; + let function_file = self.parse_function(&decorators, None, identifier, args, statements)?; + out_functions.push(function_file); } _ => {} } - Ok(()) + Ok(out_functions) + } + + fn parse_decorators(&self, decorators: Pair) -> Result, pest::error::Error> { + 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::::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::::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::::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::>() + ) + } + + 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( &self, + decorators: &[Decorator], namespace: Option, identifier: Pair, args: Option>, - statements: Option> - ) -> color_eyre::Result { - let mut file = McFunctionFile::new(namespace, identifier.as_str().to_string()); - let Some(statements) = statements else { - return Ok(file); - }; + statements: Pairs + ) -> Result> { + log::debug!("Compiling '{}'", identifier.as_str()); + let mut file = McFunctionFile::new(namespace, identifier.as_str().to_string(), decorators); for statement in statements { let statements = self.parse_statement(statement)?; file.add_lines(statements); } - println!("{}", file.lines().join("\n")); - println!(); + log::debug!("Compile Result:\n{}", file.lines().join("\n")); Ok(file) } - fn parse_statement(&self, statement: Pair) -> color_eyre::Result> { + fn parse_statement(&self, statement: Pair) -> Result, pest::error::Error> { let statement = statement.into_inner().next().unwrap(); let mut lines = Vec::new(); match statement.as_rule() { @@ -88,37 +163,29 @@ impl MagmaCompiler { lines.push(self.parse_command(command)?); } } - _ => { - println!("{:?}", statement); - } + _ => {} } Ok(lines) } - fn parse_command(&self, command: Pair) -> color_eyre::Result { + fn parse_command(&self, command: Pair) -> Result> { let mut primitives = Vec::::new(); + let span = command.as_span(); for primitive in command.into_inner() { match primitive.as_rule() { Rule::string => { - let string = unbox_string(primitive); + let string = helpers::unbox_string(primitive); primitives.push(string); } _ => { primitives.push(primitive.as_str().to_string())} } } 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::::new_from_span(ErrorVariant::CustomError { + message: e.to_string() + }, span)); + } Ok(unboxed_command) } } - -fn expand_next_rule(mut rule: Pairs) -> Option> { - if let Some(inner) = rule.next() { - Some(inner.into_inner()) - } else { None } -} - -fn unbox_string(string: Pair) -> String { - let str = string.as_str(); - str[1..str.len()-1].to_string() -} diff --git a/src/grammar.pest b/src/grammar.pest index 0f3539b..a8368f3 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -8,15 +8,23 @@ fileScopeNamespaceExpression = { namespaceExpression = { "namespace" ~ namespaceIdentifier ~ "{" ~ functionExpression* ~ "}" } +includeExpression = { + "include" ~ string ~ ";" +} +decorators = { + decoratorExpression* +} +decoratorExpression = { + "@" ~ #value = identifier ~ NEWLINE? +} + +// function functionExpression = { - "function" ~ identifier ~ "(" ~ functionArgs? ~ ")" ~ block + #decorators = decorators? ~ "function" ~ #name = identifier ~ "(" ~ #args = functionArgs? ~ ")" ~ #block = block } functionArgs = { identifier ~ ("," ~ identifier)* } -includeExpression = { - "include" ~ string ~ ";" -} // rules block = { "{" ~ statements* ~ "}" } @@ -25,7 +33,7 @@ namespaceIdentifier = @{ identifier ~ ("." ~ identifier)* } identifier = @{ (ASCII_ALPHA) ~ (ASCII_ALPHANUMERIC | "_")* } number = @{ "-"? ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? } string = @{ "\"" ~ ( escapeCharacters | !"\"" ~ ANY )* ~ "\"" } -statements = { commandStatement | returnStatement | ifStatement } +statements = { commandStatement | returnStatement | ifStatement | ifCommandStatement | assignmentStatement } // statements ifStatement = { @@ -38,18 +46,24 @@ elseStatement = { "else" ~ block } +ifCommandStatement = { + "if" ~ "command" ~ command ~ block ~ elseIfCommandStatement* ~ elseStatement? +} +elseIfCommandStatement = { + "else" ~ "if" ~ "command" ~ command ~ block +} + returnStatement = { "return" ~ value ~ ";" } assignmentStatement = { identifier ~ "=" ~ binaryExpression ~ ";" } commandStatement = _{ commandLine | commandBlock } // mc commands -commandLine = { "command" ~ command } -commandBlock = { "command" ~ "{" ~ command* ~ "}" } +commandLine = { "command" ~ command ~ ";" } +commandBlock = { "command" ~ "{" ~ (command ~ ";")* ~ "}" } -command = { commandName ~ mcArg+ ~ ";" } -commandName = @{ ASCII_ALPHA+ } +command = { mcArg+ } mcArg = _{ nbtBlock | string | mcPrimitive } -nbtBlock = { "{" ~ (nbtBlock | string | !("}" | "{") ~ ANY)* ~ "}" } +nbtBlock = @{ "{" ~ (nbtBlock | string | !("}" | "{") ~ ANY)* ~ "}" } mcPrimitive = @{ (!(";" | "{" | "}" | "\"" | WHITESPACE) ~ ANY)+ } // Binary operations with precedence (using PEG climbing) @@ -86,7 +100,7 @@ OR = { "||" } AND = { "&&" } // constants -escapeCharacters = @{ "\\" ~ ("\"" | "\\" | "n" | "r" | "t") } +escapeCharacters = @{ "\\" ~ ANY } WHITESPACE = _{ " " | "\t" | "\r" | "\n" } COMMENT = _{ multiLineComment | singleLineComment } multiLineComment = { "/*" ~ (!"*/" ~ ANY)* ~ "*/" } diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 4e50ec7..5673737 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1,4 +1,6 @@ use std::path::PathBuf; +use pest::iterators::{Pair, Pairs}; +use crate::parser::Rule; pub const FILE_EXTENSION: &str = "mg"; @@ -14,4 +16,72 @@ pub(crate) fn expand_tilde(path: PathBuf) -> color_eyre::Result { return Ok(home.join(relative)); } Ok(path) +} + +pub(crate) fn expand_next_rule(mut rule: Pairs) -> Option> { + if let Some(inner) = rule.next() { + Some(inner.into_inner()) + } else { None } +} + +pub(crate) fn unbox_string(string: Pair) -> String { + let str = string.as_str(); + str[1..str.len()-1].to_string() +} + +pub(crate) fn format_pair(pair: Pair, 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") + ), + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ee9529d..bb82263 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,8 @@ mod compiler; #[tokio::main] async fn main() -> color_eyre::Result<()> { + env_logger::init(); + color_eyre::install()?; cli::Cli::parse().run().await?; Ok(()) } \ No newline at end of file diff --git a/src/types/decorator.rs b/src/types/decorator.rs new file mode 100644 index 0000000..39c0da7 --- /dev/null +++ b/src/types/decorator.rs @@ -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 { + match s { + "tick" => Ok(Decorator::Tick), + "load" => Ok(Decorator::Load), + _ => Err(String::from("Invalid decorator type")), + } + } +} + +impl Into 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) + } +} \ No newline at end of file diff --git a/src/types/mcfunction_file.rs b/src/types/mcfunction_file.rs index 512d474..91b9780 100644 --- a/src/types/mcfunction_file.rs +++ b/src/types/mcfunction_file.rs @@ -1,16 +1,20 @@ +use crate::types::decorator::Decorator; + #[derive(Debug, Clone)] pub(crate) struct McFunctionFile { content: Vec, namespace: Option, - name: String + name: String, + attributes: Vec } impl McFunctionFile { - pub fn new(namespace: Option, name: String) -> Self { + pub fn new(namespace: Option, name: String, attributes: &[Decorator]) -> Self { Self { content: Vec::new(), namespace, - name + name, + attributes: attributes.to_vec() } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 7f73038..eb2442a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,7 @@ mod magma_project_config; mod mcfunction_file; +mod decorator; pub use magma_project_config::*; -pub(crate) use mcfunction_file::*; \ No newline at end of file +pub(crate) use mcfunction_file::*; +pub use decorator::*;