Skip to content

Commit

Permalink
Merge branch 'main' into opt-rework
Browse files Browse the repository at this point in the history
  • Loading branch information
sasial-dev committed Feb 9, 2024
2 parents 5ee420d + bb80746 commit e12c17c
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 19 deletions.
33 changes: 32 additions & 1 deletion docs/config/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:

<CodeBlock :code="enumCatchAllExample" />

## Structs

Structs are similar to Interfaces, and are a collection of statically named fields with different types.
Expand All @@ -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`.
:::

<CodeBlock code="unknown" />

## CFrames

Zap supports sending CFrames. There are two types of CFrame you may send - a regular `CFrame`, and an `AlignedCFrame`.
Expand Down Expand Up @@ -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:
<CodeBlock code="type Character = Instance (Player)?" />
<CodeBlock code="type Character = Instance (Model)?" />
1 change: 1 addition & 0 deletions zap/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ pub enum Enum<'src> {
Tagged {
tag: &'src str,
variants: Vec<(&'src str, Struct<'src>)>,
catch_all: Option<Struct<'src>>,
},
}

Expand Down
2 changes: 1 addition & 1 deletion zap/src/output/luau/base.luau
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
87 changes: 75 additions & 12 deletions zap/src/output/luau/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -194,15 +198,28 @@ 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);
}
}

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");
}
},
Expand Down Expand Up @@ -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!(
Expand All @@ -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}\""));
Expand All @@ -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");
}
},
Expand Down Expand Up @@ -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();
Expand All @@ -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("}");
}
}
},

Expand Down
46 changes: 43 additions & 3 deletions zap/src/output/typescript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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("}");
}
}
},

Expand Down
8 changes: 7 additions & 1 deletion zap/src/parser/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand All @@ -620,6 +625,7 @@ impl<'src> Converter<'src> {
Enum::Tagged {
tag: tag_name,
variants,
catch_all: catch_all_struct,
}
}
}
Expand Down
17 changes: 16 additions & 1 deletion zap/src/parser/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,22 @@ Enum: SyntaxEnum<'input> = {
EnumKind: SyntaxEnumKind<'input> = {
"{" <enumerators:Comma<Identifier>> "}" => SyntaxEnumKind::Unit(enumerators),

<tag:StrLit> "{" <variants:Comma<(<Identifier> <Struct>)>> "}" => SyntaxEnumKind::Tagged { tag, variants },
<tag:StrLit> "{" <mut variants:(<Identifier> <Struct> ",")*> <lv:(<Identifier> <Struct>)?> "}" =>
SyntaxEnumKind::Tagged {
tag,
variants: match lv {
Some(lv) => { variants.push(lv); variants },
None => variants,
},
catch_all: None
},

<tag:StrLit> "{" <variants:(<Identifier> <Struct> ",")*> <ca:("..." <Struct> ","?)> "}" =>
SyntaxEnumKind::Tagged {
tag,
variants,
catch_all: Some(ca),
},
}

Struct: SyntaxStruct<'input> = {
Expand Down
1 change: 1 addition & 0 deletions zap/src/parser/syntax_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ pub enum SyntaxEnumKind<'src> {
Tagged {
tag: SyntaxStrLit<'src>,
variants: Vec<(SyntaxIdentifier<'src>, SyntaxStruct<'src>)>,
catch_all: Option<SyntaxStruct<'src>>,
},
}

Expand Down

0 comments on commit e12c17c

Please sign in to comment.