Pure MoonBit reader/writer for the NumPy NPY binary array format.
Dependencies
中文说明见 README_CN.md。
Numerical Computing
numbt / numoon / moonNum
│ arrays
▼
┌───────────┐
│ moon-npy │
└───────────┘
│ .npy
▼
NumPy / PyTorch / ML ecosystemmoon-npy is not another NumPy implementation. Existing MoonBit numerical libraries focus on numerical computation and multidimensional arrays; moon-npy focuses specifically on interoperable NumPy binary serialization — the missing .npy bridge that lets a MoonBit program exchange arrays with the Python / ML data ecosystem byte-for-byte.
| 阶段 | 状态 |
|---|---|
| M0 工具链验证 gate(附录 B.2 全 7 项) | ✅ GO — 见 AGENTS.md |
| NumPy 兼容性 Oracle + fixtures(31) | ✅ 就位(interoperability/, tests/fixtures/) |
| M1 NPY header 解析(v1/v2/v3) | ✅ src/header/ |
| M2 dtype codec + Reader(decode → 类型化数组) | ✅ src/dtype/, src/reader/ |
| M3 Writer(encode → 字节级对齐 np.save) | ✅ src/writer/ |
| M4 NumPy → MoonBit → NumPy 字节级双向 round-trip + CI | ✅ 31/31(第一阶段硬目标达成,§23) |
| CLI(inspect / validate / dump) | ✅ src/cli/, cmd/main/(退出码 0/1/2 $LASTEXITCODE 实测) |
| M5 边缘 / Fuzz / 覆盖率(§18 totality、§14 阈值) | ✅ 156 测试全绿;core parser 98.5%、overall 92.4%(CI 强制门禁) |
| S1 complex64 / complex128 读取(v0.2.0 Stretch,§6) | ✅ src/dtype/(Complex + read_c64 / read_c16)、src/reader/(to_c64 / to_c16) |
| S5 CLI dump [--limit N](v0.2.0 Stretch,§6) | ✅ src/cli/(run_dump,13 个 dtype 全覆盖)、cmd/main/(CI 冒烟实测) |
| S4 storage-order 分块读取(v0.2.0 Stretch,§6) | ✅ src/reader/(flat_range + to_f32_chunk / to_f64_chunk)、src/error/ + src/cli/(第 13 变体 InvalidChunkRange 及其渲染) |
| S6 moonNum 生态适配(v0.2.0 Stretch,§6) | ✅ src/adapter/moonnum/(读方向 to_moonnum_f32 / to_moonnum_f64);选型 go/no-go 见 docs/s6-api-card.md |
| C1 NPZ 容器读取(v0.3.0,只读 + 未压缩) | ✅ src/npz/(decode_npz → NpzArchive,get(name) / names();压缩 / zip64 / 路径穿越 / 重复成员容器层拒绝) |
moon add ShunjunGu/moon-npy// your moon.pkg
import {
"ShunjunGu/moon-npy/src/reader",
"ShunjunGu/moon-npy/src/writer",
"ShunjunGu/moon-npy/src/dtype",
"ShunjunGu/moon-npy/src/error",
}git clone https://github.com/ShunjunGu/moon-npy && cd moon-npy
moon check --target native # 类型检查
moon run cmd/main --target native -- inspect tests/fixtures/f4_2x3_c_le_v1.npymoon 可能不在 PATH:PowerShell 下 $env:Path = "$env:USERPROFILE\.moon\bin;$env:Path"。
// bytes: 文件字节,例如 @fs.read_file_to_bytes("array.npy")(raise IOError,需 try 桥接)
match @reader.decode(bytes) {
Ok(arr) => {
println("dtype=\{@dtype.name(arr.dtype)} shape=\{arr.header.shape}")
let flat : Array[Float] = arr.to_f32().unwrap() // storage-order 扁平元素
println("elements=\{arr.element_count} first=\{flat[0]}")
}
Err(err) => println(@cli.render_error(err)) // 结构化错误渲染
}let z : Array[@dtype.Complex] = arr.to_c64().unwrap()
assert_eq(z[1], @dtype.Complex::{ re: 1.0, im: 2.0 }) // tests/fixtures/c8_4_c_le_v1.npylet row1 : Array[Float] = arr.to_f32_chunk(3, 3).unwrap() // (2,3) 的第 1 行 = 元素 [3, 6)
assert_eq(row1.map(fn(x) { x.to_double() }), [3.0, 4.0, 5.0]) // tests/fixtures/f4_2x3_c_le_v1.npy# 1) NumPy 产出 Oracle 数组
python -c "import numpy as np; np.save('embeddings.npy', np.arange(100*768, dtype='float32').reshape(100,768))"
# 2) MoonBit 读取 -> decode -> encode -> 写回(无 Python 运行时、无 FFI)
moon run examples/roundtrip --target native -- embeddings.npy embeddings-moonbit.npy
# -> roundtrip OK: embeddings.npy -> embeddings-moonbit.npy | 307328 bytes | 76800 elements
# 3) NumPy 校验:np.array_equal + 逐字节一致
python interoperability/verify_moonbit_output.py embeddings-moonbit.npy \
--reference embeddings.npy --byte-exact embeddings.npy
# -> [verify] PASSpython interoperability/roundtrip.py # emit(moon run) + verify(numpy) 聚合,31/31| 维度 | 支持取值 | 说明 |
|---|---|---|
| version | 1.0 / 2.0 / 3.0 | v1 用 uint16 header-length,v2/v3 用 uint32 |
| dtype | bool · i1 i2 i4 i8 · u1 u2 u4 u8 · f4 f8 · c8 c16 | 13 种:11 种 primitive numeric + 2 种 complex(c8 / c16 自 v0.2.0 S1);各有 to_* accessor |
| shape | 0-d () · 1-d · N-d(实测含 (2,3)、(2,2,2)) | 0-d scalar → element_count == 1 |
| memory order | C / Fortran | fortran_order 作 metadata;accessor 返回 storage-order 扁平数组 |
| byte order | < little · > big · \| N/A · = native | = native 读作 little-endian(MoonBit 各后端均小端,平台假设) |
python interoperability/generate_fixtures.py # 生成入库 fixture 集
python interoperability/generate_fixtures.py --check # 校验未漂移(CI)
python interoperability/generate_fixtures.py --full # §15 完整矩阵moon run cmd/main --target native -- inspect tests/fixtures/f4_2x3_c_le_v1.npy
moon run cmd/main --target native -- validate tests/fixtures/f4_2x3_c_le_v1.npy
moon run cmd/main --target native -- dump tests/fixtures/c8_4_c_le_v1.npy
moon run cmd/main --target native -- dump tests/fixtures/f4_2x3_c_le_v1.npy --limit 3NPY Array
──────────────────────────
Format NPY 1.0
DType float32
Byte order little-endian
Shape [2, 3]
Dimensions 2
Elements 6
Memory order C
Data size 24 B
──────────────────────────
Status validNPY Dump: tests/fixtures/c8_4_c_le_v1.npy
──────────────────────────
[0] (0, 0)
[1] (1, 2)
[2] (2, 4)
[3] (3, 6)
(showing 4 of 4 elements)NPY Dump: tests/fixtures/f4_2x3_c_le_v1.npy
──────────────────────────
[0] 0
[1] 1
[2] 2
(showing 3 of 6 elements)// src/format — magic / version / header-length 前缀
pub(all) enum NpyVersion { V1_0; V2_0; V3_0 }
pub struct NpyPrefix { version; major; minor; header_len : Int64; header_start; data_offset : Int64 }
pub fn parse_prefix(data : Bytes) -> Result[NpyPrefix, NpyError]
pub fn has_magic(data : Bytes) -> Bool
// src/header — 受限 Python-literal 头字典
pub struct NpyHeader { descr : String; shape : Array[UInt64]; fortran_order : Bool }
// src/dtype — descr → (DType, ByteOrder) + itemsize + 逐元素 codec
pub(all) enum DType { Bool; Int8; Int16; Int32; Int64; UInt8; UInt16; UInt32; UInt64; Float32;
Float64; Complex64; Complex128 }
pub(all) enum ByteOrder { Little; Big; NotApplicable; Native }
pub fn parse_dtype(descr : String) -> Result[(DType, ByteOrder), NpyError]
pub fn itemsize(dt : DType) -> Int
pub fn name(dt : DType) -> String
pub(all) struct Complex { re : Double; im : Double } // c8 / c16 的元素类型,分量统一 Double
pub fn read_c64(data : Bytes, off : Int, order : ByteOrder) -> Complex // 2 x f32 -> Double
pub fn read_c16(data : Bytes, off : Int, order : ByteOrder) -> Complex // 2 x f64
// src/reader — 校验 / 解码 / 类型化访问
pub struct NpyMeta { version; header; dtype; byte_order; element_count : Int64; data_offset : Int64; payload_len : Int64 }
pub struct NpyArray { version; header; dtype; byte_order; element_count : Int64; data : Bytes }
pub fn validate(data : Bytes) -> Result[NpyMeta, NpyError]
pub fn decode(data : Bytes) -> Result[NpyArray, NpyError]
pub fn NpyArray::to_f32(self) -> Result[Array[Float], NpyError] // 另有 to_bool / to_i8…to_i64 /
pub fn NpyArray::to_f64(self) -> Result[Array[Double], NpyError] // to_u8…to_u64(共 13 个全量 accessor)
pub fn NpyArray::to_c64(self) -> Result[Array[Complex], NpyError] // complex64(v0.2.0 S1)
pub fn NpyArray::to_c16(self) -> Result[Array[Complex], NpyError] // complex128(v0.2.0 S1)
pub fn NpyArray::to_f32_chunk(self, start : Int, len : Int) -> Result[Array[Float], NpyError]
pub fn NpyArray::to_f64_chunk(self, start : Int, len : Int) -> Result[Array[Double], NpyError]
// storage-order 窗口 [start, start + len);越界 / 负值 → InvalidChunkRange(v0.2.0 S4)
// src/writer — 序列化回字节级对齐 np.save 的 NPY
pub fn encode(array : NpyArray) -> Result[Bytes, NpyError]
// src/npz — NPZ(np.savez ZIP 容器)读取:只读 + 未压缩(v0.3.0 C1)
pub struct NpzArchive { members : Array[NpzMember] } // 成员序 = central directory 序
pub(all) struct NpzMember { name : String; array : @reader.NpyArray } // name 已剥 .npy 后缀
pub fn decode_npz(data : Bytes) -> Result[NpzArchive, NpyError]
pub fn NpzArchive::get(self, name : String) -> Result[NpzMember, NpyError]
pub fn NpzArchive::names(self) -> Array[String]
// src/cli — 纯参数解析 + 输出渲染(无 IO)
pub(all) enum Subcommand { Inspect; Validate; Dump(Int) }
pub(all) enum ExitCode { Success; Invalid; Operational } // 0 / 1 / 2
pub fn parse_args(argv : Array[String]) -> Result[(Subcommand, String), String]
pub fn run_inspect(path : String, data : Bytes) -> CliOutcome
pub fn run_validate(path : String, data : Bytes) -> CliOutcome
pub fn run_dump(path : String, data : Bytes, limit : Int) -> CliOutcome // 全 13 个 dtype(v0.2.0 S5)
pub fn render_error(e : NpyError) -> String
// src/error — 19 个结构化错误构造子(13 NPY + 6 NPZ)
pub(all) enum NpyError {
InvalidMagic; UnsupportedVersion(Int, Int); TruncatedHeader; InvalidHeaderLength
InvalidHeaderSyntax; MissingHeaderField(String); InvalidDType(String); UnsupportedDType(String)
ShapeOverflow; DataLengthMismatch(Int64, Int64); UnsupportedObjectArray
InvalidByteOrder(Byte); InvalidChunkRange(Int, Int)
// v0.3.0 C1 NPZ 容器层
NpzBadStructure(String); NpzCompressedMember(String); NpzUnsafeMemberName(String)
NpzDuplicateMember(String); NpzZip64Unsupported; NpzMemberNotFound(String)
}// src/adapter/moonnum — 读方向,float32 / float64 only
pub(all) enum AdapterError {
UnsupportedDType(String) // descr 不是 f4 / f8
BigEndianNotSupported(String) // moonNum 的缓冲模型只有小端(card §3.6)
ShapeDimensionTooLarge(UInt64) // 维度装不进 32 位 Int:拒绝,不截断
}
pub fn AdapterError::message(self : AdapterError) -> String
pub fn to_moonnum_f32(arr : @reader.NpyArray) -> Result[@core.NdArray, AdapterError]
pub fn to_moonnum_f64(arr : @reader.NpyArray) -> Result[@core.NdArray, AdapterError]let data = @fs.read_file_to_bytes("tests/fixtures/f4_2x3_c_le_v1.npy").unwrap()
let arr = @reader.decode(data).unwrap()
let nd = @moonnum.to_moonnum_f32(arr).unwrap()
nd.shape() // => [2, 3]
nd.strides() // => [12, 4] —— 字节步长,与 NumPy 的 (12, 4) 直接可比
nd.get_f32(4) // => 4.0 Bytes (来自 @fs.read_file_to_bytes 或内存)
│
▼
format magic · version · header-length 前缀 ──► NpyPrefix
│
▼
header lexer + 受限 Python-literal parser ──► NpyHeader { descr, shape, fortran_order }
│
▼
dtype descr → (DType, ByteOrder) · itemsize · codec
│
├──► reader.validate(Bytes) → NpyMeta (只校验,不解元素)
├──► reader.decode(Bytes) → NpyArray → to_f32() / to_i64() / … (惰性类型化访问)
│ └─ to_f32_chunk(start, len) / to_f64_chunk:storage-order 窗口(S4)
├──► writer.encode(NpyArray) → Bytes (字节级对齐 np.save)
└──► npz.decode_npz(Bytes) → NpzArchive (成员透传 reader.decode;只读 + 未压缩,v0.3.0 C1)
adapter/moonnum NpyArray → moonNum NdArray(S6,读方向,f32/f64 LE;失败用适配器自己的 AdapterError)
error enum NpyError(19 构造子:13 NPY + 6 NPZ)贯穿所有层的 Result 失败通道
cli parse_args · run_inspect · run_validate · run_dump · render_error(纯逻辑,可黑盒测试)
│
▼
cmd/main 薄壳:@fs 读字节 + extern "c" exit 设退出码(唯一 IO 边界)moon-npy/
├── AGENTS.md # M0 实测固化的工具链事实与项目约定(权威)
├── LICENSE # Apache-2.0
├── moon.mod # 模块清单(ShunjunGu/moon-npy, native)
├── src/
│ ├── format/ # magic + version 常量
│ ├── header/ # v1/v2/v3 header lexer + parser
│ ├── dtype/ # dtype codec + endian
│ ├── reader/ # decode(Bytes) -> NpyArray
│ ├── writer/ # encode(NpyArray) -> Bytes(字节级对齐 np.save)
│ ├── npz/ # NPZ ZIP 容器读取(np.savez;只读 + 未压缩,v0.3.0 C1)
│ ├── error/ # enum NpyError + Result
│ ├── cli/ # inspect / validate / dump 纯逻辑(parse_args + 渲染,无 IO)
│ └── adapter/moonnum/ # S6 读方向适配 amor2025/moonNum(本仓唯一第三方依赖)
├── cmd/main/ # CLI 可执行薄壳(@fs 读字节 + extern "c" exit 设退出码)
├── tests/ # *_test.mbt(156,含 edge / fuzz / security / property / adapter / npz)+ fixtures/(31 *.npy + 3 *.npz + expected.json / npz_expected.json)
├── interoperability/ # generate_fixtures.py / verify_moonbit_output.py / roundtrip.py
├── examples/roundtrip/ # emit harness(decode -> encode -> write,`moon run`)
└── .github/workflows/ # ci.yml(§19:fmt/check/test/coverage/fixture/round-trip)moon fmt # 格式化(CI 用 git diff --exit-code 强制无改动)
moon check --target native # 类型检查
moon test --target native # 156 个单元测试moon test --target native --enable-coverage; moon coverage analyze
moon coverage report -f summary # 当前 core parser 98.5%、overall 92.4%python interoperability/generate_fixtures.py --check # fixture 未漂移
python interoperability/roundtrip.py # 31 fixture 字节级 round-tripInstall
Download zipPure MoonBit reader/writer for the NumPy NPY binary array format.
Dependencies