diff --git a/docs/config/types.md b/docs/config/types.md
index 63676280..a3766872 100644
--- a/docs/config/types.md
+++ b/docs/config/types.md
@@ -17,6 +17,23 @@ const enumExample = `enum "Type" {
},
}`
+const enumCatchAllExample = `event UpdateStore = {
+ from: Server,
+ type: Reliable,
+ call: SingleSync,
+ data: enum "name" {
+ UpdateItem {
+ arguments: string[2]
+ },
+ SetPosition {
+ arguments: Vector3[1]
+ },
+ ... {
+ value: unknown[]
+ }
+ }
+}`
+
const structExample = `type Item = struct {
name: string,
price: u16,
@@ -154,6 +171,10 @@ type t = { type: "number", value: number }
Tagged enums allow you to pass different data depending on a variant. They are extremely powerful and can be used to represent many different types of data.
+Tagged enums also have a catch-all clause, for when you want to have optimisation paths for your data, but aren't always sure what shape it is. The an example usecase for a catch-all clause, is serialising [reflex](https://littensy.github.io/reflex/) state from a broadcaster:
+
+
+
## Structs
Structs are similar to Interfaces, and are a collection of statically named fields with different types.
@@ -180,6 +201,16 @@ You can also specify what kind of instance you want to accept, for example:
Classes that inherit your specified class will be accepted, for example `Part`.
+## Unknown
+
+There are times where we do not know the shape that the data will be at runtime, and we'd like to have Roblox serialise it instead of Zap. This is where the `unknown` type comes in, and zap will serialise the value like instances - passing it to Roblox.
+
+::: warning
+As the `unknown` type extends every possible type - the value sent may be `nil`.
+:::
+
+
+
## CFrames
Zap supports sending CFrames. There are two types of CFrame you may send - a regular `CFrame`, and an `AlignedCFrame`.
@@ -224,4 +255,4 @@ The following Roblox Classes are also available as types in Zap:
## Optional Types
A type can be made optional by appending a `?` after the **whole type**, such as:
-
+
diff --git a/zap/src/config.rs b/zap/src/config.rs
index 54c2a605..116cce1b 100644
--- a/zap/src/config.rs
+++ b/zap/src/config.rs
@@ -261,6 +261,7 @@ pub enum Enum<'src> {
Tagged {
tag: &'src str,
variants: Vec<(&'src str, Struct<'src>)>,
+ catch_all: Option>,
},
}
diff --git a/zap/src/output/luau/base.luau b/zap/src/output/luau/base.luau
index b71d5f33..3ad9c955 100644
--- a/zap/src/output/luau/base.luau
+++ b/zap/src/output/luau/base.luau
@@ -43,7 +43,7 @@ local CFrameSpecialCases = {
local function alloc(len: number)
if outgoing_used + len > outgoing_size then
while outgoing_used + len > outgoing_size do
- outgoing_size = outgoing_size * 2
+ outgoing_size = outgoing_size * 1.5
end
local new_buff = buffer.create(outgoing_size)
diff --git a/zap/src/output/luau/mod.rs b/zap/src/output/luau/mod.rs
index 5b6cdfbe..3b2ed3d2 100644
--- a/zap/src/output/luau/mod.rs
+++ b/zap/src/output/luau/mod.rs
@@ -183,8 +183,12 @@ pub trait Output {
self.push_dedent_line("end");
}
- Enum::Tagged { tag, variants } => {
- let numty = NumTy::from_f64(0.0, variants.len() as f64 - 1.0);
+ Enum::Tagged {
+ tag,
+ variants,
+ catch_all,
+ } => {
+ let numty = NumTy::from_f64(0.0, variants.len() as f64);
for (i, (variant_name, variant_struct)) in variants.iter().enumerate() {
if i == 0 {
@@ -194,7 +198,7 @@ pub trait Output {
}
self.push_line(&format!("alloc({})", numty.size()));
- self.push_line(&format!("buffer.write{numty}(outgoing_buff, outgoing_apos, {i})"));
+ self.push_line(&format!("buffer.write{numty}(outgoing_buff, outgoing_apos, {})", i + 1));
for (field_name, field_ty) in &variant_struct.fields {
self.push_ser(&format!("{from}.{field_name}"), field_ty, checks);
@@ -202,7 +206,20 @@ pub trait Output {
}
self.push_dedent_line_indent("else");
- self.push_line("error(\"invalid variant value\")");
+
+ if let Some(catch_all) = catch_all {
+ self.push_line(&format!("alloc({})", numty.size()));
+ self.push_line(&format!("buffer.write{numty}(outgoing_buff, outgoing_apos, 0)"));
+
+ self.push_ser(&format!("{from}.{tag}"), &Ty::Str(Range::default()), checks);
+
+ for (field_name, field_ty) in &catch_all.fields {
+ self.push_ser(&format!("{from}.{field_name}"), field_ty, checks);
+ }
+ } else {
+ self.push_line("error(\"invalid variant value\")");
+ }
+
self.push_dedent_line("end");
}
},
@@ -409,8 +426,12 @@ pub trait Output {
self.push_dedent_line("end");
}
- Enum::Tagged { tag, variants } => {
- let numty = NumTy::from_f64(0.0, variants.len() as f64 - 1.0);
+ Enum::Tagged {
+ tag,
+ variants,
+ catch_all,
+ } => {
+ let numty = NumTy::from_f64(0.0, variants.len() as f64);
self.push_line(&format!("{into} = {{}}"));
self.push_line(&format!(
@@ -420,9 +441,9 @@ pub trait Output {
for (i, (variant_name, variant_struct)) in variants.iter().enumerate() {
if i == 0 {
- self.push_line_indent("if enum_index == 0 then");
+ self.push_line_indent("if enum_index == 1 then");
} else {
- self.push_dedent_line_indent(&format!("elseif enum_index == {i} then"));
+ self.push_dedent_line_indent(&format!("elseif enum_index == {} then", i + 1));
}
self.push_line(&format!("{into}.{tag} = \"{variant_name}\""));
@@ -433,7 +454,17 @@ pub trait Output {
}
self.push_dedent_line_indent("else");
- self.push_line("error(\"unknown enum index\")");
+
+ if let Some(catch_all) = catch_all {
+ self.push_des(&format!("{into}.{tag}"), &Ty::Str(Range::default()), checks);
+
+ for (field_name, field_ty) in &catch_all.fields {
+ self.push_des(&format!("{into}.{field_name}"), field_ty, checks);
+ }
+ } else {
+ self.push_line("error(\"unknown enum index\")");
+ }
+
self.push_dedent_line("end");
}
},
@@ -554,11 +585,18 @@ pub trait Output {
.to_string(),
),
- Enum::Tagged { tag, variants } => {
- for (i, (name, struct_ty)) in variants.iter().enumerate() {
- if i != 0 {
+ Enum::Tagged {
+ tag,
+ variants,
+ catch_all,
+ } => {
+ let mut first = true;
+
+ for (name, struct_ty) in variants.iter() {
+ if !first {
self.push(" | ");
}
+ first = false;
self.push("{\n");
self.indent();
@@ -579,6 +617,31 @@ pub trait Output {
self.push_indent();
self.push("}");
}
+
+ if let Some(catch_all) = catch_all {
+ if !first {
+ self.push(" | ");
+ }
+
+ self.push("{\n");
+ self.indent();
+
+ self.push_indent();
+
+ self.push(&format!("{tag}: string,\n"));
+
+ for (name, ty) in catch_all.fields.iter() {
+ self.push_indent();
+ self.push(&format!("{name}: "));
+ self.push_ty(ty);
+ self.push(",\n");
+ }
+
+ self.dedent();
+
+ self.push_indent();
+ self.push("}");
+ }
}
},
diff --git a/zap/src/output/typescript/mod.rs b/zap/src/output/typescript/mod.rs
index 183b2f26..d3431dc0 100644
--- a/zap/src/output/typescript/mod.rs
+++ b/zap/src/output/typescript/mod.rs
@@ -127,11 +127,18 @@ pub trait Output {
.to_string(),
),
- Enum::Tagged { tag, variants } => {
- for (i, (name, struct_ty)) in variants.iter().enumerate() {
- if i != 0 {
+ Enum::Tagged {
+ tag,
+ variants,
+ catch_all,
+ } => {
+ let mut first = true;
+
+ for (name, struct_ty) in variants.iter() {
+ if !first {
self.push(" | ");
}
+ first = false;
self.push("{\n");
self.indent();
@@ -160,6 +167,39 @@ pub trait Output {
self.push_indent();
self.push("}");
}
+
+ if let Some(catch_all) = catch_all {
+ if !first {
+ self.push(" | ");
+ }
+
+ self.push("{\n");
+ self.indent();
+
+ self.push_indent();
+
+ self.push(&format!("{tag}: string,\n"));
+
+ for (name, ty) in catch_all.fields.iter() {
+ self.push_indent();
+ 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");
+ }
+
+ self.dedent();
+
+ self.push_indent();
+ self.push("}");
+ }
}
},
diff --git a/zap/src/parser/convert.rs b/zap/src/parser/convert.rs
index c2eceb9c..c2c7fe8d 100644
--- a/zap/src/parser/convert.rs
+++ b/zap/src/parser/convert.rs
@@ -595,8 +595,13 @@ impl<'src> Converter<'src> {
Enum::Unit(enumerators.iter().map(|e| e.name).collect())
}
- SyntaxEnumKind::Tagged { tag, variants } => {
+ SyntaxEnumKind::Tagged {
+ tag,
+ variants,
+ catch_all,
+ } => {
let tag_name = Self::str(tag);
+ let catch_all_struct = catch_all.as_ref().map(|syntax_struct| self.struct_ty(syntax_struct));
if variants.is_empty() {
self.report(Report::AnalyzeEmptyEnum { span });
@@ -620,6 +625,7 @@ impl<'src> Converter<'src> {
Enum::Tagged {
tag: tag_name,
variants,
+ catch_all: catch_all_struct,
}
}
}
diff --git a/zap/src/parser/grammar.lalrpop b/zap/src/parser/grammar.lalrpop
index 443ab248..74fa2afe 100644
--- a/zap/src/parser/grammar.lalrpop
+++ b/zap/src/parser/grammar.lalrpop
@@ -126,7 +126,22 @@ Enum: SyntaxEnum<'input> = {
EnumKind: SyntaxEnumKind<'input> = {
"{" > "}" => SyntaxEnumKind::Unit(enumerators),
- "{" )>> "}" => SyntaxEnumKind::Tagged { tag, variants },
+ "{" ",")*> )?> "}" =>
+ SyntaxEnumKind::Tagged {
+ tag,
+ variants: match lv {
+ Some(lv) => { variants.push(lv); variants },
+ None => variants,
+ },
+ catch_all: None
+ },
+
+ "{" ",")*> ","?)> "}" =>
+ SyntaxEnumKind::Tagged {
+ tag,
+ variants,
+ catch_all: Some(ca),
+ },
}
Struct: SyntaxStruct<'input> = {
diff --git a/zap/src/parser/syntax_tree.rs b/zap/src/parser/syntax_tree.rs
index 43dfdca3..85332cfa 100644
--- a/zap/src/parser/syntax_tree.rs
+++ b/zap/src/parser/syntax_tree.rs
@@ -171,6 +171,7 @@ pub enum SyntaxEnumKind<'src> {
Tagged {
tag: SyntaxStrLit<'src>,
variants: Vec<(SyntaxIdentifier<'src>, SyntaxStruct<'src>)>,
+ catch_all: Option>,
},
}