Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add split functions #7

Merged
merged 2 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ makedocs(;
"MemoryViews" => "index.md",
"MemoryViews in interfaces" => "interfaces.md",
"MemoryViews in Base" => "base.md",
"Reference" => "reference.md",
],
authors="Jakob Nybo Nissen",
checkdocs=:public,
Expand Down
5 changes: 5 additions & 0 deletions docs/src/reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Reference
```@autodocs
Modules = [MemoryViews]
Order = [:type, :function]
```
1 change: 1 addition & 0 deletions src/MemoryViews.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,6 @@ MemoryKind(::Type{Union{}}) = NotMemory()

include("construction.jl")
include("basic.jl")
include("experimental.jl")

end # module
123 changes: 123 additions & 0 deletions src/experimental.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# API which I'm not sure should be kept

export split_first, split_last, split_at, split_unaligned

"""
split_first(v::MemoryView{T}) -> Tuple{T, MemoryView{T}}

Return the first element of `v` and all other elements as a new memory view.

This function will throw a `BoundsError` if `v` is empty.

See also: [`split_last`](@ref)

# Examples
```jldoctest
julia> v = MemoryView([0x01, 0x02, 0x03]);

julia> split_first(v)
(0x01, UInt8[0x02, 0x03])

julia> split_first(v[1:1])
(0x01, UInt8[])

julia> split_first(v[1:0])
ERROR: BoundsError: attempt to access 0-element MutableMemoryView{UInt8} at index [1]
[...]
```
"""
function split_first(v::MemoryView)
@boundscheck checkbounds(v, 1)
newref = @inbounds memoryref(v.ref, 1 + (length(v) > 1))
fst = @inbounds v[1]
(fst, typeof(v)(unsafe, newref, length(v) - 1))
end

"""
split_last(v::MemoryView{T}) -> Tuple{T, MemoryView{T}}

Return the last element of `v` and all other elements as a new memory view.

This function will throw a `BoundsError` if `v` is empty.

See also: [`split_first`](@ref)

# Examples
```jldoctest
julia> v = MemoryView([0x01, 0x02, 0x03]);

julia> split_last(v)
(0x03, UInt8[0x01, 0x02])

julia> split_last(v[1:1])
(0x01, UInt8[])

julia> split_last(v[1:0])
ERROR: BoundsError: attempt to access 0-element MutableMemoryView{UInt8} at index [1]
[...]
```
"""
function split_last(v::MemoryView)
@boundscheck checkbounds(v, 1)
lst = @inbounds v[end]
(lst, typeof(v)(unsafe, v.ref, length(v) - 1))
end

"""
split_at(v::T, i::Int) -> Tuple{T, T} where {T <: MemoryView}

Split a memory view into two at an index.

The first will contain all indices in `1:i-1`, the second `i:end`.
This function will throw a `BoundsError` if `i` is not in `1:end+1`.

# Examples
```jldocstest
julia> split_at(MemoryView([1,2,3,4,5]), 2)
([1], [2, 3, 4, 5])

julia> split_at(MemoryView(Int8[1, 2, 3]), 4)
(Int8[1, 2, 3], Int8[])
```
"""
function split_at(v::MemoryView, i::Int)
@boundscheck checkbounds(1:(lastindex(v) + 1), i)
fst = typeof(v)(unsafe, v.ref, i - 1)
ref = i > lastindex(v) ? v.ref : @inbounds memoryref(v.ref, i)
lst = typeof(v)(unsafe, ref, length(v) - i + 1)
(fst, lst)
end

"""
split_unaligned(v::T, ::Val{A}) -> Tuple{T, T} where {T <: MemoryView}

Split memory view `v` into two views `a` and `b`, `a` is the smallest prefix of `v`
that gaurantees `b` is aligned to the integer value `A`.
`A` must be a normal bit-integer, and a power of two in the range 1:64.
If `v` is empty or already aligned, `a` will be empty.
The element type of `v` must be a bitstype.


# Examples:
```
julia> split_unaligned(MemoryView(Int16[1, 2, 3]), Val(8))
(Int16[], Int16[1, 2, 3])

julia> split_unaligned(MemoryView(collect(0x01:0x20))[6:13], Val(8))
(UInt8[0x06, 0x07, 0x08], UInt8[0x09, 0x0a, 0x0b, 0x0c, 0x0d])
```
"""
function split_unaligned(v::MemoryView, ::Val{A}) where {A}
isbitstype(eltype(v)) || error("Alignment can only be computed for views of bitstypes")
A isa Bits || error("Invalid alignment")
in(A, (1, 2, 4, 8, 16, 32, 64)) || error("Invalid alignment")
alignment = A % UInt
mask = alignment - 1
sz = Base.elsize(v)
# Early return here to avoid division by zero: Size sz is statically known,
# this will be compiled away
iszero(sz) && return (typeof(v)(unsafe, v.ref, 0), v)
unaligned_bytes = ((alignment - (UInt(pointer(v)) & mask)) & mask)
n_elements = div(unaligned_bytes, sz % UInt) % Int
@inbounds split_at(v, n_elements + 1)
end
42 changes: 42 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,48 @@ end
@test parent(v) === mem
@test parent(ImmutableMemoryView(mem)) === mem
end

@testset "Split first and last and at" begin
for mem in Any[
MemoryView(b"abcde"),
MemoryView(Any["abc", "def", "ghi"]),
ImmutableMemoryView(rand(2, 2)),
]
@test split_first(mem) == (mem[1], mem[2:end])
@test split_last(mem) == (mem[end], mem[1:(end - 1)])
@test split_at(mem, 1) == (mem[1:0], mem[1:end])
@test split_at(mem, 2) == (mem[1:1], mem[2:end])
@test split_at(mem, lastindex(mem)) == (mem[1:(end - 1)], mem[end:end])
@test split_at(mem, lastindex(mem) + 1) == (mem[1:end], mem[1:0])
mem = mem[2:2]
@test split_first(mem) == (mem[1], mem[2:end])
@test split_last(mem) == (mem[end], mem[1:(end - 1)])
mem = mem[1:0]
@test_throws BoundsError split_first(mem)
@test_throws BoundsError split_last(mem)
end
end

@testset "Split unaligned" begin
for v in Any[["abc", "def"], Union{Int, UInt}[1, 2, 3, 4], Signed[4, 1, 2]]
@test_throws Exception split_unaligned(MemoryView(v), Val(1))
end
v = MemoryView(collect(0x00:0x3f))[2:end]
@test_throws Exception split_unaligned(v, Val(3))
@test_throws Exception split_unaligned(v, Val(0))
@test_throws Exception split_unaligned(v, Val(-2))

@test split_unaligned(v, Val(1)) == split_at(v, 1)
@test split_unaligned(v, Val(4)) == split_at(v, 4)
@test split_unaligned(v, Val(8)) == split_at(v, 8)
@test split_unaligned(v, Val(16)) == split_at(v, 16)

v = MemoryView(collect(0x0000:0x003f))[3:end]
@test split_unaligned(v, Val(1)) == split_at(v, 1)
@test split_unaligned(v, Val(4)) == split_at(v, 1)
@test split_unaligned(v, Val(8)) == split_at(v, 3)
@test split_unaligned(v, Val(16)) == split_at(v, 7)
end
end

@testset "Iterators.reverse" begin
Expand Down
Loading