diff --git a/src/api.rs b/src/api.rs index b2b7d1d..48b374a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -122,9 +122,11 @@ fn library() -> lu::Library { .with_function_norm("boolean", boolean) .with_function_norm("u8", u8) .with_function_norm("u16", u16) + .with_function_norm("u24", u24) .with_function_norm("u32", u32) .with_function_norm("i8", i8) .with_function_norm("i16", i16) + .with_function_norm("i24", i24) .with_function_norm("i32", i32) .with_function_norm("f32", f32) .with_function_norm("f64", f64) @@ -315,6 +317,37 @@ extern "C-unwind" fn u16(ctx: Context) -> lu::FnReturn { ctx.ret_with(1) } +extern "C-unwind" fn u24(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && !(0f64..=16777215f64).contains(&min) + { + ctx.error_msg("u24 min must be between 0 and 16777215") + } + + if let Some(max) = max + && !(0f64..=16777215f64).contains(&max) + { + ctx.error_msg("u24 max must be between 0 and 16777215") + } + + 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::U24, + range: Range { min, max }, + })); + + ctx.ret_with(1) +} + extern "C-unwind" fn u32(ctx: Context) -> lu::FnReturn { let min = ctx.arg_number_opt(1); let max = ctx.arg_number_opt(2); @@ -408,6 +441,37 @@ extern "C-unwind" fn i16(ctx: Context) -> lu::FnReturn { ctx.ret_with(1) } +extern "C-unwind" fn i24(ctx: Context) -> lu::FnReturn { + let min = ctx.arg_number_opt(1); + let max = ctx.arg_number_opt(2); + + if let Some(min) = min + && !(-8388608f64..=8388607f64).contains(&min) + { + ctx.error_msg("i24 min must be between -8388608 and 8388607") + } + + if let Some(max) = max + && !(-8388608f64..=8388607f64).contains(&max) + { + ctx.error_msg("i24 max must be between -8388608 and 8388607") + } + + 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::I24, + range: Range { min, max }, + })); + + 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); diff --git a/src/mir/mod.rs b/src/mir/mod.rs index acdfb04..beb5047 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -141,9 +141,9 @@ impl Display for Instr { 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;")?; @@ -228,6 +228,13 @@ pub enum FuncK { I16, I32, + // u24 and i24 are special cases that are not natively supported in the + // buffer library. at serdes time special instrs are emitted to handle + // these types. at instr display time these two types are written and read + // as u32 and i32. + U24, + I24, + F32, F64, } @@ -237,10 +244,12 @@ impl From for FuncK { match value { NumberKind::U8 => FuncK::U8, NumberKind::U16 => FuncK::U16, + NumberKind::U24 => FuncK::U24, NumberKind::U32 => FuncK::U32, NumberKind::I8 => FuncK::I8, NumberKind::I16 => FuncK::I16, + NumberKind::I24 => FuncK::I24, NumberKind::I32 => FuncK::I32, NumberKind::F32 | NumberKind::NaNF32 => FuncK::F32, @@ -254,6 +263,7 @@ impl FuncK { match self { Self::U8 | Self::I8 => 1, Self::U16 | Self::I16 => 2, + Self::U24 | Self::I24 => 3, Self::U32 | Self::I32 => 4, Self::F32 => 4, Self::F64 => 8, @@ -266,10 +276,12 @@ impl Display for FuncK { match self { Self::U8 => write!(f, "u8"), Self::U16 => write!(f, "u16"), + Self::U24 => write!(f, "u32"), Self::U32 => write!(f, "u32"), Self::I8 => write!(f, "i8"), Self::I16 => write!(f, "i16"), + Self::I24 => write!(f, "i32"), Self::I32 => write!(f, "i32"), Self::F32 => write!(f, "f32"), @@ -311,6 +323,8 @@ pub enum Expr { Type(Box), Utf8(Box), Bit(Box), + + Band(Box, Box), } impl From for Expr { @@ -397,6 +411,10 @@ impl Expr { pub fn bit(self) -> Self { Expr::Bit(Box::new(self)) } + + pub fn band(self, rhs: impl Into) -> Self { + Expr::Band(Box::new(self), Box::new(rhs.into())) + } } impl Display for Expr { @@ -430,6 +448,8 @@ impl Display for Expr { Expr::Type(expr) => write!(f, "type({expr})"), Expr::Utf8(expr) => write!(f, "utf8.len({expr})"), Expr::Bit(expr) => write!(f, "bit[{expr}]"), + + Expr::Band(lhs, rhs) => write!(f, "bit32.band({lhs}, {rhs})"), } } } diff --git a/src/mir/serdes.rs b/src/mir/serdes.rs index 6264c6a..492dd87 100644 --- a/src/mir/serdes.rs +++ b/src/mir/serdes.rs @@ -213,7 +213,15 @@ impl Serdes for hir::NumberType { check!(des, b.assert(value.expr().eq(&value), "value is nan")); } - value + if matches!(self.kind, NumberKind::U24 | NumberKind::I24) { + if des.native { + b.expr(value.expr().band(0x00FFFFFF)) + } else { + b.expr(value.expr().mud(256 * 256 * 256)) + } + } else { + value + } } } } diff --git a/src/shared.rs b/src/shared.rs index cf7312c..7c26477 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -102,10 +102,12 @@ impl Options { pub enum NumberKind { U8, U16, + U24, U32, I8, I16, + I24, I32, F32, @@ -120,6 +122,7 @@ impl NumberKind { match self { Self::U8 | Self::I8 => 1, Self::U16 | Self::I16 => 2, + Self::U24 | Self::I24 => 3, Self::U32 | Self::I32 => 4, Self::F32 | Self::NaNF32 => 4, Self::F64 | Self::NaNF64 => 8, @@ -130,12 +133,14 @@ impl NumberKind { match self { Self::U8 => 0.0, Self::U16 => 0.0, + Self::U24 => 0.0, Self::U32 => 0.0, Self::I8 => i8::MIN as f64, Self::I16 => i16::MIN as f64, + Self::I24 => -2f64.powi(23), Self::I32 => i32::MIN as f64, Self::F32 | Self::NaNF32 => f32::MIN as f64, - Self::F64 | Self::NaNF64 => f64::MIN as f64, + Self::F64 | Self::NaNF64 => f64::MIN, } } @@ -143,12 +148,14 @@ impl NumberKind { match self { Self::U8 => u8::MAX as f64, Self::U16 => u16::MAX as f64, + Self::U24 => 2f64.powi(24) - 1.0, Self::U32 => u32::MAX as f64, Self::I8 => i8::MAX as f64, Self::I16 => i16::MAX as f64, + Self::I24 => 2f64.powi(23) - 1.0, Self::I32 => i32::MAX as f64, Self::F32 | Self::NaNF32 => f32::MAX as f64, - Self::F64 | Self::NaNF64 => f64::MAX as f64, + Self::F64 | Self::NaNF64 => f64::MAX, } } } diff --git a/src/zap.d.luau b/src/zap.d.luau index 485b182..e24e749 100644 --- a/src/zap.d.luau +++ b/src/zap.d.luau @@ -15,10 +15,12 @@ declare zap: { u8: (min: number?, max: number?) -> ZapNumberType, u16: (min: number?, max: number?) -> ZapNumberType, + u24: (min: number?, max: number?) -> ZapNumberType, u32: (min: number?, max: number?) -> ZapNumberType, i8: (min: number?, max: number?) -> ZapNumberType, i16: (min: number?, max: number?) -> ZapNumberType, + i24: (min: number?, max: number?) -> ZapNumberType, i32: (min: number?, max: number?) -> ZapNumberType, f32: (min: number?, max: number?) -> ZapNumberType,