diff --git a/src/api.rs b/src/api.rs index 112b492..a05ecfd 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,14 +2,26 @@ use std::ops::ControlFlow; use uuid::Uuid; -use crate::types::{ - ArrayType, BinaryStringType, Event, EventFrom, Item, MapType, NumberKind, NumberType, Range, - SetType, StructType, Type, Utf8StringType, VectorType, +use crate::{ + shared::NetworkSide, + shared::{NumberKind, Range}, }; -pub fn exec(code: &[u8]) -> Result, String> { +#[derive(Default)] +struct Config; + +impl lu::Config for Config { + type Allocator = lu::DefaultAllocator; + + type MainData = (); + type ThreadData = (); +} + +type Context = lu::Context; + +pub fn exec(source: &[u8]) -> Result { let compiler = lu::Compiler::default(); - let bytecode = compiler.compile(code); + let bytecode = compiler.compile(source); let mut state = lu::State::new((), lu::DefaultAllocator); state.open_std(); @@ -42,69 +54,62 @@ pub fn exec(code: &[u8]) -> Result, String> { Err(format!("yielded: {trace}")) }, - _ => table_to_module(stack), + _ => convert(stack).map(Item::Table), } } -fn table_to_module(stack: &lu::Stack) -> Result, String> { - let mut module = Vec::new(); +#[derive(Clone)] +pub enum Item { + Table(Vec<(String, Item)>), + Event(Event), +} + +fn convert(stack: &lu::Stack) -> Result, String> { + let mut items = Vec::new(); if let Some(err) = stack.iter(-1, || { let Some(name) = stack.to_string_str(-2) else { - return ControlFlow::Break("module name must be a string".to_string()); + return ControlFlow::Break("item name must be a string".to_string()); }; match stack.type_of(-1) { lu::Type::Table => { - let item = match table_to_module(stack).map(Item::Table) { + let item = match convert(stack).map(Item::Table) { Ok(item) => item, Err(err) => return ControlFlow::Break(err), }; - module.push((name.to_owned(), item)); + items.push((name.to_owned(), item)); } lu::Type::Userdata => { let Some(event) = stack.to_userdata::(-1) else { - return ControlFlow::Break(format!( - "module item '{name}' must be a table or event" - )); + return ControlFlow::Break(format!("item {name} must be a table or event")); }; - module.push((name.to_owned(), Item::Event(event.borrow().clone()))); + items.push((name.to_owned(), Item::Event(event.borrow().clone()))); } _ => { - return ControlFlow::Break(format!( - "module item '{name}' must be a table or event" - )); + return ControlFlow::Break(format!("item {name} must be a table or event")); } } ControlFlow::Continue(()) }) { - return Err(err); + Err(err) + } else { + Ok(items) } - - Ok(module) -} - -#[derive(Default)] -struct Config; - -impl lu::Config for Config { - type Allocator = lu::DefaultAllocator; - - type MainData = (); - type ThreadData = (); } fn library() -> lu::Library { let string = lu::Library::default() - .with_function_norm("binary", string_binary) - .with_function_norm("utf8", string_utf8); + .with_function_norm("binary", binary_string) + .with_function_norm("utf8", utf8_string); lu::Library::default() + .with_function_norm("event", event) .with_function_norm("u8", u8) .with_function_norm("u16", u16) .with_function_norm("u32", u32) @@ -120,123 +125,333 @@ fn library() -> lu::Library { .with_function_norm("array", array) .with_function_norm("set", set) .with_function_norm("map", map) - .with_function_norm("struct", struckt) - .with_function_norm("reliable", reliable_event) - .with_function_norm("unreliable", unreliable_event) + .with_function_norm("struct", strukt) } -type Context = lu::Context; +#[derive(lu::Userdata, Clone)] +pub struct Event { + pub uuid: Uuid, + pub from: NetworkSide, + pub data: Vec, +} -extern "C-unwind" fn reliable_event(ctx: Context) -> lu::FnReturn { - let from = match ctx.arg_string_str(1) { - "server" => EventFrom::Server, - "client" => EventFrom::Client, +extern "C-unwind" fn event(ctx: Context) -> lu::FnReturn { + let from = ctx.arg_string_str(1); + ctx.arg_table(2); + + let uuid = Uuid::new_v4(); + let from = match from { + "server" => NetworkSide::Server, + "client" => NetworkSide::Client, - _ => ctx.arg_error(1, c"expected 'server' or 'client'"), + _ => ctx.arg_error(1, c"must be 'server' or 'client'"), }; - ctx.arg_table(2); let mut data = Vec::new(); - ctx.iter(2, || { - let ty = ctx.to_userdata::(-1).map(|u| u.borrow().clone()); + if !ctx.is_number(-2) { + ctx.error_msg("event data must be an array") + } - match ty { - Some(ty) => data.push(ty), - None => ctx.error_msg("event data must be a type"), + if let Some(item) = ctx.to_userdata::(-1) { + data.push(item.borrow().clone()); + } else { + ctx.error_msg("event data must be a type"); } ControlFlow::<()>::Continue(()) }); - ctx.push_userdata(Event { - name: Uuid::new_v4(), - from, - data, - reliable: true, - }); + ctx.push_userdata(Event { uuid, from, data }); + ctx.ret_with(1) +} + +#[derive(lu::Userdata, Clone)] +pub enum Type { + Number(NumberType), + Vector(VectorType), + BinaryString(BinaryStringType), + Utf8String(Utf8StringType), + Array(ArrayType), + Set(SetType), + Map(MapType), + Struct(StructType), +} + +#[derive(Clone)] +pub struct NumberType { + pub kind: NumberKind, + pub range: Range, +} + +extern "C-unwind" fn u8(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && !(0f64..=255f64).contains(&min) + { + ctx.error_msg("u8 min must be between 0 and 255") + } + + if let Some(max) = max + && !(0f64..=255f64).contains(&max) + { + ctx.error_msg("u8 max must be between 0 and 255") + } + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } + + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::U8, + range: Range { min, max }, + })); ctx.ret_with(1) } -extern "C-unwind" fn unreliable_event(ctx: Context) -> lu::FnReturn { - let from = match ctx.arg_string_str(1) { - "server" => EventFrom::Server, - "client" => EventFrom::Client, +extern "C-unwind" fn u16(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); - _ => ctx.arg_error(1, c"expected 'server' or 'client'"), - }; + if let Some(min) = min + && !(0f64..=65535f64).contains(&min) + { + ctx.error_msg("u16 min must be between 0 and 65535") + } - ctx.arg_table(2); - let mut data = Vec::new(); + if let Some(max) = max + && !(0f64..=65535f64).contains(&max) + { + ctx.error_msg("u16 max must be between 0 and 65535") + } - ctx.iter(2, || { - let ty = ctx.to_userdata::(-1).map(|u| u.borrow().clone()); + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } - match ty { - Some(ty) => data.push(ty), - None => ctx.error_msg("event data must be a type"), - } + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::U16, + range: Range { min, max }, + })); - ControlFlow::<()>::Continue(()) - }); + ctx.ret_with(1) +} - ctx.push_userdata(Event { - name: Uuid::new_v4(), - from, - data, - reliable: false, - }); +extern "C-unwind" fn u32(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && !(0f64..=4294967295f64).contains(&min) + { + ctx.error_msg("u32 min must be between 0 and 4294967295") + } + + if let Some(max) = max + && !(0f64..=4294967295f64).contains(&max) + { + ctx.error_msg("u32 max must be between 0 and 4294967295") + } + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } + + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::U32, + range: Range { min, max }, + })); ctx.ret_with(1) } -macro_rules! number { - ($ty:ty, $kind:ident, $name:ident) => { - extern "C-unwind" fn $name(ctx: Context) -> lu::FnReturn { - let min = ctx.arg_number_opt(1); - let max = ctx.arg_number_opt(2); +extern "C-unwind" fn i8(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); - if let Some(min) = min { - if !((<$ty>::MIN as f64) < min || min < (<$ty>::MAX as f64)) { - ctx.error_msg("range minimum out of bounds"); - } - } + if let Some(min) = min + && !(-128f64..=127f64).contains(&min) + { + ctx.error_msg("i8 min must be between -128 and 127") + } - if let Some(max) = max { - if !((<$ty>::MIN as f64) < max || max < (<$ty>::MAX as f64)) { - ctx.error_msg("range maximum out of bounds"); - } - } + if let Some(max) = max + && !(-128f64..=127f64).contains(&max) + { + ctx.error_msg("i8 max must be between -128 and 127") + } - if let (Some(min), Some(max)) = (min, max) { - if min > max { - ctx.error_msg("range minimum cannot be greater than maximum"); - } - } + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } - ctx.push_userdata(Type::Number(NumberType { - kind: NumberKind::$kind, - range: Range { min, max }, - })); + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::I8, + range: Range { min, max }, + })); - ctx.ret_with(1) - } - }; + ctx.ret_with(1) } -number!(u8, U8, u8); -number!(u16, U16, u16); -number!(u32, U32, u32); +extern "C-unwind" fn i16(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && !(-32768f64..=32767f64).contains(&min) + { + ctx.error_msg("i16 min must be between -32768 and 32767") + } + + if let Some(max) = max + && !(-32768f64..=32767f64).contains(&max) + { + ctx.error_msg("i16 max must be between -32768 and 32767") + } + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } -number!(i8, I8, i8); -number!(i16, I16, i16); -number!(i32, I32, i32); + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::I16, + range: Range { min, max }, + })); -number!(f32, F32, f32); -number!(f64, F64, f64); -number!(f32, NaNF32, nanf32); -number!(f64, NaNF64, nanf64); + ctx.ret_with(1) +} + +extern "C-unwind" fn i32(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && !(-2147483648f64..=2147483647f64).contains(&min) + { + ctx.error_msg("i32 min must be between -2147483648 and 2147483647") + } + + if let Some(max) = max + && !(-2147483648f64..=2147483647f64).contains(&max) + { + ctx.error_msg("i32 max must be between -2147483648 and 2147483647") + } + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } + + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::I32, + range: Range { min, max }, + })); + + ctx.ret_with(1) +} + +extern "C-unwind" fn f32(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } + + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::F32, + range: Range { min, max }, + })); + + ctx.ret_with(1) +} + +extern "C-unwind" fn f64(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } + + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::F64, + range: Range { min, max }, + })); + + ctx.ret_with(1) +} + +extern "C-unwind" fn nanf32(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } + + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::NaNF32, + range: Range { min, max }, + })); + + ctx.ret_with(1) +} + +extern "C-unwind" fn nanf64(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max") + } + + ctx.push_userdata(Type::Number(NumberType { + kind: NumberKind::NaNF64, + range: Range { min, max }, + })); + + ctx.ret_with(1) +} + +#[derive(Clone)] +pub struct VectorType { + pub x: NumberType, + pub y: NumberType, + pub z: Option, +} extern "C-unwind" fn vector(ctx: Context) -> lu::FnReturn { let x = ctx.arg_userdata::(1).borrow().clone(); @@ -249,7 +464,7 @@ extern "C-unwind" fn vector(ctx: Context) -> lu::FnReturn { || matches!(y.kind, NumberKind::F64 | NumberKind::NaNF64) || matches!(z.kind, NumberKind::F64 | NumberKind::NaNF64) { - ctx.error_msg("vector components cannot be f64 or NaNF64"); + ctx.error_msg("vector components cannot be f64 or nanf64"); } ctx.push_userdata(Type::Vector(VectorType { x, y, z: Some(z) })); @@ -259,7 +474,7 @@ extern "C-unwind" fn vector(ctx: Context) -> lu::FnReturn { if matches!(x.kind, NumberKind::F64 | NumberKind::NaNF64) || matches!(y.kind, NumberKind::F64 | NumberKind::NaNF64) { - ctx.error_msg("vector components cannot be f64 or NaNF64"); + ctx.error_msg("vector components cannot be f64 or nanf64"); } ctx.push_userdata(Type::Vector(VectorType { x, y, z: None })); @@ -271,71 +486,199 @@ extern "C-unwind" fn vector(ctx: Context) -> lu::FnReturn { ctx.ret_with(1) } -fn len_range(ctx: &Context, offset: u32) -> Range { - let min = ctx.arg_number_opt(offset + 1); - let max = ctx.arg_number_opt(offset + 2); +#[derive(Clone)] +pub struct BinaryStringType { + pub len: Range, +} - if let Some(min) = min { - if min < 0.0 { - ctx.error_msg("length minimum cannot be negative"); - } - } +extern "C-unwind" fn binary_string(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); - if let Some(max) = max { - if max < 0.0 { - ctx.error_msg("length maximum cannot be negative"); - } + if let Some(min) = min + && min < 0f64 + { + ctx.error_msg("length min cannot be negative"); } - if let (Some(min), Some(max)) = (min, max) { - if min > max { - ctx.error_msg("length minimum cannot be greater than maximum"); - } + if let Some(max) = max + && max < 0f64 + { + ctx.error_msg("length max cannot be negative"); } - Range { min, max } -} + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max"); + } -extern "C-unwind" fn string_binary(ctx: Context) -> lu::FnReturn { - let len = len_range(&ctx, 0); + ctx.push_userdata(Type::BinaryString(BinaryStringType { + len: Range { min, max }, + })); - ctx.push_userdata(Type::BinaryString(BinaryStringType { len })); ctx.ret_with(1) } -extern "C-unwind" fn string_utf8(ctx: Context) -> lu::FnReturn { - let len = len_range(&ctx, 0); +#[derive(Clone)] +pub struct Utf8StringType { + pub len: Range, +} + +extern "C-unwind" fn utf8_string(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && min < 0f64 + { + ctx.error_msg("length min cannot be negative"); + } + + if let Some(max) = max + && max < 0f64 + { + ctx.error_msg("length max cannot be negative"); + } + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max"); + } + + ctx.push_userdata(Type::Utf8String(Utf8StringType { + len: Range { min, max }, + })); - ctx.push_userdata(Type::Utf8String(Utf8StringType { len })); ctx.ret_with(1) } +#[derive(Clone)] +pub struct ArrayType { + pub len: Range, + pub item: Box, +} + extern "C-unwind" fn array(ctx: Context) -> lu::FnReturn { - let item = Box::new(ctx.arg_userdata::(1).borrow().clone()); - let len = len_range(&ctx, 1); + let item = ctx.arg_userdata::(1).borrow().clone(); + let min = ctx.arg_number_opt(2); + let max = ctx.arg_number_opt(3); + + if let Some(min) = min + && min < 0f64 + { + ctx.error_msg("length min cannot be negative"); + } + + if let Some(max) = max + && max < 0f64 + { + ctx.error_msg("length max cannot be negative"); + } + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max"); + } + + ctx.push_userdata(Type::Array(ArrayType { + len: Range { min, max }, + item: Box::new(item), + })); - ctx.push_userdata(Type::Array(ArrayType { len, item })); ctx.ret_with(1) } +#[derive(Clone)] +pub struct SetType { + pub len: Range, + pub item: Box, +} + extern "C-unwind" fn set(ctx: Context) -> lu::FnReturn { - let item = Box::new(ctx.arg_userdata::(1).borrow().clone()); - let len = len_range(&ctx, 1); + let item = ctx.arg_userdata::(1).borrow().clone(); + let min = ctx.arg_number_opt(2); + let max = ctx.arg_number_opt(3); + + if let Some(min) = min + && min < 0f64 + { + ctx.error_msg("length min cannot be negative"); + } + + if let Some(max) = max + && max < 0f64 + { + ctx.error_msg("length max cannot be negative"); + } + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max"); + } + + ctx.push_userdata(Type::Set(SetType { + len: Range { min, max }, + item: Box::new(item), + })); - ctx.push_userdata(Type::Set(SetType { len, item })); ctx.ret_with(1) } +#[derive(Clone)] +pub struct MapType { + pub len: Range, + pub index: Box, + pub value: Box, +} + extern "C-unwind" fn map(ctx: Context) -> lu::FnReturn { - let index = Box::new(ctx.arg_userdata::(1).borrow().clone()); - let value = Box::new(ctx.arg_userdata::(2).borrow().clone()); - let len = len_range(&ctx, 2); + let index = ctx.arg_userdata::(1).borrow().clone(); + let value = ctx.arg_userdata::(2).borrow().clone(); + let min = ctx.arg_number_opt(3); + let max = ctx.arg_number_opt(4); + + if let Some(min) = min + && min < 0f64 + { + ctx.error_msg("length min cannot be negative"); + } + + if let Some(max) = max + && max < 0f64 + { + ctx.error_msg("length max cannot be negative"); + } + + if let Some(min) = min + && let Some(max) = max + && min > max + { + ctx.error_msg("min must be less than or equal to max"); + } + + ctx.push_userdata(Type::Map(MapType { + len: Range { min, max }, + index: Box::new(index), + value: Box::new(value), + })); - ctx.push_userdata(Type::Map(MapType { index, value, len })); ctx.ret_with(1) } -extern "C-unwind" fn struckt(ctx: Context) -> lu::FnReturn { +#[derive(Clone)] +pub struct StructType { + pub fields: Vec<(String, Type)>, +} + +extern "C-unwind" fn strukt(ctx: Context) -> lu::FnReturn { ctx.arg_table(1); let mut fields = Vec::new(); diff --git a/src/client.rs b/src/client.rs deleted file mode 100644 index 3c45e6f..0000000 --- a/src/client.rs +++ /dev/null @@ -1,240 +0,0 @@ -use crate::{ - builder::{Builder, InitVar}, - ir::Expr, - serdes, - types::{Event, EventFrom, Item, Type}, -}; - -pub fn client(c: Client, items: &[(String, Item)]) -> Result { - use std::fmt::Write; - - let mut b = Builder::default(); - - for (name, item) in items { - self::item(c.push_location(name), &mut b, item) - } - - let mut s = String::new(); - writeln!(s, "{}", include_str!("header.luau"))?; - writeln!(s, "local result = {{}}")?; - writeln!(s, "local folder = game.ReplicatedStorage:WaitForChild('Z')")?; - writeln!(s, "{b}")?; - writeln!(s, "return result")?; - - Ok(s) -} - -#[derive(Default, Debug, Clone)] -pub struct Client { - pub apicheck: serdes::ApiCheck, - pub location: String, -} - -impl Client { - pub fn push_location(&self, location: &str) -> Self { - Self { - apicheck: self.apicheck, - location: self.location.clone() + "." + location, - } - } - - pub fn function(&self, name: &str) -> String { - self.location.clone() + "." + name - } -} - -fn ser(c: &Client, b: &mut Builder, ty: Type, from: Expr) { - let ser = serdes::Ser { - apicheck: c.apicheck, - }; - - serdes::ser(ser, b, ty, from) -} - -fn des(c: &Client, b: &mut Builder, ty: Type) -> InitVar { - let des = serdes::Des { check: false }; - - serdes::des(des, b, ty) -} - -pub fn item(c: Client, b: &mut Builder, item: &Item) { - match item { - Item::Table(table) => self::table(c, b, table), - Item::Event(event) => self::event(c, b, event), - } -} - -pub fn table(c: Client, b: &mut Builder, items: &[(String, Item)]) { - b.export_table(c.location.clone()); - - for (name, item) in items { - self::item(c.push_location(name), b, item) - } -} - -fn event(c: Client, b: &mut Builder, event: &Event) { - b.export_table(c.location.clone()); - - match event.from { - EventFrom::Server => event_recv(c, b, event), - EventFrom::Client => event_send(c, b, event), - } -} - -fn event_remote(b: &mut Builder, event: &Event) -> InitVar { - let var = b.var().init(); - let name = event.name; - - b.stmt(format!("local {var} = folder:WaitForChild(\"{name}\")")); - - var -} - -fn event_recv(c: Client, b: &mut Builder, event: &Event) { - match event.data.len() { - 0 => event_recv_iter_0data(&c, b, event), - 1 => event_recv_iter_1data(&c, b, event), - _ => event_recv_iter_ndata(&c, b, event), - } -} - -fn event_recv_counter(c: &Client, b: &mut Builder, event: &Event) -> InitVar { - let remote = event_remote(b, event); - let counter = b.expr(Expr::Number(0f64)); - - let on_event = b.local_function(|b, []| { - b.assign(&counter, counter.expr().add(1f64)); - }); - - b.stmt(format!("{remote}.OnClientEvent:Connect({on_event})")); - counter -} - -fn event_recv_queue(c: &Client, b: &mut Builder, event: &Event) -> InitVar { - let remote = event_remote(b, event); - let queue = b.expr(Expr::Table); - - let on_event = b.local_function(|b, [buf]| { - b.stmt(format!("local buf, pos = {buf}, 0")); - - for ty in &event.data { - let value = des(c, b, ty.clone()); - b.stmt(format!("table.insert({queue}, {value})")); - } - }); - - b.stmt(format!("{remote}.OnClientEvent:Connect({on_event})")); - queue -} - -fn event_recv_iter_0data(c: &Client, b: &mut Builder, event: &Event) { - let counter = event_recv_counter(c, b, event); - - b.export_function( - c.function("iter"), - vec![], - vec![ - "(number, number) -> number".to_string(), - "number".to_string(), - ], - |b, _| { - let captured = b.expr(&counter); - b.assign(&counter, Expr::Number(0f64)); - - let iter = b.local_function(|b, [i]| { - b.stmt(format!("if {i} < {captured} then return {i} + 1 end")); - }); - - b.stmt(format!("return {iter}, {captured}, 0")); - }, - ); -} - -fn event_recv_iter_1data(c: &Client, b: &mut Builder, event: &Event) { - let queue = event_recv_queue(c, b, event); - - b.export_function( - c.function("iter"), - vec![], - vec![format!("{{ {} }}", event.data[0])], - |b, _| { - let captured = b.expr(&queue); - b.assign(&queue, Expr::Table); - b.stmt(format!("return {captured}")); - }, - ); -} - -fn event_recv_iter_ndata(c: &Client, b: &mut Builder, event: &Event) { - let n = event.data.len(); - let queue = event_recv_queue(c, b, event); - let tys = event - .data - .iter() - .map(ToString::to_string) - .collect::>() - .join(", "); - - b.export_function( - c.function("iter"), - vec![], - vec![ - format!("({{ any }}, number) -> (number, {tys})"), - "{ any }".to_string(), - "number".to_string(), - ], - |b, _| { - let captured = b.expr(&queue); - b.assign(&queue, Expr::Table); - - let iter = b.local_function(|b, [captured, i]| { - let cond = format!("{captured}[{i}]"); - let rets = (0..n) - .map(|j| format!(", {captured}[{i} + {j}]")) - .collect::(); - let body = format!("return {i} + {n}{rets}"); - - b.stmt(format!("if {cond} then {body} end")); - }); - - b.stmt(format!("return {iter}, {captured}, 1")); - }, - ) -} - -fn event_send(c: Client, b: &mut Builder, event: &Event) { - match event.data.len() { - 0 => event_send_0data(&c, b, event), - _ => event_send_ndata(&c, b, event), - } -} - -fn event_send_0data(c: &Client, b: &mut Builder, event: &Event) { - let remote = event_remote(b, event); - - b.export_function(c.function("fire"), vec![], vec![], |b, _| { - b.stmt(format!("{remote}:FireServer()")); - }); -} - -fn event_send_ndata(c: &Client, b: &mut Builder, event: &Event) { - let remote = event_remote(b, event); - let args = event - .data - .iter() - .map(ToString::to_string) - .collect::>(); - - b.export_function(c.function("fire"), args, vec![], |b, args| { - b.stmt("pos = 0"); - - for (i, arg) in args.iter().enumerate() { - let ty = event.data[i].clone(); - ser(c, b, ty, arg.expr()); - } - - b.stmt("local out = buffer.create(pos)"); - b.stmt("buffer.copy(out, 0, buf, 0, pos)"); - b.stmt(format!("{remote}:FireServer(out)")); - }); -} diff --git a/src/hir/build.rs b/src/hir/build.rs new file mode 100644 index 0000000..12c2b4c --- /dev/null +++ b/src/hir/build.rs @@ -0,0 +1,132 @@ +use crate::{ + api, + hir::{ + ArrayType, BinaryStringType, Event, Item, Length, MapType, NumberType, SetType, StructType, + Type, Utf8StringType, VectorType, + }, + shared::Range, +}; + +impl From for Item { + fn from(value: api::Item) -> Self { + match value { + api::Item::Table(table) => Item::Table( + table + .into_iter() + .map(|(name, item)| (name, Item::from(item))) + .collect(), + ), + + api::Item::Event(event) => Item::Event(event.into()), + } + } +} + +impl From for Event { + fn from(value: api::Event) -> Self { + let uuid = value.uuid; + let from = value.from; + let data = value.data.into_iter().map(Type::from).collect(); + + Event { uuid, from, data } + } +} + +impl From for Type { + fn from(value: api::Type) -> Self { + match value { + api::Type::Number(ty) => Type::Number(ty.into()), + api::Type::Vector(ty) => Type::Vector(ty.into()), + api::Type::BinaryString(ty) => Type::BinaryString(ty.into()), + api::Type::Utf8String(ty) => Type::Utf8String(ty.into()), + api::Type::Array(ty) => Type::Array(ty.into()), + api::Type::Set(ty) => Type::Set(ty.into()), + api::Type::Map(ty) => Type::Map(ty.into()), + api::Type::Struct(ty) => Type::Struct(ty.into()), + } + } +} + +impl From for NumberType { + fn from(value: api::NumberType) -> Self { + let kind = value.kind; + let range = value.range; + + NumberType { kind, range } + } +} + +impl From for VectorType { + fn from(value: api::VectorType) -> Self { + let x = NumberType::from(value.x); + let y = NumberType::from(value.y); + let z = value.z.map(NumberType::from); + + VectorType { x, y, z } + } +} + +impl From for BinaryStringType { + fn from(value: api::BinaryStringType) -> Self { + let len = Length::from(value.len); + + BinaryStringType { len } + } +} + +impl From for Utf8StringType { + fn from(value: api::Utf8StringType) -> Self { + let len = Length::from(value.len); + + Utf8StringType { len } + } +} + +impl From for ArrayType { + fn from(value: api::ArrayType) -> Self { + let len = Length::from(value.len); + let item = Box::new(Type::from(*value.item)); + + ArrayType { len, item } + } +} + +impl From for SetType { + fn from(value: api::SetType) -> Self { + let len = Length::from(value.len); + let item = Box::new(Type::from(*value.item)); + + SetType { len, item } + } +} + +impl From for MapType { + fn from(value: api::MapType) -> Self { + let len = Length::from(value.len); + let index = Box::new(Type::from(*value.index)); + let value = Box::new(Type::from(*value.value)); + + MapType { len, index, value } + } +} + +impl From for StructType { + fn from(value: api::StructType) -> Self { + let fields = value + .fields + .into_iter() + .map(|(name, ty)| (name, Type::from(ty))) + .collect(); + + StructType { fields } + } +} + +impl From for Length { + fn from(value: Range) -> Self { + let min = value.min.map(|n| n as u32); + let max = value.max.map(|n| n as u32); + + Length { min, max } + } +} diff --git a/src/hir/mod.rs b/src/hir/mod.rs new file mode 100644 index 0000000..0ae571d --- /dev/null +++ b/src/hir/mod.rs @@ -0,0 +1,121 @@ +use uuid::Uuid; + +use crate::shared::{NetworkSide, NumberKind, Range}; + +mod build; +mod size; + +#[derive(Clone)] +pub enum Item { + Table(Vec<(String, Item)>), + Event(Event), +} + +#[derive(Clone)] +pub struct Event { + pub uuid: Uuid, + pub from: NetworkSide, + pub data: Vec, +} + +#[derive(Clone)] +pub enum Type { + Number(NumberType), + Vector(VectorType), + BinaryString(BinaryStringType), + Utf8String(Utf8StringType), + Array(ArrayType), + Set(SetType), + Map(MapType), + Struct(StructType), +} + +#[derive(Clone)] +pub struct NumberType { + pub kind: NumberKind, + pub range: Range, +} + +#[derive(Clone)] +pub struct VectorType { + pub x: NumberType, + pub y: NumberType, + pub z: Option, +} + +#[derive(Clone)] +pub struct BinaryStringType { + pub len: Length, +} + +#[derive(Clone)] +pub struct Utf8StringType { + pub len: Length, +} + +#[derive(Clone)] +pub struct ArrayType { + pub len: Length, + pub item: Box, +} + +#[derive(Clone)] +pub struct SetType { + pub len: Length, + pub item: Box, +} + +#[derive(Clone)] +pub struct MapType { + pub len: Length, + pub index: Box, + pub value: Box, +} + +#[derive(Clone)] +pub struct StructType { + pub fields: Vec<(String, Type)>, +} + +#[derive(Clone, Copy)] +pub struct Length { + pub min: Option, + pub max: Option, +} + +impl Length { + pub fn exact(&self) -> Option { + if self.min == self.max { self.min } else { None } + } + + pub fn kind(&self) -> NumberKind { + let max = self.max.unwrap_or(u32::MAX); + + if max <= u8::MAX as u32 { + NumberKind::U8 + } else if max <= u16::MAX as u32 { + NumberKind::U16 + } else { + NumberKind::U32 + } + } + + pub fn number_type(&self) -> NumberType { + NumberType { + kind: self.kind(), + range: Range { + min: self.min.map(|min| min as f64), + max: self.max.map(|max| max as f64), + }, + } + } +} + +impl From for Range { + fn from(value: Length) -> Self { + Range { + min: value.min.map(|min| min as f64), + max: value.max.map(|max| max as f64), + } + } +} diff --git a/src/hir/size.rs b/src/hir/size.rs new file mode 100644 index 0000000..4ad1e90 --- /dev/null +++ b/src/hir/size.rs @@ -0,0 +1,150 @@ +use std::ops::{Add, Mul}; + +use crate::hir; + +#[derive(Default, Clone, Copy)] +pub struct Size { + pub min: u32, + pub max: Option, +} + +impl Add for Size { + type Output = Self; + + fn add(self, rhs: u32) -> Self::Output { + Self { + min: self.min + rhs, + max: self.max.map(|m| m + rhs), + } + } +} + +impl Add for Size { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + min: self.min + rhs.min, + max: match (self.max, rhs.max) { + (Some(a), Some(b)) => Some(a + b), + _ => None, + }, + } + } +} + +impl Mul for Size { + type Output = Self; + + fn mul(self, rhs: u32) -> Self::Output { + Self { + min: self.min * rhs, + max: self.max.map(|m| m * rhs), + } + } +} + +impl Mul for Size { + type Output = Self; + + fn mul(self, rhs: hir::Length) -> Self::Output { + let min = rhs.min.unwrap_or(0); + let max = rhs.max; + + Self { + min: self.min * min, + max: match (self.max, max) { + (Some(a), Some(b)) => Some(a * b), + _ => None, + }, + } + } +} + +impl From for Size { + fn from(value: u32) -> Self { + Self { + min: value, + max: Some(value), + } + } +} + +impl Size { + pub fn is_exact(&self) -> Option { + if Some(self.min) == self.max { + self.max + } else { + None + } + } +} + +impl hir::Type { + pub fn size(&self) -> Size { + match self { + hir::Type::Number(ty) => ty.size(), + hir::Type::Vector(ty) => ty.size(), + hir::Type::BinaryString(ty) => ty.size(), + hir::Type::Utf8String(ty) => ty.size(), + hir::Type::Array(ty) => ty.size(), + hir::Type::Set(ty) => ty.size(), + hir::Type::Map(ty) => ty.size(), + hir::Type::Struct(ty) => ty.size(), + } + } +} + +impl hir::NumberType { + pub fn size(&self) -> Size { + Size { + min: self.kind.size(), + max: Some(self.kind.size()), + } + } +} + +impl hir::VectorType { + pub fn size(&self) -> Size { + self.x.size() + self.y.size() + self.z.as_ref().map_or(Size::default(), |z| z.size()) + } +} + +impl hir::BinaryStringType { + pub fn size(&self) -> Size { + Size::from(1) * self.len + self.len.kind().size() + } +} + +impl hir::Utf8StringType { + pub fn size(&self) -> Size { + Size::from(1) * self.len + self.len.kind().size() + } +} + +impl hir::ArrayType { + pub fn size(&self) -> Size { + self.item.size() * self.len + self.len.kind().size() + } +} + +impl hir::SetType { + pub fn size(&self) -> Size { + self.item.size() * self.len + self.len.kind().size() + } +} + +impl hir::MapType { + pub fn size(&self) -> Size { + self.index.size() + self.value.size() * self.len + self.len.kind().size() + } +} + +impl hir::StructType { + pub fn size(&self) -> Size { + self.fields + .iter() + .map(|(_, field_type)| field_type.size()) + .fold(Size::default(), |acc, size| acc + size) + } +} diff --git a/src/main.rs b/src/main.rs index f76f2b3..a020e48 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,9 @@ use std::default::Default; use clap::{Parser, Subcommand}; mod api; -mod builder; -mod client; -mod ir; -mod serdes; -mod server; -mod types; +mod hir; +mod mir; +mod shared; #[derive(Parser)] struct Cli { @@ -41,16 +38,16 @@ fn main() { } }; - let server = server::server(server::Server::default(), &items) - .expect("failed to generate server code"); - let client = client::client(client::Client::default(), &items) - .expect("failed to generate client code"); + let items = hir::Item::from(items); + + let server = mir::server(&items).unwrap(); + let client = mir::client(&items).unwrap(); std::fs::create_dir_all("./zap/out").expect("failed to create zap/out directory"); - std::fs::write("./zap/out/server.luau", server).expect("failed to write server.luau"); - std::fs::write("./zap/out/client.luau", client).expect("failed to write client.luau"); + std::fs::write("./zap/out/server.luau", server).expect("failed to write server code"); + std::fs::write("./zap/out/client.luau", client).expect("failed to write client code"); - println!("Generated server and client code in `zap/out/` directory."); + println!("Generated server and client code in `zap/out/`"); } Command::New => { diff --git a/src/builder.rs b/src/mir/builder.rs similarity index 50% rename from src/builder.rs rename to src/mir/builder.rs index 8f0965b..fbe14fd 100644 --- a/src/builder.rs +++ b/src/mir/builder.rs @@ -1,20 +1,20 @@ use std::{cell::RefCell, fmt::Display, rc::Rc}; -use crate::ir::{Block, Expr, FuncD, FuncK, Instr, Var}; +use crate::mir::{Block, Expr, FuncD, FuncK, Instr, Var}; #[derive(Default)] struct RegistryInner { - vars: u16, + next: u16, free: Vec, } #[derive(Default, Clone)] -struct Registry { +pub struct Registry { inner: Rc>, } impl Registry { - fn var(&self) -> UninitVar { + pub fn var(&self) -> UninitVar { let mut inner = self.inner.borrow_mut(); if let Some(var) = inner.free.pop() { @@ -23,8 +23,8 @@ impl Registry { reg: self.clone(), } } else { - let var = inner.vars; - inner.vars += 1; + let var = inner.next; + inner.next += 1; UninitVar { var, reg: self.clone(), @@ -93,18 +93,12 @@ pub struct Builder { out: Vec, } -impl Display for Builder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for instr in &self.out { - write!(f, "{instr}")?; - } - - Ok(()) +impl Builder { + pub fn build(self) -> Block { + Block { instrs: self.out } } -} -impl Builder { - pub fn block(&mut self, block: impl FnOnce(&mut Builder)) -> Block { + pub fn block(&mut self, block: impl FnOnce(&mut Self)) -> Block { let start = self.out.len(); block(self); @@ -119,6 +113,7 @@ impl Builder { pub fn stmt(&mut self, stmt: impl ToString) { let stmt = stmt.to_string(); + self.out.push(Instr::Stmt { stmt }); } @@ -126,7 +121,7 @@ impl Builder { let expr = expr.into(); let msg = msg.to_string(); - self.out.push(Instr::Assert { expr, msg }) + self.out.push(Instr::Assert { expr, msg }); } pub fn alloc_k(&mut self, size: u32) { @@ -134,25 +129,24 @@ impl Builder { } pub fn alloc_d(&mut self, size: impl Into) { - self.out.push(Instr::AllocD { size: size.into() }); + let size = size.into(); + + self.out.push(Instr::AllocD { size }); } pub fn reserve_k(&mut self, size: u32) -> InitVar { - let var = self.reg.var().init(); - - self.out.push(Instr::ReserveK { - into: Var::from(&var), - size, - }); + let var = self.var().init(); + let into = Var::from(&var); + self.out.push(Instr::ReserveK { into, size }); var } pub fn write_k(&mut self, func: impl Into, expr: impl Into) { - self.out.push(Instr::WriteK { - func: func.into(), - expr: expr.into(), - }); + let func = func.into(); + let expr = expr.into(); + + self.out.push(Instr::WriteK { func, expr }); } pub fn write_d( @@ -161,11 +155,11 @@ impl Builder { expr: impl Into, size: impl Into, ) { - self.out.push(Instr::WriteD { - func: func.into(), - expr: expr.into(), - size: size.into(), - }); + let func = func.into(); + let expr = expr.into(); + let size = size.into(); + + self.out.push(Instr::WriteD { func, expr, size }); } pub fn write_reserved_k( @@ -174,92 +168,92 @@ impl Builder { at: impl Into, expr: impl Into, ) { - self.out.push(Instr::WriteReservedK { - func: func.into(), - at: at.into(), - expr: expr.into(), - }); + let func = func.into(); + let at = at.into(); + let expr = expr.into(); + + self.out.push(Instr::WriteReservedK { func, at, expr }); } pub fn read_k(&mut self, func: impl Into) -> InitVar { - let var = self.reg.var().init(); - - self.out.push(Instr::ReadK { - into: Var::from(&var), - func: func.into(), - }); + let var = self.var().init(); + let into = Var::from(&var); + let func = func.into(); + self.out.push(Instr::ReadK { into, func }); var } pub fn read_d(&mut self, func: impl Into, size: impl Into) -> InitVar { - let var = self.reg.var().init(); - - self.out.push(Instr::ReadD { - into: Var::from(&var), - func: func.into(), - size: size.into(), - }); + let var = self.var().init(); + let into = Var::from(&var); + let func = func.into(); + let size = size.into(); + self.out.push(Instr::ReadD { into, func, size }); var } pub fn expr(&mut self, expr: impl Into) -> InitVar { - let var = self.reg.var().init(); - - self.out.push(Instr::Expr { - into: Var::from(&var), - expr: expr.into(), - }); + let var = self.var().init(); + let into = Var::from(&var); + let expr = expr.into(); + self.out.push(Instr::Expr { into, expr }); var } - pub fn assign(&mut self, var: &InitVar, expr: impl Into) { - self.out.push(Instr::Assign { - into: Var::from(var), - expr: expr.into(), - }); + pub fn assign(&mut self, into: &InitVar, expr: impl Into) { + let expr = expr.into(); + let into = Var::from(into); + + self.out.push(Instr::Assign { into, expr }) } - pub fn assign_index(&mut self, var: &InitVar, index: impl Into, expr: impl Into) { - self.out.push(Instr::AssignIndex { - into: Var::from(var), - index: index.into(), - expr: expr.into(), - }); + pub fn assign_index(&mut self, into: &InitVar, index: impl Into, expr: impl Into) { + let into = Var::from(into); + let index = index.into(); + let expr = expr.into(); + + self.out.push(Instr::AssignIndex { into, index, expr }); } - pub fn iter_range( + pub fn for_range( &mut self, start: impl Into, end: impl Into, - block: impl FnOnce(&mut Builder, &InitVar), + block: impl FnOnce(&mut Self, &InitVar), ) { - let var = self.reg.var().init(); + let var = self.var().init(); let block = self.block(|b| block(b, &var)); - self.out.push(Instr::IterRange { - into: Var::from(&var), - start: start.into(), - end: end.into(), + let into = Var::from(&var); + let start = start.into(); + let end = end.into(); + + self.out.push(Instr::ForRange { + into, + start, + end, block, }); } - pub fn iter_map( + pub fn for_table( &mut self, - map: impl Into, - block: impl FnOnce(&mut Builder, &InitVar, &InitVar), + table: impl Into, + block: impl FnOnce(&mut Self, &InitVar, &InitVar), ) { - let index = self.reg.var().init(); - let value = self.reg.var().init(); + let index = self.var().init(); + let value = self.var().init(); let block = self.block(|b| block(b, &index, &value)); - self.out.push(Instr::IterMap { + let table = table.into(); + + self.out.push(Instr::ForTable { index: Var::from(&index), value: Var::from(&value), - map: map.into(), + table, block, }); } @@ -267,59 +261,50 @@ impl Builder { pub fn branch( &mut self, cond: impl Into, - then_block: impl FnOnce(&mut Builder), - else_block: impl FnOnce(&mut Builder), + then_block: impl FnOnce(&mut Self), + else_block: impl FnOnce(&mut Self), ) { + let cond = cond.into(); let then_block = self.block(then_block); let else_block = self.block(else_block); self.out.push(Instr::Branch { - cond: cond.into(), + cond, then_block, else_block, }); } - pub fn local_function( + pub fn function( &mut self, - block: impl FnOnce(&mut Builder, &[InitVar; ARGS]), + block: impl FnOnce(&mut Self, &[InitVar; ARGS]), ) -> InitVar { - let into = self.reg.var().init(); - let args: [_; ARGS] = std::array::from_fn(|_| self.reg.var().init()); - let block = self.block(|b| block(b, &args)); + let into = self.var().init(); + let vars: [_; ARGS] = std::array::from_fn(|_| self.var().init()); + let body = self.block(|b| block(b, &vars)); + let args = vars.iter().map(Var::from).collect(); - self.out.push(Instr::LocalFunction { + self.out.push(Instr::Function { into: Var::from(&into), - args: args.iter().map(Var::from).collect(), - body: block, + args, + body, }); into } - pub fn export_table(&mut self, path: impl ToString) { - self.out.push(Instr::ExportTable { - path: path.to_string(), - }); - } - - pub fn export_function( - &mut self, - path: impl ToString, - args: Vec, - rets: Vec, - block: impl FnOnce(&mut Builder, &[InitVar]), - ) { - let path = path.to_string(); - let vars = args.iter().map(|_| self.var().init()).collect::>(); + pub fn function_n(&mut self, n: usize, block: impl FnOnce(&mut Self, &[InitVar])) -> InitVar { + let into = self.var().init(); + let vars = Vec::from_iter((0..n).map(|_| self.var().init())); let body = self.block(|b| block(b, &vars)); - let args = vars.iter().map(Var::from).zip(args).collect::>(); + let args = vars.iter().map(Var::from).collect(); - self.out.push(Instr::ExportFunction { - path, + self.out.push(Instr::Function { + into: Var::from(&into), args, - rets, body, }); + + into } } diff --git a/src/mir/client/iter.rs b/src/mir/client/iter.rs new file mode 100644 index 0000000..455ac94 --- /dev/null +++ b/src/mir/client/iter.rs @@ -0,0 +1,117 @@ +use crate::{ + hir::Event, + mir::{ + Expr, + builder::{Builder, InitVar}, + client::Client, + serdes::Serdes, + }, +}; + +impl Client { + pub fn event_recv_iter(&self, b: &mut Builder, event: &Event) { + match event.data.len() { + 0 => self.event_recv_iter_0data(b, event), + 1 => self.event_recv_iter_1data(b, event), + _ => self.event_recv_iter_ndata(b, event), + } + } + + fn event_recv_iter_ndata(&self, b: &mut Builder, event: &Event) { + let queue = self.event_recv_queue(b, event); + let n = event.data.len(); + + let iter = b.function(|b, []| { + let captured = b.expr(queue.expr()); + b.assign(&queue, Expr::Table(vec![])); + + let next = b.function(|b, [captured, i]| { + b.branch( + captured.expr().index(i), + |b| { + let rets = (0..n) + .map(|j| format!(", {captured}[{i} + {j}]")) + .collect::(); + + b.stmt(format!("return {i} + {n}{rets}")); + }, + |_| {}, + ); + }); + + b.stmt(format!("return {next}, {captured}, 1")); + }); + + self.export(b, "iter", &iter); + } + + fn event_recv_iter_1data(&self, b: &mut Builder, event: &Event) { + let queue = self.event_recv_queue(b, event); + + let iter = b.function(|b, []| { + let captured = b.expr(queue.expr()); + b.assign(&queue, Expr::Table(vec![])); + b.stmt(format!("return {captured}")); + }); + + self.export(b, "iter", &iter); + } + + fn event_recv_iter_0data(&self, b: &mut Builder, event: &Event) { + let counter = self.event_recv_counter(b, event); + + let iter = b.function(|b, []| { + let captured = b.expr(counter.expr()); + b.assign(&counter, 0); + + let next = b.function(|b, [captured, i]| { + b.branch( + i.expr().lt(captured), + |b| { + b.stmt(format!("return {i} + 1")); + }, + |_| {}, + ); + }); + + b.stmt(format!("return {next}, {captured}, 0")); + }); + + self.export(b, "iter", &iter); + } + + fn event_recv_queue(&self, b: &mut Builder, event: &Event) -> InitVar { + let remote = self.remote(b, event); + let queue = b.expr(Expr::Table(vec![])); + + let des = event + .data + .iter() + .map(|ty| Box::new(ty.des(b, &self.des))) + .collect::>(); + + let on_event = b.function(|b, [buf]| { + b.stmt(format!("local buf, pos = {buf}, pos")); + + for des in des { + let value = des(b); + b.stmt(format!("table.insert({queue}, {value})")); + } + }); + + b.stmt(format!("{remote}.OnClientEvent:Connect({on_event})")); + queue + } + + fn event_recv_counter(&self, b: &mut Builder, event: &Event) -> InitVar { + let remote = self.remote(b, event); + let counter = b.expr(0); + + let on_event = b.function(|b, []| { + b.assign(&counter, counter.expr().add(1)); + }); + + b.stmt(format!("{remote}.OnClientEvent:Connect({on_event})")); + counter + } +} diff --git a/src/mir/client/mod.rs b/src/mir/client/mod.rs new file mode 100644 index 0000000..346ddd7 --- /dev/null +++ b/src/mir/client/mod.rs @@ -0,0 +1,82 @@ +use crate::{ + hir::{Event, Item, Type}, + mir::{ + Expr, + builder::{Builder, InitVar}, + serdes::{Des, Ser, Serdes}, + }, + shared::{ApiCheck, NetworkSide}, +}; + +mod iter; +mod send; + +#[derive(Clone)] +pub struct Client { + pub location: String, + + pub ser: Ser, + pub des: Des, +} + +impl Client { + pub fn new(apicheck: ApiCheck) -> Self { + let location = "result".to_string(); + let ser = Ser { + apicheck, + native: false, + }; + let des = Des { + apicheck, + native: false, + check: false, + }; + + Client { location, ser, des } + } + + pub fn item(&self, b: &mut Builder, item: &Item) { + match item { + Item::Table(table) => self.table(b, table), + Item::Event(event) => self.event(b, event), + } + } + + fn table(&self, b: &mut Builder, table: &[(String, Item)]) { + for (name, item) in table { + self.export(b, name, Expr::Table(vec![])); + self.location(name).item(b, item); + } + } + + fn event(&self, b: &mut Builder, event: &Event) { + match event.from { + NetworkSide::Server => self.event_recv_iter(b, event), + NetworkSide::Client => self.event_send(b, event), + } + } + + fn location(&self, name: &str) -> Self { + Client { + location: self.location.clone() + "." + name, + + ser: self.ser.clone(), + des: self.des.clone(), + } + } + + fn export(&self, b: &mut Builder, name: &str, expr: impl Into) { + let expr = expr.into(); + let location = &self.location; + b.stmt(format!("{location}.{name} = {expr}")); + } + + fn remote(&self, b: &mut Builder, event: &Event) -> InitVar { + let var = b.var().init(); + let uuid = event.uuid; + + b.stmt(format!("local {var} = folder:WaitForChild('{uuid}')")); + + var + } +} diff --git a/src/mir/client/send.rs b/src/mir/client/send.rs new file mode 100644 index 0000000..a8d4b78 --- /dev/null +++ b/src/mir/client/send.rs @@ -0,0 +1,51 @@ +use crate::{ + hir::Event, + mir::{builder::Builder, client::Client, serdes::Serdes}, +}; + +impl Client { + pub fn event_send(&self, b: &mut Builder, event: &Event) { + match event.data.len() { + 0 => self.event_send_0data(b, event), + _ => self.event_send_ndata(b, event), + } + } + + pub fn event_send_ndata(&self, b: &mut Builder, event: &Event) { + let remote = self.remote(b, event); + let n = event.data.len(); + + let ser = event + .data + .iter() + .map(|ty| ty.ser(b, &self.ser)) + .collect::>(); + + let send = b.function_n(n, |b, args| { + b.stmt("pos = 0"); + + for i in 0..n { + let arg = &args[i]; + let ser = &ser[i]; + + ser(b, arg.expr()); + } + + b.stmt("local out = buffer.create(pos)"); + b.stmt("buffer.copy(out, 0, buf, 0, pos)"); + b.stmt(format!("{remote}:FireServer(out)")); + }); + + self.export(b, "send", &send); + } + + pub fn event_send_0data(&self, b: &mut Builder, event: &Event) { + let remote = self.remote(b, event); + + let send = b.function(|b, []| { + b.stmt(format!("{remote}:FireServer()")); + }); + + self.export(b, "send", &send); + } +} diff --git a/src/ir.rs b/src/mir/mod.rs similarity index 55% rename from src/ir.rs rename to src/mir/mod.rs index 5450174..c10efee 100644 --- a/src/ir.rs +++ b/src/mir/mod.rs @@ -1,15 +1,63 @@ use std::fmt::Display; -use crate::types::NumberKind; +use crate::{ + hir, + shared::{ApiCheck, NumberKind}, +}; -#[derive(Debug, Clone)] +mod builder; +mod client; +mod serdes; +mod server; + +pub fn server(items: &hir::Item) -> Result { + use std::fmt::Write; + + let mut b = builder::Builder::default(); + let server = server::Server::new(ApiCheck::Full); + + server.item(&mut b, items); + + let mut s = String::new(); + + writeln!(s, "{}", include_str!("../header.luau"))?; + writeln!(s, "local result = {{}}")?; + writeln!(s, "local folder = Instance.new('Folder')")?; + writeln!(s, "folder.Name = 'Z'")?; + writeln!(s, "folder.Parent = game.ReplicatedStorage")?; + writeln!(s, "{}", b.build())?; + writeln!(s, "return result")?; + + Ok(s) +} + +pub fn client(items: &hir::Item) -> Result { + use std::fmt::Write; + + let mut b = builder::Builder::default(); + let client = client::Client::new(ApiCheck::Full); + + client.item(&mut b, items); + + let mut s = String::new(); + + writeln!(s, "{}", include_str!("../header.luau"))?; + writeln!(s, "local result = {{}}")?; + writeln!(s, "local folder = game.ReplicatedStorage:WaitForChild('Z')")?; + writeln!(s, "{}", b.build())?; + writeln!(s, "return result")?; + + Ok(s) +} + +#[derive(Clone)] pub struct Block { pub instrs: Vec, } impl Display for Block { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for instr in &self.instrs { + for instr in self.instrs.iter() { write!(f, "{instr}")?; } @@ -17,69 +65,60 @@ impl Display for Block { } } -#[derive(Debug, Clone)] +#[derive(Clone)] pub enum Instr { - /// An arbitrary statement. + /// An arbitrary Luau statement. Stmt { stmt: String }, - /// Assert that the given expression is truthy. If it is not, an error will - /// be thrown. + /// Assert that the given expression is truthy. Assert { expr: Expr, msg: String }, - /// Allocate a fixed-size number of bytes. This should be used instead of - /// `AllocD` when the size is known at compile time as it allows for the - /// compiler to further optimize when the allocation takes place. + /// Allocate a fixed-size number of bytes. AllocK { size: u32 }, - /// Allocate a dynamic number of bytes. This should be used when the size is - /// dependent on runtime values, such as the length of an array. + /// Allocate a dynamic number of bytes. AllocD { size: Expr }, - /// Reserve a fixed-size number of bytes. Normally `WriteK` or `WriteD` will - /// reserve and write to some memory, but this instruction can be used to - /// reserve space without writing to it. This is useful for maps, where the - /// length cannot be determined until the map is fully iterated, but the - /// length must be written ahead of the values so that it can be decoded. + /// Reserve a fixed-size number of bytes. ReserveK { into: Var, size: u32 }, - /// Write a value whose size is known at compile time. + /// Write a value of a fixed size. WriteK { func: FuncK, expr: Expr }, - /// Write a value whose size is not known at compile time. + /// Write a value of a dynamic size. WriteD { func: FuncD, expr: Expr, size: Expr }, - /// Write a value whose size is known at compile time to a reserved - /// location. + /// Write a value of a fixed size to a reserved location. WriteReservedK { func: FuncK, at: Expr, expr: Expr }, - /// Read a value whose size is known at compile time. + /// Initialize a variable by reading a value of a fixed size. ReadK { into: Var, func: FuncK }, - /// Read a value whose size is not known at compile time. + /// Initialize a variable by reading a value of a dynamic size. ReadD { into: Var, func: FuncD, size: Expr }, /// Initialize a variable with an expression. Expr { into: Var, expr: Expr }, - /// Assign to a variable. + /// Assign a value to a variable. Assign { into: Var, expr: Expr }, - /// Assign to the index of a variable. + /// Assign a value to the index of a variable. AssignIndex { into: Var, index: Expr, expr: Expr }, - /// Iterate over a range. - IterRange { + /// Iterate over an integer range. + ForRange { into: Var, start: Expr, end: Expr, block: Block, }, - /// Iterate over a map. - IterMap { + /// Iterate over the indices and values of a table. + ForTable { index: Var, value: Var, - map: Expr, + table: Expr, block: Block, }, @@ -90,43 +129,24 @@ pub enum Instr { else_block: Block, }, - /// Declares a local function. - LocalFunction { + /// Declare a function and initialize the variable with it. + Function { into: Var, args: Vec, body: Block, }, - - /// Declares an exported table. - ExportTable { path: String }, - - /// Declares an exported function. - ExportFunction { - path: String, - args: Vec<(Var, String)>, - rets: Vec, - body: Block, - }, } impl Display for Instr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Instr::Stmt { stmt } => { - write!(f, "{stmt};")?; - } + Instr::Stmt { stmt } => write!(f, "{stmt};")?, - Instr::Assert { expr, msg } => { - write!(f, "if not {expr} then error(\"{msg}\") end;")?; - } + Instr::Assert { expr, msg } => write!(f, "if not {expr} then error(\"{msg}\") end;")?, - Instr::AllocK { size } => { - write!(f, "if pos + {size} > len then resize({size}) end;")?; - } + Instr::AllocK { size } => write!(f, "if pos + {size} > len then resize({size}) end;")?, - Instr::AllocD { size } => { - write!(f, "if pos + {size} > len then resize({size}) end;")?; - } + Instr::AllocD { size } => write!(f, "if pos + {size} > len then resize({size}) end;")?, Instr::ReserveK { into, size } => { write!(f, "local {into} = pos;")?; @@ -139,12 +159,14 @@ impl Display for Instr { } Instr::WriteD { func, expr, size } => { - write!(f, "buffer.write{func}(buf, pos, {expr}, {size});")?; - write!(f, "pos += {size};")?; + write!(f, "local size = {size};")?; + write!(f, "buffer.write{func}(buf, pos, {expr}, size);")?; + write!(f, "pos += size;")?; } Instr::WriteReservedK { func, at, expr } => { write!(f, "buffer.write{func}(buf, {at}, {expr});")?; + write!(f, "pos += {};", func.size())?; } Instr::ReadK { into, func } => { @@ -153,89 +175,52 @@ impl Display for Instr { } Instr::ReadD { into, func, size } => { - write!(f, "local {into} = buffer.read{func}(buf, pos, {size});")?; - write!(f, "pos += {size};")?; + write!(f, "local size = {size};")?; + write!(f, "local {into} = buffer.read{func}(buf, pos, size);")?; + write!(f, "pos += size;")?; } - Instr::Expr { into, expr } => { - write!(f, "local {into} = {expr};")?; - } + Instr::Expr { into, expr } => write!(f, "local {into} = {expr};")?, - Instr::Assign { into, expr } => { - write!(f, "{into} = {expr};")?; - } + Instr::Assign { into: var, expr } => write!(f, "{var} = {expr};")?, - Instr::AssignIndex { into, index, expr } => { - write!(f, "{into}[{index}] = {expr};")?; - } + Instr::AssignIndex { into, index, expr } => write!(f, "{into}[{index}] = {expr};")?, - Instr::IterRange { + Instr::ForRange { into, start, end, block, - } => { - write!(f, "for {into} = {start}, {end} do {block} end;")?; - } + } => write!(f, "for {into} = {start}, {end} do {block} end;")?, - Instr::IterMap { + Instr::ForTable { index, value, - map, + table, block, - } => { - write!(f, "for {index}, {value} in {map} do {block} end;")?; - } + } => write!(f, "for {index}, {value} in {table} do {block} end;")?, Instr::Branch { cond, then_block, else_block, - } => { - write!(f, "if {cond} then {then_block} else {else_block} end;")?; - } + } => write!(f, "if {cond} then {then_block} else {else_block} end;")?, - Instr::LocalFunction { into, args, body } => { + Instr::Function { into, args, body } => { let args = args .iter() - .map(ToString::to_string) + .map(|arg| arg.to_string()) .collect::>() .join(", "); write!(f, "local function {into}({args}) {body} end;")?; } - - Instr::ExportTable { path } => { - write!(f, "result{path} = {{}};")?; - } - - Instr::ExportFunction { - path, - args, - rets, - body, - } => { - let args = args - .iter() - .map(|(var, ty)| format!("{var}: {ty}")) - .collect::>() - .join(", "); - - let rets = rets - .iter() - .map(ToString::to_string) - .collect::>() - .join(", "); - - write!(f, "function result{path}({args}): ({rets}) {body} end;")?; - } } Ok(()) } } -/// A writable type with a known size at compile time. #[derive(Debug, Clone, Copy)] pub enum FuncK { U8, @@ -296,7 +281,6 @@ impl Display for FuncK { } } -/// A writable type whose size is not known at compile time. #[derive(Debug, Clone, Copy)] pub enum FuncD { String, @@ -310,18 +294,14 @@ impl Display for FuncD { } } -/// An expression. -#[derive(Debug, Clone)] +#[derive(Clone)] pub enum Expr { - Named(String), - Boolean(bool), Number(f64), String(String), - Table, + Table(Vec<(Expr, Expr)>), Array(Box), - Struct(Vec<(String, Expr)>), Var(Var), @@ -347,6 +327,12 @@ impl From for Expr { } } +impl From for Expr { + fn from(value: u32) -> Self { + Expr::Number(value as f64) + } +} + impl From for Expr { fn from(value: String) -> Self { Expr::String(value) @@ -370,8 +356,8 @@ impl Expr { Expr::Index(Box::new(self), Box::new(index.into())) } - pub fn and(self, other: impl Into) -> Self { - Expr::Binary(Box::new(self), BinaryOp::And, Box::new(other.into())) + pub fn and(self, rhs: impl Into) -> Self { + Expr::Binary(Box::new(self), BinaryOp::And, Box::new(rhs.into())) } pub fn add(self, rhs: impl Into) -> Self { @@ -405,57 +391,49 @@ impl Expr { pub fn len(self) -> Self { Expr::Unary(UnaryOp::Len, Box::new(self)) } - - pub fn ty(self) -> Self { - Expr::Type(Box::new(self)) - } - - pub fn utf8(self) -> Self { - Expr::Utf8(Box::new(self)) - } } impl Display for Expr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Named(name) => write!(f, "{name}"), - - Self::Boolean(b) => write!(f, "{b}"), - Self::Number(n) => write!(f, "{n}"), - Self::String(s) => write!(f, "\"{}\"", s.escape_default()), + Expr::Boolean(b) => write!(f, "{b}"), + Expr::Number(n) => write!(f, "{n}"), + Expr::String(s) => write!(f, "\"{}\"", s.escape_default()), - Self::Table => write!(f, "{{}}"), - Self::Array(expr) if matches!(**expr, Expr::Number(0.0)) => write!(f, "{{}}"), - Self::Array(expr) => write!(f, "table.create({expr})"), - Self::Struct(fields) => { - write!(f, "{{ ")?; - - for (name, expr) in fields { - write!(f, "{name} = {expr}, ")?; - } + Expr::Table(fields) => { + let fields = fields + .iter() + .map(|(i, v)| format!("{i} = {v}")) + .collect::>() + .join(", "); - write!(f, "}}") + write!(f, "{{ {fields} }}") } - Self::Var(v) => write!(f, "{v}"), + Expr::Array(expr) if matches!(**expr, Expr::Number(0.0)) => write!(f, "{{}}"), + Expr::Array(expr) => write!(f, "table.create({expr})"), - Self::Index(expr, index) => write!(f, "{expr}[{index}]"), + Expr::Var(v) => write!(f, "{v}"), - Self::Binary(lhs, op, rhs) => write!(f, "({lhs} {op} {rhs})"), - Self::Unary(op, expr) => write!(f, "({op}{expr})"), + Expr::Index(expr, index) => write!(f, "{expr}[{index}]"), - Self::Vector(x, y, z) => write!(f, "vector.create({x}, {y}, {z})"), - Self::Type(expr) => write!(f, "type({expr})"), - Self::Utf8(expr) => write!(f, "utf8.len({expr})"), + Expr::Binary(lhs, op, rhs) => write!(f, "({lhs} {op} {rhs})"), + Expr::Unary(op, expr) => write!(f, "{op}{expr}"), + + Expr::Vector(x, y, z) => write!(f, "vector.create({x}, {y}, {z})"), + Expr::Type(expr) => write!(f, "type({expr})"), + Expr::Utf8(expr) => write!(f, "utf8.len({expr})"), } } } -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] pub enum BinaryOp { And, + Add, Mul, + Eq, Lt, Gt, @@ -466,19 +444,19 @@ pub enum BinaryOp { impl Display for BinaryOp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::And => write!(f, "and"), - Self::Add => write!(f, "+"), - Self::Mul => write!(f, "*"), - Self::Eq => write!(f, "=="), - Self::Lt => write!(f, "<"), - Self::Gt => write!(f, ">"), - Self::Le => write!(f, "<="), - Self::Ge => write!(f, ">="), + BinaryOp::And => write!(f, "and"), + BinaryOp::Add => write!(f, "+"), + BinaryOp::Mul => write!(f, "*"), + BinaryOp::Eq => write!(f, "=="), + BinaryOp::Lt => write!(f, "<"), + BinaryOp::Gt => write!(f, ">"), + BinaryOp::Le => write!(f, "<="), + BinaryOp::Ge => write!(f, ">="), } } } -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] pub enum UnaryOp { Len, } @@ -486,12 +464,12 @@ pub enum UnaryOp { impl Display for UnaryOp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Len => write!(f, "#"), + UnaryOp::Len => write!(f, "#"), } } } -#[derive(Debug, Clone)] +#[derive(Clone, Copy)] pub struct Var(pub u16); impl Display for Var { diff --git a/src/mir/serdes.rs b/src/mir/serdes.rs new file mode 100644 index 0000000..65672a5 --- /dev/null +++ b/src/mir/serdes.rs @@ -0,0 +1,535 @@ +use crate::{ + hir, + mir::{ + Expr, FuncD, + builder::{Builder, InitVar}, + }, + shared::{ApiCheck, NumberKind, Range}, +}; + +fn check_type(b: &mut Builder, expr: impl Into, ty: &'static str) { + b.assert( + Expr::Type(Box::new(expr.into())).eq(ty), + format!("expected {ty}"), + ); +} + +fn check_range(b: &mut Builder, expr: impl Into, range: Range) { + let expr = expr.into(); + + if let Some(exact) = range.exact() { + b.assert(expr.clone().eq(exact), format!("not equal to {exact}")); + } else { + match (range.min, range.max) { + (Some(min), Some(max)) => b.assert( + expr.clone().ge(min).and(expr.clone().le(max)), + format!("value out of range [{min}, {max}]"), + ), + + (Some(min), None) => b.assert(expr.clone().ge(min), format!("value less than {min}")), + + (None, Some(max)) => { + b.assert(expr.clone().le(max), format!("value greater than {max}")) + } + + (None, None) => {} + } + } +} + +fn check_utf8(b: &mut Builder, expr: impl Into) { + b.assert(Expr::Utf8(Box::new(expr.into())), "not a valid utf8 string"); +} + +#[derive(Clone)] +pub struct Ser { + pub apicheck: ApiCheck, + pub native: bool, +} + +#[derive(Clone)] +pub struct Des { + pub apicheck: ApiCheck, + pub native: bool, + pub check: bool, +} + +macro_rules! apicheck_some { + ($serdes:expr, $block:block) => { + if matches!($serdes.apicheck, ApiCheck::Some | ApiCheck::Full) { + $block + } + }; + + ($serdes:expr, $stmt:stmt) => { + if matches!($serdes.apicheck, ApiCheck::Some | ApiCheck::Full) { + $stmt + } + }; +} + +macro_rules! apicheck_full { + ($serdes:expr, $block:block) => { + if matches!($serdes.apicheck, ApiCheck::Full) { + $block + } + }; + + ($serdes:expr, $stmt:stmt) => { + if matches!($serdes.apicheck, ApiCheck::Full) { + $stmt + } + }; +} + +macro_rules! check { + ($serdes:expr, $block:block) => { + if $serdes.check { + $block + } + }; + + ($serdes:expr, $stmt:stmt) => { + if $serdes.check { + $stmt + } + }; +} + +pub trait Serdes { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + b: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser, Self> + 'ty; + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + b: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des, Self> + 'ty; +} + +impl Serdes for hir::Type { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + b: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> + 'ty { + #[allow(clippy::type_complexity)] + let cb: Box = match self { + hir::Type::Number(ty) => Box::new(ty.ser(b, ser)), + hir::Type::Vector(ty) => Box::new(ty.ser(b, ser)), + hir::Type::BinaryString(ty) => Box::new(ty.ser(b, ser)), + hir::Type::Utf8String(ty) => Box::new(ty.ser(b, ser)), + hir::Type::Array(ty) => Box::new(ty.ser(b, ser)), + hir::Type::Set(ty) => Box::new(ty.ser(b, ser)), + hir::Type::Map(ty) => Box::new(ty.ser(b, ser)), + hir::Type::Struct(ty) => Box::new(ty.ser(b, ser)), + }; + + move |b: &mut Builder, from: Expr| { + cb(b, from); + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + b: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> + 'ty { + let cb: Box InitVar> = match self { + hir::Type::Number(ty) => Box::new(ty.des(b, des)), + hir::Type::Vector(ty) => Box::new(ty.des(b, des)), + hir::Type::BinaryString(ty) => Box::new(ty.des(b, des)), + hir::Type::Utf8String(ty) => Box::new(ty.des(b, des)), + hir::Type::Array(ty) => Box::new(ty.des(b, des)), + hir::Type::Set(ty) => Box::new(ty.des(b, des)), + hir::Type::Map(ty) => Box::new(ty.des(b, des)), + hir::Type::Struct(ty) => Box::new(ty.des(b, des)), + }; + + move |b: &mut Builder| cb(b) + } +} + +impl Serdes for hir::NumberType { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + _: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> + 'ty { + move |b: &mut Builder, from: Expr| { + apicheck_full!(ser, check_type(b, from.clone(), "number")); + apicheck_some!(ser, check_range(b, from.clone(), self.range)); + + b.alloc_k(self.kind.size()); + b.write_k(self.kind, from); + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + _: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> { + move |b: &mut Builder| { + let value = b.read_k(self.kind); + check!(des, check_range(b, &value, self.range)); + + if !matches!(self.kind, NumberKind::NaNF32 | NumberKind::NaNF64) { + check!(des, b.assert(value.expr().eq(&value), "value is nan")); + } + + value + } + } +} + +impl Serdes for hir::VectorType { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + b: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> + 'ty { + let x = self.x.ser(b, ser); + let y = self.y.ser(b, ser); + let z = self.z.as_ref().map(|z| z.ser(b, ser)); + + move |b: &mut Builder, from: Expr| { + apicheck_full!(ser, check_type(b, from.clone(), "vector")); + + x(b, from.clone().index("x")); + y(b, from.clone().index("y")); + + if let Some(z) = &z { + z(b, from.clone().index("z")); + } + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + b: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> { + let x = self.x.des(b, des); + let y = self.y.des(b, des); + let z = self.z.as_ref().map(|z| z.des(b, des)); + + move |b: &mut Builder| { + let x = x(b); + let y = y(b); + let z = z.as_ref().map(|z| z(b)); + + b.expr(Expr::Vector( + Box::new(x.expr()), + Box::new(y.expr()), + Box::new(z.map_or(Expr::Number(0f64), |z| z.expr())), + )) + } + } +} + +impl Serdes for hir::BinaryStringType { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + _: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> + 'ty { + move |b: &mut Builder, from: Expr| { + apicheck_full!(ser, check_type(b, from.clone(), "string")); + + let len = self.len.ser_obj(b, ser, from.clone()); + if let Some(exact) = self.len.exact() { + b.alloc_k(exact); + b.write_d(FuncD::String, from, exact); + } else { + b.alloc_d(&len); + b.write_d(FuncD::String, from, &len); + } + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + _: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> { + move |b: &mut Builder| { + let len = self.len.des(b, des); + b.read_d(FuncD::String, &len) + } + } +} + +impl Serdes for hir::Utf8StringType { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + _: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> + 'ty { + move |b: &mut Builder, from: Expr| { + apicheck_full!(ser, check_type(b, from.clone(), "string")); + apicheck_some!(ser, check_utf8(b, from.clone())); + + let len = self.len.ser_obj(b, ser, from.clone()); + if let Some(exact) = self.len.exact() { + b.alloc_k(exact); + b.write_d(FuncD::String, from, exact); + } else { + b.alloc_d(&len); + b.write_d(FuncD::String, from, &len); + } + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + _: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> { + move |b: &mut Builder| { + let len = self.len.des(b, des); + let str = b.read_d(FuncD::String, &len); + check!(des, check_utf8(b, &str)); + + str + } + } +} + +impl Serdes for hir::ArrayType { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + b: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> + 'ty { + let item = self.item.ser(b, ser); + + move |b: &mut Builder, from: Expr| { + apicheck_full!(ser, check_type(b, from.clone(), "table")); + let len = self.len.ser_obj(b, ser, from.clone()); + + b.for_range(1, &len, |b, i| { + let value = b.expr(from.clone().index(i)); + item(b, value.expr()); + }); + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + b: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> { + let item = self.item.des(b, des); + + move |b: &mut Builder| { + let len = self.len.des(b, des); + let tbl = b.expr(if let Some(exact) = self.len.exact() { + Expr::Array(Box::new(Expr::Number(exact as f64))) + } else { + Expr::Array(Box::new(len.expr())) + }); + + b.for_range(1, &len, |b, i| { + let value = item(b); + b.assign_index(&tbl, i, &value); + }); + + tbl + } + } +} + +impl Serdes for hir::SetType { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + b: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> + 'ty { + let item = self.item.ser(b, ser); + + move |b: &mut Builder, from: Expr| { + apicheck_full!(ser, check_type(b, from.clone(), "table")); + + b.alloc_k(self.len.kind().size()); + let loc = b.reserve_k(self.len.kind().size()); + let len = b.expr(0); + + b.for_table(from, |b, index, _| { + item(b, index.expr()); + b.assign(&len, len.expr().add(1)); + }); + + self.len.ser_check(b, ser, &len); + b.write_reserved_k(self.len.kind(), &loc, &len); + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + b: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> { + let item = self.item.des(b, des); + + move |b: &mut Builder| { + let len = self.len.des(b, des); + let tbl = b.expr(Expr::Table(vec![])); + + b.for_range(1, &len, |b, _| { + let value = item(b); + b.assign_index(&tbl, &value, Expr::Boolean(true)); + }); + + tbl + } + } +} + +impl Serdes for hir::MapType { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + b: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> + 'ty { + let index = self.index.ser(b, ser); + let value = self.value.ser(b, ser); + + move |b: &mut Builder, from: Expr| { + apicheck_full!(ser, check_type(b, from.clone(), "table")); + + b.alloc_k(self.len.kind().size()); + let loc = b.reserve_k(self.len.kind().size()); + let len = b.expr(0); + + b.for_table(from, |b, key, val| { + index(b, key.expr()); + value(b, val.expr()); + b.assign(&len, len.expr().add(1)); + }); + + self.len.ser_check(b, ser, &len); + b.write_reserved_k(self.len.kind(), &loc, &len); + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + b: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> { + let index = self.index.des(b, des); + let value = self.value.des(b, des); + + move |b: &mut Builder| { + let len = self.len.des(b, des); + let tbl = b.expr(Expr::Table(vec![])); + + b.for_range(1, &len, |b, _| { + let key = index(b); + let val = value(b); + b.assign_index(&tbl, &key, &val); + }); + + tbl + } + } +} + +impl Serdes for hir::StructType { + fn ser<'ty, 'b, 'ser: 'ty>( + &'ty self, + b: &'b mut Builder, + ser: &'ser Ser, + ) -> impl Fn(&mut Builder, Expr) + use<'ty, 'ser> { + let fields = self + .fields + .iter() + .map(|(name, ty)| (name.as_str(), ty.ser(b, ser))) + .collect::>(); + + move |b: &mut Builder, from: Expr| { + apicheck_full!(ser, check_type(b, from.clone(), "table")); + + for (name, ser) in &fields { + let value = b.expr(from.clone().index(*name)); + ser(b, value.expr()); + } + } + } + + fn des<'ty, 'b, 'des: 'ty>( + &'ty self, + b: &'b mut Builder, + des: &'des Des, + ) -> impl Fn(&mut Builder) -> InitVar + use<'ty, 'des> { + let fields = self + .fields + .iter() + .map(|(name, ty)| (name.as_str(), ty.des(b, des))) + .collect::>(); + + move |b: &mut Builder| { + let mut values = Vec::new(); + + for (name, des) in &fields { + values.push((name.to_string(), des(b))); + } + + b.expr(Expr::Table( + values + .iter() + .map(|(name, value)| (Expr::from(name.as_str()), Expr::from(value))) + .collect(), + )) + } + } +} + +impl hir::Length { + fn ser_check(&self, b: &mut Builder, ser: &Ser, len: impl Into) { + if let Some(exact) = self.exact() { + apicheck_some!(ser, b.assert(len.into().eq(exact), "bad length")); + } else { + apicheck_some!(ser, check_range(b, len.into(), Range::from(*self))); + } + } + + fn ser_len(&self, b: &mut Builder, ser: &Ser, len: impl Into) { + if let Some(exact) = self.exact() { + self.ser_check(b, ser, exact); + } else { + let len = len.into(); + apicheck_some!(ser, check_range(b, len.clone(), Range::from(*self))); + + b.alloc_k(self.kind().size()); + b.write_k(self.kind(), len); + } + } + + fn ser_obj(&self, b: &mut Builder, ser: &Ser, obj: Expr) -> InitVar { + if let Some(exact) = self.exact() { + self.ser_len(b, ser, obj.len()); + b.expr(exact) + } else { + let len = b.expr(obj.len()); + self.ser_len(b, ser, &len); + + len + } + } + + fn des(&self, b: &mut Builder, des: &Des) -> InitVar { + if let Some(exact) = self.exact() { + b.expr(exact) + } else { + let len = b.read_k(self.kind()); + check!(des, check_range(b, &len, Range::from(*self))); + + len + } + } +} diff --git a/src/mir/server/iter.rs b/src/mir/server/iter.rs new file mode 100644 index 0000000..506fb52 --- /dev/null +++ b/src/mir/server/iter.rs @@ -0,0 +1,98 @@ +use crate::{ + hir::Event, + mir::{ + Expr, + builder::{Builder, InitVar}, + serdes::Serdes, + server::Server, + }, +}; + +impl Server { + pub fn event_recv_iter(&self, b: &mut Builder, event: &Event) { + match event.data.len() { + 0 => self.event_recv_iter_0data(b, event), + _ => self.event_recv_iter_ndata(b, event), + } + } + + fn event_recv_iter_ndata(&self, b: &mut Builder, event: &Event) { + let queue = self.event_recv_queue_ndata(b, event); + let n = event.data.len(); + + let iter = b.function(|b, []| { + let captured = b.expr(&queue); + b.assign(&queue, Expr::Table(vec![])); + + let next = b.function(|b, [captured, i]| { + b.branch( + captured.expr().index(i), + |b| { + let rets = (0..=n) + .map(|j| format!(", {captured}[{i} + {j}]")) + .collect::(); + + b.stmt(format!("return {i} + {n}{rets}")); + }, + |_| {}, + ) + }); + + b.stmt(format!("return {next}, {captured}, 1")); + }); + + self.export(b, "iter", &iter); + } + + fn event_recv_iter_0data(&self, b: &mut Builder, event: &Event) { + let queue = self.event_recv_queue_0data(b, event); + + let iter = b.function(|b, []| { + let captured = b.expr(&queue); + b.assign(&queue, Expr::Table(vec![])); + b.stmt(format!("return {captured}")); + }); + + self.export(b, "iter", &iter); + } + + fn event_recv_queue_ndata(&self, b: &mut Builder, event: &Event) -> InitVar { + let remote = self.remote(b, event); + let queue = b.expr(Expr::Table(vec![])); + + let des = event + .data + .iter() + .map(|ty| ty.des(b, &self.des)) + .collect::>(); + + let on_event = b.function(|b, [player, buf]| { + b.stmt(format!("local buf, pos = {buf}, 0")); + b.stmt(format!("table.insert({queue}, {player})")); + + for des in des { + let value = des(b); + b.stmt(format!("table.insert({queue}, {value})")); + } + }); + + b.stmt(format!( + "{remote}.OnServerEvent:Connect(function(player, buf) pcall({on_event}, player, buf) end)" + )); + + queue + } + + fn event_recv_queue_0data(&self, b: &mut Builder, event: &Event) -> InitVar { + let remote = self.remote(b, event); + let queue = b.expr(Expr::Table(vec![])); + + let on_event = b.function(|b, [player]| { + b.stmt(format!("table.insert({queue}, {player})")); + }); + + b.stmt(format!("{remote}.OnServerEvent:Connect({on_event})")); + + queue + } +} diff --git a/src/mir/server/mod.rs b/src/mir/server/mod.rs new file mode 100644 index 0000000..3972258 --- /dev/null +++ b/src/mir/server/mod.rs @@ -0,0 +1,83 @@ +use crate::{ + hir::{Event, Item}, + mir::{ + Expr, + builder::{Builder, InitVar}, + serdes::{Des, Ser}, + }, + shared::{ApiCheck, NetworkSide}, +}; + +mod iter; +mod send; + +#[derive(Clone)] +pub struct Server { + pub location: String, + + pub ser: Ser, + pub des: Des, +} + +impl Server { + pub fn new(apicheck: ApiCheck) -> Self { + let location = "result".to_string(); + let ser = Ser { + apicheck, + native: false, + }; + let des = Des { + apicheck, + native: false, + check: false, + }; + + Server { location, ser, des } + } + + pub fn item(&self, b: &mut Builder, item: &Item) { + match item { + Item::Table(table) => self.table(b, table), + Item::Event(event) => self.event(b, event), + } + } + + fn table(&self, b: &mut Builder, table: &[(String, Item)]) { + for (name, item) in table { + self.export(b, name, Expr::Table(vec![])); + self.location(name).item(b, item); + } + } + + fn event(&self, b: &mut Builder, event: &Event) { + match event.from { + NetworkSide::Server => self.event_send(b, event), + NetworkSide::Client => self.event_recv_iter(b, event), + } + } + + fn location(&self, name: &str) -> Self { + Server { + location: self.location.clone() + "." + name, + ser: self.ser.clone(), + des: self.des.clone(), + } + } + + fn export(&self, b: &mut Builder, name: &str, expr: impl Into) { + let expr = expr.into(); + let location = &self.location; + b.stmt(format!("{location}.{name} = {expr}")); + } + + fn remote(&self, b: &mut Builder, event: &Event) -> InitVar { + let var = b.var().init(); + let uuid = event.uuid; + + b.stmt(format!("local {var} = Instance.new('RemoteEvent')")); + b.stmt(format!("{var}.Name = '{uuid}'")); + b.stmt(format!("{var}.Parent = folder")); + + var + } +} diff --git a/src/mir/server/send.rs b/src/mir/server/send.rs new file mode 100644 index 0000000..caeefc3 --- /dev/null +++ b/src/mir/server/send.rs @@ -0,0 +1,52 @@ +use crate::{ + hir::Event, + mir::{builder::Builder, serdes::Serdes, server::Server}, +}; + +impl Server { + pub fn event_send(&self, b: &mut Builder, event: &Event) { + match event.data.len() { + 0 => self.event_send_0data(b, event), + _ => self.event_send_ndata(b, event), + } + } + + fn event_send_ndata(&self, b: &mut Builder, event: &Event) { + let remote = self.remote(b, event); + let n = event.data.len(); + + let ser = event + .data + .iter() + .map(|ty| ty.ser(b, &self.ser)) + .collect::>(); + + let send = b.function_n(n, |b, args| { + let player = &args[0]; + b.stmt("pos = 0"); + + for i in 0..n { + let arg = &args[i + 1]; + let ser = &ser[i]; + + ser(b, arg.expr()); + } + + b.stmt("local out = buffer.create(pos)"); + b.stmt("buffer.copy(out, 0, buf, 0, pos)"); + b.stmt(format!("{remote}:FireClient({player}, out)")); + }); + + self.export(b, "send", &send); + } + + fn event_send_0data(&self, b: &mut Builder, event: &Event) { + let remote = self.remote(b, event); + + let send = b.function(|b, [player]| { + b.stmt(format!("{remote}:FireClient({player})")); + }); + + self.export(b, "send", &send); + } +} diff --git a/src/net.luau b/src/net.luau index 8818095..b6a7b05 100644 --- a/src/net.luau +++ b/src/net.luau @@ -1,4 +1,4 @@ -local hello_world = zap.reliable("client", { zap.string.utf8() }) +local hello_world = zap.event("client", { zap.string.utf8() }) return { hello_world = hello_world, diff --git a/src/nums.rs b/src/nums.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/nums.rs @@ -0,0 +1 @@ + diff --git a/src/range.rs b/src/range.rs new file mode 100644 index 0000000..99a670c --- /dev/null +++ b/src/range.rs @@ -0,0 +1 @@ +use crate::nums::NumberKind; diff --git a/src/serdes.rs b/src/serdes.rs deleted file mode 100644 index 058687e..0000000 --- a/src/serdes.rs +++ /dev/null @@ -1,348 +0,0 @@ -use crate::{ - builder::{Builder, InitVar}, - ir::{Expr, FuncD}, - types::{NumberKind, Range, Type}, -}; - -fn check_type(b: &mut Builder, expr: impl Into, ty: &'static str) { - b.assert(expr.into().ty().eq(ty), format!("expected {ty}")); -} - -fn check_range(b: &mut Builder, expr: impl Into, range: &Range) { - let expr = expr.into(); - - if let Some(exact) = range.exact() { - b.assert(expr.clone().eq(exact), format!("not equal to {exact}")); - } else { - match (range.min, range.max) { - (Some(min), Some(max)) => b.assert( - expr.clone().ge(min).and(expr.clone().le(max)), - format!("value out of range [{min}, {max}]"), - ), - - (Some(min), None) => b.assert(expr.clone().ge(min), format!("value less than {min}")), - - (None, Some(max)) => { - b.assert(expr.clone().le(max), format!("value greater than {max}")) - } - - (None, None) => {} - } - } -} - -fn check_utf8(b: &mut Builder, expr: impl Into) { - b.assert(expr.into().utf8(), "not a valid utf8 string"); -} - -#[derive(Default, Debug, Clone, Copy)] -pub enum ApiCheck { - None, - Some, - - #[default] - Full, -} - -#[derive(Debug, Clone, Copy)] -pub struct Ser { - pub apicheck: ApiCheck, -} - -macro_rules! apicheck_some { - ($b:expr, $block:block) => { - if matches!($b.apicheck, ApiCheck::Some | ApiCheck::Full) { - $block - } - }; - - ($b:expr, $stmt:stmt) => { - if matches!($b.apicheck, ApiCheck::Some | ApiCheck::Full) { - $stmt - } - }; -} - -macro_rules! apicheck_full { - ($s:expr, $block:block) => { - if matches!($s.apicheck, ApiCheck::Full) { - $block - } - }; - - ($s:expr, $stmt:stmt) => { - if matches!($s.apicheck, ApiCheck::Full) { - $stmt - } - }; -} - -pub fn ser(s: Ser, b: &mut Builder, ty: Type, from: Expr) { - match ty { - Type::Number(ty) => { - apicheck_full!(s, check_type(b, from.clone(), "number")); - apicheck_some!(s, check_range(b, from.clone(), &ty.range)); - - if !matches!(ty.kind, NumberKind::NaNF32 | NumberKind::NaNF64) { - apicheck_some!(s, b.assert(from.clone().eq(from.clone()), "value is nan")) - } - - b.alloc_k(ty.kind.size()); - b.write_k(ty.kind, from); - } - - Type::Vector(ty) => { - apicheck_full!(s, check_type(b, from.clone(), "vector")); - - ser(s, b, Type::Number(ty.x), from.clone().index("x")); - ser(s, b, Type::Number(ty.y), from.clone().index("y")); - - if let Some(z) = ty.z { - ser(s, b, Type::Number(z), from.clone().index("z")); - } - } - - Type::BinaryString(ty) => { - apicheck_full!(s, check_type(b, from.clone(), "string")); - - if let Some(len) = ty.len.exact() { - apicheck_some!(s, check_range(b, from.clone().len(), &ty.len)); - - b.alloc_k(len as u32); - b.write_d(FuncD::String, from, len); - } else { - let len = b.expr(from.clone().len()); - apicheck_some!(s, check_range(b, &len, &ty.len)); - - b.alloc_k(ty.len.len_kind().size()); - b.write_k(ty.len.len_kind(), &len); - b.alloc_d(&len); - b.write_d(FuncD::String, from, &len); - } - } - - Type::Utf8String(ty) => { - apicheck_full!(s, check_type(b, from.clone(), "string")); - apicheck_some!(s, check_utf8(b, from.clone())); - - if let Some(len) = ty.len.exact() { - apicheck_some!(s, check_range(b, from.clone().len(), &ty.len)); - - b.alloc_k(len as u32); - b.write_d(FuncD::String, from, len); - } else { - let len = b.expr(from.clone().len()); - apicheck_some!(s, check_range(b, &len, &ty.len)); - - b.alloc_k(ty.len.len_kind().size()); - b.write_k(ty.len.len_kind(), &len); - b.alloc_d(&len); - b.write_d(FuncD::String, from, &len); - } - } - - Type::Array(ty) => { - apicheck_full!(s, check_type(b, from.clone(), "table")); - - if let Some(len) = ty.len.exact() { - apicheck_some!(s, check_range(b, from.clone().len(), &ty.len)); - - b.iter_range(1f64, len, |b, index| { - let item = b.expr(from.clone().index(index)); - ser(s, b, *ty.item, Expr::from(&item)); - }); - } else { - let len = b.expr(from.clone().len()); - apicheck_some!(s, check_range(b, &len, &ty.len)); - - b.alloc_k(ty.len.len_kind().size()); - b.write_k(ty.len.len_kind(), &len); - - b.iter_range(1f64, &len, |b, index| { - let item = b.expr(from.clone().index(index)); - ser(s, b, *ty.item, Expr::from(&item)); - }); - } - } - - Type::Set(ty) => { - apicheck_full!(s, check_type(b, from.clone(), "table")); - - let loc = b.reserve_k(ty.len.len_kind().size()); - let len = b.expr(0f64); - - b.iter_map(from, |b, value, _| { - b.assign(&len, Expr::from(&len).add(1f64)); - ser(s, b, *ty.item, Expr::from(value)); - }); - - apicheck_some!(s, check_range(b, &len, &ty.len)); - b.write_reserved_k(ty.len.len_kind(), &loc, &len); - } - - Type::Map(ty) => { - apicheck_full!(s, check_type(b, from.clone(), "table")); - - let loc = b.reserve_k(ty.len.len_kind().size()); - let len = b.expr(0f64); - - b.iter_map(from, |b, index, value| { - b.assign(&len, Expr::from(&len).add(1f64)); - ser(s, b, *ty.index, Expr::from(index)); - ser(s, b, *ty.value, Expr::from(value)); - }); - - apicheck_some!(s, check_range(b, &len, &ty.len)); - b.write_reserved_k(ty.len.len_kind(), &loc, &len); - } - - Type::Struct(ty) => { - apicheck_full!(s, check_type(b, from.clone(), "table")); - - for (name, ty) in &ty.fields { - let field = b.expr(from.clone().index(name.as_str())); - ser(s, b, ty.clone(), Expr::from(&field)); - } - } - } -} - -#[derive(Debug, Clone, Copy)] -pub struct Des { - pub check: bool, -} - -macro_rules! check { - ($b:expr, $block:block) => { - if $b.check { - $block - } - }; - - ($b:expr, $stmt:stmt) => { - if $b.check { - $stmt - } - }; -} - -pub fn des(d: Des, b: &mut Builder, ty: Type) -> InitVar { - match ty { - Type::Number(ty) => { - let value = b.read_k(ty.kind); - check!(d, check_range(b, &value, &ty.range)); - - if !matches!(ty.kind, NumberKind::NaNF32 | NumberKind::NaNF64) { - check!(d, b.assert(Expr::from(&value).eq(&value), "value is nan")) - } - - value - } - - Type::Vector(ty) => { - let x = des(d, b, Type::Number(ty.x)); - let y = des(d, b, Type::Number(ty.y)); - let z = ty.z.map(|z| des(d, b, Type::Number(z))); - - b.expr(Expr::Vector( - Box::new(Expr::from(&x)), - Box::new(Expr::from(&y)), - Box::new(z.map(|z| Expr::from(&z)).unwrap_or(Expr::from(0f64))), - )) - } - - Type::BinaryString(ty) => { - if let Some(len) = ty.len.exact() { - b.read_d(FuncD::String, len) - } else { - let len = b.read_k(ty.len.len_kind()); - check!(d, check_range(b, &len, &ty.len)); - - b.read_d(FuncD::String, &len) - } - } - - Type::Utf8String(ty) => { - if let Some(len) = ty.len.exact() { - let str = b.read_d(FuncD::String, len); - check!(d, check_utf8(b, &str)); - - str - } else { - let len = b.read_k(ty.len.len_kind()); - check!(d, check_range(b, &len, &ty.len)); - - let str = b.read_d(FuncD::String, &len); - check!(d, check_utf8(b, &str)); - - str - } - } - - Type::Array(ty) => { - if let Some(len) = ty.len.exact() { - let arr = b.expr(Expr::Array(Box::new(Expr::from(len)))); - b.iter_range(1f64, len, |b, index| { - let value = des(d, b, *ty.item); - b.assign_index(&arr, index, &value); - }); - - arr - } else { - let len = b.read_k(ty.len.len_kind()); - check!(d, check_range(b, &len, &ty.len)); - - let arr = b.expr(Expr::Array(Box::new(Expr::from(&len)))); - b.iter_range(1f64, &len, |b, index| { - let value = des(d, b, *ty.item); - b.assign_index(&arr, index, &value); - }); - - arr - } - } - - Type::Set(ty) => { - let len = b.read_k(ty.len.len_kind()); - check!(d, check_range(b, &len, &ty.len)); - - let set = b.expr(Expr::Table); - b.iter_range(1f64, &len, |b, _| { - let value = des(d, b, *ty.item); - b.assign_index(&set, &value, Expr::Boolean(true)); - }); - - set - } - - Type::Map(ty) => { - let len = b.read_k(ty.len.len_kind()); - check!(d, check_range(b, &len, &ty.len)); - - let map = b.expr(Expr::Table); - b.iter_range(1f64, &len, |b, _| { - let key = des(d, b, *ty.index); - let value = des(d, b, *ty.value); - b.assign_index(&map, &key, &value); - }); - - map - } - - Type::Struct(ty) => { - let mut fields = Vec::new(); - - for (name, ty) in &ty.fields { - let field = des(d, b, ty.clone()); - fields.push((name.clone(), field)); - } - - b.expr(Expr::Struct( - fields - .iter() - .map(|(name, var)| (name.clone(), Expr::from(var))) - .collect(), - )) - } - } -} diff --git a/src/server.rs b/src/server.rs deleted file mode 100644 index 7f17a7a..0000000 --- a/src/server.rs +++ /dev/null @@ -1,236 +0,0 @@ -use crate::{ - builder::{Builder, InitVar}, - ir::Expr, - serdes, - types::{Event, EventFrom, Item, Type}, -}; - -pub fn server(s: Server, items: &[(String, Item)]) -> Result { - use std::fmt::Write; - - let mut b = Builder::default(); - - for (name, item) in items { - self::item(s.push_location(name), &mut b, item) - } - - let mut s = String::new(); - writeln!(s, "{}", include_str!("header.luau"))?; - writeln!(s, "local result = {{}}")?; - writeln!(s, "local folder = Instance.new('Folder')")?; - writeln!(s, "folder.Name = 'Z'")?; - writeln!(s, "folder.Parent = game.ReplicatedStorage")?; - writeln!(s, "{b}")?; - writeln!(s, "return result")?; - - Ok(s) -} - -#[derive(Default, Debug, Clone)] -pub struct Server { - pub apicheck: serdes::ApiCheck, - pub location: String, -} - -impl Server { - pub fn push_location(&self, location: &str) -> Self { - Self { - apicheck: self.apicheck, - location: self.location.clone() + "." + location, - } - } - - pub fn function(&self, name: &str) -> String { - self.location.clone() + "." + name - } -} - -fn ser(s: &Server, b: &mut Builder, ty: Type, from: Expr) { - let ser = serdes::Ser { - apicheck: s.apicheck, - }; - - serdes::ser(ser, b, ty, from) -} - -fn des(s: &Server, b: &mut Builder, ty: Type) -> InitVar { - let des = serdes::Des { check: false }; - - serdes::des(des, b, ty) -} - -pub fn item(s: Server, b: &mut Builder, item: &Item) { - match item { - Item::Table(table) => self::table(s, b, table), - Item::Event(event) => self::event(s, b, event), - } -} - -pub fn table(s: Server, b: &mut Builder, items: &[(String, Item)]) { - b.export_table(s.location.clone()); - - for (name, item) in items { - self::item(s.push_location(name), b, item) - } -} - -pub fn event(s: Server, b: &mut Builder, event: &Event) { - b.export_table(s.location.clone()); - - match event.from { - EventFrom::Server => event_send(s, b, event), - EventFrom::Client => event_recv(s, b, event), - } -} - -fn event_remote(b: &mut Builder, event: &Event) -> InitVar { - let var = b.var().init(); - let name = event.name; - - b.stmt(format!("local {var} = Instance.new('RemoteEvent', folder)")); - b.stmt(format!("{var}.Name = '{name}'")); - - var -} - -fn event_recv(s: Server, b: &mut Builder, event: &Event) { - match event.data.len() { - 0 => event_recv_iter_0data(&s, b, event), - _ => event_recv_iter_ndata(&s, b, event), - } -} - -fn event_recv_queue_0data(s: &Server, b: &mut Builder, event: &Event) -> InitVar { - let remote = event_remote(b, event); - let queue = b.expr(Expr::Table); - - let on_event = b.local_function(|b, [player]| { - b.stmt(format!("table.insert({queue}, {player})")); - }); - - b.stmt(format!("{remote}.OnServerEvent:Connect({on_event})")); - queue -} - -fn event_recv_queue_ndata(s: &Server, b: &mut Builder, event: &Event) -> InitVar { - let remote = event_remote(b, event); - let queue = b.expr(Expr::Table); - - let on_event = b.local_function(|b, [player, buf]| { - b.stmt(format!("local buf, pos = {buf}, 0")); - b.stmt(format!("table.insert({queue}, {player})")); - - for ty in &event.data { - let value = des(s, b, ty.clone()); - b.stmt(format!("table.insert({queue}, {value})")); - } - }); - - b.stmt(format!( - "{remote}.OnServerEvent:Connect(function(player, buf) pcall({on_event}, player, buf) end)" - )); - - queue -} - -fn event_recv_iter_0data(s: &Server, b: &mut Builder, event: &Event) { - let queue = event_recv_queue_0data(s, b, event); - - b.export_function( - s.function("iter"), - vec![], - vec!["{ Player }".to_string()], - |b, _| { - let captured = b.expr(&queue); - b.assign(&queue, Expr::Table); - b.stmt(format!("return {captured}")); - }, - ) -} - -fn event_recv_iter_ndata(s: &Server, b: &mut Builder, event: &Event) { - let n = event.data.len(); - let queue = event_recv_queue_ndata(s, b, event); - let tys = event - .data - .iter() - .map(ToString::to_string) - .collect::>() - .join(", "); - - b.export_function( - s.function("iter"), - vec![], - vec![ - format!("({{ any }}, number) -> (number, Player, {tys})"), - "{ any }".to_string(), - "number".to_string(), - ], - |b, _| { - let captured = b.expr(&queue); - b.assign(&queue, Expr::Table); - - let iter = b.local_function(|b, [captured, i]| { - let cond = format!("{captured}[{i}]"); - let rets = (0..=n) - .map(|j| format!(", {captured}[{i} + {j}]")) - .collect::(); - let body = format!("return {i} + {}{rets}", n + 1); - - b.stmt(format!("if {cond} then {body} end")); - }); - - b.stmt(format!("return {iter}, {captured}, 1")); - }, - ); -} - -fn event_send(s: Server, b: &mut Builder, event: &Event) { - match event.data.len() { - 0 => event_send_0data(&s, b, event), - _ => event_send_ndata(&s, b, event), - } -} - -fn event_send_0data(s: &Server, b: &mut Builder, event: &Event) { - let remote = event_remote(b, event); - - b.export_function( - s.function("fire"), - vec!["Player".to_string()], - vec![], - |b, args| { - let player = &args[0]; - b.stmt(format!("{remote}:FireClient({player})")); - }, - ); -} - -fn event_send_ndata(s: &Server, b: &mut Builder, event: &Event) { - let remote = event_remote(b, event); - let args = event - .data - .iter() - .map(ToString::to_string) - .collect::>(); - - b.export_function( - s.function("fire"), - std::iter::once("Player".to_string()) - .chain(args.clone()) - .collect(), - vec![], - |b, args| { - let player = &args[0]; - b.stmt("pos = 0"); - - for (i, ty) in event.data.iter().enumerate() { - ser(s, b, ty.clone(), args[i + 1].expr()); - } - - b.stmt("local out = buffer.create(pos)"); - b.stmt("buffer.copy(out, 0, buf, 0, pos)"); - b.stmt(format!("{remote}:FireClient({player}, out)")); - }, - ) -} diff --git a/src/shared.rs b/src/shared.rs new file mode 100644 index 0000000..ee0863e --- /dev/null +++ b/src/shared.rs @@ -0,0 +1,91 @@ +#[derive(Clone, Copy)] +pub enum ApiCheck { + None, + Some, + Full, +} + +#[derive(Debug, Clone, Copy)] +pub enum NumberKind { + U8, + U16, + U32, + + I8, + I16, + I32, + + F32, + F64, + + NaNF32, + NaNF64, +} + +impl NumberKind { + pub fn size(&self) -> u32 { + match self { + Self::U8 | Self::I8 => 1, + Self::U16 | Self::I16 => 2, + Self::U32 | Self::I32 => 4, + Self::F32 | Self::NaNF32 => 4, + Self::F64 | Self::NaNF64 => 8, + } + } + + pub fn min(&self) -> f64 { + match self { + Self::U8 => 0.0, + Self::U16 => 0.0, + Self::U32 => 0.0, + Self::I8 => i8::MIN as f64, + Self::I16 => i16::MIN as f64, + Self::I32 => i32::MIN as f64, + Self::F32 | Self::NaNF32 => f32::MIN as f64, + Self::F64 | Self::NaNF64 => f64::MIN as f64, + } + } + + pub fn max(&self) -> f64 { + match self { + Self::U8 => u8::MAX as f64, + Self::U16 => u16::MAX as f64, + Self::U32 => u32::MAX as f64, + Self::I8 => i8::MAX as f64, + Self::I16 => i16::MAX as f64, + Self::I32 => i32::MAX as f64, + Self::F32 | Self::NaNF32 => f32::MAX as f64, + Self::F64 | Self::NaNF64 => f64::MAX as f64, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Range { + pub min: Option, + pub max: Option, +} + +impl Range { + pub fn exact(&self) -> Option { + if self.min == self.max { self.min } else { None } + } + + pub fn len_kind(&self) -> NumberKind { + let max = self.max.unwrap_or(f64::MAX); + + if max <= u8::MAX as f64 { + NumberKind::U8 + } else if max <= u16::MAX as f64 { + NumberKind::U16 + } else { + NumberKind::U32 + } + } +} + +#[derive(Clone, Copy)] +pub enum NetworkSide { + Server, + Client, +} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 905650e..0000000 --- a/src/types.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::fmt::Display; - -use lu::Userdata; -use uuid::Uuid; - -#[derive(Debug, Clone)] -pub enum Item { - Event(Event), - Table(Vec<(String, Item)>), -} - -#[derive(Debug, Clone, Userdata)] -pub struct Event { - pub name: Uuid, - pub from: EventFrom, - pub data: Vec, - pub reliable: bool, -} - -#[derive(Debug, Clone, Copy)] -pub enum EventFrom { - Server, - Client, -} - -#[derive(Debug, Clone, Userdata)] -pub enum Type { - Number(NumberType), - Vector(VectorType), - BinaryString(BinaryStringType), - Utf8String(Utf8StringType), - Array(ArrayType), - Set(SetType), - Map(MapType), - Struct(StructType), -} - -impl Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Type::Number(ty) => write!(f, "{ty}"), - Type::Vector(ty) => write!(f, "{ty}"), - Type::BinaryString(ty) => write!(f, "{ty}"), - Type::Utf8String(ty) => write!(f, "{ty}"), - Type::Array(ty) => write!(f, "{ty}"), - Type::Set(ty) => write!(f, "{ty}"), - Type::Map(ty) => write!(f, "{ty}"), - Type::Struct(ty) => write!(f, "{ty}"), - } - } -} - -#[derive(Debug, Clone)] -pub struct NumberType { - pub kind: NumberKind, - pub range: Range, -} - -impl Display for NumberType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "number") - } -} - -#[derive(Debug, Clone)] -pub struct VectorType { - pub x: NumberType, - pub y: NumberType, - pub z: Option, -} - -impl Display for VectorType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "vector") - } -} - -#[derive(Debug, Clone)] -pub struct BinaryStringType { - pub len: Range, -} - -impl Display for BinaryStringType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "string") - } -} - -#[derive(Debug, Clone)] -pub struct Utf8StringType { - pub len: Range, -} - -impl Display for Utf8StringType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "string") - } -} - -#[derive(Debug, Clone)] -pub struct ArrayType { - pub len: Range, - pub item: Box, -} - -impl Display for ArrayType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{{ {} }}", self.item) - } -} - -#[derive(Debug, Clone)] -pub struct SetType { - pub len: Range, - pub item: Box, -} - -impl Display for SetType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{{ [{}]: unknown }}", self.item) - } -} - -#[derive(Debug, Clone)] -pub struct MapType { - pub len: Range, - pub index: Box, - pub value: Box, -} - -impl Display for MapType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{{ [{}]: {} }}", self.index, self.value) - } -} - -#[derive(Debug, Clone)] -pub struct StructType { - pub fields: Vec<(String, Type)>, -} - -impl Display for StructType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{{ ")?; - for (name, ty) in &self.fields { - write!(f, "[\"{}\"]: {ty}, ", name.escape_default())?; - } - write!(f, "}}") - } -} - -#[derive(Debug, Clone)] -pub struct Range { - pub min: Option, - pub max: Option, -} - -impl Range { - pub fn exact(&self) -> Option { - if self.min == self.max { self.min } else { None } - } - - pub fn len_kind(&self) -> NumberKind { - let max = self.max.unwrap_or(f64::MAX); - - if max <= u8::MAX as f64 { - NumberKind::U8 - } else if max <= u16::MAX as f64 { - NumberKind::U16 - } else { - NumberKind::U32 - } - } -} - -#[derive(Debug, Clone, Copy)] -pub enum NumberKind { - U8, - U16, - U32, - - I8, - I16, - I32, - - F32, - F64, - - NaNF32, - NaNF64, -} - -impl NumberKind { - pub fn size(&self) -> u32 { - match self { - Self::U8 | Self::I8 => 1, - Self::U16 | Self::I16 => 2, - Self::U32 | Self::I32 => 4, - Self::F32 | Self::NaNF32 => 4, - Self::F64 | Self::NaNF64 => 8, - } - } -} diff --git a/src/zap.d.luau b/src/zap.d.luau index 7f6137b..bab7e23 100644 --- a/src/zap.d.luau +++ b/src/zap.d.luau @@ -34,6 +34,5 @@ declare zap: { map: (ZapType, ZapType, min: number?, max: number?) -> ZapType, struct: ({ [string]: ZapType }, min: number?, max: number?) -> ZapType, - reliable: (from: "server" | "client", { ZapType }) -> ZapEvent, - unreliable: (from: "server" | "client", { ZapType }) -> ZapEvent, + event: (from: "server" | "client", { ZapType }) -> ZapEvent, } diff --git a/zap/net.luau b/zap/net.luau new file mode 100644 index 0000000..b6a7b05 --- /dev/null +++ b/zap/net.luau @@ -0,0 +1,5 @@ +local hello_world = zap.event("client", { zap.string.utf8() }) + +return { + hello_world = hello_world, +} \ No newline at end of file diff --git a/zap/out/client.luau b/zap/out/client.luau new file mode 100644 index 0000000..8396191 --- /dev/null +++ b/zap/out/client.luau @@ -0,0 +1,44 @@ +--!nocheck +--!nolint +-- This file was generated by zap +local buf, pos, len = buffer.create(1024), 0, 1024 +local function resize(bytes) + local newlen = pos * 2 + if newlen < pos + bytes then + newlen = pos + bytes + end + local newbuf = buffer.create(newlen) + buffer.copy(newbuf, 0, buf, 0, pos) + buf, len = newbuf, newlen +end + +local result = {} +local folder = game.ReplicatedStorage:WaitForChild("Z") +result.hello_world = {} +local v0 = folder:WaitForChild("45392a7d-74c1-4733-82c2-f1d3f4f3387b") +local function v1(v2) + pos = 0 + if not (type(v2) == "string") then + error("expected string") + end + if not utf8.len(v2) then + error("not a valid utf8 string") + end + local v3 = #v2 + if pos + 4 > len then + resize(4) + end + buffer.writeu32(buf, pos, v3) + pos += 4 + if pos + v3 > len then + resize(v3) + end + local size = v3 + buffer.writestring(buf, pos, v2, size) + pos += size + local out = buffer.create(pos) + buffer.copy(out, 0, buf, 0, pos) + v0:FireServer(out) +end +result.hello_world.send = v1 +return result diff --git a/zap/out/server.luau b/zap/out/server.luau new file mode 100644 index 0000000..8e2e1d6 --- /dev/null +++ b/zap/out/server.luau @@ -0,0 +1,49 @@ +--!nocheck +--!nolint +-- This file was generated by zap +local buf, pos, len = buffer.create(1024), 0, 1024 +local function resize(bytes) + local newlen = pos * 2 + if newlen < pos + bytes then + newlen = pos + bytes + end + local newbuf = buffer.create(newlen) + buffer.copy(newbuf, 0, buf, 0, pos) + buf, len = newbuf, newlen +end + +local result = {} +local folder = Instance.new("Folder") +folder.Name = "Z" +folder.Parent = game.ReplicatedStorage +result.hello_world = {} +local v0 = Instance.new("RemoteWEvent") +v0.Name = "45392a7d-74c1-4733-82c2-f1d3f4f3387b" +v0.Parent = folder +local v1 = {} +local function v2(v3, v4) + local buf, pos = v4, 0 + table.insert(v1, v3) + local v5 = buffer.readu32(buf, pos) + pos += 4 + local size = v5 + local v6 = buffer.readstring(buf, pos, size) + pos += size + table.insert(v1, v6) +end +v0.OnServerEvent:Connect(function(player, buf) + pcall(v2, player, buf) +end) +local function v0() + local v2 = v1 + v1 = {} + local function v4(v3, v6) + if v3[v6] then + return v6 + 1, v3[v6 + 0], v3[v6 + 1] + else + end + end + return v4, v2, 1 +end +result.hello_world.iter = v0 +return result diff --git a/zap/zap.d.luau b/zap/zap.d.luau new file mode 100644 index 0000000..7f6137b --- /dev/null +++ b/zap/zap.d.luau @@ -0,0 +1,39 @@ +declare class ZapType +end + +declare class ZapNumberType extends ZapType +end + +declare class ZapEvent +end + +declare zap: { + u8: (min: number?, max: number?) -> ZapNumberType, + u16: (min: number?, max: number?) -> ZapNumberType, + u32: (min: number?, max: number?) -> ZapNumberType, + + i8: (min: number?, max: number?) -> ZapNumberType, + i16: (min: number?, max: number?) -> ZapNumberType, + i32: (min: number?, max: number?) -> ZapNumberType, + + f32: (min: number?, max: number?) -> ZapNumberType, + f64: (min: number?, max: number?) -> ZapType, + + nanf32: (min: number?, max: number?) -> ZapNumberType, + nanf64: (min: number?, max: number?) -> ZapType, + + vector: (ZapNumberType, ZapNumberType, ZapNumberType?) -> ZapType, + + string: { + binary: (min: number?, max: number?) -> ZapType, + utf8: (min: number?, max: number?) -> ZapType, + }, + + array: (ZapType, min: number?, max: number?) -> ZapType, + set: (ZapType, min: number?, max: number?) -> ZapType, + map: (ZapType, ZapType, min: number?, max: number?) -> ZapType, + struct: ({ [string]: ZapType }, min: number?, max: number?) -> ZapType, + + reliable: (from: "server" | "client", { ZapType }) -> ZapEvent, + unreliable: (from: "server" | "client", { ZapType }) -> ZapEvent, +}