diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8e9034f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Functions + - without arguments +- Raw commands +- Comments +- Doc comments +- `if`-`else` statements +- execute blocks +- `run` keyword +- lua blocks +- imports +- group + +### Changed + +### Removed diff --git a/Cargo.toml b/Cargo.toml index ad3a18e..cd636c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ getset = "0.1.2" mlua = { version = "0.9.7", features = ["lua54", "vendored"], optional = true } path-absolutize = "3.1.1" serde = { version = "1.0.197", features = ["derive", "rc"], optional = true } -shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", default-features = false, optional = true, rev = "7efe73eb80fb66d3d9eb67eeb7bcb10727462956" } +shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", default-features = false, optional = true, rev = "a2d20dab8ea97bbd873edafb23afaad34292457f" } strum = { version = "0.26.2", features = ["derive"] } strum_macros = "0.26.2" thiserror = "1.0.58" diff --git a/grammar.md b/grammar.md index 469d952..fcacd1a 100644 --- a/grammar.md +++ b/grammar.md @@ -14,7 +14,12 @@ Namespace: 'namespace' StringLiteral; ### Declaration ```ebnf -Declaration: FunctionDeclaration; +Declaration: FunctionDeclaration | Import; +``` + +### Import +```ebnf +Import: 'from' StringLiteral 'import' Identifier; ``` ### FunctionDeclaration diff --git a/src/base/file_provider.rs b/src/base/file_provider.rs index a62e3bc..ca69af9 100644 --- a/src/base/file_provider.rs +++ b/src/base/file_provider.rs @@ -96,5 +96,22 @@ mod vfs { assert_eq!(normalize_path_str("./a/b/c"), Some("a/b/c".to_string())); assert_eq!(normalize_path_str("../a/b/c"), None); } + + #[test] + fn test_vfolder_provider() { + let mut dir = VFolder::new(); + dir.add_file("foo.txt", VFile::Text("foo".to_string())); + dir.add_file("bar/baz.txt", VFile::Text("bar, baz".to_string())); + + assert_eq!(dir.read_to_string("foo.txt").unwrap(), "foo".to_string()); + assert_eq!( + dir.read_to_string("bar/baz.txt").unwrap(), + "bar, baz".to_string() + ); + assert!(matches!( + dir.read_to_string("nonexistent.txt"), + Err(Error::IoError(_)) + )); + } } } diff --git a/src/syntax/syntax_tree/declaration.rs b/src/syntax/syntax_tree/declaration.rs index f0f87b4..5eccd54 100644 --- a/src/syntax/syntax_tree/declaration.rs +++ b/src/syntax/syntax_tree/declaration.rs @@ -26,6 +26,7 @@ use super::{statement::Block, ConnectedList}; /// ``` ebnf /// Declaration: /// Function +/// | Import /// ; /// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/tests/parsing/invalid.shu b/tests/parsing/invalid.shu new file mode 100644 index 0000000..7f7f885 --- /dev/null +++ b/tests/parsing/invalid.shu @@ -0,0 +1,3 @@ +fn main() { + /say Invalid +} \ No newline at end of file diff --git a/tests/parsing/main.rs b/tests/parsing/main.rs new file mode 100644 index 0000000..a0a5741 --- /dev/null +++ b/tests/parsing/main.rs @@ -0,0 +1,52 @@ +use std::path::Path; + +use shulkerbox::virtual_fs::{VFile, VFolder}; +use shulkerscript::{ + base::source_file::SourceElement, + syntax::syntax_tree::{declaration::Declaration, statement::Statement}, +}; + +#[test] +fn parsing_test1() { + let source = include_str!("./test1.shu"); + let mut dir = VFolder::new(); + dir.add_file("test1.shu", VFile::Text(source.to_string())); + + let parsed = shulkerscript::parse(&dir, Path::new("test1.shu")).expect("Failed to parse"); + + assert_eq!( + parsed.namespace().namespace_name().str_content(), + "parsing-test" + ); + + let declarations = parsed.declarations(); + assert_eq!(declarations.len(), 1); + + let main_fn = declarations.first().unwrap(); + if let Declaration::Function(func) = main_fn { + assert!(!func.is_public()); + assert_eq!(func.identifier().span().str(), "main"); + assert!(func.parameters().is_none()); + let annotations = func.annotations(); + assert!(annotations.is_empty()); + let statements = func.block().statements(); + assert_eq!(statements.len(), 1); + let hello_cmd = statements.first().unwrap(); + if let Statement::LiteralCommand(hello_cmd) = hello_cmd { + assert_eq!(hello_cmd.span().str(), "/say Hello, World!"); + } else { + panic!("Expected hello command"); + } + } else { + panic!("Expected main function declaration"); + } +} + +#[test] +fn parsing_invalid() { + let source = include_str!("./invalid.shu"); + let mut dir = VFolder::new(); + dir.add_file("invalid.shu", VFile::Text(source.to_string())); + + shulkerscript::parse(&dir, Path::new("invalid.shu")).expect_err("Expecting parsing failure"); +} diff --git a/tests/parsing/test1.shu b/tests/parsing/test1.shu new file mode 100644 index 0000000..abb56d4 --- /dev/null +++ b/tests/parsing/test1.shu @@ -0,0 +1,5 @@ +namespace "parsing-test"; + +fn main() { + /say Hello, World! +} \ No newline at end of file diff --git a/tests/transpiling/main.rs b/tests/transpiling/main.rs new file mode 100644 index 0000000..d55383d --- /dev/null +++ b/tests/transpiling/main.rs @@ -0,0 +1,43 @@ +use shulkerbox::{ + datapack::{Command, Condition, Datapack, Execute}, + virtual_fs::{VFile, VFolder}, +}; + +#[test] +fn transpile_test1() { + let source = include_str!("./test1.shu"); + let mut dir = VFolder::new(); + dir.add_file("test1.shu", VFile::Text(source.to_string())); + + let transpiled = shulkerscript::transpile(&dir, &[("test1".to_string(), "./test1.shu")]) + .expect("Failed to transpile"); + + let expected = { + let mut dp = Datapack::new(48); + + let namespace = dp.namespace_mut("transpiling-test"); + + let main_fn = namespace.function_mut("main"); + + main_fn.add_command(Command::Raw("say Hello, World!".to_string())); + + let exec_cmd = Command::Execute(Execute::As( + "@a".to_string(), + Box::new(Execute::If( + Condition::Atom("entity @p[distance=..5]".to_string()), + Box::new(Execute::Run(Box::new(Command::Raw( + "say You are close to me!".to_string(), + )))), + Some(Box::new(Execute::Run(Box::new(Command::Raw( + "say You are alone!".to_string(), + ))))), + )), + )); + + main_fn.add_command(exec_cmd); + + dp + }; + + assert_eq!(transpiled, expected); +} diff --git a/tests/transpiling/test1.shu b/tests/transpiling/test1.shu new file mode 100644 index 0000000..3e4ff51 --- /dev/null +++ b/tests/transpiling/test1.shu @@ -0,0 +1,12 @@ +namespace "transpiling-test"; + +#[deobfuscate] +fn main() { + /say Hello, World! + + as("@a"), if ("entity @p[distance=..5]") { + /say You are close to me! + } else { + /say You are alone! + } +} \ No newline at end of file