Skip to content

Commit

Permalink
Merge pull request #67 from krrutkow/master
Browse files Browse the repository at this point in the history
Batch of bug fixes
  • Loading branch information
krrutkow authored May 10, 2021
2 parents 9e6f733 + f2b3461 commit 83d66c9
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
matrix:
version:
- '1.5'
- '1.6-nightly'
- '1.6'
- 'nightly'
os:
- ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "CBinding"
uuid = "d43a6710-96b8-4a2d-833c-c424785e5374"
authors = ["Keith Rutkowski <[email protected]>"]
version = "1.0.2"
version = "1.0.3"

[deps]
Clang_jll = "0ee61d77-7f21-5576-8119-9fcc46b10100"
Expand Down
76 changes: 70 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ That's all that is needed to create a couple C types and a function binding in J

C API's usually come with header files, so let's just use those to create the Julia bindings and save some effort.
By default, bindings are generated from the code directly written in C string macros and header files explicitly included in them, but not headers included by those headers.
[See the `i` string macro option](#options-for-c)) to allow parsing certain implicitly included headers as well.
[See the `i` string macro option](#options-for-c) to allow parsing certain implicitly included headers as well.

```jl
julia> c"""
Expand Down Expand Up @@ -203,11 +203,12 @@ These kinds of situations can be handled with combinations of the following stri
- `d` - defer conversion of the C code block; successive blocks marked with `d` will keep deferring until a block without it (its options will be used for processing the deferred blocks)
- `f` - don't create bindings for `extern` functions
- `i` - also parse implicitly included headers that are related (in the same directory or subdirectories) to explicitly included headers
- `j` - also define bindings with Julian names (name collisions likely)
- `j` - provide additional bindings using Julian names (name collisions likely)
- `J` - provide additional bindings using Julian names with annotated user-defined types (using `struct_`, `union_`, or `enum_` prefixes)
- `m` - skip conversion of C macros
- `n` - show warnings for macros or inline functions that are skipped
- `n` - show warnings for macros or inline functions that are skipped (and other conversion issues)
- `p` - mark the C code as "private" content that will not be exported
- `q` - quietly parse the block of C code, suppressing any compiler messages
- `q` - quietly parse the block of C code, suppressing any compiler/linker messages
- `r` - the C code is only a reference to something in C-land and bindings are not to be generated
- `s` - skip processing of this block of C code
- `t` - skip conversion of C types
Expand All @@ -222,7 +223,7 @@ julia> c"""

julia> c"""
struct File { // do not include this type in module exports, and suppress compiler messages
FILE *f;
FILE *f;
};
"""pq;
```
Expand Down Expand Up @@ -320,7 +321,7 @@ User-defined aggregate types (`struct` and `union`) have several ways to be cons
- `t = c"struct T"(i = 123)` - zero-ed immutable object with field `i` initialized to 123
- `t = c"struct T"(t, i = 321)` - copy of `t` with field `i` initialized to 321

These objects are immutable and changing fields will have no effect, so a copy must be constructed with the desired field overrides or ((pointers must be used)[#working-with-pointers]).
These objects are immutable and changing fields will have no effect, so a copy must be constructed with the desired field overrides or [pointers must be used](#working-with-pointers).
Nested field access is transparent, and performance should match that of accessing fields within standard Julia immutable structs.

Statically-sized arrays (i.e. `c"typedef int IntArray[4];"`) can be constructed:
Expand Down Expand Up @@ -433,6 +434,69 @@ julia> c"set_callback"(saferadd)
```


## Getting help

Unless explicitly disabled, the generated bindings include doc-strings.
An attempt is made at converting any structured comments from the C blocks into somewhat equivalent doc-strings, as this example illustrates:

```jl
help?> libsdl2.SDL_CreateRGBSurface
extern SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)

Defined at SDL_surface.h:130 (file:///usr/include/SDL2/SDL_surface.h)

Allocate and free an RGB surface.

Details
=========

If the depth is 4 or 8 bits, an empty palette is allocated for the surface. If the depth is greater than 8 bits, the pixel format is set
using the flags '[RGB]mask'.

If the function runs out of memory, it will return NULL.

Parameters
============

• flags: The flags are obsolete and should be set to 0.

• width: The width in pixels of the surface to create.

• height: The height in pixels of the surface to create.

• depth: The depth in bits of the surface to create.

• Rmask: The red mask of the surface to create.

• Gmask: The green mask of the surface to create.

• Bmask: The blue mask of the surface to create.

• Amask: The alpha mask of the surface to create.

```

However, if such exquisite documentation cannot be generated, the doc-string simply conveys the item's original C definition:

```jl
help?> libclang.clang_visitChildren
unsigned int clang_visitChildren(CXCursor parent, CXCursorVisitor visitor, CXClientData client_data)

Defined at Index.h:4189 (file:///usr/include/clang-c/Index.h)

help?> libclang.CXCursor
struct {
enum CXCursorKind kind;
int xdata;
const void *data[3];
}

Defined at Index.h:2664 (file:///usr/include/clang-c/Index.h)
```

Check for comments near the referenced definition location for C documentation that libclang failed to associate with the binding.


# Any gotchas?

Since Julia does not yet provide `incomplete type` (please voice your support of the feature here: https://github.com/JuliaLang/julia/issues/269), abstract types are used to allow forward declarations in C.
Expand Down
40 changes: 31 additions & 9 deletions src/accessors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function loadbytes(::Type{int}, isptr::Bool, bytes::Integer, bits::Integer, size
if isptr
push!(expr, :(load |= $(uint)(unsafe_load(Core.Intrinsics.bitcast(Cptr{UInt8}, ca), $(bytes+i)))))
else
push!(expr, :(load |= $(uint)(getfield(ca, :mem)[$(bytes+i)])))
push!(expr, :(load |= $(uint)(getfield(getfield(ca, :mem), $(bytes+i)))))
end
end
end
Expand All @@ -69,7 +69,7 @@ function loadbytes(::Type{int}, isptr::Bool, bytes::Integer, bits::Integer, size
end


function storebytes(::Type{int}, isptr::Bool, bytes::Integer, bits::Integer, size::Integer) where {int<:Integer}
function storebytes(::Type{int}, casize::Integer, isptr::Bool, bytes::Integer, bits::Integer, size::Integer) where {int<:Integer}
expr = []

uint = unsigned(int)
Expand All @@ -81,7 +81,10 @@ function storebytes(::Type{int}, isptr::Bool, bytes::Integer, bits::Integer, siz
if isptr
push!(expr, :(unsafe_store!(Core.Intrinsics.bitcast(Cptr{UInt8}, ca), store & 0xff, $(bytes+i))))
else
push!(expr, :(mem = (mem[1:$(bytes+i-1)]..., UInt8(store & 0xff), mem[$(bytes+i+1):end]...,)))
mem = Expr(:tuple,
(x == bytes+i ? :(UInt8(store & 0xff)::UInt8) : :(getfield(mem, $(x))) for x in 1:casize)...,
)
push!(expr, :(mem = $(mem)))
end
end
push!(expr, :(store = store >> 8))
Expand All @@ -93,11 +96,21 @@ end



load(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}) where {CA<:Union{Caggregate, Carray, Cptr{<:Caggregates}}, t, u, i, o, b, s} = load(ca, Tuple{t, u, i, o, b, s}, Val(!(CA <: Cptr) || s >= 0))
function load(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}) where {CA<:Union{Caggregate, Carray, Cptr{<:Caggregates}}, t, u, i, o, b, s}
return load(ca, Tuple{t, u, i, o, b, s}, Val(!(CA <: Cptr) || s >= 0))
end

function load(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}, ::Val) where {CA<:Union{Caggregate, Carray}, t, u, i<:Nothing, o, b, s}
@generated function load(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}, ::Val) where {CA<:Union{Caggregate, Carray}, t, u, i<:Nothing, o, b, s}
u <: Union{Caggregate, Carray} || return error("Unexpected type $(t) in load function")
return u(undef, getfield(ca, :mem)[o+1:o+sizeof(u)])

expr = quote end

mem = Expr(:tuple,
(:(getfield(getfield(ca, :mem), $(x))) for x in o+1:o+sizeof(u))...,
)
push!(expr.args, :(return u(undef, $(mem))))

return expr
end

function load(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}, ::Val{false}) where {CA<:Cptr{<:Caggregates}, t, u, i, o, b, s}
Expand All @@ -120,9 +133,18 @@ end

store!(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}, val) where {CA<:Union{Caggregate, Carray, Cptr{<:Caggregates}}, t, u, i, o, b, s} = store!(ca, Tuple{t, u, i, o, b, s}, Val(!(CA <: Cptr) || s >= 0), val)

function store!(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}, ::Val, val) where {CA<:Union{Caggregate, Carray}, t, u, i<:Nothing, o, b, s}
@generated function store!(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}, ::Val, val) where {CA<:Union{Caggregate, Carray}, t, u, i<:Nothing, o, b, s}
u <: Union{Caggregate, Carray} || return error("Unexpected type $(t) in store! function")
return CA(undef, (getfield(ca, :mem)[1:o]..., getfield(convert(u, val), :mem)..., getfield(ca, :mem)[o+sizeof(u)+1:end]...,))

expr = quote end

push!(expr.args, :(v = convert(u, val)))
mem = Expr(:tuple,
(x-o in 1:sizeof(u) ? :(getfield(getfield(v, :mem), $(x-o))) : :(getfield(getfield(ca, :mem), $(x))) for x in 1:sizeof(CA))...,
)
push!(expr.args, :(return CA(undef, $(mem))))

return expr
end

function store!(ca::CA, ::Type{Tuple{t, u, i, o, b, s}}, ::Val{false}, val) where {CA<:Cptr{<:Caggregates}, t, u, i, o, b, s}
Expand All @@ -137,7 +159,7 @@ end
uint = unsigned(i)
push!(expr.args, :(store = Core.Intrinsics.bitcast($(uint), convert($(u), val))))
s >= 0 && append!(expr.args, storemasked(i, CA <: Cptr, o, b, s))
append!(expr.args, storebytes(i, CA <: Cptr, o, b, s))
append!(expr.args, storebytes(i, sizeof(CA), CA <: Cptr, o, b, s))
push!(expr.args, CA <: Cptr ? :(return val) : :(return CA(undef, mem)))

return expr
Expand Down
3 changes: 2 additions & 1 deletion src/arrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ Base.eltype(::Type{CA}) where {T, N, CA<:Carrays{T, N}} = T
Base.convert(::Type{CA}, t::Tuple) where {CA<:Carray} = CA(t...)

Base.convert(::Type{CA}, str::String) where {T<:Union{Int8, UInt8, Cconst{Int8}, Cconst{UInt8}}, CA<:Carray{T}} = CA(str...)
Base.String(ca::Carray{T}) where {T<:Union{Int8, UInt8, Cconst{Int8}, Cconst{UInt8}}} = String(map(t -> reinterpret(UInt8, convert(unqualifiedtype(T), t)), collect(ca)))
Base.convert(::Type{String}, ca::Carray{T}) where {T<:Union{Int8, UInt8, Cconst{Int8}, Cconst{UInt8}}} = String(map(t -> reinterpret(UInt8, convert(unqualifiedtype(T), t)), collect(ca)))
Base.String(ca::Carray{T}) where {T<:Union{Int8, UInt8, Cconst{Int8}, Cconst{UInt8}}} = convert(String, ca)
Base.show(io::IO, ca::Carray{T}) where {T<:Union{Int8, UInt8, Cconst{Int8}, Cconst{UInt8}}} = show(io, String(ca))


Expand Down
44 changes: 25 additions & 19 deletions src/comments.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function Markdown.MD(ctx::Context, cursor::CXCursor)

loc = getlocation(cursor)
if isnothing(loc)
loc = "Defined in the C Standard Library"
loc = "Defined by the parser/compiler"
else
loc = first(loc)
loc = loc.file == header(ctx) ? getblock(ctx, loc).loc : loc
Expand All @@ -40,7 +40,7 @@ function Markdown.MD(ctx::Context, cursor::CXCursor, comment::CXComment)
hasParams = false
hasReturns = false

contents = Markdown.MD(ctx, cursor).content
contents = []
for ind in 1:clang_Comment_getNumChildren(comment)
child = clang_Comment_getChild(comment, ind-1)
kind = clang_Comment_getKind(child)
Expand All @@ -51,17 +51,23 @@ function Markdown.MD(ctx::Context, cursor::CXCursor, comment::CXComment)
end

if kind == CXComment_Paragraph
Bool(clang_Comment_isWhitespace(child)) || push!(contents, Markdown.Paragraph(child))
Bool(clang_Comment_isWhitespace(child)) || push!(contents, Markdown.Paragraph(ctx, child))
elseif kind == CXComment_BlockCommand
para = clang_BlockCommandComment_getParagraph(child)
para = Markdown.Paragraph(para)
para = Markdown.Paragraph(ctx, para)

num = clang_BlockCommandComment_getNumArgs(child)
cmd = _string(clang_BlockCommandComment_getCommandName, child)
if cmd == "brief" || cmd == "par" || cmd == "paragraph"
push!(contents, para)
elseif cmd == "note" || cmd == "warning" || cmd == "deprecated"
para = Markdown.Paragraph(["$(uppercase(cmd)):", para.content...])
elseif cmd == "bug" || cmd == "note" || cmd == "warning" || cmd == "deprecated" || cmd == "attention"
category =
cmd == "bug" ? "danger" :
cmd == "note" ? "info" :
cmd == "warning" ? "warning" :
cmd == "deprecated" ? "warning" :
cmd == "attention" ? "danger" : "info"
para = Markdown.Admonition("danger", titlecase(cmd), [para])
push!(contents, para)
elseif cmd == "sa" || cmd == "see"
para = isempty(para.content) ? para : Markdown.Paragraph("See also: [`$(strip(first(para.content)))`](@ref)")
Expand All @@ -86,7 +92,7 @@ function Markdown.MD(ctx::Context, cursor::CXCursor, comment::CXComment)
push!(contents, Markdown.List(para))
end
else
@warn "Unhandled comment block-command comment: $(cmd)"
getblock(ctx).flags.notify && @warn "Unhandled comment block-command: $(cmd)"
end
elseif kind == CXComment_ParamCommand
if !hasParams
Expand All @@ -100,26 +106,26 @@ function Markdown.MD(ctx::Context, cursor::CXCursor, comment::CXComment)
end
end

addParameter(contents, child)
addParameter(ctx, contents, child)
elseif kind == CXComment_VerbatimBlockCommand
push!(contents, Markdown.Code(child))
push!(contents, Markdown.Code(ctx, child))
elseif kind == CXComment_VerbatimLine
text = _string(clang_VerbatimLineComment_getText, child)
push!(contents, Markdown.Paragraph(text))
else
@warn "Unhandled comment document child: $(kind)"
getblock(ctx).flags.notify && @warn "Unhandled comment document child: $(kind)"
end
end

if length(contents) > 1 && contents[end] isa Markdown.Header
contents = contents[1:end-1]
end

return Markdown.MD(contents)
return Markdown.MD([Markdown.MD(ctx, cursor).content..., contents...])
end


function Markdown.Paragraph(cxcomment::CXComment)
function Markdown.Paragraph(ctx::Context, cxcomment::CXComment)
contents = []

for ind in 1:clang_Comment_getNumChildren(cxcomment)
Expand All @@ -130,19 +136,19 @@ function Markdown.Paragraph(cxcomment::CXComment)
text = _string(clang_TextComment_getText, child)
push!(contents, text)
elseif kind == CXComment_InlineCommand
addInline(contents, child)
addInline(ctx, contents, child)
elseif kind == CXComment_HTMLStartTag || kind == CXComment_HTMLEndTag
# TODO: handle HTML stuff...
else
@warn "Unhandled comment paragraph child: $(kind)"
getblock(ctx).flags.notify && @warn "Unhandled comment paragraph child: $(kind)"
end
end

return Markdown.Paragraph(contents)
end


function Markdown.Code(cxcomment::CXComment)
function Markdown.Code(ctx::Context, cxcomment::CXComment)
lines = []

for ind in 1:clang_Comment_getNumChildren(cxcomment)
Expand All @@ -155,15 +161,15 @@ function Markdown.Code(cxcomment::CXComment)
# a line with newlines is probably incorrectly parsed, so only keep before a newline
push!(lines, first(split(line, '\n', limit=2)))
else
@warn "Unhandled comment code-block child: $(kind)"
getblock(ctx).flags.notify && @warn "Unhandled comment code-block child: $(kind)"
end
end

return Markdown.Code(join(lines, '\n'))
end


function addInline(contents, cxcomment)
function addInline(ctx::Context, contents, cxcomment)
num = clang_InlineCommandComment_getNumArgs(cxcomment)
cmd = _string(clang_InlineCommandComment_getCommandName, cxcomment)

Expand All @@ -181,15 +187,15 @@ function addInline(contents, cxcomment)
end


function addParameter(contents, cxcomment)
function addParameter(ctx::Context, contents, cxcomment)
num = clang_Comment_getNumChildren(cxcomment)
num == 1 || error("Incorrect number of parameter-command comment children")

para = clang_Comment_getChild(cxcomment, 0)
clang_Comment_getKind(para) == CXComment_Paragraph || error("Expected a parameter-command comment paragraph")

param = _string(clang_ParamCommandComment_getParamName, cxcomment)
param = Markdown.Paragraph([Markdown.Code(param), ":", Markdown.Paragraph(para).content...,])
param = Markdown.Paragraph([Markdown.Code(param), ":", Markdown.Paragraph(ctx, para).content...,])

if !isempty(contents) && contents[end] isa Markdown.List
push!(contents[end].items, param)
Expand Down
Loading

2 comments on commit 83d66c9

@krrutkow
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/36426

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.0.3 -m "<description of version>" 83d66c9749d501614bc0d61a4998f2b79c5fb35c
git push origin v1.0.3

Please sign in to comment.