297 lines
9.2 KiB
Odin
297 lines
9.2 KiB
Odin
package jnigen
|
|
|
|
import "core:fmt"
|
|
import "core:strings"
|
|
|
|
Emitter :: struct {
|
|
b: strings.Builder,
|
|
}
|
|
|
|
w :: proc(e: ^Emitter, format: string, args: ..any) {
|
|
fmt.sbprintf(&e.b, format, ..args)
|
|
strings.write_byte(&e.b, '\n')
|
|
}
|
|
|
|
emit_file :: proc(g: ^Gen, package_name: string) -> string {
|
|
e: Emitter
|
|
e.b = strings.builder_make()
|
|
|
|
w(&e, "// GENERATED by jnigen from the JNI spec. Do not edit.")
|
|
w(&e, "//")
|
|
w(&e, "// Every binding returns `ok` -- false means the class, method or field")
|
|
w(&e, "// failed to resolve, or the call raised a Java exception (already printed")
|
|
w(&e, "// to logcat and cleared). Class and method IDs resolve once and are cached.")
|
|
w(&e, "package %s", package_name)
|
|
w(&e, "")
|
|
w(&e, "import \"shared:android\"")
|
|
w(&e, "import \"jni\"")
|
|
w(&e, "")
|
|
|
|
if len(g.class_decls) > 0 {
|
|
w(&e, "//")
|
|
w(&e, "// Object types")
|
|
w(&e, "//")
|
|
w(&e, "")
|
|
for c in g.class_decls {
|
|
w(&e, "// %s", c.java_class)
|
|
w(&e, "%s :: distinct android.jobject", c.odin_name)
|
|
w(&e, "")
|
|
}
|
|
}
|
|
|
|
for b in g.bindings {
|
|
emit_binding(&e, b)
|
|
}
|
|
|
|
return strings.to_string(e.b)
|
|
}
|
|
|
|
@(private = "file")
|
|
emit_signature :: proc(e: ^Emitter, b: Binding) {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
|
|
if b.has_recv {
|
|
fmt.sbprintf(&sb, "%s: %s", b.recv.name, b.recv.type.odin)
|
|
}
|
|
for p in b.params {
|
|
if strings.builder_len(sb) > 0 {
|
|
strings.write_string(&sb, ", ")
|
|
}
|
|
fmt.sbprintf(&sb, "%s: %s", p.name, p.type.odin)
|
|
}
|
|
if allocates(b.result) {
|
|
if strings.builder_len(sb) > 0 {
|
|
strings.write_string(&sb, ", ")
|
|
}
|
|
strings.write_string(&sb, "allocator := context.allocator")
|
|
}
|
|
|
|
if b.result.of == .Void {
|
|
w(e, "%s :: proc(%s) -> (ok: bool) {{", b.odin_name, strings.to_string(sb))
|
|
} else {
|
|
w(e, "%s :: proc(%s) -> (result: %s, ok: bool) {{", b.odin_name, strings.to_string(sb), b.result.odin)
|
|
}
|
|
}
|
|
|
|
@(private = "file")
|
|
emit_binding :: proc(e: ^Emitter, b: Binding) {
|
|
switch b.kind {
|
|
case .Method:
|
|
w(e, "// %s.%s%s", b.java_class, b.java_name, b.sig)
|
|
case .Ctor:
|
|
w(e, "// new %s%s", b.java_class, b.sig)
|
|
case .Field_Get:
|
|
w(e, "// %s.%s : %s (get)", b.java_class, b.java_name, b.sig)
|
|
case .Field_Set:
|
|
w(e, "// %s.%s : %s (set)", b.java_class, b.java_name, b.sig)
|
|
}
|
|
|
|
emit_signature(e, b)
|
|
w(e, " env := jni.env()")
|
|
w(e, " if env == nil {{")
|
|
w(e, " return")
|
|
w(e, " }}")
|
|
w(e, "")
|
|
|
|
// Resolved once; a global ref keeps the class alive past this frame.
|
|
w(e, " @(static) cls: android.jclass")
|
|
w(e, " if cls == nil {{")
|
|
w(e, " cls = jni.class_ref(env, \"%s\")", b.java_class)
|
|
w(e, " if cls == nil {{")
|
|
w(e, " return")
|
|
w(e, " }}")
|
|
w(e, " }}")
|
|
|
|
is_field := b.kind == .Field_Get || b.kind == .Field_Set
|
|
if is_field {
|
|
lookup := b.is_static ? "static_field_id" : "field_id"
|
|
w(e, " @(static) fid: android.jfieldID")
|
|
w(e, " if fid == nil {{")
|
|
w(e, " fid = jni.%s(env, cls, \"%s\", \"%s\")", lookup, b.java_name, b.sig)
|
|
w(e, " if fid == nil {{")
|
|
w(e, " return")
|
|
w(e, " }}")
|
|
w(e, " }}")
|
|
} else {
|
|
lookup := (b.is_static && b.kind != .Ctor) ? "static_method_id" : "method_id"
|
|
w(e, " @(static) mid: android.jmethodID")
|
|
w(e, " if mid == nil {{")
|
|
w(e, " mid = jni.%s(env, cls, \"%s\", \"%s\")", lookup, b.java_name, b.sig)
|
|
w(e, " if mid == nil {{")
|
|
w(e, " return")
|
|
w(e, " }}")
|
|
w(e, " }}")
|
|
}
|
|
w(e, "")
|
|
|
|
switch b.kind {
|
|
case .Method, .Ctor:
|
|
emit_call(e, b)
|
|
case .Field_Get:
|
|
emit_field_get(e, b)
|
|
case .Field_Set:
|
|
emit_field_set(e, b)
|
|
}
|
|
|
|
w(e, "}}")
|
|
w(e, "")
|
|
}
|
|
|
|
// Marshals one argument into `args[idx]`, emitting any temporary the value needs.
|
|
@(private = "file")
|
|
emit_marshal :: proc(e: ^Emitter, p: Param, idx: int) {
|
|
t := p.type
|
|
switch t.of {
|
|
case .Prim:
|
|
w(e, " args[%d].%s = %s(%s)", idx, t.jval, t.jcast, p.name)
|
|
case .String:
|
|
w(e, " _a%d := jni.to_jstring(env, %s)", idx, p.name)
|
|
w(e, " defer jni.free_local(env, android.jobject(_a%d))", idx)
|
|
w(e, " args[%d].l = android.jobject(_a%d)", idx, idx)
|
|
case .Array:
|
|
conv := t.elem == "string" ? "to_string_array" : fmt.tprintf("to_%s_array", t.elem)
|
|
w(e, " _a%d := jni.%s(env, %s)", idx, conv, p.name)
|
|
w(e, " defer jni.free_local(env, android.jobject(_a%d))", idx)
|
|
w(e, " args[%d].l = android.jobject(_a%d)", idx, idx)
|
|
case .Object:
|
|
w(e, " args[%d].l = android.jobject(%s)", idx, p.name)
|
|
case .Void:
|
|
}
|
|
}
|
|
|
|
@(private = "file")
|
|
emit_call :: proc(e: ^Emitter, b: Binding) {
|
|
args_expr := "nil"
|
|
if len(b.params) > 0 {
|
|
w(e, " args := [%d]android.jvalue{{}}", len(b.params))
|
|
for p, i in b.params {
|
|
emit_marshal(e, p, i)
|
|
}
|
|
w(e, "")
|
|
args_expr = "raw_data(args[:])"
|
|
}
|
|
|
|
kind := title(b.result.call)
|
|
switch {
|
|
case b.kind == .Ctor:
|
|
w(e, " _ret := env^^.NewObjectA(env, cls, mid, %s)", args_expr)
|
|
case b.result.of == .Void && b.is_static:
|
|
// A void call has no value to bind.
|
|
w(e, " env^^.CallStaticVoidMethodA(env, cls, mid, %s)", args_expr)
|
|
case b.result.of == .Void:
|
|
w(e, " env^^.CallVoidMethodA(env, android.jobject(%s), mid, %s)", b.recv.name, args_expr)
|
|
case b.is_static:
|
|
w(e, " _ret := env^^.CallStatic%sMethodA(env, cls, mid, %s)", kind, args_expr)
|
|
case:
|
|
w(e, " _ret := env^^.Call%sMethodA(env, android.jobject(%s), mid, %s)", kind, b.recv.name, args_expr)
|
|
}
|
|
|
|
w(e, " if jni.failed(env) {{")
|
|
w(e, " return")
|
|
w(e, " }}")
|
|
emit_result(e, b, "_ret")
|
|
w(e, " ok = true")
|
|
w(e, " return")
|
|
}
|
|
|
|
@(private = "file")
|
|
emit_result :: proc(e: ^Emitter, b: Binding, ret: string) {
|
|
t := b.result
|
|
switch t.of {
|
|
case .Void:
|
|
case .Prim:
|
|
w(e, " result = %s(%s)", t.odin, ret)
|
|
case .String:
|
|
w(e, " defer jni.free_local(env, %s)", ret)
|
|
w(e, " result = jni.from_jstring(env, android.jstring(%s), allocator)", ret)
|
|
case .Array:
|
|
conv := t.elem == "string" ? "from_string_array" : fmt.tprintf("from_%s_array", t.elem)
|
|
jarr := t.elem == "string" ? "android.jobjectArray" : fmt.tprintf("android.j%sArray", java_arr_name(t.elem))
|
|
w(e, " defer jni.free_local(env, %s)", ret)
|
|
w(e, " result = jni.%s(env, %s(%s), allocator)", conv, jarr, ret)
|
|
case .Object:
|
|
w(e, " result = %s(%s)", t.odin, ret)
|
|
}
|
|
}
|
|
|
|
// Odin element type -> the jni.odin array type infix (jintArray, jlongArray...).
|
|
java_arr_name :: proc(elem: string) -> string {
|
|
for p in prims {
|
|
if p.odin == elem {
|
|
return p.call
|
|
}
|
|
}
|
|
return "object"
|
|
}
|
|
|
|
@(private = "file")
|
|
emit_field_get :: proc(e: ^Emitter, b: Binding) {
|
|
t := b.result
|
|
getter := field_accessor(t, b.is_static, true)
|
|
target := b.is_static ? "cls" : fmt.tprintf("android.jobject(%s)", b.recv.name)
|
|
w(e, " _ret := env^^.%s(env, %s, fid)", getter, target)
|
|
w(e, " if jni.failed(env) {{")
|
|
w(e, " return")
|
|
w(e, " }}")
|
|
emit_result(e, b, "_ret")
|
|
w(e, " ok = true")
|
|
w(e, " return")
|
|
}
|
|
|
|
@(private = "file")
|
|
emit_field_set :: proc(e: ^Emitter, b: Binding) {
|
|
p := b.params[0]
|
|
setter := field_accessor(p.type, b.is_static, false)
|
|
target := b.is_static ? "cls" : fmt.tprintf("android.jobject(%s)", b.recv.name)
|
|
|
|
value := ""
|
|
switch p.type.of {
|
|
case .Prim:
|
|
value = fmt.tprintf("%s(%s)", p.type.jcast, p.name)
|
|
case .String:
|
|
w(e, " _v := jni.to_jstring(env, %s)", p.name)
|
|
w(e, " defer jni.free_local(env, android.jobject(_v))")
|
|
value = "android.jobject(_v)"
|
|
case .Array:
|
|
conv := p.type.elem == "string" ? "to_string_array" : fmt.tprintf("to_%s_array", p.type.elem)
|
|
w(e, " _v := jni.%s(env, %s)", conv, p.name)
|
|
w(e, " defer jni.free_local(env, android.jobject(_v))")
|
|
value = "android.jobject(_v)"
|
|
case .Object:
|
|
value = fmt.tprintf("android.jobject(%s)", p.name)
|
|
case .Void:
|
|
}
|
|
|
|
w(e, " env^^.%s(env, %s, fid, %s)", setter, target, value)
|
|
w(e, " if jni.failed(env) {{")
|
|
w(e, " return")
|
|
w(e, " }}")
|
|
w(e, " ok = true")
|
|
w(e, " return")
|
|
}
|
|
|
|
// GetStaticIntField / SetObjectField / ...
|
|
field_accessor :: proc(t: Type_Ref, is_static: bool, is_get: bool) -> string {
|
|
kind := "Object"
|
|
if t.of == .Prim {
|
|
for p in prims {
|
|
if p.odin == t.odin {
|
|
kind = title(p.call)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
verb := is_get ? "Get" : "Set"
|
|
if is_static {
|
|
return fmt.tprintf("%sStatic%sField", verb, kind)
|
|
}
|
|
return fmt.tprintf("%s%sField", verb, kind)
|
|
}
|
|
|
|
title :: proc(s: string) -> string {
|
|
if len(s) == 0 {
|
|
return s
|
|
}
|
|
return fmt.tprintf("%c%s", s[0] - 32, s[1:])
|
|
}
|