Skip to content

Commit

Permalink
Use statically-defined type info instead of relying on reflection.
Browse files Browse the repository at this point in the history
  • Loading branch information
maleadt committed Mar 2, 2023
1 parent 49da097 commit f9d613a
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 170 deletions.
1 change: 0 additions & 1 deletion src/ObjectiveC.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ include("primitives.jl")
include("methods.jl")

# Calling Machinery
include("call.jl")
include("syntax.jl")

# High-level functionality
Expand Down
90 changes: 0 additions & 90 deletions src/call.jl

This file was deleted.

45 changes: 24 additions & 21 deletions src/foundation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,37 @@ module Foundation

using ..ObjectiveC

export YES, NO, nil, hostname

for c in :[NSObject
NSBundle
NSString
NSArray
NSHost].args
@eval $(Expr(:export, c))
@eval const $c = Class($(Expr(:quote, c)))
end
export YES, NO, nil

const YES = true
const NO = false
const nil = C_NULL

toobject(s::String) = @objc [[NSString alloc] initWithUTF8String:s]

hostname() =
unsafe_string(@objc [[[NSHost currentHost] localizedName] UTF8String])

function loadbundle(path)
bundle = @objc [NSBundle bundleWithPath:path]
bundle.ptr |> Int |> int2bool || error("Bundle $path not found")
loadedStuff = @objc [bundle load]
loadedStuff |> int2bool || error("Couldn't load bundle $path")
return
export NSUInteger

const NSUInteger = Culong


export NSString

struct NSString <: Object
ptr::id
end
Base.unsafe_convert(::Type{id}, str::NSString) = str.ptr

framework(name) = loadbundle("/System/Library/Frameworks/$name.framework")
NSString() = NSString(@objc [NSString string]::id)
NSString(data::String) = NSString(@objc [NSString stringWithUTF8String :data::Ptr{UInt8}]::id)
Base.length(s::NSString) = Int(@objc [s::id length]::NSUInteger)

export NSHost, hostname

struct NSHost <: Object
ptr::id
end
Base.unsafe_convert(::Type{id}, host::NSHost) = host.ptr

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

end
4 changes: 2 additions & 2 deletions src/memory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

export release, retain

release(obj) = @objc [obj release]
release(obj) = @objc [obj::id release]::Cvoid

retain(obj) = @objc [obj retain]
retain(obj) = @objc [obj::id retain]::Cvoid
34 changes: 18 additions & 16 deletions src/primitives.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export @sel_str, Selector, Class, Object
export @sel_str, Selector, Class, class, Object, id


# Selectors

selname(s::Ptr{Cvoid}) =
ccall(:sel_getName, Ptr{Cchar}, (Ptr{Cvoid},),
s) |> unsafe_string
ccall(:sel_getName, Ptr{Cchar}, (Ptr{Cvoid},), s) |> unsafe_string

struct Selector
ptr::Ptr{Cvoid}
Expand All @@ -14,8 +14,7 @@ end
Base.unsafe_convert(::Type{Ptr{Cvoid}}, sel::Selector) = sel.ptr

function Selector(name)
Selector(ccall(:sel_registerName, Ptr{Cvoid}, (Ptr{Cchar},),
pointer(string(name))))
Selector(ccall(:sel_registerName, Ptr{Cvoid}, (Ptr{Cchar},), name))
end

macro sel_str(name)
Expand All @@ -29,6 +28,7 @@ function Base.show(io::IO, sel::Selector)
show(io, string(name(sel)))
end


# Classes

struct Class
Expand All @@ -38,8 +38,7 @@ end

Base.unsafe_convert(::Type{Ptr{Cvoid}}, class::Class) = class.ptr

classptr(name) = ccall(:objc_getClass, Ptr{Cvoid}, (Ptr{Cchar},),
pointer(string(name)))
classptr(name) = ccall(:objc_getClass, Ptr{Cvoid}, (Ptr{Cchar},), name)

function Class(name)
ptr = classptr(name)
Expand Down Expand Up @@ -79,18 +78,21 @@ function Base.methods(class::Class)
return map(meth->selname(meth), meths)
end


# Objects

mutable struct Object
ptr::Ptr{Cvoid}
end
# Object is an abstract type, so that we can define subtypes with constructors.
# The expected interface is that any subtype of Object should be convertible to id.

abstract type OpaqueObject end
const id = Ptr{OpaqueObject}

Base.unsafe_convert(::Type{Ptr{Cvoid}}, obj::Object) = obj.ptr
abstract type Object end
# interface: object of a subtype of Object should be convertible to id

class(obj) =
ccall(:object_getClass, Ptr{Cvoid}, (Ptr{Cvoid},),
obj) |> Class
class(obj::Union{Object,id}) =
ccall(:object_getClass, Ptr{Cvoid}, (id,), obj) |> Class

