diff --git a/docs/config/events.md b/docs/config/events.md index 81f9ba3f..e63e9bf1 100644 --- a/docs/config/events.md +++ b/docs/config/events.md @@ -8,6 +8,13 @@ const example = `event MyEvent = { bar: u8, }, }` + +const dataExample = `event MyEvent = { + from: Server, + type: Reliable, + call: ManyAsync, + data: (boolean, u32, string) +}` # Events @@ -56,3 +63,8 @@ Use synchronous events with extreme caution. ### `data` 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: + + diff --git a/docs/config/functions.md b/docs/config/functions.md index d7341a5d..1b9132c5 100644 --- a/docs/config/functions.md +++ b/docs/config/functions.md @@ -7,6 +7,12 @@ const example = `funct Test = { }, rets: enum { Success, Fail } }` + +const multiArgsRetsExample = `funct MultipleArgsRets = { + call: Async, + args: (boolean, u8), + rets: (boolean, string) +}` # Functions @@ -41,6 +47,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: + + + ### `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. +- The function can return multiple values by separating each type with a comma and wrapping them all in parentheses: + + diff --git a/docs/usage/events.md b/docs/usage/events.md index e7b9b325..79f3f481 100644 --- a/docs/usage/events.md +++ b/docs/usage/events.md @@ -13,9 +13,7 @@ event AnotherEvent = { from: Client, type: Reliable, call: SingleAsync, - data: struct { - baz: boolean, - }, + data: (boolean, u8) }` @@ -50,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, Data) +Zap.AnotherEvent.SetCallback(function(Player, Bool, Number) -- Do something with the player and data end) ``` @@ -81,14 +79,12 @@ Use `Sync` events only when performance is critical. ## Firing From the Client -The client only has a single function for firing events, `Fire`. This function takes the event's data as its only argument. +The client only has a single function for firing events, `Fire`. This function takes the event's data as it arguments. ```lua local Zap = require(Path.To.Zap) -Zap.AnotherEvent.Fire({ - baz = true, -}) +Zap.AnotherEvent.Fire(true, 32) ``` ## Firing From the Server @@ -116,7 +112,7 @@ Zap.MyEvent.Fire(Player, { ### FireAll -The `FireAll` function takes the event's data as its only argument. It will fire the event to all players. +The `FireAll` function takes the event's data as its arguments. It will fire the event to all players. ```lua local Zap = require(Path.To.Zap) diff --git a/zap/src/config.rs b/zap/src/config.rs index e8e9d83e..b88383fa 100644 --- a/zap/src/config.rs +++ b/zap/src/config.rs @@ -76,8 +76,8 @@ impl std::fmt::Display for YieldType { pub struct FnDecl<'src> { pub name: &'src str, pub call: FnCall, - pub args: Option>, - pub rets: Option>, + pub args: Option>>, + pub rets: Option>>, pub id: usize, } @@ -93,7 +93,7 @@ pub struct EvDecl<'src> { pub from: EvSource, pub evty: EvType, pub call: EvCall, - pub data: Option>, + pub data: Option>>, pub id: usize, } diff --git a/zap/src/irgen/des.rs b/zap/src/irgen/des.rs index c83caad0..860ccb74 100644 --- a/zap/src/irgen/des.rs +++ b/zap/src/irgen/des.rs @@ -14,8 +14,20 @@ impl Gen for Des { self.buf.push(stmt); } - fn gen(mut self, var: Var, ty: &Ty) -> Vec { - self.push_ty(ty, var); + 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); + } + self.buf } @@ -419,11 +431,11 @@ impl Des { } } -pub fn gen(ty: &Ty, var: &str, checks: bool) -> Vec { +pub fn gen(types: &[Ty], var: &str, checks: bool) -> Vec { Des { checks, buf: vec![], var_occurrences: HashMap::new(), } - .gen(var.into(), ty) + .gen(var.into(), types) } diff --git a/zap/src/irgen/mod.rs b/zap/src/irgen/mod.rs index 03b3c051..88c16014 100644 --- a/zap/src/irgen/mod.rs +++ b/zap/src/irgen/mod.rs @@ -9,7 +9,7 @@ pub mod ser; pub trait Gen { fn push_stmt(&mut self, stmt: Stmt); - fn gen(self, var: Var, ty: &Ty<'_>) -> Vec; + fn gen(self, var: Var, types: &[Ty]) -> Vec; 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 73ca1c1a..5fcadaf0 100644 --- a/zap/src/irgen/ser.rs +++ b/zap/src/irgen/ser.rs @@ -14,8 +14,20 @@ impl Gen for Ser { self.buf.push(stmt); } - fn gen(mut self, var: Var, ty: &Ty) -> Vec { - self.push_ty(ty, var); + 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); + } + self.buf } @@ -357,11 +369,11 @@ impl Ser { } } -pub fn gen(ty: &Ty, var: &str, checks: bool) -> Vec { +pub fn gen(types: &[Ty], var: &str, checks: bool) -> Vec { Ser { checks, buf: vec![], var_occurrences: HashMap::new(), } - .gen(var.into(), ty) + .gen(var.into(), types) } diff --git a/zap/src/output/luau/client.rs b/zap/src/output/luau/client.rs index a6703919..0e89e6ae 100644 --- a/zap/src/output/luau/client.rs +++ b/zap/src/output/luau/client.rs @@ -1,5 +1,5 @@ use crate::{ - config::{Config, EvCall, EvDecl, EvSource, EvType, FnDecl, TyDecl, YieldType}, + config::{Config, EvCall, EvDecl, EvSource, EvType, FnDecl, Ty, TyDecl, YieldType}, irgen::{des, ser}, }; @@ -103,14 +103,14 @@ impl<'src> ClientOutput<'src> { self.push_line(&format!("function types.write_{name}(value: {name})")); self.indent(); - self.push_stmts(&ser::gen(ty, "value", self.config.write_checks)); + self.push_stmts(&ser::gen(&[ty.clone()], "value", 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, "value", false)); + self.push_stmts(&des::gen(&[ty.clone()], "value", false)); self.push_line("return value"); self.dedent(); self.push_line("end"); @@ -174,6 +174,23 @@ impl<'src> ClientOutput<'src> { )); } + fn get_values(&self, data: &Option>) -> String { + if let Some(types) = data { + (1..=types.len()) + .map(|i| { + if i == 1 { + "value".to_string() + } else { + format!("value{}", i) + } + }) + .collect::>() + .join(", ") + } else { + "value".to_string() + } + } + fn push_reliable_callback(&mut self, first: bool, ev: &EvDecl) { let id = ev.id; @@ -193,7 +210,9 @@ impl<'src> ClientOutput<'src> { self.indent(); - self.push_line("local value"); + let values = self.get_values(&ev.data); + + self.push_line(&format!("local {values}")); if let Some(data) = &ev.data { self.push_stmts(&des::gen(data, "value", true)); @@ -213,10 +232,10 @@ impl<'src> ClientOutput<'src> { } match ev.call { - EvCall::SingleSync => self.push_line(&format!("events[{id}](value)")), - EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], value)")), - EvCall::ManySync => self.push_line("cb(value)"), - EvCall::ManyAsync => self.push_line("task.spawn(cb, value)"), + EvCall::SingleSync => self.push_line(&format!("events[{id}]({values})")), + EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], {values})")), + EvCall::ManySync => self.push_line(&format!("cb({values})")), + EvCall::ManyAsync => self.push_line(&format!("task.spawn(cb, {values})")), } if ev.call == EvCall::ManySync || ev.call == EvCall::ManyAsync { @@ -228,8 +247,13 @@ impl<'src> ClientOutput<'src> { self.push_line("else"); self.indent(); - if ev.data.is_some() { - self.push_line(&format!("table.insert(event_queue[{id}], value)")); + if let Some(types) = &ev.data { + if types.len() > 1 { + self.push_line(&format!("table.insert(event_queue[{id}], {{ {values} }})")); + } else { + self.push_line(&format!("table.insert(event_queue[{id}], value)")); + } + self.push_line(&format!("if #event_queue[{id}] > 64 then")); } else { self.push_line(&format!("event_queue[{id}] += 1")); @@ -280,7 +304,9 @@ impl<'src> ClientOutput<'src> { self.push_line("local call_id = buffer.readu8(incoming_buff, read(1))"); - self.push_line("local value"); + let values = self.get_values(&fndecl.rets); + + self.push_line(&format!("local {values}")); if let Some(data) = &fndecl.rets { self.push_stmts(&des::gen(data, "value", true)); @@ -288,10 +314,10 @@ impl<'src> ClientOutput<'src> { match self.config.yield_type { YieldType::Yield | YieldType::Future => { - self.push_line(&format!("task.spawn(event_queue[{id}][call_id], value)")); + self.push_line(&format!("task.spawn(event_queue[{id}][call_id], {values})")); } YieldType::Promise => { - self.push_line(&format!("event_queue[{id}][call_id](value)")); + self.push_line(&format!("event_queue[{id}][call_id]({values})")); } } @@ -369,7 +395,9 @@ impl<'src> ClientOutput<'src> { self.indent(); - self.push_line("local value"); + let values = self.get_values(&ev.data); + + self.push_line(&format!("local {values}")); if let Some(data) = &ev.data { self.push_stmts(&des::gen(data, "value", self.config.write_checks)); @@ -389,10 +417,10 @@ impl<'src> ClientOutput<'src> { } match ev.call { - EvCall::SingleSync => self.push_line(&format!("events[{id}](value)")), - EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], value)")), - EvCall::ManySync => self.push_line("cb(value)"), - EvCall::ManyAsync => self.push_line("task.spawn(cb, value)"), + EvCall::SingleSync => self.push_line(&format!("events[{id}]({values})")), + EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], {values})")), + EvCall::ManySync => self.push_line(&format!("cb({values})")), + EvCall::ManyAsync => self.push_line(&format!("task.spawn(cb, {values})")), } if ev.call == EvCall::ManySync || ev.call == EvCall::ManyAsync { @@ -404,8 +432,13 @@ impl<'src> ClientOutput<'src> { self.push_line("else"); self.indent(); - if ev.data.is_some() { - self.push_line(&format!("table.insert(event_queue[{id}], value)")); + if let Some(types) = &ev.data { + if types.len() > 1 { + self.push_line(&format!("table.insert(event_queue[{id}], {{ {values} }})")); + } else { + self.push_line(&format!("table.insert(event_queue[{id}], value)")); + } + self.push_line(&format!("if #event_queue[{id}] > 64 then")); } else { self.push_line(&format!("event_queue[{id}] += 1")); @@ -515,6 +548,23 @@ 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() } + ); + + if i > 0 { + self.push(", "); + } + + self.push(&format!("{value}: ")); + self.push_ty(ty); + } + } + fn push_return_fire(&mut self, ev: &EvDecl) { let fire = self.config.casing.with("Fire", "fire", "fire"); let value = self.config.casing.with("Value", "value", "value"); @@ -522,9 +572,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{fire} = function(")); - if let Some(data) = &ev.data { - self.push(&format!("{value}: ")); - self.push_ty(data); + if let Some(types) = &ev.data { + self.push_value_parameters(types); } self.push(")\n"); @@ -569,6 +618,14 @@ impl<'src> ClientOutput<'src> { } } + fn push_queued_value(&mut self, types: &[Ty]) { + if types.len() > 1 { + self.push("unpack(value)"); + } else { + self.push("value"); + } + } + fn push_return_setcallback(&mut self, ev: &EvDecl) { let id = ev.id; @@ -578,8 +635,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{set_callback} = function({callback}: (")); - if let Some(data) = &ev.data { - self.push_ty(data); + if let Some(types) = &ev.data { + self.push_value_parameters(types); } self.push(") -> ()): () -> ()\n"); @@ -587,14 +644,20 @@ impl<'src> ClientOutput<'src> { self.push_line(&format!("events[{id}] = {callback}")); - if ev.data.is_some() { + if let Some(types) = &ev.data { self.push_line(&format!("for _, value in event_queue[{id}] do")); self.indent(); if ev.call == EvCall::SingleSync { - self.push_line(&format!("{callback}(value)")) + self.push_indent(); + self.push(&format!("{callback}(")); + self.push_queued_value(types); + self.push_line(")\n"); } else { - self.push_line(&format!("task.spawn({callback}, value)")) + self.push_indent(); + self.push(&format!("task.spawn({callback}, ")); + self.push_queued_value(types); + self.push(")\n"); } self.dedent(); @@ -638,8 +701,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{on} = function({callback}: (")); - if let Some(data) = &ev.data { - self.push_ty(data); + if let Some(types) = &ev.data { + self.push_value_parameters(types); } self.push(") -> ())\n"); @@ -647,14 +710,20 @@ impl<'src> ClientOutput<'src> { self.push_line(&format!("table.insert(events[{id}], {callback})")); - if ev.data.is_some() { + if let Some(types) = &ev.data { self.push_line(&format!("for _, value in event_queue[{id}] do")); self.indent(); if ev.call == EvCall::ManySync { - self.push_line(&format!("{callback}(value)")) + self.push_indent(); + self.push(&format!("{callback}(")); + self.push_queued_value(types); + self.push_line(")\n"); } else { - self.push_line(&format!("task.spawn({callback}, value)")) + self.push_indent(); + self.push(&format!("task.spawn({callback}, ")); + self.push_queued_value(types); + self.push(")\n"); } self.dedent(); @@ -724,23 +793,35 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{call} = function(")); - if let Some(ty) = &fndecl.args { - self.push(&format!("{value}: ")); - self.push_ty(ty); + if let Some(types) = &fndecl.args { + self.push_value_parameters(types); } self.push(")"); - if let Some(ty) = &fndecl.rets { + if let Some(types) = &fndecl.rets { match self.config.yield_type { YieldType::Future => { - self.push(": Future.Future<"); - self.push_ty(ty); - self.push(">"); + self.push(": Future.Future<("); + + for (i, ty) in types.iter().enumerate() { + if i > 0 { + self.push(", "); + } + self.push_ty(ty); + } + + self.push(")>"); } YieldType::Yield => { - self.push(": "); - self.push_ty(ty); + self.push(": ("); + for (i, ty) in types.iter().enumerate() { + if i > 0 { + self.push(", "); + } + self.push_ty(ty); + } + self.push(")"); } _ => (), } @@ -774,10 +855,10 @@ impl<'src> ClientOutput<'src> { match self.config.yield_type { YieldType::Yield => { self.push_line(&format!("event_queue[{id}][function_call_id] = coroutine.running()",)); - self.push_line("local async_value = coroutine.yield()"); + self.push_line("return coroutine.yield()"); } YieldType::Future => { - self.push_line("local async_value = Future.new(function()"); + self.push_line("return Future.new(function()"); self.indent(); self.push_line(&format!("event_queue[{id}][function_call_id] = coroutine.running()",)); @@ -787,7 +868,7 @@ impl<'src> ClientOutput<'src> { self.push_line("end)"); } YieldType::Promise => { - self.push_line("local async_value = Promise.new(function(resolve)"); + self.push_line("return Promise.new(function(resolve)"); self.indent(); self.push_line(&format!("event_queue[{id}][function_call_id] = resolve")); @@ -797,8 +878,6 @@ impl<'src> ClientOutput<'src> { } } - self.push_line("return async_value"); - self.dedent(); self.push_line("end,"); diff --git a/zap/src/output/luau/server.rs b/zap/src/output/luau/server.rs index 11754a78..3e0557e5 100644 --- a/zap/src/output/luau/server.rs +++ b/zap/src/output/luau/server.rs @@ -1,5 +1,5 @@ use crate::{ - config::{Config, EvCall, EvDecl, EvSource, EvType, FnCall, FnDecl, TyDecl}, + config::{Config, EvCall, EvDecl, EvSource, EvType, FnCall, FnDecl, Ty, TyDecl}, irgen::{des, ser}, }; @@ -115,14 +115,14 @@ impl<'a> ServerOutput<'a> { self.push_line(&format!("function types.write_{name}(value: {name})")); self.indent(); - self.push_stmts(&ser::gen(ty, "value", self.config.write_checks)); + self.push_stmts(&ser::gen(&[ty.clone()], "value", 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, "value", true)); + self.push_stmts(&des::gen(&[ty.clone()], "value", true)); self.push_line("return value"); self.dedent(); self.push_line("end"); @@ -186,6 +186,23 @@ impl<'a> ServerOutput<'a> { )); } + fn get_values(&self, data: &Option>) -> String { + if let Some(types) = data { + (1..=types.len()) + .map(|i| { + if i == 1 { + "value".to_string() + } else { + format!("value{}", i) + } + }) + .collect::>() + .join(", ") + } else { + "value".to_string() + } + } + fn push_reliable_callback(&mut self, first: bool, ev: &EvDecl) { let id = ev.id; @@ -205,7 +222,9 @@ impl<'a> ServerOutput<'a> { self.indent(); - self.push_line("local value"); + let values = self.get_values(&ev.data); + + self.push_line(&format!("local {values}")); if let Some(data) = &ev.data { self.push_stmts(&des::gen(data, "value", true)); @@ -220,10 +239,10 @@ impl<'a> ServerOutput<'a> { self.indent(); match ev.call { - EvCall::SingleSync => self.push_line(&format!("events[{id}](player, value)")), - EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], player, value)")), - EvCall::ManySync => self.push_line("cb(player, value)"), - EvCall::ManyAsync => self.push_line("task.spawn(cb, player, value)"), + EvCall::SingleSync => self.push_line(&format!("events[{id}](player, {values})")), + EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], player, {values})")), + EvCall::ManySync => self.push_line(&format!("cb(player, {values})")), + EvCall::ManyAsync => self.push_line(&format!("task.spawn(cb, player, {values})")), } self.dedent(); @@ -249,7 +268,10 @@ impl<'a> ServerOutput<'a> { self.indent(); self.push_line("local call_id = buffer.readu8(buff, read(1))"); - self.push_line("local value"); + + let values = self.get_values(&fndecl.args); + + self.push_line(&format!("local {values}")); if let Some(data) = &fndecl.args { self.push_stmts(&des::gen(data, "value", true)); @@ -259,12 +281,36 @@ impl<'a> ServerOutput<'a> { self.indent(); + let rets = if let Some(types) = &fndecl.args { + (1..=types.len()) + .map(|i| { + if i > 1 { + format!("rets{}", i) + } else { + "rets".to_string() + } + }) + .collect::>() + .join(", ") + } else { + "rets".to_string() + }; + if fndecl.call == FnCall::Async { + let args = if let Some(types) = &fndecl.args { + (1..=types.len()) + .map(|i| format!("value_{}", i)) + .collect::>() + .join(", ") + } else { + "value_1".to_string() + }; + // Avoid using upvalues as an optimization. - self.push_line("task.spawn(function(player_2, call_id_2, value_2)"); + self.push_line(&format!("task.spawn(function(player_2, call_id_2, {args})")); self.indent(); - self.push_line(&format!("local rets = events[{id}](player_2, value_2)")); + self.push_line(&format!("local {rets} = events[{id}](player_2, {args})")); self.push_line("load_player(player_2)"); self.push_write_event_id(fndecl.id); @@ -279,9 +325,9 @@ impl<'a> ServerOutput<'a> { self.push_line("player_map[player_2] = save()"); self.dedent(); - self.push_line("end, player, call_id, value)"); + self.push_line(&format!("end, player, call_id, {values})")); } else { - self.push_line(&format!("local rets = events[{id}](player, value)")); + self.push_line(&format!("local {rets} = events[{id}](player, {values})")); self.push_line("load_player(player)"); self.push_write_event_id(fndecl.id); @@ -371,7 +417,9 @@ impl<'a> ServerOutput<'a> { self.indent(); - self.push_line("local value"); + let values = self.get_values(&ev.data); + + self.push_line(&format!("local {}", values)); if let Some(data) = &ev.data { self.push_stmts(&des::gen(data, "value", true)); @@ -386,10 +434,10 @@ impl<'a> ServerOutput<'a> { self.indent(); match ev.call { - EvCall::SingleSync => self.push_line(&format!("events[{id}](player, value)")), - EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], player, value)")), - EvCall::ManySync => self.push_line("cb(player, value)"), - EvCall::ManyAsync => self.push_line("task.spawn(cb, player, value)"), + EvCall::SingleSync => self.push_line(&format!("events[{id}](player, {values})")), + EvCall::SingleAsync => self.push_line(&format!("task.spawn(events[{id}], player, {values})")), + EvCall::ManySync => self.push_line(&format!("cb(player, {values})")), + EvCall::ManyAsync => self.push_line(&format!("task.spawn(cb, player, {values})")), } self.dedent(); @@ -447,8 +495,25 @@ 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() } + ); + + if i > 0 { + self.push(", "); + } + + self.push(&format!("{value}: ")); + self.push_ty(ty); + } + } + fn push_return_fire(&mut self, ev: &EvDecl) { - let ty = &ev.data; + let types = &ev.data; let fire = self.config.casing.with("Fire", "fire", "fire"); let player = self.config.casing.with("Player", "player", "player"); @@ -457,9 +522,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire} = function({player}: Player")); - if let Some(ty) = ty { - self.push(&format!(", {value}: ")); - self.push_ty(ty); + if let Some(types) = types { + self.push(", "); + self.push_value_parameters(types); } self.push(")\n"); @@ -472,8 +537,8 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(ty) = ty { - self.push_stmts(&ser::gen(ty, value, self.config.write_checks)); + if let Some(types) = types { + self.push_stmts(&ser::gen(types, value, self.config.write_checks)); } match ev.evty { @@ -490,7 +555,7 @@ impl<'a> ServerOutput<'a> { } fn push_return_fire_all(&mut self, ev: &EvDecl) { - let ty = &ev.data; + let types = &ev.data; let fire_all = self.config.casing.with("FireAll", "fireAll", "fire_all"); let value = self.config.casing.with("Value", "value", "value"); @@ -498,9 +563,8 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_all} = function(")); - if let Some(ty) = ty { - self.push(&format!("{value}: ")); - self.push_ty(ty); + if let Some(types) = types { + self.push_value_parameters(types); } self.push(")\n"); @@ -510,8 +574,8 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(ty) = ty { - self.push_stmts(&ser::gen(ty, value, self.config.write_checks)); + if let Some(types) = types { + self.push_stmts(&ser::gen(types, value, self.config.write_checks)); } match ev.evty { @@ -540,7 +604,7 @@ impl<'a> ServerOutput<'a> { } fn push_return_fire_except(&mut self, ev: &EvDecl) { - let ty = &ev.data; + let types = &ev.data; let fire_except = self.config.casing.with("FireExcept", "fireExcept", "fire_except"); let except = self.config.casing.with("Except", "except", "except"); @@ -549,9 +613,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_except} = function({except}: Player")); - if let Some(ty) = ty { - self.push(&format!(", {value}: ")); - self.push_ty(ty); + if let Some(types) = types { + self.push(", "); + self.push_value_parameters(types); } self.push(")\n"); @@ -561,8 +625,8 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(ty) = ty { - self.push_stmts(&ser::gen(ty, value, self.config.write_checks)); + if let Some(types) = types { + self.push_stmts(&ser::gen(types, value, self.config.write_checks)); } match ev.evty { @@ -603,7 +667,7 @@ impl<'a> ServerOutput<'a> { } fn push_return_fire_list(&mut self, ev: &EvDecl) { - let ty = &ev.data; + let types = &ev.data; let fire_list = self.config.casing.with("FireList", "fireList", "fire_list"); let list = self.config.casing.with("List", "list", "list"); @@ -612,9 +676,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_list} = function({list}: {{ Player }}")); - if let Some(ty) = ty { - self.push(&format!(", {value}: ")); - self.push_ty(ty); + if let Some(types) = types { + self.push(", "); + self.push_value_parameters(types); } self.push(")\n"); @@ -624,7 +688,7 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(ty) = ty { + if let Some(ty) = types { self.push_stmts(&ser::gen(ty, value, self.config.write_checks)); } @@ -658,7 +722,7 @@ impl<'a> ServerOutput<'a> { } fn push_return_fire_set(&mut self, ev: &EvDecl) { - let ty = &ev.data; + let types = &ev.data; let fire_set = self.config.casing.with("FireSet", "fireSet", "fire_set"); let set = self.config.casing.with("Set", "set", "set"); @@ -667,9 +731,9 @@ impl<'a> ServerOutput<'a> { self.push_indent(); self.push(&format!("{fire_set} = function({set}: {{ [Player]: true }}")); - if let Some(ty) = ty { - self.push(&format!(", {value}: ")); - self.push_ty(ty); + if let Some(types) = types { + self.push(", "); + self.push_value_parameters(types); } self.push(")\n"); @@ -679,7 +743,7 @@ impl<'a> ServerOutput<'a> { self.push_write_event_id(ev.id); - if let Some(ty) = ty { + if let Some(ty) = types { self.push_stmts(&ser::gen(ty, value, self.config.write_checks)); } @@ -742,13 +806,14 @@ impl<'a> ServerOutput<'a> { let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback"); let callback = self.config.casing.with("Callback", "callback", "callback"); + let player = self.config.casing.with("Player", "player", "player"); self.push_indent(); - self.push(&format!("{set_callback} = function({callback}: (Player")); + self.push(&format!("{set_callback} = function({callback}: ({player}: Player")); - if let Some(ty) = &ev.data { + if let Some(types) = &ev.data { self.push(", "); - self.push_ty(ty); + self.push_value_parameters(types); } self.push(") -> ()): () -> ()\n"); @@ -773,13 +838,14 @@ impl<'a> ServerOutput<'a> { let on = self.config.casing.with("On", "on", "on"); let callback = self.config.casing.with("Callback", "callback", "callback"); + let player = self.config.casing.with("Player", "player", "player"); self.push_indent(); - self.push(&format!("{on} = function({callback}: (Player")); + self.push(&format!("{on} = function({callback}: ({player}: Player")); - if let Some(ty) = &ev.data { + if let Some(types) = &ev.data { self.push(", "); - self.push_ty(ty); + self.push_value_parameters(types); } self.push(") -> ()): () -> ()\n"); @@ -806,19 +872,25 @@ impl<'a> ServerOutput<'a> { let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback"); let callback = self.config.casing.with("Callback", "callback", "callback"); + let player = self.config.casing.with("Player", "player", "player"); self.push_indent(); - self.push(&format!("{set_callback} = function({callback}: (Player")); + self.push(&format!("{set_callback} = function({callback}: ({player}: Player")); - if let Some(ty) = &fndecl.args { + if let Some(types) = &fndecl.args { self.push(", "); - self.push_ty(ty); + self.push_value_parameters(types); } self.push(") -> ("); - if let Some(ty) = &fndecl.rets { - self.push_ty(ty); + if let Some(types) = &fndecl.rets { + for (i, ty) in types.iter().enumerate() { + if i > 0 { + self.push(", "); + } + self.push_ty(ty); + } } self.push(")): () -> ()\n"); diff --git a/zap/src/output/tooling.rs b/zap/src/output/tooling.rs index ebbf0a2b..2a228a3d 100644 --- a/zap/src/output/tooling.rs +++ b/zap/src/output/tooling.rs @@ -1,5 +1,5 @@ use crate::{ - config::{Config, EvDecl, FnDecl, TyDecl}, + config::{Config, EvDecl, FnDecl, Ty, TyDecl}, irgen::{des, Stmt}, Output, }; @@ -115,7 +115,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, "value", true)); + self.push_stmts(&des::gen(&[ty.clone()], "value", true)); self.push_line("return value"); self.dedent(); self.push_line("end"); @@ -129,6 +129,22 @@ impl<'src> ToolingOutput<'src> { } } + fn get_values(&self, data: &Option>) -> String { + if let Some(types) = data { + (1..=types.len()) + .map(|i| { + if i == 1 { + "value".to_string() + } else { + format!("value{}", i) + } + }) + .collect::>() + .join(", ") + } else { + "value".to_string() + } + } fn push_event_callback(&mut self, first: bool, ev: &EvDecl) { let id = ev.id; @@ -148,7 +164,9 @@ impl<'src> ToolingOutput<'src> { self.indent(); - self.push_line("local value"); + let values = self.get_values(&ev.data); + + self.push_line(&format!("local {values}")); if let Some(data) = &ev.data { self.push_stmts(&des::gen(data, "value", true)); @@ -169,7 +187,7 @@ impl<'src> ToolingOutput<'src> { )); } - self.push("value }"); + self.push(&format!("{values} }}")); self.push("\n"); self.dedent(); @@ -204,7 +222,9 @@ impl<'src> ToolingOutput<'src> { self.push_line("if isServer then"); self.indent(); - self.push_line("local value"); + let values = self.get_values(&fn_decl.args); + + self.push_line(&format!("local {values}")); if let Some(data) = &fn_decl.args { self.push_stmts(&des::gen(data, "value", true)); @@ -222,7 +242,7 @@ impl<'src> ToolingOutput<'src> { self.push(&format!("{{ {} = id, {} = call_id }}, ", event_id, call_id)); } - self.push("value }"); + self.push(&format!("{values} }}")); self.push("\n"); self.dedent(); @@ -232,7 +252,7 @@ impl<'src> ToolingOutput<'src> { self.push_line("else"); self.indent(); - self.push_line("local value"); + self.push_line(&format!("local {values}")); if let Some(data) = &fn_decl.rets { self.push_stmts(&des::gen(data, "value", true)); @@ -250,7 +270,7 @@ impl<'src> ToolingOutput<'src> { self.push(&format!("{{ {} = id, {} = call_id }}, ", event_id, call_id)); } - self.push("value }"); + self.push(&format!("{values} }}")); self.push("\n"); self.dedent(); diff --git a/zap/src/output/typescript/client.rs b/zap/src/output/typescript/client.rs index bc9a4ea9..2590655b 100644 --- a/zap/src/output/typescript/client.rs +++ b/zap/src/output/typescript/client.rs @@ -1,3 +1,4 @@ +use crate::config::Ty; use crate::config::{Config, EvCall, EvSource, TyDecl, YieldType}; use super::ConfigProvider; @@ -73,7 +74,6 @@ impl<'src> ClientOutput<'src> { .filter(|(_, ev_decl)| ev_decl.from == EvSource::Client) { let fire = self.config.casing.with("Fire", "fire", "fire"); - let value = self.config.casing.with("Value", "value", "value"); self.push_line(&format!("export declare const {name}: {{", name = ev.name)); self.indent(); @@ -81,9 +81,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{fire}: (")); - if let Some(data) = &ev.data { - self.push(value); - self.push_arg_ty(data); + if let Some(types) = &ev.data { + self.push_parameters(types); } self.push(") => void;\n"); @@ -108,7 +107,6 @@ impl<'src> ClientOutput<'src> { EvCall::ManySync | EvCall::ManyAsync => self.config.casing.with("On", "on", "on"), }; let callback = self.config.casing.with("Callback", "callback", "callback"); - let value = self.config.casing.with("Value", "value", "value"); self.push_line(&format!("export declare const {name}: {{", name = ev.name)); self.indent(); @@ -116,9 +114,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{set_callback}: ({callback}: (")); - if let Some(data) = &ev.data { - self.push(value); - self.push_arg_ty(data); + if let Some(types) = &ev.data { + self.push_parameters(types); } self.push(") => void) => () => void;\n"); @@ -130,7 +127,6 @@ impl<'src> ClientOutput<'src> { fn push_return_functions(&mut self) { let call = self.config.casing.with("Call", "call", "call"); - let value = self.config.casing.with("Value", "value", "value"); for fndecl in self.config.fndecls.iter() { self.push_line(&format!("export declare const {}: {{", fndecl.name)); @@ -139,9 +135,8 @@ impl<'src> ClientOutput<'src> { self.push_indent(); self.push(&format!("{call}: (")); - if let Some(data) = &fndecl.args { - self.push(value); - self.push_arg_ty(data); + if let Some(types) = &fndecl.args { + self.push_parameters(types); } self.push(") => "); @@ -150,8 +145,22 @@ impl<'src> ClientOutput<'src> { self.push("Promise<") } - if let Some(data) = &fndecl.rets { - self.push_ty(data); + if let Some(types) = &fndecl.rets { + if types.len() > 1 { + self.push("LuaTuple<["); + } + + for (i, ty) in types.iter().enumerate() { + if i > 0 { + self.push(", "); + } + + self.push_ty(ty); + } + + if types.len() > 1 { + self.push("]>"); + } } else { self.push("void"); } diff --git a/zap/src/output/typescript/mod.rs b/zap/src/output/typescript/mod.rs index ca833977..71a29755 100644 --- a/zap/src/output/typescript/mod.rs +++ b/zap/src/output/typescript/mod.rs @@ -224,6 +224,22 @@ pub trait Output: ConfigProvider { } } + fn push_parameters(&mut self, types: &[Ty]) { + let value = self.get_config().casing.with("Value", "value", "value"); + + for (i, ty) in types.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); + } + } + fn push_file_header(&mut self, scope: &str) { self.push_line(&format!( "// {scope} generated by Zap v{} (https://github.com/red-blox/zap)", diff --git a/zap/src/output/typescript/server.rs b/zap/src/output/typescript/server.rs index cc0b6fb4..398ff719 100644 --- a/zap/src/output/typescript/server.rs +++ b/zap/src/output/typescript/server.rs @@ -1,3 +1,4 @@ +use crate::config::Ty; use crate::config::{Config, EvCall, EvDecl, EvSource, TyDecl}; use super::ConfigProvider; @@ -67,14 +68,13 @@ impl<'a> ServerOutput<'a> { fn push_return_fire(&mut self, ev: &EvDecl) { let fire = self.config.casing.with("Fire", "fire", "fire"); let player = self.config.casing.with("Player", "player", "player"); - let value = self.config.casing.with("Value", "value", "value"); self.push_indent(); self.push(&format!("{fire}: ({player}: Player")); - if let Some(data) = &ev.data { - self.push(&format!(", {value}")); - self.push_arg_ty(data); + if let Some(types) = &ev.data { + self.push(", "); + self.push_parameters(types); } self.push(") => void;\n"); @@ -82,14 +82,12 @@ impl<'a> ServerOutput<'a> { fn push_return_fire_all(&mut self, ev: &EvDecl) { let fire_all = self.config.casing.with("FireAll", "fireAll", "fire_all"); - let value = self.config.casing.with("Value", "value", "value"); self.push_indent(); self.push(&format!("{fire_all}: (")); - if let Some(data) = &ev.data { - self.push(value); - self.push_arg_ty(data); + if let Some(types) = &ev.data { + self.push_parameters(types); } self.push(") => void;\n"); @@ -98,14 +96,13 @@ impl<'a> ServerOutput<'a> { fn push_return_fire_except(&mut self, ev: &EvDecl) { let fire_except = self.config.casing.with("FireExcept", "fireExcept", "fire_except"); let except = self.config.casing.with("Except", "except", "except"); - let value = self.config.casing.with("Value", "value", "value"); self.push_indent(); self.push(&format!("{fire_except}: ({except}: Player")); - if let Some(data) = &ev.data { - self.push(&format!(", {value}")); - self.push_arg_ty(data); + if let Some(types) = &ev.data { + self.push(", "); + self.push_parameters(types); } self.push(") => void;\n"); @@ -114,14 +111,13 @@ impl<'a> ServerOutput<'a> { fn push_return_fire_list(&mut self, ev: &EvDecl) { let fire_list = self.config.casing.with("FireList", "fireList", "fire_list"); let list = self.config.casing.with("List", "list", "list"); - let value = self.config.casing.with("Value", "value", "value"); self.push_indent(); self.push(&format!("{fire_list}: ({list}: Player[]")); - if let Some(data) = &ev.data { - self.push(&format!(", {value}")); - self.push_arg_ty(data); + if let Some(types) = &ev.data { + self.push(", "); + self.push_parameters(types); } self.push(") => void;\n"); @@ -130,14 +126,13 @@ impl<'a> ServerOutput<'a> { fn push_return_fire_set(&mut self, ev: &EvDecl) { let fire_set = self.config.casing.with("FireSet", "fireSet", "fire_set"); let set = self.config.casing.with("Set", "set", "set"); - let value = self.config.casing.with("Value", "value", "value"); self.push_indent(); self.push(&format!("{fire_set}: ({set}: Set")); - if let Some(data) = &ev.data { - self.push(&format!(", {value}")); - self.push_arg_ty(data); + if let Some(types) = &ev.data { + self.push(", "); + self.push_parameters(types); } self.push(") => void\n"); @@ -188,14 +183,13 @@ impl<'a> ServerOutput<'a> { }; let callback = self.config.casing.with("Callback", "callback", "callback"); let player = self.config.casing.with("Player", "player", "player"); - let value = self.config.casing.with("Value", "value", "value"); self.push_indent(); self.push(&format!("{set_callback}: ({callback}: ({player}: Player")); - if let Some(data) = &ev.data { - self.push(&format!(", {value}")); - self.push_arg_ty(data); + if let Some(types) = &ev.data { + self.push(", "); + self.push_parameters(types); } self.push(") => void) => () => void;\n"); @@ -213,20 +207,33 @@ impl<'a> ServerOutput<'a> { let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback"); let callback = self.config.casing.with("Callback", "callback", "callback"); let player = self.config.casing.with("Player", "player", "player"); - let value = self.config.casing.with("Value", "value", "value"); self.push_indent(); self.push(&format!("{set_callback}: ({callback}: ({player}: Player")); - if let Some(data) = &fndecl.args { - self.push(&format!(", {value}")); - self.push_arg_ty(data); + if let Some(types) = &fndecl.args { + self.push(", "); + self.push_parameters(types); } self.push(") => "); - if let Some(data) = &fndecl.rets { - self.push_ty(data); + if let Some(types) = &fndecl.rets { + if types.len() > 1 { + self.push("LuaTuple<["); + } + + for (i, ty) in types.iter().enumerate() { + if i > 0 { + self.push(", "); + } + + self.push_ty(ty); + } + + if types.len() > 1 { + self.push("]>"); + } } else { self.push("void"); } diff --git a/zap/src/parser/convert.rs b/zap/src/parser/convert.rs index 54a139f6..25d9915f 100644 --- a/zap/src/parser/convert.rs +++ b/zap/src/parser/convert.rs @@ -319,10 +319,26 @@ impl<'src> Converter<'src> { let from = evdecl.from; let evty = evdecl.evty; let call = evdecl.call; - let data = evdecl.data.as_ref().map(|ty| self.ty(ty)); + let data = evdecl + .data + .as_ref() + .map(|types| types.iter().map(|ty| self.ty(ty)).collect::>()); if data.is_some() && evty == EvType::Unreliable { - let (min, max) = data.as_ref().unwrap().size(tydecls, &mut HashSet::new()); + 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()); + + min += ty_min; + + if let (Some(ty_max), Some(max)) = (ty_max, max.as_mut()) { + *max += ty_max; + } else { + max = None; + } + } if min > self.max_unreliable_size { self.report(Report::AnalyzeOversizeUnreliable { @@ -353,8 +369,14 @@ impl<'src> Converter<'src> { fn fndecl(&mut self, fndecl: &SyntaxFnDecl<'src>, id: usize) -> FnDecl<'src> { let name = fndecl.name.name; let call = fndecl.call; - let args = fndecl.args.as_ref().map(|ty| self.ty(ty)); - let rets = fndecl.rets.as_ref().map(|ty| self.ty(ty)); + 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::>()); FnDecl { name, diff --git a/zap/src/parser/grammar.lalrpop b/zap/src/parser/grammar.lalrpop index e98db5c2..dee0fca0 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 }, } @@ -58,8 +58,8 @@ EvDecl: SyntaxEvDecl<'input> = { "event" "=" "{" "from" ":" "," "type" ":" - "," "call" ":" - )?> + "," "call" ":" + )?> ","? "}" ";"? => SyntaxEvDecl { start, name, from, evty, call, data, end }, } @@ -116,6 +116,11 @@ TyKind: SyntaxTyKind<'input> = { "Instance" ")")?> => SyntaxTyKind::Instance(c), } +TyList: Vec> = { + => vec![ty], + "(" > ")" => tys, +} + Enum: SyntaxEnum<'input> = { => SyntaxEnum { start, kind, end }, } diff --git a/zap/src/parser/syntax_tree.rs b/zap/src/parser/syntax_tree.rs index 08dcce24..9acdd38c 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, } @@ -133,6 +133,12 @@ impl<'src> Spanned for SyntaxTy<'src> { } } +impl<'src> Spanned for Vec> { + fn span(&self) -> Span { + self.first().unwrap().start..self.last().unwrap().end + } +} + #[derive(Debug, Clone, PartialEq)] pub enum SyntaxTyKind<'src> { Num(NumTy, Option>),