diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15fa27ce..37b70de1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,7 +77,7 @@ jobs: profile: minimal - name: Build Release - run: cargo build --release --locked --verbose --target ${{ matrix.target }} + run: cargo build --release --locked --verbose --target ${{ matrix.target }} --package cli env: # Build into a known directory so we can find our build artifact more # easily. diff --git a/.gitignore b/.gitignore index 239d02c8..d45879be 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ # Zap Input & Output /network -net.zap \ No newline at end of file +net.zap + +# WASM +zap/pkg diff --git a/Cargo.lock b/Cargo.lock index 178f1524..b918ed49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "ascii-canvas" version = "3.0.0" @@ -101,6 +107,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "cfg-if" version = "1.0.0" @@ -147,6 +159,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "zap", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -587,18 +608,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", @@ -638,6 +659,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + [[package]] name = "winapi" version = "0.3.9" @@ -796,8 +871,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" name = "zap" version = "0.1.0" dependencies = [ - "clap", "lalrpop", "lalrpop-util", "num-traits", + "thiserror", + "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index 88b2bce3..3e5c6489 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,2 @@ -[package] -name = "zap" -authors = ["Redblox Organization"] -description = "A blazingly fast networking solution for Roblox." -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[build-dependencies] -lalrpop = "0.20.0" - -[dependencies] -clap = { version = "4.4.11", features = ["derive"] } -lalrpop-util = { version = "0.20.0", features = ["lexer", "unicode"] } -num-traits = "0.2.17" +[workspace] +members = ["zap", "cli"] diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 00000000..7fc080b5 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cli" +authors = ["Redblox Organization"] +description = "A blazingly fast networking solution for Roblox." +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +clap = { version = "4.4.11", features = ["derive"] } +zap = { path = "../zap" } + +[[bin]] +name = "zap" +path = "src/main.rs" diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 00000000..921dc076 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,32 @@ +use std::path::PathBuf; + +use anyhow::Result; +use clap::Parser; +use zap::run; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[arg(default_value = "net.zap")] + config: Option, + + #[arg(short, long, default_value = "network")] + output: Option, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + let config_path = args.config.unwrap(); + let output_dir_path = args.output.unwrap(); + + let config = std::fs::read_to_string(config_path)?; + + let code = run(config.as_str())?; + + std::fs::create_dir_all(&output_dir_path)?; + std::fs::write(output_dir_path.join("server.luau"), code.server)?; + std::fs::write(output_dir_path.join("client.luau"), code.client)?; + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 12c8ac77..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -mod irgen; -mod output; -mod parser; -mod util; - -pub struct Code { - pub server: String, - pub client: String, -} - -pub fn run(config: &str) -> Result { - let file = parser::parse(config)?; - - Ok(Code { - server: output::server::code(&file), - client: output::client::code(&file), - }) -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index fea9147d..00000000 --- a/src/main.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::path::PathBuf; - -use clap::Parser; -use zap::run; - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Args { - #[arg(default_value = "net.zap")] - config: Option, - - #[arg(short, long, default_value = "network")] - output: Option, -} - -fn main() { - let args = Args::parse(); - - let config_path = args.config.unwrap(); - let output_dir_path = args.output.unwrap(); - - let config = match std::fs::read_to_string(&config_path) { - Ok(config) => config, - Err(err) => { - eprintln!("Failed to read config file: {}", err); - return; - } - }; - - match run(config.as_str()) { - Ok(code) => { - std::fs::create_dir_all(&output_dir_path).expect("Failed to create output directory!"); - - std::fs::write(output_dir_path.join("server.luau"), code.server).expect("Failed to write server code!"); - std::fs::write(output_dir_path.join("client.luau"), code.client).expect("Failed to write client code!"); - } - - Err(err) => eprintln!("{}", err), - } -} diff --git a/zap/Cargo.toml b/zap/Cargo.toml new file mode 100644 index 00000000..aa2299f6 --- /dev/null +++ b/zap/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "zap" +authors = ["Redblox Organization"] +description = "A blazingly fast networking solution for Roblox." +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["lib", "cdylib"] + +[build-dependencies] +lalrpop = "0.20.0" + +[dependencies] +lalrpop-util = { version = "0.20.0", features = ["lexer", "unicode"] } +num-traits = "0.2.17" +thiserror = "1.0" +wasm-bindgen = "0.2" diff --git a/build.rs b/zap/build.rs similarity index 100% rename from build.rs rename to zap/build.rs diff --git a/src/irgen/gen.rs b/zap/src/irgen/gen.rs similarity index 95% rename from src/irgen/gen.rs rename to zap/src/irgen/gen.rs index 1e8de99b..c926f9bf 100644 --- a/src/irgen/gen.rs +++ b/zap/src/irgen/gen.rs @@ -151,7 +151,7 @@ impl Gen { } else { self.block_start(); - self.local("len".into()); + self.local("len"); self.assign("len".into(), from_expr.clone().len()); self.write_num("len".into(), NumTy::U16); @@ -171,7 +171,7 @@ impl Gen { } else { self.block_start(); - self.local("len".into()); + self.local("len"); self.assign("len".into(), from_expr.clone().len()); self.write_num("len".into(), NumTy::U16); @@ -189,10 +189,10 @@ impl Gen { Ty::Map { key, val } => { self.block_start(); - self.local("len".into()); + self.local("len"); self.assign("len".into(), 0.into()); - self.local("len_pos".into()); + self.local("len_pos"); self.alloc("len_pos".into(), 2.into()); self.gen_for("key".into(), "val".into(), from_expr); @@ -264,7 +264,7 @@ impl Gen { Ty::Bool => { self.block_start(); - self.local("val".into()); + self.local("val"); self.read_num("val".into(), NumTy::U8); self.if_(Expr::from("val").eq(Expr::Num(0.0))); @@ -293,7 +293,7 @@ impl Gen { } else { self.block_start(); - self.local("len".into()); + self.local("len"); self.read_num("len".into(), NumTy::U16); self.read_str(into.clone(), "len".into()); @@ -314,7 +314,7 @@ impl Gen { } else { self.block_start(); - self.local("len".into()); + self.local("len"); self.read_num("len".into(), NumTy::U16); self.num_for("i".into(), 1.into(), "len".into()); @@ -332,15 +332,15 @@ impl Gen { self.block_start(); - self.local("len".into()); + self.local("len"); self.read_num("len".into(), NumTy::U16); self.num_for("i".into(), 1.into(), "len".into()); - self.local("key".into()); + self.local("key"); self.des(key, &"key".into()); - self.local("val".into()); + self.local("val"); self.des(val, &"val".into()); self.assign(into.clone().expr_index("key".into()), "val".into()); @@ -363,7 +363,7 @@ impl Gen { self.block_start(); - self.local("val".into()); + self.local("val"); self.read_num("val".into(), num_ty); for (i, variant) in variants.iter().enumerate() { @@ -413,7 +413,7 @@ impl Gen { _ => { self.block_start(); - self.local("val".into()); + self.local("val"); self.read_num("val".into(), NumTy::U8); self.if_(Expr::from("val").eq(0.into())); @@ -532,29 +532,23 @@ impl Gen { } } - Ty::Instance(class) => match class { - Some(class) => self.assert( + Ty::Instance(Some(class)) => { + self.assert( Expr::from(var.clone()).is_a(class.clone().into()), format!("Instance is not of class {}!", class), - ), - - None => {} - }, + ); + } - Ty::Optional(ty) => match *ty.clone() { - Ty::Instance(class) => match class { - Some(class) => self.assert( + Ty::Optional(ty) => { + if let Ty::Instance(Some(class)) = *ty.clone() { + self.assert( Expr::from(var.clone()) .eq(Expr::Nil) .or(Expr::from(var.clone()).is_a(class.clone().into())), format!("Instance is not of class {}!", class), - ), - - None => {} - }, - - _ => {} - }, + ) + } + } _ => {} } diff --git a/src/irgen/mod.rs b/zap/src/irgen/mod.rs similarity index 98% rename from src/irgen/mod.rs rename to zap/src/irgen/mod.rs index 63b20af3..99a529b5 100644 --- a/src/irgen/mod.rs +++ b/zap/src/irgen/mod.rs @@ -85,6 +85,7 @@ pub enum Expr { } impl Expr { + #[allow(clippy::wrong_self_convention)] pub fn is_a(self, ty: Expr) -> Expr { Expr::InstanceIsA(Box::new(self), Box::new(ty)) } diff --git a/zap/src/lib.rs b/zap/src/lib.rs new file mode 100644 index 00000000..c55955b6 --- /dev/null +++ b/zap/src/lib.rs @@ -0,0 +1,43 @@ +mod irgen; +mod output; +mod parser; +mod util; + +use thiserror::Error; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Unable to parse config file: {0}")] + ParseError(String), + #[error("File System error: {0}")] + FSError(#[from] std::io::Error), + #[error("Unknown type referenced: `{0}`")] + UnknownTypeRef(String), + #[error("Duplicate type declared: `{0}`")] + DuplicateType(String), + #[error("Duplicate event declared: `{0}`")] + DuplicateEvent(String), +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))] +pub struct Code { + pub server: String, + pub client: String, +} + +pub fn run(config: &str) -> Result { + let file = parser::parse(config)?; + + Ok(Code { + server: output::server::code(&file), + client: output::client::code(&file), + }) +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub fn run_wasm(config: &str) -> Result { + Ok(run(config)?) +} diff --git a/src/output/base.luau b/zap/src/output/base.luau similarity index 100% rename from src/output/base.luau rename to zap/src/output/base.luau diff --git a/src/output/client.luau b/zap/src/output/client.luau similarity index 100% rename from src/output/client.luau rename to zap/src/output/client.luau diff --git a/src/output/client.rs b/zap/src/output/client.rs similarity index 99% rename from src/output/client.rs rename to zap/src/output/client.rs index 7104a62c..ff417575 100644 --- a/src/output/client.rs +++ b/zap/src/output/client.rs @@ -12,6 +12,10 @@ impl<'a> Display for ClientFile<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let file = self.0; + if file.ev_decls.is_empty() { + return Ok(()); + } + write!(f, "{}", include_str!("base.luau"))?; writeln!(f, "local events = table.create({})", file.ev_decls.len())?; diff --git a/src/output/mod.rs b/zap/src/output/mod.rs similarity index 99% rename from src/output/mod.rs rename to zap/src/output/mod.rs index 81afe9fb..f00c9a2f 100644 --- a/src/output/mod.rs +++ b/zap/src/output/mod.rs @@ -27,7 +27,7 @@ impl Display for TyDecl { writeln!(f, "function types.read_{name}()")?; writeln!(f, "\tlocal value;")?; - for stmt in gen_ser(&ty, "value", true) { + for stmt in gen_ser(ty, "value", true) { writeln!(f, "\t{stmt}")?; } diff --git a/src/output/server.luau b/zap/src/output/server.luau similarity index 100% rename from src/output/server.luau rename to zap/src/output/server.luau diff --git a/src/output/server.rs b/zap/src/output/server.rs similarity index 99% rename from src/output/server.rs rename to zap/src/output/server.rs index 090068a5..fd65a312 100644 --- a/src/output/server.rs +++ b/zap/src/output/server.rs @@ -12,6 +12,10 @@ impl<'a> Display for ServerFile<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let file = &self.0; + if file.ev_decls.is_empty() { + return Ok(()); + } + write!(f, "{}", include_str!("base.luau"))?; writeln!(f, "local events = table.create({})", file.ev_decls.len())?; diff --git a/src/parser/grammar.lalrpop b/zap/src/parser/grammar.lalrpop similarity index 97% rename from src/parser/grammar.lalrpop rename to zap/src/parser/grammar.lalrpop index a6fa96b2..5406cc69 100644 --- a/src/parser/grammar.lalrpop +++ b/zap/src/parser/grammar.lalrpop @@ -1,12 +1,19 @@ use std::collections::HashSet; use std::str::FromStr; + +use crate::Error; use crate::util::Range; use crate::parser::*; use crate::parser::working_ast::*; + use lalrpop_util::ParseError; grammar(ref_decl: &mut HashSet, ref_used: &mut HashSet, ev_names: &mut HashSet); +extern { + type Error = Error; +} + match { r"\s*" => { }, r"(--\[\[[^(\]\])]*\]\])|(--.*)" => { }, @@ -55,7 +62,7 @@ Decl: Decl = { EvDecl: EvDecl = "event" "=" "{" > "}" =>? { if !ev_names.insert(name.clone()) { return Err(ParseError::User { - error: "event declared twice!", + error: Error::DuplicateEvent(name), }) } @@ -100,7 +107,7 @@ EvField: EvField = { TyDecl: TyDecl = "type" "=" =>? { if !ref_decl.insert(name.clone()) { Err(ParseError::User { - error: "type declared twice!", + error: Error::DuplicateType(name), }) } else { Ok(TyDecl { name, ty }) diff --git a/src/parser/mod.rs b/zap/src/parser/mod.rs similarity index 88% rename from src/parser/mod.rs rename to zap/src/parser/mod.rs index c64a9024..ad961a54 100644 --- a/src/parser/mod.rs +++ b/zap/src/parser/mod.rs @@ -2,7 +2,10 @@ use std::collections::HashSet; use lalrpop_util::lalrpop_mod; -use crate::util::{NumTy, Range}; +use crate::{ + util::{NumTy, Range}, + Error, +}; mod working_ast; @@ -157,20 +160,19 @@ impl Ty { } } -pub fn parse(code: &str) -> Result { +pub fn parse(code: &str) -> Result { let mut ref_decl = HashSet::new(); let mut ref_used = HashSet::new(); - let file = match grammar::FileParser::new().parse(&mut ref_decl, &mut ref_used, &mut HashSet::new(), code) { - Ok(file) => file, - Err(e) => return Err(e.to_string()), - }; + let file = grammar::FileParser::new() + .parse(&mut ref_decl, &mut ref_used, &mut HashSet::new(), code) + .map_err(|e| Error::ParseError(e.to_string()))?; let unknown_refs = ref_used.difference(&ref_decl).collect::>(); // TODO: Better error reporting with error location - if unknown_refs.len() > 0 { - return Err(format!("Unknown type referenced: `{}`", unknown_refs[0])); + if !unknown_refs.is_empty() { + return Err(Error::UnknownTypeRef(unknown_refs[0].to_owned())); } Ok(file) diff --git a/src/parser/working_ast.rs b/zap/src/parser/working_ast.rs similarity index 100% rename from src/parser/working_ast.rs rename to zap/src/parser/working_ast.rs diff --git a/src/util.rs b/zap/src/util.rs similarity index 93% rename from src/util.rs rename to zap/src/util.rs index 89da66e9..b24dddf0 100644 --- a/src/util.rs +++ b/zap/src/util.rs @@ -122,16 +122,14 @@ impl NumTy { } else { NumTy::I32 } + } else if max <= u8::MAX as f64 { + NumTy::U8 + } else if max <= u16::MAX as f64 { + NumTy::U16 + } else if max <= u32::MAX as f64 { + NumTy::U32 } else { - if max <= u8::MAX as f64 { - NumTy::U8 - } else if max <= u16::MAX as f64 { - NumTy::U16 - } else if max <= u32::MAX as f64 { - NumTy::U32 - } else { - NumTy::F64 - } + NumTy::F64 } }