From 6f4c59e002e540261adf97da13fc4224932c9cd6 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 30 Nov 2024 23:35:03 +0200 Subject: [PATCH] os: add `pub (mut f File) write_le[T](x T) !`, `pub (mut f File) write_be[T](x T) !` + read equivalents, add tests --- vlib/os/file_le_be.c.v | 115 ++++++++++++++++++++++++++++++++++++++ vlib/os/file_le_be_test.v | 57 +++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 vlib/os/file_le_be.c.v create mode 100644 vlib/os/file_le_be_test.v diff --git a/vlib/os/file_le_be.c.v b/vlib/os/file_le_be.c.v new file mode 100644 index 00000000000000..d7c7d7a8258eec --- /dev/null +++ b/vlib/os/file_le_be.c.v @@ -0,0 +1,115 @@ +module os + +// write_le writes an unsigned number value to the file. +// It assumes that the value should be stored in a *little endian order*. +// If the machine is a big endian one, it will first convert the big endian value to a little endian one. +// It is safe to use as a cross platform way to write data, for which you have to use a predefined order (defined in a file format spec). +pub fn (mut f File) write_le[T](data T) ! { + mut serialized := data + $if big_endian { + serialized = swap_bytes(serialized) + } + C.errno = 0 // needed for tcc + check_fwrite(C.fwrite(voidptr(&serialized), sizeof(T), 1, f.cfile))! +} + +// write_be writes an unsigned number value to the file. +// It assumes that the value should be stored in a *big endian order*. +// If the machine is a little endian one, it will first convert the little endian value to a big endian one. +// It is safe to use as a cross platform way to write data, for which you have to use a predefined order (defined in a file format spec). +pub fn (mut f File) write_be[T](data T) ! { + mut serialized := data + $if little_endian { + serialized = swap_bytes(serialized) + } + C.errno = 0 // needed for tcc + check_fwrite(C.fwrite(voidptr(&serialized), sizeof(T), 1, f.cfile))! +} + +// read_le reads an unsigned number value from the file. +// It assumes that the value was stored in a *little endian order*. +// If the machine is a big endian one, it will convert the value to a big endian one. +// It is intended to use as a cross platform way to read data, for which the order is known (from the file format spec). +pub fn (mut f File) read_le[T]() !T { + mut serialized := T(0) + C.errno = 0 // needed for tcc + check_fread(C.fread(voidptr(&serialized), sizeof(T), 1, f.cfile))! + $if big_endian { + return swap_bytes(serialized) + } + return serialized +} + +// read_be reads an unsigned number value from the file. +// It assumes that the value was stored in a *big endian order*. +// If the machine is a little endian one, it will convert the value to a little endian one. +// It is intended to use as a cross platform way to read data, for which the order is known (from the file format spec). +pub fn (mut f File) read_be[T]() !T { + mut serialized := T(0) + C.errno = 0 // needed for tcc + check_fread(C.fread(voidptr(&serialized), sizeof(T), 1, f.cfile))! + $if little_endian { + return swap_bytes(serialized) + } + return serialized +} + +// private helpers + +@[inline] +fn swap_bytes_u16(x u16) u16 { + return ((x >> 8) & 0x00FF) | ((x << 8) & 0xFF00) +} + +@[inline] +fn swap_bytes_u32(x u32) u32 { + return ((x >> 24) & 0x0000_00FF) | ((x >> 8) & 0x0000_FF00) | ((x << 8) & 0x00FF_0000) | ((x << 24) & 0xFF00_0000) +} + +@[inline] +fn swap_bytes_u64(x u64) u64 { + return ((x >> 40) & 0x00000000_0000FF00) | ((x >> 24) & 0x00000000_00FF0000) | ((x >> 8) & 0x00000000_FF000000) | ((x << 8) & 0x000000FF_00000000) | ((x << 24) & 0x0000FF00_00000000) | ((x << 40) & 0x00FF0000_00000000) | ((x << 56) & 0xFF000000_00000000) +} + +fn swap_bytes[T](input T) T { + $if T is u8 { + return input + } $else $if T is i8 { + return input + } $else $if T is byte { + return input + } $else $if T is u16 { + return swap_bytes_u16(input) + } $else $if T is u32 { + return swap_bytes_u32(input) + } $else $if T is u64 { + return swap_bytes_u64(input) + } $else $if T is i16 { + return i16(swap_bytes_u16(u16(input))) + } $else $if T is i32 { + return i32(swap_bytes_u32(u32(input))) + } $else $if T is int { + return i32(swap_bytes_u32(u32(input))) + } $else $if T is i64 { + return i64(swap_bytes_u64(u64(input))) + } $else { + panic('type is not supported: ${typeof[T]()}') + } +} + +fn check_cf(x usize, label string) ! { + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if x == 0 { + return error(label) + } +} + +fn check_fwrite(x usize) ! { + check_cf(x, 'fwrite')! +} + +fn check_fread(x usize) ! { + check_cf(x, 'fread')! +} diff --git a/vlib/os/file_le_be_test.v b/vlib/os/file_le_be_test.v new file mode 100644 index 00000000000000..575b78e1e5e046 --- /dev/null +++ b/vlib/os/file_le_be_test.v @@ -0,0 +1,57 @@ +import os + +const tfolder = os.join_path(os.vtmp_dir(), 'os_file_le_be') + +fn testsuite_begin() { + os.mkdir_all(tfolder) or {} + os.chdir(tfolder)! + dump(tfolder) + assert os.is_dir(tfolder) +} + +fn testsuite_end() { + os.rmdir_all(tfolder) or {} +} + +fn test_write_be_read_be() { + fname := os.join_path(tfolder, 'f_be') + os.write_file(os.join_path(tfolder, 'abc'), 'hello')! + mut f := os.open_file(fname, 'wb')! + f.write_be[u8](0x08)! + f.write_be[u16](0x1617)! + f.write_be[u32](0x3233)! + f.write_be[u64](0x6465)! + f.close() + // vfmt off + assert os.read_bytes(fname)! == [ + u8(0x08), + 0x16, 0x17, + 0x00, 0x00, 0x32, 0x33, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, + ] + // vfmt on +} + +fn test_write_le_read_le() { + fname := os.join_path(tfolder, 'f_le') + mut f := os.open_file(fname, 'wb')! + f.write_le[u8](0x08)! + f.write_le[u16](0x1617)! + f.write_le[u32](0x3233)! + f.write_le[u64](0x6465)! + f.close() + // vfmt off + assert os.read_bytes('f_le')! == [ + u8(0x08), + 0x17, 0x16, + 0x33, 0x32, 0x00, 0x00, + 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + // vfmt on + mut r := os.open_file(fname, 'rb')! + assert r.read_le[u8]()! == 0x08 + assert r.read_le[u16]()! == 0x1617 + assert r.read_le[u32]()! == 0x3233 + assert r.read_le[u64]()! == 0x6465 + r.close() +}