///|
/// Supported dense array classes. All payloads use little-endian, column-major order.
pub(all) enum DType {
Float64
Float32
Int8
UInt8
Int16
UInt16
Int32
UInt32
Int64
UInt64
Logical
} derive(Eq, @debug.Debug)
///|
/// Exact scalar representation; integer values never pass through Double.
pub(all) enum Value {
Signed(Int64)
Unsigned(UInt64)
Floating(Double)
Boolean(Bool)
} derive(@debug.Debug)
///|
/// Error offsets refer to the current MAT stream, or the expanded stream for compressed data.
pub(all) suberror MatError {
MatError(String, Int, String)
} derive(Eq, @debug.Debug)
///|
pub fn MatError::code(self : MatError) -> String {
let MatError(code, _, _) = self
code
}
///|
pub fn MatError::offset(self : MatError) -> Int {
let MatError(_, offset, _) = self
offset
}
///|
pub fn MatError::message(self : MatError) -> String {
let MatError(_, _, message) = self
message
}
///|
/// Limits are validated before reading or allocating according to file-controlled lengths.
pub(all) struct Limits {
max_file_bytes : Int
max_expanded_bytes : Int
max_elements : Int
max_variables : Int
max_dimensions : Int
max_name_bytes : Int
} derive(Eq, @debug.Debug)
///|
pub fn default_limits() -> Limits {
{
max_file_bytes: 64 * 1024 * 1024,
max_expanded_bytes: 64 * 1024 * 1024,
max_elements: 4 * 1024 * 1024,
max_variables: 4096,
max_dimensions: 32,
max_name_bytes: 1024,
}
}
///|
/// Arrays own their canonical payload. Writers revalidate public fields before serialization.
pub(all) struct NumericArray {
name : String
dtype : DType
shape : Array[Int]
real : Bytes
imag : Bytes?
is_global : Bool
} derive(@debug.Debug)
///|
/// Original file byte order is reported; arrays are normalized to little-endian.
pub(all) struct MatFile {
description : String
little_endian : Bool
arrays : Array[NumericArray]
} derive(@debug.Debug)
///|
fn fail(code : String, offset : Int, message : String) -> Unit raise MatError {
raise MatError(code, offset, message)
}
///|
fn validate_limits(limits : Limits) -> Unit raise MatError {
if limits.max_file_bytes < 128 ||
limits.max_file_bytes > 1073741824 ||
limits.max_expanded_bytes < 8 ||
limits.max_expanded_bytes > 1073741824 ||
limits.max_elements < 1 ||
limits.max_elements > 134217728 ||
limits.max_variables < 1 ||
limits.max_variables > 65536 ||
limits.max_dimensions < 2 ||
limits.max_dimensions > 128 ||
limits.max_name_bytes < 1 ||
limits.max_name_bytes > 65536 {
fail(
"invalid-limits", 0, "Limits must be positive and within documented bounds",
)
}
}
///|
pub fn DType::name(self : DType) -> String {
match self {
Float64 => "float64"
Float32 => "float32"
Int8 => "int8"
UInt8 => "uint8"
Int16 => "int16"
UInt16 => "uint16"
Int32 => "int32"
UInt32 => "uint32"
Int64 => "int64"
UInt64 => "uint64"
Logical => "logical"
}
}
///|
pub fn dtype_from_name(name : String) -> DType raise MatError {
match name {
"float64" => Float64
"float32" => Float32
"int8" => Int8
"uint8" => UInt8
"int16" => Int16
"uint16" => UInt16
"int32" => Int32
"uint32" => UInt32
"int64" => Int64
"uint64" => UInt64
"logical" => Logical
_ => {
fail("unsupported-dtype", 0, "Unknown numeric type: " + name)
Float64
}
}
}
///|
pub fn DType::width(self : DType) -> Int {
match self {
Float64 | Int64 | UInt64 => 8
Float32 | Int32 | UInt32 => 4
Int16 | UInt16 => 2
Int8 | UInt8 | Logical => 1
}
}
///|
fn DType::mat_class(self : DType) -> Int {
match self {
Float64 => 6
Float32 => 7
Int8 => 8
UInt8 | Logical => 9
Int16 => 10
UInt16 => 11
Int32 => 12
UInt32 => 13
Int64 => 14
UInt64 => 15
}
}
///|
fn DType::mi_type(self : DType) -> Int {
match self {
Int8 => 1
UInt8 | Logical => 2
Int16 => 3
UInt16 => 4
Int32 => 5
UInt32 => 6
Float32 => 7
Float64 => 9
Int64 => 12
UInt64 => 13
}
}
///|
fn from_mi_type(tag : Int, offset : Int) -> DType raise MatError {
match tag {
1 => Int8
2 => UInt8
3 => Int16
4 => UInt16
5 => Int32
6 => UInt32
7 => Float32
9 => Float64
12 => Int64
13 => UInt64
_ => {
fail("unsupported-storage", offset, "Unsupported numeric storage tag")
Float64
}
}
}
///|
fn from_mat_class(class : Int, offset : Int) -> DType raise MatError {
match class {
6 => Float64
7 => Float32
8 => Int8
9 => UInt8
10 => Int16
11 => UInt16
12 => Int32
13 => UInt32
14 => Int64
15 => UInt64
_ => {
fail(
"unsupported-class", offset, "Only dense numeric and logical arrays are supported",
)
Float64
}
}
}
///|
fn element_count(
shape : Array[Int],
limits : Limits,
offset : Int,
) -> Int raise MatError {
if shape.length() < 2 || shape.length() > limits.max_dimensions {
fail(
"invalid-shape", offset, "MAT arrays require 2 to max_dimensions dimensions",
)
}
let mut empty = false
for dim in shape {
if dim < 0 {
fail("invalid-shape", offset, "Negative dimension")
}
if dim == 0 {
empty = true
}
}
if empty {
return 0
}
let mut count = 1
for dim in shape {
if dim > limits.max_elements / count {
fail("element-limit", offset, "Dimension product exceeds max_elements")
}
count = count * dim
}
count
}
///|
pub fn NumericArray::length(self : NumericArray) -> Int {
self.real.length() / self.dtype.width()
}
///|
pub fn NumericArray::is_complex(self : NumericArray) -> Bool {
self.imag is Some(_)
}
///|
pub fn MatFile::get(self : MatFile, name : String) -> NumericArray? {
for array in self.arrays {
if array.name == name {
return Some(array)
}
}
None
}
///|
pub extend DType with Eq::{equal, not_equal}
///|
pub extend DType with @debug.Debug::{to_repr}
///|
pub extend Value with @debug.Debug::{to_repr}
///|
pub extend MatError with Eq::{equal, not_equal}
///|
pub extend MatError with @debug.Debug::{to_repr}
///|
pub extend Limits with Eq::{equal, not_equal}
///|
pub extend Limits with @debug.Debug::{to_repr}
///|
pub extend NumericArray with @debug.Debug::{to_repr}
///|
pub extend MatFile with @debug.Debug::{to_repr}