Skip to content

Commit

Permalink
os: add pub (mut f File) write_le[T](x T) !, `pub (mut f File) writ…
Browse files Browse the repository at this point in the history
…e_be[T](x T) !` + read equivalents, add tests
  • Loading branch information
spytheman committed Nov 30, 2024
1 parent 602b097 commit 6f4c59e
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 0 deletions.
115 changes: 115 additions & 0 deletions vlib/os/file_le_be.c.v
Original file line number Diff line number Diff line change
@@ -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')!
}
57 changes: 57 additions & 0 deletions vlib/os/file_le_be_test.v
Original file line number Diff line number Diff line change
@@ -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()
}

0 comments on commit 6f4c59e

Please sign in to comment.