diff --git a/docs/.vitepress/components/Editor.vue b/docs/.vitepress/components/Editor.vue
index 7fea8908..54544992 100644
--- a/docs/.vitepress/components/Editor.vue
+++ b/docs/.vitepress/components/Editor.vue
@@ -91,12 +91,14 @@ const beforeMount = (monaco: Monaco) => {
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
+ { open: "'", close: "'" },
],
surroundingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
+ { open: "'", close: "'" },
],
});
@@ -112,9 +114,10 @@ const beforeMount = (monaco: Monaco) => {
const Calls = ["SingleSync", "SingleAsync", "ManySync", "ManyAsync"] as const;
- const Options = ["typescript", "write_checks", "casing", "server_output", "client_output", "manual_event_loop"] as const;
+ const Options = ["typescript", "write_checks", "casing", "server_output", "client_output", "manual_event_loop", "yield_type", "async_lib"] as const;
const Casing = ["PascalCase", "camelCase", "snake_case"].map((value) => `"${value}"`);
+ const YieldType = ["yield", "future", "promise"].map((value) => `"${value}"`);
const setting = [...Locations, ...Brand, ...Calls, ...Casing] as const;
@@ -151,6 +154,7 @@ const beforeMount = (monaco: Monaco) => {
opt: Options,
casing: Casing,
+ yield_type: YieldType,
typescript: Operators,
write_checks: Operators,
@@ -158,6 +162,7 @@ const beforeMount = (monaco: Monaco) => {
output_server: [],
output_client: [],
+ async_lib: [],
} as const;
monaco.languages.registerTokensProviderFactory("zapConfig", {
@@ -292,6 +297,22 @@ const beforeMount = (monaco: Monaco) => {
documentation: "Event",
range: range,
},
+ {
+ label: "funct",
+ kind: monaco.languages.CompletionItemKind.Snippet,
+ insertText: [
+ "funct ${1} = {",
+ "\tcall: ${2},",
+ "\targs: ${3},",
+ "\trets: ${4},",
+ "}\n",
+ ].join("\n"),
+ insertTextRules:
+ monaco.languages.CompletionItemInsertTextRule
+ .InsertAsSnippet,
+ documentation: "Event",
+ range: range,
+ },
];
return { suggestions };
} else {
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index d007189e..d5ef0983 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -19,6 +19,7 @@ const sidebar = [
{ text: 'Options', link: '/config/options' },
{ text: 'Types', link: '/config/types' },
{ text: 'Events', link: '/config/events' },
+ { text: 'Functions', link: '/config/functions' },
]
},
{
diff --git a/docs/config/functions.md b/docs/config/functions.md
new file mode 100644
index 00000000..97c4dbba
--- /dev/null
+++ b/docs/config/functions.md
@@ -0,0 +1,46 @@
+
+
+# Functions
+
+Events are another method of communication where the client can send arguments and have them returned by the server. For security, Zap only supports Client -> Server -> Client functions, not Server -> Client -> Server.
+
+## Defining Events
+
+Events are defined in your config file using the `funct` keyword.
+
+
+
+As you can see they have three fields. Let's go over them one by one:
+
+### `call`
+
+This field determines how the event is listened to on the server. The function will take the `args` as parameters and return `rets`.
+
+- `Async` events can be listened to by one function, and they are called asynchronously.
+- `Sync` events can be listened to by one function, and they are called synchronously.
+
+::: danger
+Synchronous events are not recommended, and should only be used when performance is critical.
+
+- If a synchronous event callback yields it will cause **undefined and game-breaking behavior**.
+- If a synchronous event callback errors it will cause **the packet to be dropped**.
+
+Use synchronous events with extreme caution.
+:::
+
+### `args`
+
+This field determines the data that is sent to the server. It can be any [Zap type](./types.md).
+
+### `rets`
+
+This field determines the data that is sent back to the client from the server. It can be any [Zap type](./types.md).
diff --git a/docs/config/options.md b/docs/config/options.md
index 182f374d..fc4779b3 100644
--- a/docs/config/options.md
+++ b/docs/config/options.md
@@ -4,6 +4,9 @@ opt client_output = "path/to/client/output.lua"`
const outputExample = `opt server_output = "./network/client.luau"
opt client_output = "src/client/zap.luau"`
+
+const asyncLibExample = `opt yield_type = "promise"
+opt async_lib = "require(game:GetService('ReplicatedStorage').Promise)"`
# Options
@@ -42,7 +45,7 @@ This option does not change the casing of your event or type names. It only chan
### Default
-`PascalCase`
+`"PascalCase"`
### Options
@@ -98,7 +101,7 @@ When enabled, Zap will generate a `.d.ts` file for the server and client with th
## `manual_event_loop`
-This option determines if Zap automatically sends reliable events each Heartbeat.
+This option determines if Zap automatically sends reliable events and functions each Heartbeat.
When enabled, a `SendEvents` function will be exported from the client and server modules that must be called manually.
@@ -140,3 +143,55 @@ Note that Zap uses `RunService.Heartbeat` and a 61 hz rate by default.
### Example
+
+## `yield_type`
+
+This option changes the way functions yield in zap.
+
+### Default
+
+`"yield"`
+
+### Options
+
+::: info
+The `"future"` option is not avaliable when `typescript` is enabled.
+:::
+
+- `"yield"`
+- `"future"`
+- `"promise"`
+
+### Example
+
+
+
+## `async_lib`
+
+::: info
+This option is not required when `yield_type` is set to `yield`
+:::
+
+::: tip WARNING
+When using `typescript`, provide the path to the RuntimeLib.
+:::
+
+This option provides the async library to Zap. The option must include a `require` statement, as it will be fed directly into the Luau code.
+
+When using Futures, you must provide a path to [Future by red-blox](https://github.com/red-blox/Util/tree/main/libs/Future). As of writing, there are no other future libraries for Roblox.
+
+Zap is also compatible with almost any Promise library. Some common examples are:
+
+- [Promise by evaera](https://github.com/evaera/roblox-lua-promise/)*
+- [Promise by Quenty](https://github.com/Quenty/NevermoreEngine/tree/main/src/promise)
+- [Promise by red-blox](https://github.com/red-blox/Util/tree/main/libs/Promise)
+
+*The default in roblox-ts.
+
+### Default
+
+The path is empty.
+
+### Example
+
+
diff --git a/zap/src/config.rs b/zap/src/config.rs
index b15e3da1..b72fe670 100644
--- a/zap/src/config.rs
+++ b/zap/src/config.rs
@@ -7,6 +7,7 @@ use std::{
pub struct Config<'src> {
pub tydecls: Vec>,
pub evdecls: Vec>,
+ pub fndecls: Vec>,
pub write_checks: bool,
pub typescript: bool,
@@ -16,6 +17,8 @@ pub struct Config<'src> {
pub client_output: &'src str,
pub casing: Casing,
+ pub yield_type: YieldType,
+ pub async_lib: &'src str,
}
impl<'src> Config<'src> {
@@ -41,13 +44,47 @@ impl Casing {
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum YieldType {
+ Yield,
+ Future,
+ Promise,
+}
+
+impl std::fmt::Display for YieldType {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ // do nothing, as yield will never import
+ YieldType::Yield => Ok(()),
+ YieldType::Future => write!(f, "Future"),
+ YieldType::Promise => write!(f, "Promise"),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct FnDecl<'src> {
+ pub name: &'src str,
+ pub call: FnCall,
+ pub args: Option>,
+ pub rets: Option>,
+ pub id: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FnCall {
+ Async,
+ Sync,
+}
+
#[derive(Debug, Clone)]
pub struct EvDecl<'src> {
pub name: &'src str,
pub from: EvSource,
pub evty: EvType,
pub call: EvCall,
- pub data: Ty<'src>,
+ pub data: Option>,
+ pub id: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -106,7 +143,7 @@ impl<'src> Ty<'src> {
/// size of the type in the buffer will be 0 bytes.
pub fn size(
&self,
- tydecls: &HashMap<&'src str, TyDecl<'src>>,
+ tydecls: &HashMap<&'src str, &Ty<'src>>,
recursed: &mut HashSet<&'src str>,
) -> (usize, Option) {
match self {
@@ -168,7 +205,7 @@ impl<'src> Ty<'src> {
let tydecl = tydecls.get(name).unwrap();
- tydecl.ty.size(tydecls, recursed)
+ tydecl.size(tydecls, recursed)
}
}
@@ -200,7 +237,7 @@ pub enum Enum<'src> {
impl<'src> Enum<'src> {
pub fn size(
&self,
- tydecls: &HashMap<&'src str, TyDecl<'src>>,
+ tydecls: &HashMap<&'src str, &Ty<'src>>,
recursed: &mut HashSet<&'src str>,
) -> (usize, Option) {
match self {
@@ -246,7 +283,7 @@ pub struct Struct<'src> {
impl<'src> Struct<'src> {
pub fn size(
&self,
- tydecls: &HashMap<&'src str, TyDecl<'src>>,
+ tydecls: &HashMap<&'src str, &Ty<'src>>,
recursed: &mut HashSet<&'src str>,
) -> (usize, Option) {
let mut min = 0;
diff --git a/zap/src/output/luau/client.luau b/zap/src/output/luau/client.luau
index 183f59f8..32dfa372 100644
--- a/zap/src/output/luau/client.luau
+++ b/zap/src/output/luau/client.luau
@@ -8,6 +8,4 @@ local unreliable = ReplicatedStorage:WaitForChild("ZAP_UNRELIABLE")
assert(reliable:IsA("RemoteEvent"), "Expected ZAP_RELIABLE to be a RemoteEvent")
assert(unreliable:IsA("UnreliableRemoteEvent"), "Expected ZAP_UNRELIABLE to be an UnreliableRemoteEvent")
-local event_queue: { [number]: { any } } = {}
-
local time = 0
diff --git a/zap/src/output/luau/client.rs b/zap/src/output/luau/client.rs
index 0909276a..72c5b4b4 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, TyDecl},
+ config::{Config, EvCall, EvDecl, EvSource, EvType, FnDecl, TyDecl, YieldType},
irgen::{des, ser},
};
@@ -135,7 +135,9 @@ impl<'src> ClientOutput<'src> {
));
}
- fn push_reliable_callback(&mut self, first: bool, ev: &EvDecl, id: usize) {
+ fn push_reliable_callback(&mut self, first: bool, ev: &EvDecl) {
+ let id = ev.id;
+
self.push_indent();
if first {
@@ -153,7 +155,10 @@ impl<'src> ClientOutput<'src> {
self.indent();
self.push_line("local value");
- self.push_stmts(&des::gen(&ev.data, "value", true));
+
+ if let Some(data) = &ev.data {
+ self.push_stmts(&des::gen(data, "value", true));
+ }
if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync {
self.push_line(&format!("if events[{id}] then"));
@@ -184,9 +189,14 @@ impl<'src> ClientOutput<'src> {
self.push_line("else");
self.indent();
- self.push_line(&format!("table.insert(event_queue[{id}], value)"));
+ if ev.data.is_some() {
+ 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"));
+ self.push_line(&format!("if event_queue[{id}] > 16 then"));
+ }
- self.push_line(&format!("if #event_queue[{id}] > 64 then"));
self.indent();
self.push_line(&format!(
@@ -203,6 +213,47 @@ impl<'src> ClientOutput<'src> {
self.dedent();
}
+ fn push_fn_callback(&mut self, first: bool, fndecl: &FnDecl) {
+ let id = fndecl.id;
+
+ self.push_indent();
+
+ if first {
+ self.push("if ");
+ } else {
+ self.push("elseif ");
+ }
+
+ // push_line is not used here as indent was pushed above
+ // and we don't want to push it twice, especially after
+ // the if/elseif
+ self.push(&format!("id == {id} then"));
+ self.push("\n");
+
+ self.indent();
+
+ self.push_line("local call_id = buffer.readu8(incoming_buff, read(1))");
+
+ self.push_line("local value");
+
+ if let Some(data) = &fndecl.rets {
+ self.push_stmts(&des::gen(data, "value", true));
+ }
+
+ match self.config.yield_type {
+ YieldType::Yield | YieldType::Future => {
+ self.push_line(&format!("task.spawn(event_queue[{id}][call_id], value)"));
+ }
+ YieldType::Promise => {
+ self.push_line(&format!("event_queue[{id}][call_id](value)"));
+ }
+ }
+
+ self.push_line(&format!("event_queue[{id}][call_id] = nil"));
+
+ self.dedent();
+ }
+
fn push_reliable_footer(&mut self) {
self.push_line("else");
self.indent();
@@ -220,16 +271,18 @@ impl<'src> ClientOutput<'src> {
let mut first = true;
- for (i, ev) in self
+ for evdecl in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Server && ev_decl.evty == EvType::Reliable)
+ .filter(|evdecl| evdecl.from == EvSource::Server && evdecl.evty == EvType::Reliable)
{
- let id = i + 1;
+ self.push_reliable_callback(first, evdecl);
+ first = false;
+ }
- self.push_reliable_callback(first, ev, id);
+ for fndecl in self.config.fndecls.iter() {
+ self.push_fn_callback(first, fndecl);
first = false;
}
@@ -251,7 +304,9 @@ impl<'src> ClientOutput<'src> {
));
}
- fn push_unreliable_callback(&mut self, first: bool, ev: &EvDecl, id: usize) {
+ fn push_unreliable_callback(&mut self, first: bool, ev: &EvDecl) {
+ let id = ev.id;
+
self.push_indent();
if first {
@@ -269,7 +324,10 @@ impl<'src> ClientOutput<'src> {
self.indent();
self.push_line("local value");
- self.push_stmts(&des::gen(&ev.data, "value", self.config.write_checks));
+
+ if let Some(data) = &ev.data {
+ self.push_stmts(&des::gen(data, "value", self.config.write_checks));
+ }
if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync {
self.push_line(&format!("if events[{id}] then"));
@@ -300,14 +358,19 @@ impl<'src> ClientOutput<'src> {
self.push_line("else");
self.indent();
- self.push_line(&format!("table.insert(event_queue[{id}], value)"));
+ if ev.data.is_some() {
+ 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"));
+ self.push_line(&format!("if event_queue[{id}] > 64 then"));
+ }
- self.push_line(&format!("if #event_queue[{id}] > 64 then"));
self.indent();
self.push_line(&format!(
- "warn(`[ZAP] {{#event_queue[{id}]}} events in queue for {}. Did you forget to attach a listener?`)",
- ev.name
+ "warn(`[ZAP] {{#event_queue[{}]}} events in queue for {}. Did you forget to attach a listener?`)",
+ ev.id, ev.name
));
self.dedent();
@@ -334,16 +397,13 @@ impl<'src> ClientOutput<'src> {
let mut first = true;
- for (i, ev) in self
+ for ev in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Server && ev_decl.evty == EvType::Unreliable)
+ .filter(|ev_decl| ev_decl.from == EvSource::Server && ev_decl.evty == EvType::Unreliable)
{
- let id = i + 1;
-
- self.push_unreliable_callback(first, ev, id);
+ self.push_unreliable_callback(first, ev);
first = false;
}
@@ -351,42 +411,71 @@ impl<'src> ClientOutput<'src> {
}
fn push_callback_lists(&mut self) {
- self.push_line(&format!("local events = table.create({})", self.config.evdecls.len()));
+ self.push_line(&format!(
+ "local events = table.create({})",
+ self.config.evdecls.len() + self.config.fndecls.len()
+ ));
+ self.push_line(&format!(
+ "local event_queue: {{ [number]: {{ any }} }} = table.create({})",
+ self.config.evdecls.len() + self.config.fndecls.len()
+ ));
+
+ if !self.config.fndecls.is_empty() {
+ self.push_line("local function_call_id = 0");
- for (i, ev_decl) in self
+ if !self.config.async_lib.is_empty() {
+ if self.config.typescript {
+ self.push_line(&format!("local Promise = {}.Promise", self.config.async_lib))
+ } else {
+ self.push_line(&format!("local {} = {}", self.config.yield_type, self.config.async_lib))
+ }
+ }
+ }
+
+ for evdecl in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Server)
+ .filter(|ev_decl| ev_decl.from == EvSource::Server)
{
- let id = i + 1;
+ let id = evdecl.id;
- if ev_decl.call == EvCall::ManyAsync || ev_decl.call == EvCall::ManySync {
+ if evdecl.call == EvCall::ManyAsync || evdecl.call == EvCall::ManySync {
self.push_line(&format!("events[{id}] = {{}}"));
}
- self.push_line(&format!("event_queue[{id}] = {{}}"));
+ if evdecl.data.is_some() {
+ self.push_line(&format!("event_queue[{id}] = {{}}"));
+ } else {
+ self.push_line(&format!("event_queue[{id}] = 0"));
+ }
+ }
+
+ for fndecl in self.config.fndecls.iter() {
+ self.push_line(&format!("event_queue[{}] = table.create(255)", fndecl.id));
}
}
fn push_write_event_id(&mut self, id: usize) {
- self.push_line(&format!("local pos = alloc({})", self.config.event_id_ty().size()));
+ self.push_line(&format!("alloc({})", self.config.event_id_ty().size()));
self.push_line(&format!(
- "buffer.write{}(outgoing_buff, pos, {id})",
+ "buffer.write{}(outgoing_buff, outgoing_apos, {id})",
self.config.event_id_ty()
));
}
- fn push_return_fire(&mut self, ev: &EvDecl, id: usize) {
- let ty = &ev.data;
-
+ 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");
self.push_indent();
- self.push(&format!("{fire} = function({value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire} = function("));
+
+ if let Some(data) = &ev.data {
+ self.push(&format!("{value}: "));
+ self.push_ty(data);
+ }
+
self.push(")\n");
self.indent();
@@ -395,9 +484,11 @@ impl<'src> ClientOutput<'src> {
self.push_line("load_empty()");
}
- self.push_write_event_id(id);
+ self.push_write_event_id(ev.id);
- self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ if let Some(data) = &ev.data {
+ self.push_stmts(&ser::gen(data, value, self.config.write_checks));
+ }
if ev.evty == EvType::Unreliable {
self.push_line("local buff = buffer.create(outgoing_used)");
@@ -411,105 +502,139 @@ impl<'src> ClientOutput<'src> {
}
fn push_return_outgoing(&mut self) {
- for (i, ev) in self
+ for ev in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Client)
+ .filter(|ev_decl| ev_decl.from == EvSource::Client)
{
- let id = i + 1;
-
self.push_line(&format!("{name} = {{", name = ev.name));
self.indent();
- self.push_return_fire(ev, id);
+ self.push_return_fire(ev);
self.dedent();
self.push_line("},");
}
}
- fn push_return_setcallback(&mut self, ev: &EvDecl, id: usize) {
- let ty = &ev.data;
+ fn push_return_setcallback(&mut self, ev: &EvDecl) {
+ let id = ev.id;
let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback");
let callback = self.config.casing.with("Callback", "callback", "callback");
self.push_indent();
self.push(&format!("{set_callback} = function({callback}: ("));
- self.push_ty(ty);
+
+ if let Some(data) = &ev.data {
+ self.push_ty(data);
+ }
+
self.push(") -> ())\n");
self.indent();
self.push_line(&format!("events[{id}] = {callback}"));
- self.push_line(&format!("for _, value in event_queue[{id}] do"));
- self.indent();
+ if ev.data.is_some() {
+ self.push_line(&format!("for _, value in event_queue[{id}] do"));
+ self.indent();
+
+ if ev.call == EvCall::SingleSync {
+ self.push_line(&format!("{callback}(value)"))
+ } else {
+ self.push_line(&format!("task.spawn({callback}, value)"))
+ }
- if ev.call == EvCall::SingleSync {
- self.push_line(&format!("{callback}(value)"))
+ self.dedent();
+ self.push_line("end");
+
+ self.push_line(&format!("event_queue[{id}] = {{}}"));
} else {
- self.push_line(&format!("task.spawn({callback}, value)"))
- }
+ self.push_line(&format!("for 1, event_queue[{id}] do"));
+ self.indent();
- self.dedent();
- self.push_line("end");
+ if ev.call == EvCall::SingleSync {
+ self.push_line(&format!("{callback}()"))
+ } else {
+ self.push_line(&format!("task.spawn({callback})"))
+ }
- self.push_line(&format!("event_queue[{id}] = {{}}"));
+ self.dedent();
+ self.push_line("end");
+
+ self.push_line(&format!("event_queue[{id}] = 0"));
+ }
self.dedent();
self.push_line("end,");
}
- fn push_return_on(&mut self, ev: &EvDecl, id: usize) {
- let ty = &ev.data;
+ fn push_return_on(&mut self, ev: &EvDecl) {
+ let id = ev.id;
let on = self.config.casing.with("On", "on", "on");
let callback = self.config.casing.with("Callback", "callback", "callback");
self.push_indent();
self.push(&format!("{on} = function({callback}: ("));
- self.push_ty(ty);
+
+ if let Some(data) = &ev.data {
+ self.push_ty(data);
+ }
+
self.push(") -> ())\n");
self.indent();
self.push_line(&format!("table.insert(events[{id}], {callback})"));
- self.push_line(&format!("for _, value in event_queue[{id}] do"));
- self.indent();
+ if ev.data.is_some() {
+ self.push_line(&format!("for _, value in event_queue[{id}] do"));
+ self.indent();
+
+ if ev.call == EvCall::ManySync {
+ self.push_line(&format!("{callback}(value)"))
+ } else {
+ self.push_line(&format!("task.spawn({callback}, value)"))
+ }
- if ev.call == EvCall::ManySync {
- self.push_line(&format!("{callback}(value)"))
+ self.dedent();
+ self.push_line("end");
+
+ self.push_line(&format!("event_queue[{id}] = {{}}"));
} else {
- self.push_line(&format!("task.spawn({callback}, value)"))
- }
+ self.push_line(&format!("for 1, event_queue[{id}] do"));
+ self.indent();
- self.dedent();
- self.push_line("end");
+ if ev.call == EvCall::ManySync {
+ self.push_line(&format!("{callback}()"))
+ } else {
+ self.push_line(&format!("task.spawn({callback})"))
+ }
- self.push_line(&format!("event_queue[{id}] = {{}}"));
+ self.dedent();
+ self.push_line("end");
+
+ self.push_line(&format!("event_queue[{id}] = 0"));
+ }
self.dedent();
self.push_line("end,");
}
pub fn push_return_listen(&mut self) {
- for (i, ev) in self
+ for ev in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Server)
+ .filter(|ev_decl| ev_decl.from == EvSource::Server)
{
- let id = i + 1;
-
self.push_line(&format!("{name} = {{", name = ev.name));
self.indent();
match ev.call {
- EvCall::SingleSync | EvCall::SingleAsync => self.push_return_setcallback(ev, id),
- EvCall::ManySync | EvCall::ManyAsync => self.push_return_on(ev, id),
+ EvCall::SingleSync | EvCall::SingleAsync => self.push_return_setcallback(ev),
+ EvCall::ManySync | EvCall::ManyAsync => self.push_return_on(ev),
}
self.dedent();
@@ -517,6 +642,85 @@ 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() {
+ let id = fndecl.id;
+
+ self.push_line(&format!("{name} = {{", name = fndecl.name));
+ self.indent();
+
+ self.push_indent();
+ self.push(&format!("{call} = function("));
+
+ if let Some(ty) = &fndecl.args {
+ self.push(&format!("{value}: "));
+ self.push_ty(ty);
+ }
+
+ self.push(")\n");
+ self.indent();
+
+ self.push_write_event_id(fndecl.id);
+
+ self.push_line("function_call_id += 1");
+
+ self.push_line("function_call_id %= 256");
+
+ self.push_line(&format!("if event_queue[{id}][function_call_id] then"));
+ self.indent();
+
+ self.push_line("function_call_id -= 1");
+ self.push_line("error(\"Zap has more than 256 calls awaiting a response, and therefore this packet has been dropped\")");
+
+ self.dedent();
+ self.push_line("end");
+
+ 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));
+ }
+
+ match self.config.yield_type {
+ YieldType::Yield => {
+ self.push_line(&format!("event_queue[{id}][function_call_id] = coroutine.running()",));
+ self.push_line("local value = coroutine.yield()");
+ }
+ YieldType::Future => {
+ self.push_line("local value = Future.new(function()");
+ self.indent();
+
+ self.push_line(&format!("event_queue[{id}][function_call_id] = coroutine.running()",));
+ self.push_line("return coroutine.yield()");
+
+ self.dedent();
+ self.push_line("end)");
+ }
+ YieldType::Promise => {
+ self.push_line("local value = Promise.new(function(resolve)");
+ self.indent();
+
+ self.push_line(&format!("event_queue[{id}][function_call_id] = resolve"));
+
+ self.dedent();
+ self.push_line("end)");
+ }
+ }
+
+ self.push_line("return value");
+
+ self.dedent();
+ self.push_line("end,");
+
+ self.dedent();
+ self.push_line("},");
+ }
+ }
+
pub fn push_return(&mut self) {
self.push_line("return {");
self.indent();
@@ -529,6 +733,7 @@ impl<'src> ClientOutput<'src> {
self.push_return_outgoing();
self.push_return_listen();
+ self.push_return_functions();
self.dedent();
self.push_line("}");
@@ -537,7 +742,7 @@ impl<'src> ClientOutput<'src> {
pub fn output(mut self) -> String {
self.push_file_header("Client");
- if self.config.evdecls.is_empty() {
+ if self.config.evdecls.is_empty() && self.config.fndecls.is_empty() {
return self.buf;
};
@@ -550,11 +755,12 @@ impl<'src> ClientOutput<'src> {
self.push_callback_lists();
- if self
- .config
- .evdecls
- .iter()
- .any(|ev| ev.evty == EvType::Reliable && ev.from == EvSource::Server)
+ if !self.config.fndecls.is_empty()
+ || self
+ .config
+ .evdecls
+ .iter()
+ .any(|ev| ev.evty == EvType::Reliable && ev.from == EvSource::Server)
{
self.push_reliable();
}
diff --git a/zap/src/output/luau/server.rs b/zap/src/output/luau/server.rs
index bd361d0b..0fa796f3 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, TyDecl},
+ config::{Config, EvCall, EvDecl, EvSource, EvType, FnCall, FnDecl, TyDecl},
irgen::{des, ser},
};
@@ -130,7 +130,9 @@ impl<'a> ServerOutput<'a> {
));
}
- fn push_reliable_callback(&mut self, first: bool, ev: &EvDecl, id: usize) {
+ fn push_reliable_callback(&mut self, first: bool, ev: &EvDecl) {
+ let id = ev.id;
+
self.push_indent();
if first {
@@ -148,7 +150,10 @@ impl<'a> ServerOutput<'a> {
self.indent();
self.push_line("local value");
- self.push_stmts(&des::gen(&ev.data, "value", true));
+
+ if let Some(data) = &ev.data {
+ self.push_stmts(&des::gen(data, "value", true));
+ }
if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync {
self.push_line(&format!("if events[{id}] then"))
@@ -171,6 +176,63 @@ impl<'a> ServerOutput<'a> {
self.dedent();
}
+ fn push_fn_callback(&mut self, first: bool, fndecl: &FnDecl) {
+ let id = fndecl.id;
+
+ self.push_indent();
+
+ if first {
+ self.push("if ");
+ } else {
+ self.push("elseif ");
+ }
+
+ self.push(&format!("id == {id} then"));
+ self.push("\n");
+
+ self.indent();
+
+ self.push_line("local call_id = buffer.readu8(buff, read(1))");
+ self.push_line("local value");
+
+ if let Some(data) = &fndecl.args {
+ self.push_stmts(&des::gen(data, "value", true));
+ }
+
+ self.push_line(&format!("if events[{id}] then"));
+
+ self.indent();
+
+ if fndecl.call == FnCall::Async {
+ self.push_line("task.spawn(function(player, call_id, value)");
+ self.indent();
+ }
+
+ self.push_line(&format!("local rets = events[{id}](player, value)"));
+
+ self.push_line("load_player(player)");
+ self.push_write_event_id(fndecl.id);
+
+ 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));
+ }
+
+ self.push_line("player_map[player] = save()");
+
+ if fndecl.call == FnCall::Async {
+ self.dedent();
+ self.push_line("end, player, call_id, value)");
+ }
+
+ self.dedent();
+ self.push_line("end");
+
+ self.dedent();
+ }
+
fn push_reliable_footer(&mut self) {
self.push_line("else");
self.indent();
@@ -188,16 +250,18 @@ impl<'a> ServerOutput<'a> {
let mut first = true;
- for (i, ev) in self
+ for ev in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Client && ev_decl.evty == EvType::Reliable)
+ .filter(|ev_decl| ev_decl.from == EvSource::Client && ev_decl.evty == EvType::Reliable)
{
- let id = i + 1;
+ self.push_reliable_callback(first, ev);
+ first = false;
+ }
- self.push_reliable_callback(first, ev, id);
+ for fndecl in self.config.fndecls.iter() {
+ self.push_fn_callback(first, fndecl);
first = false;
}
@@ -219,7 +283,9 @@ impl<'a> ServerOutput<'a> {
));
}
- fn push_unreliable_callback(&mut self, first: bool, ev: &EvDecl, id: usize) {
+ fn push_unreliable_callback(&mut self, first: bool, ev: &EvDecl) {
+ let id = ev.id;
+
self.push_indent();
if first {
@@ -237,7 +303,10 @@ impl<'a> ServerOutput<'a> {
self.indent();
self.push_line("local value");
- self.push_stmts(&des::gen(&ev.data, "value", true));
+
+ if let Some(data) = &ev.data {
+ self.push_stmts(&des::gen(data, "value", true));
+ }
if ev.call == EvCall::SingleSync || ev.call == EvCall::SingleAsync {
self.push_line(&format!("if events[{id}] then"))
@@ -275,16 +344,13 @@ impl<'a> ServerOutput<'a> {
let mut first = true;
- for (i, ev) in self
+ for ev in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Client && ev_decl.evty == EvType::Unreliable)
+ .filter(|ev_decl| ev_decl.from == EvSource::Client && ev_decl.evty == EvType::Unreliable)
{
- let id = i + 1;
-
- self.push_unreliable_callback(first, ev, id);
+ self.push_unreliable_callback(first, ev);
first = false;
}
@@ -292,26 +358,27 @@ impl<'a> ServerOutput<'a> {
}
fn push_callback_lists(&mut self) {
- self.push_line(&format!("local events = table.create({})", self.config.evdecls.len()));
+ self.push_line(&format!(
+ "local events = table.create({})",
+ self.config.evdecls.len() + self.config.fndecls.len()
+ ));
- for (i, _) in self.config.evdecls.iter().enumerate().filter(|(_, ev_decl)| {
+ for evdecl in self.config.evdecls.iter().filter(|ev_decl| {
ev_decl.from == EvSource::Client && matches!(ev_decl.call, EvCall::ManyAsync | EvCall::ManySync)
}) {
- let id = i + 1;
-
- self.push_line(&format!("events[{id}] = {{}}"));
+ self.push_line(&format!("events[{}] = {{}}", evdecl.id));
}
}
fn push_write_event_id(&mut self, id: usize) {
- self.push_line(&format!("local pos = alloc({})", self.config.event_id_ty().size()));
+ self.push_line(&format!("alloc({})", self.config.event_id_ty().size()));
self.push_line(&format!(
- "buffer.write{}(outgoing_buff, pos, {id})",
+ "buffer.write{}(outgoing_buff, outgoing_apos, {id})",
self.config.event_id_ty()
));
}
- fn push_return_fire(&mut self, ev: &EvDecl, id: usize) {
+ fn push_return_fire(&mut self, ev: &EvDecl) {
let ty = &ev.data;
let fire = self.config.casing.with("Fire", "fire", "fire");
@@ -319,8 +386,13 @@ impl<'a> ServerOutput<'a> {
let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
- self.push(&format!("{fire} = function({player}: Player, {value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire} = function({player}: Player"));
+
+ if let Some(ty) = ty {
+ self.push(&format!(", {value}: "));
+ self.push_ty(ty);
+ }
+
self.push(")\n");
self.indent();
@@ -329,9 +401,11 @@ impl<'a> ServerOutput<'a> {
EvType::Unreliable => self.push_line("load_empty()"),
}
- self.push_write_event_id(id);
+ self.push_write_event_id(ev.id);
- self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ if let Some(ty) = ty {
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ }
match ev.evty {
EvType::Reliable => self.push_line(&format!("player_map[{player}] = save()")),
@@ -346,23 +420,30 @@ impl<'a> ServerOutput<'a> {
self.push_line("end,");
}
- fn push_return_fire_all(&mut self, ev: &EvDecl, id: usize) {
+ fn push_return_fire_all(&mut self, ev: &EvDecl) {
let ty = &ev.data;
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} = function({value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire_all} = function("));
+
+ if let Some(ty) = ty {
+ self.push(&format!("{value}: "));
+ self.push_ty(ty);
+ }
+
self.push(")\n");
self.indent();
self.push_line("load_empty()");
- self.push_write_event_id(id);
+ self.push_write_event_id(ev.id);
- self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ if let Some(ty) = ty {
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ }
match ev.evty {
EvType::Reliable => {
@@ -389,7 +470,7 @@ impl<'a> ServerOutput<'a> {
self.push_line("end,");
}
- fn push_return_fire_except(&mut self, ev: &EvDecl, id: usize) {
+ fn push_return_fire_except(&mut self, ev: &EvDecl) {
let ty = &ev.data;
let fire_except = self.config.casing.with("FireExcept", "fireExcept", "fire_except");
@@ -397,16 +478,23 @@ impl<'a> ServerOutput<'a> {
let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
- self.push(&format!("{fire_except} = function({except}: Player, {value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire_except} = function({except}: Player"));
+
+ if let Some(ty) = ty {
+ self.push(&format!(", {value}: "));
+ self.push_ty(ty);
+ }
+
self.push(")\n");
self.indent();
self.push_line("load_empty()");
- self.push_write_event_id(id);
+ self.push_write_event_id(ev.id);
- self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ if let Some(ty) = ty {
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ }
match ev.evty {
EvType::Reliable => {
@@ -445,7 +533,7 @@ impl<'a> ServerOutput<'a> {
self.push_line("end,");
}
- fn push_return_fire_list(&mut self, ev: &EvDecl, id: usize) {
+ fn push_return_fire_list(&mut self, ev: &EvDecl) {
let ty = &ev.data;
let fire_list = self.config.casing.with("FireList", "fireList", "fire_list");
@@ -453,16 +541,23 @@ impl<'a> ServerOutput<'a> {
let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
- self.push(&format!("{fire_list} = function({list}: {{ Player }}, {value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire_list} = function({list}: {{ Player }}"));
+
+ if let Some(ty) = ty {
+ self.push(&format!(", {value}: "));
+ self.push_ty(ty);
+ }
+
self.push(")\n");
self.indent();
self.push_line("load_empty()");
- self.push_write_event_id(id);
+ self.push_write_event_id(ev.id);
- self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ if let Some(ty) = ty {
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
+ }
match ev.evty {
EvType::Reliable => {
@@ -494,37 +589,39 @@ impl<'a> ServerOutput<'a> {
}
fn push_return_outgoing(&mut self) {
- for (i, ev) in self
+ for ev in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Server)
+ .filter(|ev_decl| ev_decl.from == EvSource::Server)
{
- let id = i + 1;
-
self.push_line(&format!("{name} = {{", name = ev.name));
self.indent();
- self.push_return_fire(ev, id);
- self.push_return_fire_all(ev, id);
- self.push_return_fire_except(ev, id);
- self.push_return_fire_list(ev, id);
+ self.push_return_fire(ev);
+ self.push_return_fire_all(ev);
+ self.push_return_fire_except(ev);
+ self.push_return_fire_list(ev);
self.dedent();
self.push_line("},");
}
}
- fn push_return_setcallback(&mut self, ev: &EvDecl, id: usize) {
- let ty = &ev.data;
+ fn push_return_setcallback(&mut self, ev: &EvDecl) {
+ let id = ev.id;
let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback");
let callback = self.config.casing.with("Callback", "callback", "callback");
self.push_indent();
- self.push(&format!("{set_callback} = function({callback}: (Player, "));
- self.push_ty(ty);
+ self.push(&format!("{set_callback} = function({callback}: (Player"));
+
+ if let Some(ty) = &ev.data {
+ self.push(", ");
+ self.push_ty(ty);
+ }
+
self.push(") -> ())\n");
self.indent();
@@ -534,15 +631,20 @@ impl<'a> ServerOutput<'a> {
self.push_line("end,");
}
- fn push_return_on(&mut self, ev: &EvDecl, id: usize) {
- let ty = &ev.data;
+ fn push_return_on(&mut self, ev: &EvDecl) {
+ let id = ev.id;
let on = self.config.casing.with("On", "on", "on");
let callback = self.config.casing.with("Callback", "callback", "callback");
self.push_indent();
- self.push(&format!("{on} = function({callback}: (Player, "));
- self.push_ty(ty);
+ self.push(&format!("{on} = function({callback}: (Player"));
+
+ if let Some(ty) = &ev.data {
+ self.push(", ");
+ self.push_ty(ty);
+ }
+
self.push(") -> ())\n");
self.indent();
@@ -552,27 +654,63 @@ impl<'a> ServerOutput<'a> {
self.push_line("end,");
}
+ fn push_fn_return(&mut self, fndecl: &FnDecl) {
+ let id = fndecl.id;
+
+ let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback");
+ let callback = self.config.casing.with("Callback", "callback", "callback");
+
+ self.push_indent();
+ self.push(&format!("{set_callback} = function({callback}: (Player"));
+
+ if let Some(ty) = &fndecl.args {
+ self.push(", ");
+ self.push_ty(ty);
+ }
+
+ self.push(") -> (");
+
+ if let Some(ty) = &fndecl.rets {
+ self.push_ty(ty);
+ }
+
+ self.push("))\n");
+ self.indent();
+
+ self.push_line(&format!("events[{id}] = {callback}"));
+
+ self.dedent();
+ self.push_line("end,");
+ }
+
pub fn push_return_listen(&mut self) {
- for (i, ev) in self
+ for ev in self
.config
.evdecls
.iter()
- .enumerate()
- .filter(|(_, ev_decl)| ev_decl.from == EvSource::Client)
+ .filter(|ev_decl| ev_decl.from == EvSource::Client)
{
- let id = i + 1;
-
- self.push_line(&format!("{name} = {{", name = ev.name));
+ self.push_line(&format!("{} = {{", ev.name));
self.indent();
match ev.call {
- EvCall::SingleSync | EvCall::SingleAsync => self.push_return_setcallback(ev, id),
- EvCall::ManySync | EvCall::ManyAsync => self.push_return_on(ev, id),
+ EvCall::SingleSync | EvCall::SingleAsync => self.push_return_setcallback(ev),
+ EvCall::ManySync | EvCall::ManyAsync => self.push_return_on(ev),
}
self.dedent();
self.push_line("},");
}
+
+ for fndecl in self.config.fndecls.iter() {
+ self.push_line(&format!("{} = {{", fndecl.name));
+ self.indent();
+
+ self.push_fn_return(fndecl);
+
+ self.dedent();
+ self.push_line("},");
+ }
}
pub fn push_return(&mut self) {
@@ -595,7 +733,7 @@ impl<'a> ServerOutput<'a> {
pub fn output(mut self) -> String {
self.push_file_header("Server");
- if self.config.evdecls.is_empty() {
+ if self.config.evdecls.is_empty() && self.config.fndecls.is_empty() {
return self.buf;
};
@@ -608,11 +746,12 @@ impl<'a> ServerOutput<'a> {
self.push_callback_lists();
- if self
- .config
- .evdecls
- .iter()
- .any(|ev| ev.evty == EvType::Reliable && ev.from == EvSource::Client)
+ if !self.config.fndecls.is_empty()
+ || self
+ .config
+ .evdecls
+ .iter()
+ .any(|ev| ev.evty == EvType::Reliable && ev.from == EvSource::Client)
{
self.push_reliable();
}
diff --git a/zap/src/output/typescript/client.rs b/zap/src/output/typescript/client.rs
index 8c7a28f5..ebcf0f46 100644
--- a/zap/src/output/typescript/client.rs
+++ b/zap/src/output/typescript/client.rs
@@ -1,4 +1,4 @@
-use crate::config::{Config, EvCall, EvSource, TyDecl};
+use crate::config::{Config, EvCall, EvSource, Ty, TyDecl, YieldType};
use super::Output;
@@ -72,8 +72,20 @@ impl<'src> ClientOutput<'src> {
self.indent();
self.push_indent();
- self.push(&format!("{fire}: ({value}: "));
- self.push_ty(&ev.data);
+ self.push(&format!("{fire}: ("));
+
+ if let Some(data) = &ev.data {
+ self.push(value);
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
self.push(") => void\n");
self.dedent();
@@ -102,8 +114,20 @@ impl<'src> ClientOutput<'src> {
self.indent();
self.push_indent();
- self.push(&format!("{set_callback}: ({callback}: ({value}: "));
- self.push_ty(&ev.data);
+ self.push(&format!("{set_callback}: ({callback}: ("));
+
+ if let Some(data) = &ev.data {
+ self.push(value);
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
self.push(") => void) => void\n");
self.dedent();
@@ -111,15 +135,61 @@ 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 const {}: {{", fndecl.name));
+ self.indent();
+
+ self.push_indent();
+ self.push(&format!("{call}: ("));
+
+ if let Some(data) = &fndecl.args {
+ self.push(value);
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
+ self.push(") => ");
+
+ if self.config.yield_type == YieldType::Promise {
+ self.push("Promise<")
+ }
+
+ if let Some(data) = &fndecl.rets {
+ self.push_ty(data);
+ } else {
+ self.push("void");
+ }
+
+ if self.config.yield_type == YieldType::Promise {
+ self.push(">")
+ }
+
+ self.push("\n");
+ self.dedent();
+ self.push_line("};");
+ }
+ }
+
pub fn push_return(&mut self) {
self.push_return_outgoing();
self.push_return_listen();
+ self.push_return_functions();
}
pub fn output(mut self) -> String {
self.push_file_header("Client");
- if self.config.evdecls.is_empty() {
+ if self.config.evdecls.is_empty() && self.config.fndecls.is_empty() {
return self.buf;
};
diff --git a/zap/src/output/typescript/mod.rs b/zap/src/output/typescript/mod.rs
index ebb56581..9fc3e69d 100644
--- a/zap/src/output/typescript/mod.rs
+++ b/zap/src/output/typescript/mod.rs
@@ -96,8 +96,9 @@ pub trait Output {
self.push("]>");
}
_ => {
+ self.push("(");
self.push_ty(ty);
- self.push("[]");
+ self.push(")[]");
}
},
@@ -141,8 +142,16 @@ pub trait Output {
for (name, ty) in struct_ty.fields.iter() {
self.push_indent();
- self.push(&format!("{name}: "));
- self.push_ty(ty);
+ self.push(name);
+
+ if let Ty::Opt(ty) = ty {
+ self.push("?: ");
+ self.push_ty(ty);
+ } else {
+ self.push(": ");
+ self.push_ty(ty);
+ }
+
self.push(",\n");
}
@@ -160,8 +169,16 @@ pub trait Output {
for (name, ty) in struct_ty.fields.iter() {
self.push_indent();
- self.push(&format!("{name}: "));
- self.push_ty(ty);
+ self.push(name);
+
+ if let Ty::Opt(ty) = ty {
+ self.push("?: ");
+ self.push_ty(ty);
+ } else {
+ self.push(": ");
+ self.push_ty(ty);
+ }
+
self.push(",\n");
}
diff --git a/zap/src/output/typescript/server.rs b/zap/src/output/typescript/server.rs
index 1104d024..0cace5c1 100644
--- a/zap/src/output/typescript/server.rs
+++ b/zap/src/output/typescript/server.rs
@@ -1,4 +1,4 @@
-use crate::config::{Config, EvCall, EvDecl, EvSource, TyDecl};
+use crate::config::{Config, EvCall, EvDecl, EvSource, Ty, TyDecl};
use super::Output;
@@ -58,53 +58,93 @@ impl<'a> ServerOutput<'a> {
}
fn push_return_fire(&mut self, ev: &EvDecl) {
- let ty = &ev.data;
-
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, {value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire}: ({player}: Player"));
+
+ if let Some(data) = &ev.data {
+ self.push(&format!(", {value}"));
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
self.push(") => void\n");
}
fn push_return_fire_all(&mut self, ev: &EvDecl) {
- let ty = &ev.data;
-
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}: ({value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire_all}: ("));
+
+ if let Some(data) = &ev.data {
+ self.push(value);
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
self.push(") => void\n");
}
fn push_return_fire_except(&mut self, ev: &EvDecl) {
- let ty = &ev.data;
-
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, {value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire_except}: ({except}: Player"));
+
+ if let Some(data) = &ev.data {
+ self.push(&format!(", {value}"));
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
self.push(") => void\n");
}
fn push_return_fire_list(&mut self, ev: &EvDecl) {
- let ty = &ev.data;
-
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[], {value}: "));
- self.push_ty(ty);
+ self.push(&format!("{fire_list}: ({list}: Player[]"));
+
+ if let Some(data) = &ev.data {
+ self.push(&format!(", {value}"));
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
self.push(") => void\n");
}
@@ -151,8 +191,20 @@ impl<'a> ServerOutput<'a> {
let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
- self.push(&format!("{set_callback}: ({callback}: ({player}: Player, {value}: "));
- self.push_ty(&ev.data);
+ self.push(&format!("{set_callback}: ({callback}: ({player}: Player"));
+
+ if let Some(data) = &ev.data {
+ self.push(&format!(", {value}"));
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
self.push(") => void) => void\n");
self.dedent();
@@ -160,15 +212,56 @@ impl<'a> ServerOutput<'a> {
}
}
+ pub fn push_return_functions(&mut self) {
+ for fndecl in self.config.fndecls.iter() {
+ self.push_line(&format!("export const {name}: {{", name = fndecl.name));
+ self.indent();
+
+ 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}"));
+
+ if let Ty::Opt(data) = data {
+ self.push("?: ");
+ self.push_ty(data);
+ } else {
+ self.push(": ");
+ self.push_ty(data);
+ }
+ }
+
+ self.push(") => ");
+
+ if let Some(data) = &fndecl.rets {
+ self.push_ty(data);
+ } else {
+ self.push("void");
+ }
+
+ self.push(") => void\n");
+
+ self.dedent();
+ self.push_line("};");
+ }
+ }
+
pub fn push_return(&mut self) {
self.push_return_outgoing();
self.push_return_listen();
+ self.push_return_functions();
}
pub fn output(mut self) -> String {
self.push_file_header("Server");
- if self.config.evdecls.is_empty() {
+ if self.config.evdecls.is_empty() && self.config.fndecls.is_empty() {
return self.buf;
};
diff --git a/zap/src/parser/convert.rs b/zap/src/parser/convert.rs
index a2eea11d..a5e01be8 100644
--- a/zap/src/parser/convert.rs
+++ b/zap/src/parser/convert.rs
@@ -1,6 +1,6 @@
use std::collections::{HashMap, HashSet};
-use crate::config::{Casing, Config, Enum, EvDecl, EvType, NumTy, Range, Struct, Ty, TyDecl};
+use crate::config::{Casing, Config, Enum, EvDecl, EvType, FnDecl, NumTy, Range, Struct, Ty, TyDecl, YieldType};
use super::{
reports::{Report, Span},
@@ -9,9 +9,8 @@ use super::{
struct Converter<'src> {
config: SyntaxConfig<'src>,
-
tydecls: HashMap<&'src str, SyntaxTyDecl<'src>>,
- evdecls: HashMap<&'src str, SyntaxEvDecl<'src>>,
+ max_unreliable_size: usize,
reports: Vec>,
}
@@ -19,7 +18,7 @@ struct Converter<'src> {
impl<'src> Converter<'src> {
fn new(config: SyntaxConfig<'src>) -> Self {
let mut tydecls = HashMap::new();
- let mut evdecls = HashMap::new();
+ let mut ntdecls = 0;
for decl in config.decls.iter() {
match decl {
@@ -27,17 +26,17 @@ impl<'src> Converter<'src> {
tydecls.insert(tydecl.name.name, tydecl.clone());
}
- SyntaxDecl::Ev(evdecl) => {
- evdecls.insert(evdecl.name.name, evdecl.clone());
- }
+ SyntaxDecl::Ev(_) | SyntaxDecl::Fn(_) => ntdecls += 1,
}
}
+ // We subtract two for the `inst` array.
+ let max_unreliable_size = 900 - NumTy::from_f64(1.0, ntdecls as f64).size() - 2;
+
Self {
config,
-
tydecls,
- evdecls,
+ max_unreliable_size,
reports: Vec::new(),
}
@@ -45,25 +44,44 @@ impl<'src> Converter<'src> {
fn convert(mut self) -> (Config<'src>, Vec>) {
let config = self.config.clone();
- let mut tydecls = HashMap::new();
+
+ self.check_duplicate_decls(&config.decls);
+
+ let mut tydecls = Vec::new();
+ let mut evdecls = Vec::new();
+ let mut fndecls = Vec::new();
+
+ let mut ntdecl_id = 0;
for tydecl in config.decls.iter().filter_map(|decl| match decl {
SyntaxDecl::Ty(tydecl) => Some(tydecl),
_ => None,
}) {
- tydecls.insert(tydecl.name.name, self.tydecl(tydecl));
+ tydecls.push(self.tydecl(tydecl));
}
- let mut evdecls = Vec::new();
+ let tydecl_hashmap = tydecls
+ .iter()
+ .map(|tydecl| (tydecl.name, &tydecl.ty))
+ .collect::>();
for evdecl in config.decls.iter().filter_map(|decl| match decl {
SyntaxDecl::Ev(evdecl) => Some(evdecl),
_ => None,
}) {
- evdecls.push(self.evdecl(evdecl, &tydecls));
+ ntdecl_id += 1;
+ evdecls.push(self.evdecl(evdecl, ntdecl_id, &tydecl_hashmap));
}
- if evdecls.is_empty() {
+ for fndecl in config.decls.iter().filter_map(|decl| match decl {
+ SyntaxDecl::Fn(fndecl) => Some(fndecl),
+ _ => None,
+ }) {
+ ntdecl_id += 1;
+ fndecls.push(self.fndecl(fndecl, ntdecl_id));
+ }
+
+ if evdecls.is_empty() && fndecls.is_empty() {
self.report(Report::AnalyzeEmptyEvDecls);
}
@@ -74,26 +92,14 @@ impl<'src> Converter<'src> {
let (server_output, ..) = self.str_opt("server_output", "network/server.lua", &config.opts);
let (client_output, ..) = self.str_opt("client_output", "network/client.lua", &config.opts);
- let casing = match self.str_opt("casing", "PascalCase", &config.opts) {
- ("snake_case", ..) => Casing::Snake,
- ("camelCase", ..) => Casing::Camel,
- ("PascalCase", ..) => Casing::Pascal,
-
- (_, Some(span)) => {
- self.report(Report::AnalyzeInvalidOptValue {
- span,
- expected: "`snake_case`, `camelCase`, or `PascalCase`",
- });
-
- Casing::Pascal
- }
-
- _ => unreachable!(),
- };
+ let casing = self.casing_opt(&config.opts);
+ let yield_type = self.yield_type_opt(typescript, &config.opts);
+ let async_lib = self.async_lib(yield_type, &config.opts);
let config = Config {
- tydecls: tydecls.into_values().collect(),
+ tydecls,
evdecls,
+ fndecls,
write_checks,
typescript,
@@ -103,11 +109,85 @@ impl<'src> Converter<'src> {
client_output,
casing,
+ yield_type,
+ async_lib,
};
(config, self.reports)
}
+ fn async_lib(&mut self, yield_type: YieldType, opts: &[SyntaxOpt<'src>]) -> &'src str {
+ let (async_lib, async_lib_span) = self.str_opt("async_lib", "", opts);
+
+ if let Some(span) = async_lib_span {
+ if !async_lib.starts_with("require") {
+ self.report(Report::AnalyzeInvalidOptValue {
+ span,
+ expected: "that `async_lib` path must be a `require` statement",
+ });
+ } else if yield_type == YieldType::Yield {
+ self.report(Report::AnalyzeInvalidOptValue {
+ span,
+ expected: "that `async_lib` cannot be defined when using a `yield_type` of `yield`",
+ });
+ }
+ } else if async_lib.is_empty() && yield_type != YieldType::Yield {
+ self.report(Report::AnalyzeMissingOptValue {
+ expected: "`async_lib`",
+ required_when: "`yield_type` is set to `promise` or `future`.",
+ });
+ }
+
+ async_lib
+ }
+
+ fn yield_type_opt(&mut self, typescript: bool, opts: &[SyntaxOpt<'src>]) -> YieldType {
+ match self.str_opt("yield_type", "yield", opts) {
+ ("yield", ..) => YieldType::Yield,
+ ("promise", ..) => YieldType::Promise,
+ ("future", Some(span)) => {
+ if typescript {
+ self.report(Report::AnalyzeInvalidOptValue {
+ span,
+ expected: "`yield` or `promise`",
+ });
+ }
+
+ YieldType::Future
+ }
+
+ (_, Some(span)) => {
+ self.report(Report::AnalyzeInvalidOptValue {
+ span,
+ expected: "`yield`, `future`, or `promise`",
+ });
+
+ YieldType::Yield
+ }
+
+ _ => unreachable!(),
+ }
+ }
+
+ fn casing_opt(&mut self, opts: &[SyntaxOpt<'src>]) -> Casing {
+ match self.str_opt("casing", "PascalCase", opts) {
+ ("snake_case", ..) => Casing::Snake,
+ ("camelCase", ..) => Casing::Camel,
+ ("PascalCase", ..) => Casing::Pascal,
+
+ (_, Some(span)) => {
+ self.report(Report::AnalyzeInvalidOptValue {
+ span,
+ expected: "`snake_case`, `camelCase`, or `PascalCase`",
+ });
+
+ Casing::Pascal
+ }
+
+ _ => unreachable!(),
+ }
+ }
+
fn boolean_opt(&mut self, name: &'static str, default: bool, opts: &[SyntaxOpt<'src>]) -> (bool, Option) {
let mut value = default;
let mut span = None;
@@ -171,32 +251,72 @@ impl<'src> Converter<'src> {
(value, span)
}
- fn evdecl(&mut self, evdecl: &SyntaxEvDecl<'src>, tydecls: &HashMap<&'src str, TyDecl<'src>>) -> EvDecl<'src> {
+ fn check_duplicate_decls(&mut self, decls: &[SyntaxDecl<'src>]) {
+ let mut tydecls = HashMap::new();
+ let mut ntdecls = HashMap::new();
+
+ for decl in decls.iter() {
+ match decl {
+ SyntaxDecl::Ev(ev) => {
+ if let Some(prev_span) = ntdecls.insert(ev.name.name, ev.span()) {
+ self.report(Report::AnalyzeDuplicateDecl {
+ prev_span,
+ dup_span: ev.span(),
+ name: ev.name.name,
+ });
+ }
+ }
+
+ SyntaxDecl::Fn(fn_) => {
+ if let Some(prev_span) = ntdecls.insert(fn_.name.name, fn_.span()) {
+ self.report(Report::AnalyzeDuplicateDecl {
+ prev_span,
+ dup_span: fn_.span(),
+ name: fn_.name.name,
+ });
+ }
+ }
+
+ SyntaxDecl::Ty(ty) => {
+ if let Some(prev_span) = tydecls.insert(ty.name.name, ty.span()) {
+ self.report(Report::AnalyzeDuplicateDecl {
+ prev_span,
+ dup_span: ty.span(),
+ name: ty.name.name,
+ });
+ }
+ }
+ }
+ }
+ }
+
+ fn evdecl(
+ &mut self,
+ evdecl: &SyntaxEvDecl<'src>,
+ id: usize,
+ tydecls: &HashMap<&'src str, &Ty<'src>>,
+ ) -> EvDecl<'src> {
let name = evdecl.name.name;
let from = evdecl.from;
let evty = evdecl.evty;
let call = evdecl.call;
- let data = self.ty(&evdecl.data);
-
- if let EvType::Unreliable = evty {
- let (min, max) = data.size(tydecls, &mut HashSet::new());
- let event_id_size = NumTy::from_f64(1.0, self.evdecls.len() as f64).size();
+ let data = evdecl.data.as_ref().map(|ty| self.ty(ty));
- // We subtract two for the `inst` array.
- let max_unreliable_size = 900 - event_id_size - 2;
+ if data.is_some() && evty == EvType::Unreliable {
+ let (min, max) = data.as_ref().unwrap().size(tydecls, &mut HashSet::new());
- if min > max_unreliable_size {
+ if min > self.max_unreliable_size {
self.report(Report::AnalyzeOversizeUnreliable {
ev_span: evdecl.span(),
- ty_span: evdecl.data.span(),
- max_size: max_unreliable_size,
+ ty_span: evdecl.data.as_ref().unwrap().span(),
+ max_size: self.max_unreliable_size,
size: min,
});
- } else if !max.is_some_and(|max| max < max_unreliable_size) {
+ } else if !max.is_some_and(|max| max < self.max_unreliable_size) {
self.report(Report::AnalyzePotentiallyOversizeUnreliable {
ev_span: evdecl.span(),
- ty_span: evdecl.data.span(),
- max_size: max_unreliable_size,
+ ty_span: evdecl.data.as_ref().unwrap().span(),
+ max_size: self.max_unreliable_size,
});
}
}
@@ -207,6 +327,22 @@ impl<'src> Converter<'src> {
evty,
call,
data,
+ id,
+ }
+ }
+
+ 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));
+
+ FnDecl {
+ name,
+ args,
+ call,
+ rets,
+ id,
}
}
@@ -325,7 +461,7 @@ impl<'src> Converter<'src> {
}
SyntaxEnumKind::Tagged { tag, variants } => {
- let tag_name = tag.value;
+ let tag_name = self.str(tag);
let variants = variants
.iter()
diff --git a/zap/src/parser/grammar.lalrpop b/zap/src/parser/grammar.lalrpop
index e60b8234..4ad44d1b 100644
--- a/zap/src/parser/grammar.lalrpop
+++ b/zap/src/parser/grammar.lalrpop
@@ -1,5 +1,5 @@
use crate::parser::{reports::Report, syntax_tree::*};
-use crate::config::{EvCall, EvSource, EvType, NumTy};
+use crate::config::{EvCall, EvSource, EvType, FnCall, NumTy};
use lalrpop_util::ParseError;
@@ -35,16 +35,32 @@ OptValueKind: SyntaxOptValueKind<'input> = {
}
Decl: SyntaxDecl<'input> = {
- => SyntaxDecl::Ev(decl),
=> SyntaxDecl::Ty(decl),
+ => SyntaxDecl::Ev(decl),
+ => SyntaxDecl::Fn(decl),
+}
+
+FnDecl: SyntaxFnDecl<'input> = {
+ "funct" "=" "{"
+ "call" ":"
+ )?>
+ )?>
+ ","?
+ "}" ";"? => SyntaxFnDecl { start, name, call, args, rets, end },
+}
+
+FnCall: FnCall = {
+ "Async" => FnCall::Async,
+ "Sync" => FnCall::Sync,
}
EvDecl: SyntaxEvDecl<'input> = {
"event" "=" "{"
- "from" ":" ","
- "type" ":" ","
- "call" ":" ","
- "data" ":" ","?
+ "from" ":"
+ "," "type" ":"
+ "," "call" ":"
+ )?>
+ ","?
"}" ";"? => SyntaxEvDecl { start, name, from, evty, call, data, end },
}
@@ -139,6 +155,7 @@ NumRangeKind: SyntaxRangeKind<'input> = {
StrLit: SyntaxStrLit<'input> = {
=> SyntaxStrLit { start, value, end },
+ => SyntaxStrLit { start, value, end },
}
IntLit: SyntaxNumLit<'input> = {
@@ -170,6 +187,8 @@ Identifier: SyntaxIdentifier<'input> = {
"opt" => SyntaxIdentifier { start, name: "opt", end },
"from" => SyntaxIdentifier { start, name: "from", end },
"call" => SyntaxIdentifier { start, name: "call", end },
+ "Async" => SyntaxIdentifier { start, name: "Async", end },
+ "Sync" => SyntaxIdentifier { start, name: "Sync", end },
"Reliable" => SyntaxIdentifier { start, name: "Reliable", end },
"Unreliable" => SyntaxIdentifier { start, name: "Unreliable", end },
"Server" => SyntaxIdentifier { start, name: "Server", end },
diff --git a/zap/src/parser/reports.rs b/zap/src/parser/reports.rs
index 557350e5..a1bf5dad 100644
--- a/zap/src/parser/reports.rs
+++ b/zap/src/parser/reports.rs
@@ -85,6 +85,17 @@ pub enum Report<'src> {
decl_span: Span,
use_span: Span,
},
+
+ AnalyzeMissingOptValue {
+ expected: &'static str,
+ required_when: &'static str,
+ },
+
+ AnalyzeDuplicateDecl {
+ prev_span: Span,
+ dup_span: Span,
+ name: &'src str,
+ },
}
impl<'src> Report<'src> {
@@ -109,6 +120,8 @@ impl<'src> Report<'src> {
Self::AnalyzeNumOutsideRange { .. } => Severity::Error,
Self::AnalyzeInvalidOptionalType { .. } => Severity::Error,
Self::AnalyzeUnboundedRecursiveType { .. } => Severity::Error,
+ Self::AnalyzeMissingOptValue { .. } => Severity::Error,
+ Self::AnalyzeDuplicateDecl { .. } => Severity::Error,
}
}
@@ -124,7 +137,7 @@ impl<'src> Report<'src> {
Self::ParserExtraToken { .. } => "extra token".to_string(),
Self::ParserExpectedInt { .. } => "expected integer".to_string(),
- Self::AnalyzeEmptyEvDecls => "no event declarations".to_string(),
+ Self::AnalyzeEmptyEvDecls => "no event or function declarations".to_string(),
Self::AnalyzeOversizeUnreliable { .. } => "oversize unreliable".to_string(),
Self::AnalyzePotentiallyOversizeUnreliable { .. } => "potentially oversize unreliable".to_string(),
Self::AnalyzeInvalidRange { .. } => "invalid range".to_string(),
@@ -136,6 +149,8 @@ impl<'src> Report<'src> {
Self::AnalyzeNumOutsideRange { .. } => "number outside range".to_string(),
Self::AnalyzeInvalidOptionalType { .. } => "invalid optional type".to_string(),
Self::AnalyzeUnboundedRecursiveType { .. } => "unbounded recursive type".to_string(),
+ Self::AnalyzeMissingOptValue { .. } => "missing option expected".to_string(),
+ Self::AnalyzeDuplicateDecl { name, .. } => format!("duplicate declaration '{}'", name),
}
}
@@ -160,6 +175,8 @@ impl<'src> Report<'src> {
Self::AnalyzeNumOutsideRange { .. } => "3010",
Self::AnalyzeInvalidOptionalType { .. } => "3011",
Self::AnalyzeUnboundedRecursiveType { .. } => "3012",
+ Self::AnalyzeMissingOptValue { .. } => "3013",
+ Self::AnalyzeDuplicateDecl { .. } => "3014",
}
}
@@ -244,6 +261,17 @@ impl<'src> Report<'src> {
Label::primary((), use_span.clone()).with_message("used recursively here"),
]
}
+
+ Self::AnalyzeMissingOptValue { .. } => vec![],
+
+ Self::AnalyzeDuplicateDecl {
+ prev_span, dup_span, ..
+ } => {
+ vec![
+ Label::secondary((), prev_span.clone()).with_message("previous declaration"),
+ Label::primary((), dup_span.clone()).with_message("duplicate declaration"),
+ ]
+ }
}
}
@@ -256,7 +284,9 @@ impl<'src> Report<'src> {
Self::ParserExtraToken { .. } => None,
Self::ParserExpectedInt { .. } => None,
- Self::AnalyzeEmptyEvDecls => Some(vec!["add an event declaration to allow zap to output code".to_string()]),
+ Self::AnalyzeEmptyEvDecls => Some(vec![
+ "add an event or function declaration to allow zap to output code".to_string()
+ ]),
Self::AnalyzeOversizeUnreliable { max_size, .. } => Some(vec![
format!("all unreliable events must be under {max_size} bytes in size"),
"consider adding a upper limit to any arrays or strings".to_string(),
@@ -298,6 +328,13 @@ impl<'src> Report<'src> {
"this is an unbounded recursive type".to_string(),
"unbounded recursive types cause infinite loops".to_string(),
]),
+ Self::AnalyzeMissingOptValue {
+ expected,
+ required_when,
+ } => Some(vec![format!(
+ "the {expected} option should not be empty if {required_when}"
+ )]),
+ Self::AnalyzeDuplicateDecl { .. } => None,
}
}
}
diff --git a/zap/src/parser/syntax_tree.rs b/zap/src/parser/syntax_tree.rs
index 0e6d864b..2f8a21b1 100644
--- a/zap/src/parser/syntax_tree.rs
+++ b/zap/src/parser/syntax_tree.rs
@@ -1,4 +1,4 @@
-use crate::config::{EvCall, EvSource, EvType, NumTy};
+use crate::config::{EvCall, EvSource, EvType, FnCall, NumTy};
use super::reports::Span;
@@ -68,8 +68,25 @@ pub enum SyntaxOptValueKind<'src> {
#[derive(Debug, Clone, PartialEq)]
pub enum SyntaxDecl<'src> {
- Ev(SyntaxEvDecl<'src>),
Ty(SyntaxTyDecl<'src>),
+ Ev(SyntaxEvDecl<'src>),
+ Fn(SyntaxFnDecl<'src>),
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct SyntaxFnDecl<'src> {
+ pub start: usize,
+ pub name: SyntaxIdentifier<'src>,
+ pub call: FnCall,
+ pub args: Option>,
+ pub rets: Option>,
+ pub end: usize,
+}
+
+impl<'src> Spanned for SyntaxFnDecl<'src> {
+ fn span(&self) -> Span {
+ self.start..self.end
+ }
}
#[derive(Debug, Clone, PartialEq)]
@@ -79,7 +96,7 @@ pub struct SyntaxEvDecl<'src> {
pub from: EvSource,
pub evty: EvType,
pub call: EvCall,
- pub data: SyntaxTy<'src>,
+ pub data: Option>,
pub end: usize,
}