diff --git a/docs/config/events.md b/docs/config/events.md index e63e9bf1..c35f9bac 100644 --- a/docs/config/events.md +++ b/docs/config/events.md @@ -3,17 +3,21 @@ const example = `event MyEvent = { from: Server, type: Reliable, call: ManyAsync, - data: struct { - foo: string, - bar: u8, - }, + data: (Foo: boolean, Bar: u32, Baz: string) }` -const dataExample = `event MyEvent = { +const dataExample = `event OneUnnamedParameter = { from: Server, type: Reliable, call: ManyAsync, - data: (boolean, u32, string) + data: boolean +} + +event TwoUnnamedParameters = { + from: Server, + type: Reliable, + call: ManyAsync, + data: (boolean, u32) }` @@ -65,6 +69,6 @@ Use synchronous events with extreme caution. This field determines the data that is sent with the event. It can be any [Zap type](./types.md). - If the event does not require any data, the `data` field should be excluded. -- You can pass multiple arguments to the event by separating each type with a comma and wrapping them all in parentheses: +- Parameter names and parentheses are optional to preserve backwards compatibility. If parantheses are excluded, the event can only have one unnamed parameter. diff --git a/docs/config/functions.md b/docs/config/functions.md index 1b9132c5..1d73b917 100644 --- a/docs/config/functions.md +++ b/docs/config/functions.md @@ -1,17 +1,27 @@ @@ -48,15 +58,16 @@ Use synchronous functions with extreme caution. This field determines the data that is sent to the server. It can be any [Zap type](./types.md). - If the client doesn't send any data, the `args` field should be excluded. -- You can pass multiple arguments to the function by separating each type with a comma and wrapping them all in parentheses: +- Parameter names and parentheses are optional to preserve backwards compatibility. If parantheses are excluded, the function can only have one unnamed parameter. - + ### `rets` This field determines the data that is sent back to the client from the server. It can be any [Zap type](./types.md). - If the server doesn't return any data, the `rets` field should be excluded. +- Unlike `args`, `rets` cannot be named. - The function can return multiple values by separating each type with a comma and wrapping them all in parentheses: - + diff --git a/docs/intro/getting-started.md b/docs/intro/getting-started.md index d92a2e55..e7170a70 100644 --- a/docs/intro/getting-started.md +++ b/docs/intro/getting-started.md @@ -7,25 +7,19 @@ event MyEvent = { from: Server, type: Reliable, call: ManyAsync, - data: struct { - foo: u32, - bar: string, - }, + data: (Foo: u32, Bar: string), }` const apiExample = `-- Server local Zap = require(path.to.server.output) -Zap.MyEvent.FireAll({ - foo = 123, - bar = "hello world", -}) +Zap.MyEvent.FireAll(123, "hello world") -- Client local Zap = require(path.to.client.output) -Zap.MyEvent.On(function(data) - print(data.foo, data.bar) +Zap.MyEvent.On(function(Foo, Bar) + print(Foo, Bar) end)` diff --git a/docs/usage/events.md b/docs/usage/events.md index 79f3f481..8dd5580b 100644 --- a/docs/usage/events.md +++ b/docs/usage/events.md @@ -3,17 +3,17 @@ const configFile = `event MyEvent = { from: Server, type: Reliable, call: ManyAsync, - data: struct { + data: (Options: struct { foo: string, bar: u8, - }, + }), } event AnotherEvent = { from: Client, type: Reliable, call: SingleAsync, - data: (boolean, u8) + data: (Foo: boolean, Bar: u8) }` @@ -48,7 +48,7 @@ If your event's [call field](../config/events.md#call) is `SingleAsync` or `Sing local Zap = require(Path.To.Zap) -- only server listeners are given the player argument -Zap.AnotherEvent.SetCallback(function(Player, Bool, Number) +Zap.AnotherEvent.SetCallback(function(Player, Foo, Bar) -- Do something with the player and data end) ``` @@ -58,7 +58,7 @@ If your event's [call field](../config/events.md#call) is `ManyAsync` or `ManySy ```lua local Zap = require(Path.To.Zap) -local Disconnect = Zap.MyEvent.On(function(Data) +local Disconnect = Zap.MyEvent.On(function(Options) -- Do something with the data end) diff --git a/zap/src/config.rs b/zap/src/config.rs index b88383fa..c79b2f35 100644 --- a/zap/src/config.rs +++ b/zap/src/config.rs @@ -76,7 +76,7 @@ impl std::fmt::Display for YieldType { pub struct FnDecl<'src> { pub name: &'src str, pub call: FnCall, - pub args: Option>>, + pub args: Vec>, pub rets: Option>>, pub id: usize, } @@ -93,10 +93,16 @@ pub struct EvDecl<'src> { pub from: EvSource, pub evty: EvType, pub call: EvCall, - pub data: Option>>, + pub data: Vec>, pub id: usize, } +#[derive(Debug, Clone)] +pub struct Parameter<'src> { + pub name: Option<&'src str>, + pub ty: Ty<'src>, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum EvSource { Server, diff --git a/zap/src/irgen/des.rs b/zap/src/irgen/des.rs index 860ccb74..189487a1 100644 --- a/zap/src/irgen/des.rs +++ b/zap/src/irgen/des.rs @@ -14,18 +14,12 @@ impl Gen for Des { self.buf.push(stmt); } - fn gen(mut self, var: Var, types: &[Ty]) -> Vec { - for (i, ty) in types.iter().enumerate() { - let var = if i > 0 { - Var::Name(match &var { - Var::Name(name) => format!("{name}{}", i + 1), - _ => unreachable!(), - }) - } else { - var.clone() - }; - - self.push_ty(ty, var); + fn gen<'a, I>(mut self, names: &[String], types: I) -> Vec + where + I: Iterator>, + { + for (ty, name) in types.zip(names) { + self.push_ty(ty, Var::Name(name.to_string())); } self.buf @@ -431,11 +425,14 @@ impl Des { } } -pub fn gen(types: &[Ty], var: &str, checks: bool) -> Vec { +pub fn gen<'a, I>(types: I, names: &[String], checks: bool) -> Vec +where + I: IntoIterator>, +{ Des { checks, buf: vec![], var_occurrences: HashMap::new(), } - .gen(var.into(), types) + .gen(names, types.into_iter()) } diff --git a/zap/src/irgen/mod.rs b/zap/src/irgen/mod.rs index 88c16014..03786021 100644 --- a/zap/src/irgen/mod.rs +++ b/zap/src/irgen/mod.rs @@ -9,7 +9,9 @@ pub mod ser; pub trait Gen { fn push_stmt(&mut self, stmt: Stmt); - fn gen(self, var: Var, types: &[Ty]) -> Vec; + fn gen<'a, I>(self, names: &[String], types: I) -> Vec + where + I: Iterator>; fn push_local(&mut self, name: String, expr: Option) { self.push_stmt(Stmt::Local(name, expr)) diff --git a/zap/src/irgen/ser.rs b/zap/src/irgen/ser.rs index 5fcadaf0..dda6d70f 100644 --- a/zap/src/irgen/ser.rs +++ b/zap/src/irgen/ser.rs @@ -14,18 +14,12 @@ impl Gen for Ser { self.buf.push(stmt); } - fn gen(mut self, var: Var, types: &[Ty]) -> Vec { - for (i, ty) in types.iter().enumerate() { - let var = if i > 0 { - Var::Name(match &var { - Var::Name(name) => format!("{name}{}", i + 1), - _ => unreachable!(), - }) - } else { - var.clone() - }; - - self.push_ty(ty, var); + fn gen<'a, I>(mut self, names: &[String], types: I) -> Vec + where + I: Iterator>, + { + for (ty, name) in types.zip(names) { + self.push_ty(ty, Var::Name(name.to_string())); } self.buf @@ -369,11 +363,14 @@ impl Ser { } } -pub fn gen(types: &[Ty], var: &str, checks: bool) -> Vec { +pub fn gen<'a, I>(types: I, names: &[String], checks: bool) -> Vec +where + I: IntoIterator>, +{ Ser { checks, buf: vec![], var_occurrences: HashMap::new(), } - .gen(var.into(), types) + .gen(names, types.into_iter()) } diff --git a/zap/src/output/luau/client.rs b/zap/src/output/luau/client.rs index 0e89e6ae..3a9f10af 100644 --- a/zap/src/output/luau/client.rs +++ b/zap/src/output/luau/client.rs @@ -1,6 +1,7 @@ use crate::{ - config::{Config, EvCall, EvDecl, EvSource, EvType, FnDecl, Ty, TyDecl, YieldType}, + config::{Config, EvCall, EvDecl, EvSource, EvType, FnDecl, Parameter, TyDecl, YieldType}, irgen::{des, ser}, + output::{get_named_values, get_unnamed_values}, }; use super::Output; @@ -103,14 +104,18 @@ impl<'src> ClientOutput<'src> { self.push_line(&format!("function types.write_{name}(value: {name})")); self.indent(); - self.push_stmts(&ser::gen(&[ty.clone()], "value", self.config.write_checks)); + self.push_stmts(&ser::gen( + &[ty.clone()], + &["value".to_string()], + self.config.write_checks, + )); self.dedent(); self.push_line("end"); self.push_line(&format!("function types.read_{name}()")); self.indent(); self.push_line("local value;"); - self.push_stmts(&des::gen(&[ty.clone()], "value", false)); + self.push_stmts(&des::gen(&[ty.clone()], &["value".to_string()], false)); self.push_line("return value"); self.dedent(); self.push_line("end"); @@ -174,9 +179,9 @@ impl<'src> ClientOutput<'src> { )); } - fn get_values(&self, data: &Option>) -> String { - if let Some(types) = data { - (1..=types.len()) + fn get_values(&self, count: usize) -> String { + if count > 0 { + (1..=count) .map(|i| { if i == 1 { "value".to_string() @@ -210,12 +215,16 @@ impl<'src> ClientOutput<'src> { self.indent(); - let values = self.get_values(&ev.data); + let values = self.get_values(ev.data.len()); self.push_line(&format!("local {values}")); - if let Some(data) = &ev.data { - self.push_stmts(&des::gen(data, "value", true)); + if !ev.data.is_empty() { + self.push_stmts(&des::gen( + ev.data.iter().map(|parameter| ¶meter.ty), + &get_unnamed_values("value", ev.data.len()), + true, + )); } if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync { @@ -247,8 +256,8 @@ impl<'src> ClientOutput<'src> { self.push_line("else"); self.indent(); - if let Some(types) = &ev.data { - if types.len() > 1 { + if !ev.data.is_empty() { + if ev.data.len() > 1 { self.push_line(&format!("table.insert(event_queue[{id}], {{ {values} }})")); } else { self.push_line(&format!("table.insert(event_queue[{id}], value)")); @@ -265,7 +274,7 @@ impl<'src> ClientOutput<'src> { self.push("warn(`[ZAP] {"); - if ev.data.is_some() { + if !ev.data.is_empty() { self.push("#") } @@ -304,12 +313,12 @@ impl<'src> ClientOutput<'src> { self.push_line("local call_id = buffer.readu8(incoming_buff, read(1))"); - let values = self.get_values(&fndecl.rets); + let values = self.get_values(fndecl.rets.as_ref().map_or(0, |x| x.len())); self.push_line(&format!("local {values}")); if let Some(data) = &fndecl.rets { - self.push_stmts(&des::gen(data, "value", true)); + self.push_stmts(&des::gen(data, &get_unnamed_values("value", data.len()), true)); } match self.config.yield_type { @@ -395,12 +404,16 @@ impl<'src> ClientOutput<'src> { self.indent(); - let values = self.get_values(&ev.data); + let values = self.get_values(ev.data.len()); self.push_line(&format!("local {values}")); - if let Some(data) = &ev.data { - self.push_stmts(&des::gen(data, "value", self.config.write_checks)); + if !ev.data.is_empty() { + self.push_stmts(&des::gen( + ev.data.iter().map(|parameter| ¶meter.ty), + &get_unnamed_values("value", ev.data.len()), + self.config.write_checks, + )); } if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync { @@ -432,8 +445,8 @@ impl<'src> ClientOutput<'src> { self.push_line("else"); self.indent(); - if let Some(types) = &ev.data { - if types.len() > 1 { + if !ev.data.is_empty() { + if ev.data.len() > 1 { self.push_line(&format!("table.insert(event_queue[{id}], {{ {values} }})")); } else { self.push_line(&format!("table.insert(event_queue[{id}], value)")); @@ -450,7 +463,7 @@ impl<'src> ClientOutput<'src> { self.push("warn(`[ZAP] {"); - if ev.data.is_some() { + if !ev.data.is_empty() { self.push("#") } @@ -528,7 +541,7 @@ impl<'src> ClientOutput<'src> { self.push_line(&format!("events[{id}] = {{}}")); } - if evdecl.data.is_some() { + if !evdecl.data.is_empty() { self.push_line(&format!("event_queue[{id}] = {{}}")); } else { self.push_line(&format!("event_queue[{id}] = 0")); @@ -548,20 +561,25 @@ impl<'src> ClientOutput<'src> { )); } - fn push_value_parameters(&mut self, types: &[Ty]) { - for (i, ty) in types.iter().enumerate() { - let value = format!( - "{}{}", - self.config.casing.with("Value", "value", "value"), - if i == 0 { "".to_string() } else { (i + 1).to_string() } - ); - + fn push_value_parameters(&mut self, parameters: &[Parameter]) { + for (i, parameter) in parameters.iter().enumerate() { if i > 0 { self.push(", "); } - self.push(&format!("{value}: ")); - self.push_ty(ty); + if let Some(name) = parameter.name { + self.push(&format!("{name}: ")); + } else { + let value = format!( + "{}{}", + self.config.casing.with("Value", "value", "value"), + if i == 0 { "".to_string() } else { (i + 1).to_string() } + ); + + self.push(&format!("{value}: ")); + } + + self.push_ty(¶meter.ty); } } @@ -572,8 +590,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{fire} = function(")); - if let Some(types) = &ev.data { - self.push_value_parameters(types); + if !ev.data.is_empty() { + self.push_value_parameters(&ev.data); } self.push(")\n"); @@ -586,8 +604,12 @@ impl<'src> ClientOutput<'src> { self.push_write_event_id(ev.id); - if let Some(data) = &ev.data { - self.push_stmts(&ser::gen(data, value, self.config.write_checks)); + if !ev.data.is_empty() { + self.push_stmts(&ser::gen( + ev.data.iter().map(|parameter| ¶meter.ty), + &get_named_values(value, &ev.data), + self.config.write_checks, + )); } if ev.evty == EvType::Unreliable { @@ -618,8 +640,8 @@ impl<'src> ClientOutput<'src> { } } - fn push_queued_value(&mut self, types: &[Ty]) { - if types.len() > 1 { + fn push_queued_value(&mut self, parameters: &[Parameter]) { + if parameters.len() > 1 { self.push("unpack(value)"); } else { self.push("value"); @@ -635,8 +657,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{set_callback} = function({callback}: (")); - if let Some(types) = &ev.data { - self.push_value_parameters(types); + if !ev.data.is_empty() { + self.push_value_parameters(&ev.data); } self.push(") -> ()): () -> ()\n"); @@ -644,19 +666,19 @@ impl<'src> ClientOutput<'src> { self.push_line(&format!("events[{id}] = {callback}")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push_line(&format!("for _, value in event_queue[{id}] do")); self.indent(); if ev.call == EvCall::SingleSync { self.push_indent(); self.push(&format!("{callback}(")); - self.push_queued_value(types); + self.push_queued_value(&ev.data); self.push_line(")\n"); } else { self.push_indent(); self.push(&format!("task.spawn({callback}, ")); - self.push_queued_value(types); + self.push_queued_value(&ev.data); self.push(")\n"); } @@ -701,8 +723,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{on} = function({callback}: (")); - if let Some(types) = &ev.data { - self.push_value_parameters(types); + if !ev.data.is_empty() { + self.push_value_parameters(&ev.data); } self.push(") -> ())\n"); @@ -710,19 +732,19 @@ impl<'src> ClientOutput<'src> { self.push_line(&format!("table.insert(events[{id}], {callback})")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push_line(&format!("for _, value in event_queue[{id}] do")); self.indent(); if ev.call == EvCall::ManySync { self.push_indent(); self.push(&format!("{callback}(")); - self.push_queued_value(types); + self.push_queued_value(&ev.data); self.push_line(")\n"); } else { self.push_indent(); self.push(&format!("task.spawn({callback}, ")); - self.push_queued_value(types); + self.push_queued_value(&ev.data); self.push(")\n"); } @@ -793,8 +815,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{call} = function(")); - if let Some(types) = &fndecl.args { - self.push_value_parameters(types); + if !fndecl.args.is_empty() { + self.push_value_parameters(&fndecl.args); } self.push(")"); @@ -848,8 +870,12 @@ impl<'src> ClientOutput<'src> { self.push_line("alloc(1)"); self.push_line("buffer.writeu8(outgoing_buff, outgoing_apos, function_call_id)"); - if let Some(data) = &fndecl.args { - self.push_stmts(&ser::gen(data, value, self.config.write_checks)); + if !fndecl.args.is_empty() { + self.push_stmts(&ser::gen( + fndecl.args.iter().map(|parameter| ¶meter.ty), + &get_named_values(value, &fndecl.args), + self.config.write_checks, + )); } match self.config.yield_type { diff --git a/zap/src/output/luau/server.rs b/zap/src/output/luau/server.rs index 3e0557e5..5ea4fefd 100644 --- a/zap/src/output/luau/server.rs +++ b/zap/src/output/luau/server.rs @@ -1,6 +1,7 @@ use crate::{ - config::{Config, EvCall, EvDecl, EvSource, EvType, FnCall, FnDecl, Ty, TyDecl}, + config::{Config, EvCall, EvDecl, EvSource, EvType, FnCall, FnDecl, Parameter, TyDecl}, irgen::{des, ser}, + output::{get_named_values, get_unnamed_values}, }; use super::Output; @@ -115,14 +116,18 @@ impl<'a> ServerOutput<'a> { self.push_line(&format!("function types.write_{name}(value: {name})")); self.indent(); - self.push_stmts(&ser::gen(&[ty.clone()], "value", self.config.write_checks)); + self.push_stmts(&ser::gen( + &[ty.clone()], + &["value".to_string()], + self.config.write_checks, + )); self.dedent(); self.push_line("end"); self.push_line(&format!("function types.read_{name}()")); self.indent(); self.push_line("local value;"); - self.push_stmts(&des::gen(&[ty.clone()], "value", true)); + self.push_stmts(&des::gen(&[ty.clone()], &["value".to_string()], true)); self.push_line("return value"); self.dedent(); self.push_line("end"); @@ -186,9 +191,9 @@ impl<'a> ServerOutput<'a> { )); } - fn get_values(&self, data: &Option>) -> String { - if let Some(types) = data { - (1..=types.len()) + fn get_values(&self, parameters: &[Parameter]) -> String { + if !parameters.is_empty() { + (1..=parameters.len()) .map(|i| { if i == 1 { "value".to_string() @@ -226,8 +231,12 @@ impl<'a> ServerOutput<'a> { self.push_line(&format!("local {values}")); - if let Some(data) = &ev.data { - self.push_stmts(&des::gen(data, "value", true)); + if !ev.data.is_empty() { + self.push_stmts(&des::gen( + ev.data.iter().map(|parameter| ¶meter.ty), + &get_unnamed_values("value", ev.data.len()), + true, + )); } if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync { @@ -273,16 +282,20 @@ impl<'a> ServerOutput<'a> { self.push_line(&format!("local {values}")); - if let Some(data) = &fndecl.args { - self.push_stmts(&des::gen(data, "value", true)); + if !fndecl.args.is_empty() { + self.push_stmts(&des::gen( + fndecl.args.iter().map(|parameter| ¶meter.ty), + &get_unnamed_values("value", fndecl.args.len()), + true, + )); } self.push_line(&format!("if events[{id}] then")); self.indent(); - let rets = if let Some(types) = &fndecl.args { - (1..=types.len()) + let rets = if !fndecl.args.is_empty() { + (1..=fndecl.args.len()) .map(|i| { if i > 1 { format!("rets{}", i) @@ -297,8 +310,8 @@ impl<'a> ServerOutput<'a> { }; if fndecl.call == FnCall::Async { - let args = if let Some(types) = &fndecl.args { - (1..=types.len()) + let args = if !fndecl.args.is_empty() { + (1..=fndecl.args.len()) .map(|i| format!("value_{}", i)) .collect::>() .join(", ") @@ -318,8 +331,12 @@ impl<'a> ServerOutput<'a> { self.push_line("alloc(1)"); self.push_line("buffer.writeu8(outgoing_buff, outgoing_apos, call_id_2)"); - if let Some(ty) = &fndecl.rets { - self.push_stmts(&ser::gen(ty, "rets", self.config.write_checks)); + if let Some(types) = &fndecl.rets { + self.push_stmts(&ser::gen( + types, + &get_unnamed_values("rets", types.len()), + self.config.write_checks, + )); } self.push_line("player_map[player_2] = save()"); @@ -335,8 +352,12 @@ impl<'a> ServerOutput<'a> { self.push_line("alloc(1)"); self.push_line("buffer.writeu8(outgoing_buff, outgoing_apos, call_id)"); - if let Some(ty) = &fndecl.rets { - self.push_stmts(&ser::gen(ty, "rets", self.config.write_checks)); + if let Some(types) = &fndecl.rets { + self.push_stmts(&ser::gen( + types, + &get_unnamed_values("rets", types.len()), + self.config.write_checks, + )); } self.push_line("player_map[player] = save()"); @@ -421,8 +442,12 @@ impl<'a> ServerOutput<'a> { self.push_line(&format!("local {}", values)); - if let Some(data) = &ev.data { - self.push_stmts(&des::gen(data, "value", true)); + if !ev.data.is_empty() { + self.push_stmts(&des::gen( + ev.data.iter().map(|parameter| ¶meter.ty), + &get_unnamed_values("value", ev.data.len()), + true, + )); } if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync { @@ -495,25 +520,30 @@ impl<'a> ServerOutput<'a> { )); } - fn push_value_parameters(&mut self, types: &[Ty]) { - for (i, ty) in types.iter().enumerate() { - let value = format!( - "{}{}", - self.config.casing.with("Value", "value", "value"), - if i == 0 { "".to_string() } else { (i + 1).to_string() } - ); - + fn push_value_parameters(&mut self, parameters: &[Parameter]) { + for (i, parameter) in parameters.iter().enumerate() { if i > 0 { self.push(", "); } - self.push(&format!("{value}: ")); - self.push_ty(ty); + if let Some(name) = parameter.name { + self.push(&format!("{name}: ")); + } else { + let value = format!( + "{}{}", + self.config.casing.with("Value", "value", "value"), + if i == 0 { "".to_string() } else { (i + 1).to_string() } + ); + + self.push(&format!("{value}: ")); + } + + self.push_ty(¶meter.ty); } } fn push_return_fire(&mut self, ev: &EvDecl) { - let types = &ev.data; + let parameters = &ev.data; let fire = self.config.casing.with("Fire", "fire", "fire"); let player = self.config.casing.with("Player", "player", "player"); @@ -522,9 +552,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire} = function({player}: Player")); - if let Some(types) = types { + if !parameters.is_empty() { self.push(", "); - self.push_value_parameters(types); + self.push_value_parameters(parameters); } self.push(")\n"); @@ -537,8 +567,12 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(types) = types { - self.push_stmts(&ser::gen(types, value, self.config.write_checks)); + if !parameters.is_empty() { + self.push_stmts(&ser::gen( + parameters.iter().map(|parameter| ¶meter.ty), + &get_named_values(value, parameters), + self.config.write_checks, + )); } match ev.evty { @@ -555,7 +589,7 @@ impl<'a> ServerOutput<'a> { } fn push_return_fire_all(&mut self, ev: &EvDecl) { - let types = &ev.data; + let parameters = &ev.data; let fire_all = self.config.casing.with("FireAll", "fireAll", "fire_all"); let value = self.config.casing.with("Value", "value", "value"); @@ -563,8 +597,8 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_all} = function(")); - if let Some(types) = types { - self.push_value_parameters(types); + if !parameters.is_empty() { + self.push_value_parameters(parameters); } self.push(")\n"); @@ -574,8 +608,12 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(types) = types { - self.push_stmts(&ser::gen(types, value, self.config.write_checks)); + if !parameters.is_empty() { + self.push_stmts(&ser::gen( + parameters.iter().map(|parameter| ¶meter.ty), + &get_named_values(value, parameters), + self.config.write_checks, + )); } match ev.evty { @@ -604,7 +642,7 @@ impl<'a> ServerOutput<'a> { } fn push_return_fire_except(&mut self, ev: &EvDecl) { - let types = &ev.data; + let parameters = &ev.data; let fire_except = self.config.casing.with("FireExcept", "fireExcept", "fire_except"); let except = self.config.casing.with("Except", "except", "except"); @@ -613,9 +651,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_except} = function({except}: Player")); - if let Some(types) = types { + if !parameters.is_empty() { self.push(", "); - self.push_value_parameters(types); + self.push_value_parameters(parameters); } self.push(")\n"); @@ -625,8 +663,12 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(types) = types { - self.push_stmts(&ser::gen(types, value, self.config.write_checks)); + if !parameters.is_empty() { + self.push_stmts(&ser::gen( + parameters.iter().map(|paramater| ¶mater.ty), + &get_named_values(value, parameters), + self.config.write_checks, + )); } match ev.evty { @@ -667,7 +709,7 @@ impl<'a> ServerOutput<'a> { } fn push_return_fire_list(&mut self, ev: &EvDecl) { - let types = &ev.data; + let parameters = &ev.data; let fire_list = self.config.casing.with("FireList", "fireList", "fire_list"); let list = self.config.casing.with("List", "list", "list"); @@ -676,9 +718,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_list} = function({list}: {{ Player }}")); - if let Some(types) = types { + if !parameters.is_empty() { self.push(", "); - self.push_value_parameters(types); + self.push_value_parameters(parameters); } self.push(")\n"); @@ -688,8 +730,12 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(ty) = types { - self.push_stmts(&ser::gen(ty, value, self.config.write_checks)); + if !parameters.is_empty() { + self.push_stmts(&ser::gen( + parameters.iter().map(|parameter| ¶meter.ty), + &get_named_values(value, parameters), + self.config.write_checks, + )); } match ev.evty { @@ -722,7 +768,7 @@ impl<'a> ServerOutput<'a> { } fn push_return_fire_set(&mut self, ev: &EvDecl) { - let types = &ev.data; + let parameters = &ev.data; let fire_set = self.config.casing.with("FireSet", "fireSet", "fire_set"); let set = self.config.casing.with("Set", "set", "set"); @@ -731,9 +777,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_set} = function({set}: {{ [Player]: true }}")); - if let Some(types) = types { + if !parameters.is_empty() { self.push(", "); - self.push_value_parameters(types); + self.push_value_parameters(parameters); } self.push(")\n"); @@ -743,8 +789,12 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(ty) = types { - self.push_stmts(&ser::gen(ty, value, self.config.write_checks)); + if !parameters.is_empty() { + self.push_stmts(&ser::gen( + parameters.iter().map(|parameter| ¶meter.ty), + &get_named_values(value, parameters), + self.config.write_checks, + )); } match ev.evty { @@ -811,9 +861,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{set_callback} = function({callback}: ({player}: Player")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push(", "); - self.push_value_parameters(types); + self.push_value_parameters(&ev.data); } self.push(") -> ()): () -> ()\n"); @@ -843,9 +893,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{on} = function({callback}: ({player}: Player")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push(", "); - self.push_value_parameters(types); + self.push_value_parameters(&ev.data); } self.push(") -> ()): () -> ()\n"); @@ -877,9 +927,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{set_callback} = function({callback}: ({player}: Player")); - if let Some(types) = &fndecl.args { + if !fndecl.args.is_empty() { self.push(", "); - self.push_value_parameters(types); + self.push_value_parameters(&fndecl.args); } self.push(") -> ("); diff --git a/zap/src/output/mod.rs b/zap/src/output/mod.rs index 87750f5e..493c1cf2 100644 --- a/zap/src/output/mod.rs +++ b/zap/src/output/mod.rs @@ -1,3 +1,34 @@ +use crate::config::Parameter; + pub mod luau; pub mod tooling; pub mod typescript; + +pub fn get_unnamed_values(prefix: &str, count: usize) -> Vec { + (0..count) + .map(|i| { + if i > 0 { + format!("{prefix}{}", i + 1) + } else { + prefix.to_string() + } + }) + .collect() +} + +pub fn get_named_values(default_prefix: &str, parameters: &[Parameter]) -> Vec { + parameters + .iter() + .enumerate() + .map(|(i, parameter)| match parameter.name { + Some(name) => name.to_string(), + None => { + if i > 0 { + format!("{default_prefix}{}", i + 1) + } else { + default_prefix.to_string() + } + } + }) + .collect() +} diff --git a/zap/src/output/tooling.rs b/zap/src/output/tooling.rs index 2a228a3d..10ffb5da 100644 --- a/zap/src/output/tooling.rs +++ b/zap/src/output/tooling.rs @@ -1,6 +1,7 @@ use crate::{ - config::{Config, EvDecl, FnDecl, Ty, TyDecl}, + config::{Config, EvDecl, FnDecl, Parameter, TyDecl}, irgen::{des, Stmt}, + output::get_unnamed_values, Output, }; @@ -115,7 +116,7 @@ impl<'src> ToolingOutput<'src> { self.push_line(&format!("function types.read_{name}()")); self.indent(); self.push_line("local value;"); - self.push_stmts(&des::gen(&[ty.clone()], "value", true)); + self.push_stmts(&des::gen(std::iter::once(ty), &get_unnamed_values("value", 1), true)); self.push_line("return value"); self.dedent(); self.push_line("end"); @@ -129,9 +130,9 @@ impl<'src> ToolingOutput<'src> { } } - fn get_values(&self, data: &Option>) -> String { - if let Some(types) = data { - (1..=types.len()) + fn get_values(&self, data: &[Parameter]) -> String { + if !data.is_empty() { + (1..=data.len()) .map(|i| { if i == 1 { "value".to_string() @@ -168,8 +169,12 @@ impl<'src> ToolingOutput<'src> { self.push_line(&format!("local {values}")); - if let Some(data) = &ev.data { - self.push_stmts(&des::gen(data, "value", true)); + if !ev.data.is_empty() { + self.push_stmts(&des::gen( + ev.data.iter().map(|parameter| ¶meter.ty), + &get_unnamed_values("value", ev.data.len()), + true, + )); } self.push_line("table.insert(events, {"); @@ -226,8 +231,12 @@ impl<'src> ToolingOutput<'src> { self.push_line(&format!("local {values}")); - if let Some(data) = &fn_decl.args { - self.push_stmts(&des::gen(data, "value", true)); + if !fn_decl.args.is_empty() { + self.push_stmts(&des::gen( + fn_decl.args.iter().map(|parameter| ¶meter.ty), + &get_unnamed_values("value", fn_decl.args.len()), + true, + )); } self.push_line("table.insert(events, {"); @@ -255,7 +264,7 @@ impl<'src> ToolingOutput<'src> { self.push_line(&format!("local {values}")); if let Some(data) = &fn_decl.rets { - self.push_stmts(&des::gen(data, "value", true)); + self.push_stmts(&des::gen(data, &get_unnamed_values("value", data.len()), true)); } self.push_line("table.insert(events, {"); diff --git a/zap/src/output/typescript/client.rs b/zap/src/output/typescript/client.rs index 2590655b..8dee730b 100644 --- a/zap/src/output/typescript/client.rs +++ b/zap/src/output/typescript/client.rs @@ -1,4 +1,3 @@ -use crate::config::Ty; use crate::config::{Config, EvCall, EvSource, TyDecl, YieldType}; use super::ConfigProvider; @@ -81,8 +80,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{fire}: (")); - if let Some(types) = &ev.data { - self.push_parameters(types); + if !ev.data.is_empty() { + self.push_parameters(&ev.data); } self.push(") => void;\n"); @@ -114,8 +113,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{set_callback}: ({callback}: (")); - if let Some(types) = &ev.data { - self.push_parameters(types); + if !ev.data.is_empty() { + self.push_parameters(&ev.data); } self.push(") => void) => () => void;\n"); @@ -135,8 +134,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{call}: (")); - if let Some(types) = &fndecl.args { - self.push_parameters(types); + if !fndecl.args.is_empty() { + self.push_parameters(&fndecl.args); } self.push(") => "); diff --git a/zap/src/output/typescript/mod.rs b/zap/src/output/typescript/mod.rs index 71a29755..7aa9c9ba 100644 --- a/zap/src/output/typescript/mod.rs +++ b/zap/src/output/typescript/mod.rs @@ -1,4 +1,4 @@ -use crate::config::{Config, Enum, Ty}; +use crate::config::{Config, Enum, Parameter, Ty}; pub mod client; pub mod server; @@ -224,19 +224,24 @@ pub trait Output: ConfigProvider { } } - fn push_parameters(&mut self, types: &[Ty]) { + fn push_parameters(&mut self, parameters: &[Parameter]) { let value = self.get_config().casing.with("Value", "value", "value"); - for (i, ty) in types.iter().enumerate() { + for (i, parameter) in parameters.iter().enumerate() { if i > 0 { self.push(", "); } - self.push(&format!( - "{value}{}", - if i > 0 { (i + 1).to_string() } else { "".to_string() }, - )); - self.push_arg_ty(ty); + if let Some(name) = parameter.name { + self.push(name); + } else { + self.push(&format!( + "{value}{}", + if i > 0 { (i + 1).to_string() } else { "".to_string() }, + )); + } + + self.push_arg_ty(¶meter.ty); } } diff --git a/zap/src/output/typescript/server.rs b/zap/src/output/typescript/server.rs index 398ff719..7d7cf44b 100644 --- a/zap/src/output/typescript/server.rs +++ b/zap/src/output/typescript/server.rs @@ -1,4 +1,3 @@ -use crate::config::Ty; use crate::config::{Config, EvCall, EvDecl, EvSource, TyDecl}; use super::ConfigProvider; @@ -72,9 +71,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire}: ({player}: Player")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push(", "); - self.push_parameters(types); + self.push_parameters(&ev.data); } self.push(") => void;\n"); @@ -86,8 +85,8 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_all}: (")); - if let Some(types) = &ev.data { - self.push_parameters(types); + if !ev.data.is_empty() { + self.push_parameters(&ev.data); } self.push(") => void;\n"); @@ -100,9 +99,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_except}: ({except}: Player")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push(", "); - self.push_parameters(types); + self.push_parameters(&ev.data); } self.push(") => void;\n"); @@ -115,9 +114,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_list}: ({list}: Player[]")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push(", "); - self.push_parameters(types); + self.push_parameters(&ev.data); } self.push(") => void;\n"); @@ -130,9 +129,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_set}: ({set}: Set")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push(", "); - self.push_parameters(types); + self.push_parameters(&ev.data); } self.push(") => void\n"); @@ -187,9 +186,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{set_callback}: ({callback}: ({player}: Player")); - if let Some(types) = &ev.data { + if !ev.data.is_empty() { self.push(", "); - self.push_parameters(types); + self.push_parameters(&ev.data); } self.push(") => void) => () => void;\n"); @@ -211,9 +210,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{set_callback}: ({callback}: ({player}: Player")); - if let Some(types) = &fndecl.args { + if !fndecl.args.is_empty() { self.push(", "); - self.push_parameters(types); + self.push_parameters(&fndecl.args); } self.push(") => "); diff --git a/zap/src/parser/convert.rs b/zap/src/parser/convert.rs index 25d9915f..957be2d1 100644 --- a/zap/src/parser/convert.rs +++ b/zap/src/parser/convert.rs @@ -1,6 +1,8 @@ use std::collections::{HashMap, HashSet}; -use crate::config::{Casing, Config, Enum, EvDecl, EvType, FnDecl, NumTy, Range, Struct, Ty, TyDecl, YieldType}; +use crate::config::{ + Casing, Config, Enum, EvDecl, EvType, FnDecl, NumTy, Parameter, Range, Struct, Ty, TyDecl, YieldType, +}; use super::{ reports::{Report, Span}, @@ -309,27 +311,55 @@ impl<'src> Converter<'src> { } } + fn check_duplicate_parameters(&mut self, syntax_parameters: &SyntaxParameters<'src>) { + let mut seen: HashMap<_, std::ops::Range> = HashMap::new(); + for (identifier, _) in &syntax_parameters.parameters { + if let Some(identifier) = identifier { + if let Some(first_span) = seen.get(identifier.name) { + self.report(Report::AnalyzeDuplicateParameter { + prev_span: first_span.clone(), + dup_span: identifier.span(), + name: identifier.name, + }); + } else { + seen.insert(identifier.name, identifier.span()); + } + } + } + } + fn evdecl( &mut self, evdecl: &SyntaxEvDecl<'src>, id: usize, tydecls: &HashMap<&'src str, &Ty<'src>>, ) -> EvDecl<'src> { + if let Some(syntax_parameters) = &evdecl.data { + self.check_duplicate_parameters(syntax_parameters); + } + let name = evdecl.name.name; let from = evdecl.from; let evty = evdecl.evty; let call = evdecl.call; - let data = evdecl - .data - .as_ref() - .map(|types| types.iter().map(|ty| self.ty(ty)).collect::>()); + let data = evdecl.data.as_ref().map(|parameters| { + parameters + .parameters + .iter() + .map(|(identifier, ty)| { + let name = identifier.map(|identifier| identifier.name); + + Parameter { name, ty: self.ty(ty) } + }) + .collect::>() + }); if data.is_some() && evty == EvType::Unreliable { let mut min = 0; let mut max = Some(0); - for ty in data.as_ref().unwrap() { - let (ty_min, ty_max) = ty.size(tydecls, &mut HashSet::new()); + for parameter in data.as_ref().unwrap() { + let (ty_min, ty_max) = parameter.ty.size(tydecls, &mut HashSet::new()); min += ty_min; @@ -361,26 +391,51 @@ impl<'src> Converter<'src> { from, evty, call, - data, + data: data.unwrap_or_default(), id, } } fn fndecl(&mut self, fndecl: &SyntaxFnDecl<'src>, id: usize) -> FnDecl<'src> { + if let Some(syntax_parameters) = &fndecl.args { + self.check_duplicate_parameters(syntax_parameters); + } + + if let Some(syntax_parameters) = &fndecl.rets { + for parameter in &syntax_parameters.parameters { + if let Some(identifier) = parameter.0 { + self.report(Report::AnalyzeNamedReturn { + name_span: identifier.span(), + }); + } + } + } + let name = fndecl.name.name; let call = fndecl.call; - let args = fndecl - .args - .as_ref() - .map(|types| types.iter().map(|ty| self.ty(ty)).collect::>()); - let rets = fndecl - .rets - .as_ref() - .map(|types| types.iter().map(|ty| self.ty(ty)).collect::>()); + let args = fndecl.args.as_ref().map(|parameters| { + parameters + .parameters + .iter() + .map(|(identifier, ty)| { + let name = identifier.map(|identifier| identifier.name); + + Parameter { name, ty: self.ty(ty) } + }) + .collect::>() + }); + + let rets = fndecl.rets.as_ref().map(|parameters| { + parameters + .parameters + .iter() + .map(|(_, ty)| self.ty(ty)) + .collect::>() + }); FnDecl { name, - args, + args: args.unwrap_or_default(), call, rets, id, diff --git a/zap/src/parser/grammar.lalrpop b/zap/src/parser/grammar.lalrpop index dee0fca0..73abf904 100644 --- a/zap/src/parser/grammar.lalrpop +++ b/zap/src/parser/grammar.lalrpop @@ -43,8 +43,8 @@ Decl: SyntaxDecl<'input> = { FnDecl: SyntaxFnDecl<'input> = { "funct" "=" "{" "call" ":" - )?> - )?> + )?> + )?> ","? "}" ";"? => SyntaxFnDecl { start, name, call, args, rets, end }, } @@ -59,7 +59,7 @@ EvDecl: SyntaxEvDecl<'input> = { "from" ":" "," "type" ":" "," "call" ":" - )?> + )?> ","? "}" ";"? => SyntaxEvDecl { start, name, from, evty, call, data, end }, } @@ -116,9 +116,20 @@ TyKind: SyntaxTyKind<'input> = { "Instance" ")")?> => SyntaxTyKind::Instance(c), } -TyList: Vec> = { - => vec![ty], - "(" > ")" => tys, +Parameters: SyntaxParameters<'input> = { + + + => SyntaxParameters { start, parameters, end }, +} + +ParameterList: Vec<(Option>, SyntaxTy<'input>)> = { + => vec![(None, ty)], + "(" > ")" => tys, +} + +Parameter: (Option>, SyntaxTy<'input>) = { + ":" => (Some(name), ty), + => (None, ty), } Enum: SyntaxEnum<'input> = { diff --git a/zap/src/parser/reports.rs b/zap/src/parser/reports.rs index bf02dc38..79e2f376 100644 --- a/zap/src/parser/reports.rs +++ b/zap/src/parser/reports.rs @@ -96,6 +96,16 @@ pub enum Report<'src> { dup_span: Span, name: &'src str, }, + + AnalyzeDuplicateParameter { + prev_span: Span, + dup_span: Span, + name: &'src str, + }, + + AnalyzeNamedReturn { + name_span: Span, + }, } impl<'src> Report<'src> { @@ -122,6 +132,8 @@ impl<'src> Report<'src> { Self::AnalyzeUnboundedRecursiveType { .. } => Severity::Error, Self::AnalyzeMissingOptValue { .. } => Severity::Error, Self::AnalyzeDuplicateDecl { .. } => Severity::Error, + Self::AnalyzeDuplicateParameter { .. } => Severity::Error, + Self::AnalyzeNamedReturn { .. } => Severity::Error, } } @@ -151,6 +163,8 @@ impl<'src> Report<'src> { Self::AnalyzeUnboundedRecursiveType { .. } => "unbounded recursive type".to_string(), Self::AnalyzeMissingOptValue { .. } => "missing option expected".to_string(), Self::AnalyzeDuplicateDecl { name, .. } => format!("duplicate declaration '{}'", name), + Self::AnalyzeDuplicateParameter { name, .. } => format!("duplicate parameter '{}'", name), + Self::AnalyzeNamedReturn { .. } => "rets cannot be named".to_string(), } } @@ -177,6 +191,8 @@ impl<'src> Report<'src> { Self::AnalyzeUnboundedRecursiveType { .. } => "3012", Self::AnalyzeMissingOptValue { .. } => "3013", Self::AnalyzeDuplicateDecl { .. } => "3014", + Self::AnalyzeDuplicateParameter { .. } => "3015", + Self::AnalyzeNamedReturn { .. } => "3016", } } @@ -272,6 +288,19 @@ impl<'src> Report<'src> { Label::primary((), dup_span.clone()).with_message("duplicate declaration"), ] } + + Self::AnalyzeDuplicateParameter { + prev_span, dup_span, .. + } => { + vec![ + Label::secondary((), prev_span.clone()).with_message("first parameter"), + Label::primary((), dup_span.clone()).with_message("duplicate parameter"), + ] + } + + Self::AnalyzeNamedReturn { name_span } => { + vec![Label::primary((), name_span.clone()).with_message("must be removed")] + } } } @@ -335,6 +364,8 @@ impl<'src> Report<'src> { "the {expected} option should not be empty if {required_when}" )]), Self::AnalyzeDuplicateDecl { .. } => None, + Self::AnalyzeDuplicateParameter { .. } => None, + Self::AnalyzeNamedReturn { .. } => None, } } diff --git a/zap/src/parser/syntax_tree.rs b/zap/src/parser/syntax_tree.rs index 9acdd38c..0d77b111 100644 --- a/zap/src/parser/syntax_tree.rs +++ b/zap/src/parser/syntax_tree.rs @@ -78,8 +78,8 @@ pub struct SyntaxFnDecl<'src> { pub start: usize, pub name: SyntaxIdentifier<'src>, pub call: FnCall, - pub args: Option>>, - pub rets: Option>>, + pub args: Option>, + pub rets: Option>, pub end: usize, } @@ -96,7 +96,7 @@ pub struct SyntaxEvDecl<'src> { pub from: EvSource, pub evty: EvType, pub call: EvCall, - pub data: Option>>, + pub data: Option>, pub end: usize, } @@ -106,6 +106,19 @@ impl<'src> Spanned for SyntaxEvDecl<'src> { } } +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxParameters<'src> { + pub start: usize, + pub parameters: Vec<(Option>, SyntaxTy<'src>)>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxParameters<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + #[derive(Debug, Clone, PartialEq)] pub struct SyntaxTyDecl<'src> { pub start: usize,