Base.methods(obj::Object) = methods(class(obj))
Base.methods(obj::Union{Object,id}) = methods(class(obj))

Base.show(io::IO, obj::Object) = print(io, "Object{", class(obj), "}")
Base.show(io::IO, obj::T) where {T <: Object} = print(io, "$T (object of type ", class(obj), ")")
99 changes: 68 additions & 31 deletions src/syntax.jl
Original file line number Diff line number Diff line change
@@ -1,46 +1,83 @@
export @objc, @classes

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

function flatvcat(ex::Expr)
any(ex->isexpr(ex, :row), ex.args) || return ex
flat = Expr(:hcat)
for row in ex.args
isexpr(row, :row) ?
push!(flat.args, row.args...) :
push!(flat.args, row)
end
return calltransform(flat)
end
callerror() = error("ObjectiveC call: use [obj method]::typ or [obj method :param::typ ...]::typ")

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

# parse the call
Meta.isexpr(call, :hcat) || callerror()
obj, method, args... = call.args

function calltransform(ex::Expr)
obj = objcm(ex.args[1])
args = ex.args[2:end]
isempty(args) && callerror()
# the method should be a simple symbol. the resulting selector name includes : for args
method isa Symbol || callerror()
sel = String(method) * ":"^(length(args))

if isexpr(args[1], Symbol)
length(args) > 1 && callerror()
return :($message($obj, $(Selector(args[1]))))
end
# deconstruct the arguments, which should all be typed expressions
argtyps, argvals = [], []
for arg in args
Meta.isexpr(arg, :(::)) || callerror()
val, typ = arg.args
if val isa QuoteNode
# this comes from a prepended symbol indicating another arg
val = val.value
end
push!(argvals, val)
push!(argtyps, typ)
end

all(arg->iscall(arg, :(:)) && isexpr(arg.args[1], Symbol), args) || callerror()
msg = join(vcat([arg.args[2] for arg in args], ""), ":") |> Selector
args = [objcm(arg.args[3]) for arg in args]
:($message($obj, $msg, $(args...)))
# the object should be a class (single symbol) or an instance (var + typeassert)
ex = if obj isa Symbol
# class
class_message(obj, sel, rettyp, argtyps, argvals)
elseif Meta.isexpr(obj, :(::))
# instance
val, typ = obj.args
if val isa Expr
# possibly dealing with a nested expression, so recurse
quote
obj = $(objcm(obj))
$(instance_message(:obj, sel, rettyp, argtyps, argvals))
end
else
instance_message(esc(val), sel, rettyp, argtyps, argvals)
end
# XXX: do something with the instance type?
else
callerror()
end

return ex
end

objcm(ex::Expr) =
isexpr(ex, :hcat) ? calltransform(ex) :
isexpr(ex, :vcat) ? flatvcat(ex) :
isexpr(ex, :block, :let) ? Expr(:block, map(objcm, ex.args)...) :
ex
function class_message(class_name, msg, rettyp, argtyps, argvals)
quote
class = Class($(String(class_name)))
sel = Selector($(String(msg)))
ccall(:objc_msgSend, $(esc(rettyp)),
(Ptr{Cvoid}, Ptr{Cvoid}, $(map(esc, argtyps)...)),
class, sel, $(map(esc, argvals)...))
end
end

objcm(ex) = ex
function instance_message(instance, msg, rettyp, argtyps, argvals)
quote
sel = Selector($(String(msg)))
ccall(:objc_msgSend, $(esc(rettyp)),
(id, Ptr{Cvoid}, $(map(esc, argtyps)...)),
$instance, sel, $(map(esc, argvals)...))
end
end

macro objc(ex)
esc(objcm(ex))
objcm(ex)
end


# Import Classes

macro classes(names)
Expand Down
26 changes: 17 additions & 9 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@ using Test

using ObjectiveC

# smoke test
let data = "test"
ptr = @objc [NSString stringWithUTF8String :data::Ptr{UInt8}]::id
@test ptr isa id
@test class(ptr) isa Class
@test "length" in methods(ptr)
@test 4 == @objc [ptr::id length]::Culong
end

@testset "foundation" begin

using .Foundation

# smoke test
let str = @objc [NSString new]
@test str isa Object
end

@test "UTF8String" in methods(NSString)
@testset "NSString" begin
str = NSString()
@test length(str) == 0

let obj = @objc [NSHost currentHost]
@test contains(sprint(show, obj), "NSHost")
str = NSString("test")
@test length(str) == 4
end

@test hostname() == gethostname()
@testset "NSHost" begin
@test hostname() == gethostname()
end

end

0 comments on commit f9d613a

Please sign in to comment.