Skip to content

Commit

Permalink
Use syntax to represent the (currently unused) type information.
Browse files Browse the repository at this point in the history
  • Loading branch information
maleadt committed Mar 2, 2023
1 parent 9766be4 commit 4a542f5
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 76 deletions.
20 changes: 10 additions & 10 deletions src/foundation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ Base.unsafe_convert(::Type{id}, str::NSString) = str.ptr

Base.cconvert(::Type{id}, str::String) = NSString(str)

NSString() = @objc [NSString string]::NSString
NSString(data::String) = @objc [NSString stringWithUTF8String :data::Ptr{UInt8}]::NSString
Base.length(s::NSString) = Int(@objc [s::NSString length]::NSUInteger)
NSString() = NSString(@objc [NSString string]::id{NSString})
NSString(data::String) = NSString(@objc [NSString stringWithUTF8String :data::Ptr{UInt8}]::id{NSString})
Base.length(s::NSString) = Int(@objc [s::id{NSString} length]::NSUInteger)

export NSHost, hostname

Expand All @@ -35,24 +35,24 @@ end
Base.unsafe_convert(::Type{id}, host::NSHost) = host.ptr

hostname() =
unsafe_string(@objc [[[NSHost currentHost]::NSHost localizedName]::NSString UTF8String]::Ptr{UInt8})
unsafe_string(@objc [[[NSHost currentHost]::id{NSHost} localizedName]::id{NSString} UTF8String]::Ptr{UInt8})


export NSBundle, framework

struct NSBundle <: Object
ptr::id
function NSBundle(ptr::id)
id == nil && error("Couldn't find bundle")
new(ptr)
end
end
Base.unsafe_convert(::Type{id}, bundle::NSBundle) = bundle.ptr

NSBundle(path::Union{String,NSString}) = @objc [NSBundle bundleWithPath :path::NSString]::NSBundle
function NSBundle(path::Union{String,NSString})
ptr = @objc [NSBundle bundleWithPath :path::id{NSString}]::id{NSBundle}
ptr == nil && error("Couldn't find bundle '$path'")
NSBundle(ptr)
end

function load(bundle::NSBundle)
loaded = @objc [bundle::NSBundle load]::Bool
loaded = @objc [bundle::id{NSBundle} load]::Bool
loaded || error("Couldn't load bundle")
end

Expand Down
4 changes: 2 additions & 2 deletions src/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ abstract type OpaqueObject end
const id = Ptr{OpaqueObject}

abstract type Object end
# interface: subtypes of Object should be constructible from and convertible to `id`s
# (i.e., requiring `MyObj(::id)` and `Base.unsafe_convert(::Type{id}, ::MyObj)`)
# interface: subtypes of Object should be convertible to `id`s
# (i.e., `Base.unsafe_convert(::Type{id}, ::MyObj)`)

class(obj::Union{Object,id}) =
ccall(:object_getClass, Ptr{Cvoid}, (id,), obj) |> Class
Expand Down
68 changes: 14 additions & 54 deletions src/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,17 @@ export @objc, @classes

callerror() = error("ObjectiveC call: use [obj method]::typ or [obj method :param::typ ...]::typ")

ctype(T::Type) = T
ctype(T::Type{<:Object}) = id

# version of ccall that calls `ctype` on types to figure out if we need pass-by-reference,
# further relying on `Base.unsafe_convert` to get a hold of such a reference.
# it works around the limitation that ccall type tuples need to be literals.
# this is a bit of hack, as it relies on internals of ccall.
@inline @generated function byref_ccall(f::Ptr, _rettyp, _types, vals...)
ex = quote end

rettyp = _rettyp.parameters[1]
types = _types.parameters[1].parameters
args = [:(vals[$i]) for i in 1:length(vals)]

# unwrap
reference_rettyp = ctype(rettyp)
reference_types = map(ctype, types)

# cconvert
cconverted = [Symbol("cconverted_$i") for i in 1:length(vals)]
for (dst, typ, src) in zip(cconverted, reference_types, args)
append!(ex.args, (quote
$dst = Base.cconvert($typ, $src)
end).args)
end

# unsafe_convert
unsafe_converted = [Symbol("unsafe_converted_$i") for i in 1:length(vals)]
for (dst, typ, src) in zip(unsafe_converted, reference_types, cconverted)
append!(ex.args, (quote
$dst = Base.unsafe_convert($typ, $src)
end).args)
end

call = Expr(:foreigncall, :f, reference_rettyp, Core.svec(reference_types...), 0, QuoteNode(:ccall), unsafe_converted..., cconverted...)
push!(ex.args, call)

# re-wrap if necessary
if rettyp != reference_rettyp
ex = quote
val = $(ex)
$rettyp(val)
end
end

return ex
end

function objcm(ex)
# handle a single call, [dst method: param::typ]::typ

# parse the call return type
Meta.isexpr(ex, :(::)) || callerror()
call, rettyp = ex.args
if Meta.isexpr(rettyp, :curly) && rettyp.args[1] == :id
# we're returning an object pointer, with additional type info.
# currently that info isn't used, so just strip it
rettyp = rettyp.args[1]
end

# parse the call
Meta.isexpr(call, :hcat) || callerror()
Expand All @@ -74,6 +31,11 @@ function objcm(ex)
# this comes from a prepended symbol indicating another arg
val = val.value
end
if Meta.isexpr(typ, :curly) && typ.args[1] == :id
# we're passing an object pointer, with additional type info.
# currently that info isn't used, so just strip it
typ = typ.args[1]
end
push!(argvals, val)
push!(argtyps, typ)
end
Expand Down Expand Up @@ -102,23 +64,21 @@ function objcm(ex)
return ex
end

const msgSend = cglobal(:objc_msgSend)

function class_message(class_name, msg, rettyp, argtyps, argvals)
quote
class = Class($(String(class_name)))
sel = Selector($(String(msg)))
byref_ccall(msgSend, $(esc(rettyp)),
Tuple{Ptr{Cvoid}, Ptr{Cvoid}, $(map(esc, argtyps)...)},
ccall(:objc_msgSend, $(esc(rettyp)),
(Ptr{Cvoid}, Ptr{Cvoid}, $(map(esc, argtyps)...)),
class, sel, $(map(esc, argvals)...))
end
end

function instance_message(instance, msg, rettyp, argtyps, argvals)
quote
sel = Selector($(String(msg)))
byref_ccall(msgSend, $(esc(rettyp)),
Tuple{id, Ptr{Cvoid}, $(map(esc, argtyps)...)},
ccall(:objc_msgSend, $(esc(rettyp)),
(id, Ptr{Cvoid}, $(map(esc, argtyps)...)),
$instance, sel, $(map(esc, argvals)...))
end
end
Expand Down
12 changes: 2 additions & 10 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,9 @@ let data = "test"
@test class(ptr) isa Class
@test "length" in methods(ptr)
@test 4 == @objc [ptr::id length]::Culong
end

# test re-wrapping of Objective-C objects (which are passed by-reference)
struct TestNSString <: Object
canary::Int
ptr::id
TestNSString(ptr::id) = new(42, ptr)
end
let str = @objc [NSString string]::TestNSString
@assert str isa TestNSString
@assert str.canary == 42
# id pointers should be allowed to carry type info
@objc [NSString stringWithUTF8String :data::Ptr{UInt8}]::id{NSString}
end

@testset "foundation" begin
Expand Down

0 comments on commit 4a542f5

Please sign in to comment.