|
| 1 | +macro jcall(expr) |
| 2 | + return jcall_macro_lower(jcall_macro_parse(expr)...) |
| 3 | +end |
| 4 | + |
| 5 | +function jcall_macro_lower(func, rettype, types, args) |
| 6 | + @debug "args: " func rettype types args |
| 7 | + jtypes = Expr(:tuple, esc.(types)...) |
| 8 | + jargs = Expr(:tuple, esc.(args)...) |
| 9 | + jret = esc(rettype) |
| 10 | + if func isa Expr |
| 11 | + @debug "func" func.head func.args |
| 12 | + obj = resolve_dots(func.args[2]) |
| 13 | + f = string(func.args[1].value) |
| 14 | + return :(jcall($(esc(obj)), $f, $jret, $jtypes, ($jargs)...)) |
| 15 | + elseif func isa QuoteNode |
| 16 | + return :($(esc(func.value))($jtypes, ($jargs)...)) |
| 17 | + end |
| 18 | +end |
| 19 | + |
| 20 | +function resolve_dots(obj) |
| 21 | + if obj isa Expr && obj.head == :. |
| 22 | + return :(jfield($(resolve_dots(obj.args[1])), string($(obj.args[2])))) |
| 23 | + else |
| 24 | + return obj |
| 25 | + end |
| 26 | +end |
| 27 | + |
| 28 | +# @jcall implementation, based on Base.@ccall |
| 29 | +""" |
| 30 | + jcall_macro_parse(expression) |
| 31 | +
|
| 32 | +`jcall_macro_parse` is an implementation detail of `@jcall |
| 33 | +it takes an expression like `:(System.out.println("Hello"::JString)::Nothing)` |
| 34 | +returns: a tuple of `(function_name, return_type, arg_types, args)` |
| 35 | +The above input outputs this: |
| 36 | + (:(System.out.println), Nothing, [:JString], ["Hello]) |
| 37 | +""" |
| 38 | +function jcall_macro_parse(expr::Expr) |
| 39 | + # setup and check for errors |
| 40 | + if !Meta.isexpr(expr, :(::)) |
| 41 | + throw(ArgumentError("@jcall needs a function signature with a return type")) |
| 42 | + end |
| 43 | + rettype = expr.args[2] |
| 44 | + |
| 45 | + call = expr.args[1] |
| 46 | + if !Meta.isexpr(call, :call) |
| 47 | + throw(ArgumentError("@jcall has to take a function call")) |
| 48 | + end |
| 49 | + |
| 50 | + # get the function symbols |
| 51 | + func = let f = call.args[1] |
| 52 | + if Meta.isexpr(f, :.) |
| 53 | + :(($(f.args[2]), $(f.args[1]))) |
| 54 | + elseif Meta.isexpr(f, :$) |
| 55 | + f |
| 56 | + elseif f isa Symbol |
| 57 | + QuoteNode(f) |
| 58 | + else |
| 59 | + throw(ArgumentError("@jcall function name must be a symbol or a `.` node (e.g. `System.out.println`)")) |
| 60 | + end |
| 61 | + end |
| 62 | + |
| 63 | + # detect varargs |
| 64 | + varargs = nothing |
| 65 | + argstart = 2 |
| 66 | + callargs = call.args |
| 67 | + if length(callargs) >= 2 && Meta.isexpr(callargs[2], :parameters) |
| 68 | + argstart = 3 |
| 69 | + varargs = callargs[2].args |
| 70 | + end |
| 71 | + |
| 72 | + # collect args and types |
| 73 | + args = [] |
| 74 | + types = [] |
| 75 | + |
| 76 | + function pusharg!(arg) |
| 77 | + if !Meta.isexpr(arg, :(::)) |
| 78 | + throw(ArgumentError("args in @jcall need type annotations. '$arg' doesn't have one.")) |
| 79 | + end |
| 80 | + push!(args, arg.args[1]) |
| 81 | + push!(types, arg.args[2]) |
| 82 | + end |
| 83 | + |
| 84 | + for i in argstart:length(callargs) |
| 85 | + pusharg!(callargs[i]) |
| 86 | + end |
| 87 | + |
| 88 | + return func, rettype, types, args |
| 89 | +end |
| 90 | + |
0 commit comments