initial commit
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
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:])
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// jnigen -- generates JNI binding boilerplate from an annotated Odin spec.
|
||||
//
|
||||
// odin run jnigen -- [spec.odin] [out.odin] [package]
|
||||
//
|
||||
// The spec is never compiled (it carries `#+build ignore`), so it needs no
|
||||
// -custom-attribute flags and cannot collide with the generated procs.
|
||||
package jnigen
|
||||
|
||||
import "core:fmt"
|
||||
import "core:odin/ast"
|
||||
import "core:odin/parser"
|
||||
import "core:os"
|
||||
|
||||
main :: proc() {
|
||||
spec_path := "jni_spec.odin"
|
||||
out_path := "jni_generated.odin"
|
||||
package_name := "main"
|
||||
|
||||
args := os.args[1:]
|
||||
if len(args) > 0 {
|
||||
spec_path = args[0]
|
||||
}
|
||||
if len(args) > 1 {
|
||||
out_path = args[1]
|
||||
}
|
||||
if len(args) > 2 {
|
||||
package_name = args[2]
|
||||
}
|
||||
|
||||
src, read_err := os.read_entire_file(spec_path, context.allocator)
|
||||
if read_err != nil {
|
||||
fmt.eprintfln("jnigen: cannot read %s: %v", spec_path, read_err)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
p := parser.default_parser()
|
||||
f := ast.File{
|
||||
src = string(src),
|
||||
fullpath = spec_path,
|
||||
}
|
||||
if !parser.parse_file(&p, &f) {
|
||||
fmt.eprintfln("jnigen: failed to parse %s", spec_path)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
g: Gen
|
||||
g.classes = make(map[string]string)
|
||||
|
||||
collect_classes(&g, &f)
|
||||
collect_bindings(&g, &f)
|
||||
|
||||
if len(g.errors) > 0 {
|
||||
for e in g.errors {
|
||||
fmt.eprintfln("jnigen: %s", e)
|
||||
}
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
out := emit_file(&g, package_name)
|
||||
if werr := os.write_entire_file(out_path, transmute([]byte)out); werr != nil {
|
||||
fmt.eprintfln("jnigen: cannot write %s: %v", out_path, werr)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
fmt.printfln(
|
||||
"jnigen: %s -> %s (%d classes, %d bindings)",
|
||||
spec_path,
|
||||
out_path,
|
||||
len(g.class_decls),
|
||||
len(g.bindings),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package jnigen
|
||||
|
||||
import "core:fmt"
|
||||
import "core:odin/ast"
|
||||
import "core:strings"
|
||||
|
||||
Kind :: enum {
|
||||
Method,
|
||||
Ctor,
|
||||
Field_Get,
|
||||
Field_Set,
|
||||
}
|
||||
|
||||
Class_Of :: enum {
|
||||
Void,
|
||||
Prim,
|
||||
String,
|
||||
Object,
|
||||
Array,
|
||||
}
|
||||
|
||||
Type_Ref :: struct {
|
||||
odin: string, // Odin type as written in the spec
|
||||
sig: string, // JNI signature fragment
|
||||
of: Class_Of,
|
||||
call: string, // "int" / "object" / "void" -- picks the jni.call_* wrapper
|
||||
jval: string, // jvalue union field
|
||||
jcast: string, // android.jint etc, for widening into the union
|
||||
elem: string, // element type, arrays only
|
||||
}
|
||||
|
||||
Param :: struct {
|
||||
name: string,
|
||||
type: Type_Ref,
|
||||
}
|
||||
|
||||
Binding :: struct {
|
||||
odin_name: string,
|
||||
java_class: string,
|
||||
java_name: string,
|
||||
kind: Kind,
|
||||
is_static: bool,
|
||||
recv: Param,
|
||||
has_recv: bool,
|
||||
params: []Param,
|
||||
result: Type_Ref,
|
||||
sig: string,
|
||||
line: int,
|
||||
}
|
||||
|
||||
Class_Bind :: struct {
|
||||
odin_name: string,
|
||||
java_class: string,
|
||||
}
|
||||
|
||||
Gen :: struct {
|
||||
classes: map[string]string, // Odin type name -> java/class/path
|
||||
class_decls: [dynamic]Class_Bind,
|
||||
bindings: [dynamic]Binding,
|
||||
errors: [dynamic]string,
|
||||
}
|
||||
|
||||
errorf :: proc(g: ^Gen, line: int, format: string, args: ..any) {
|
||||
msg := fmt.tprintf(format, ..args)
|
||||
append(&g.errors, fmt.aprintf("spec:%d: %s", line, msg))
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
attr_map :: proc(f: ^ast.File, attrs: [dynamic]^ast.Attribute) -> map[string]string {
|
||||
out := make(map[string]string)
|
||||
for attr in attrs {
|
||||
for elem in attr.elems {
|
||||
#partial switch e in elem.derived {
|
||||
case ^ast.Field_Value:
|
||||
key := src_of(f, e.field)
|
||||
val := strings.trim(src_of(f, e.value), "\"")
|
||||
out[key] = val
|
||||
case ^ast.Ident:
|
||||
out[e.name] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
src_of :: proc(f: ^ast.File, e: ^ast.Node) -> string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.trim_space(f.src[e.pos.offset:e.end.offset])
|
||||
}
|
||||
|
||||
// First pass: every `@(java_class=...) Name :: distinct jobject` becomes a
|
||||
// registered object type, so later signatures can resolve `Toast` to
|
||||
// `Landroid/widget/Toast;`.
|
||||
collect_classes :: proc(g: ^Gen, f: ^ast.File) {
|
||||
for decl in f.decls {
|
||||
vd, ok := decl.derived.(^ast.Value_Decl)
|
||||
if !ok || len(vd.attributes) == 0 || len(vd.values) == 0 {
|
||||
continue
|
||||
}
|
||||
// A proc type/lit is a method binding, not a class registration.
|
||||
#partial switch _ in vd.values[0].derived {
|
||||
case ^ast.Proc_Type:
|
||||
continue
|
||||
case ^ast.Proc_Lit:
|
||||
continue
|
||||
}
|
||||
|
||||
attrs := attr_map(f, vd.attributes)
|
||||
defer delete(attrs)
|
||||
cls, has := attrs["java_class"]
|
||||
if !has || len(vd.names) == 0 {
|
||||
continue
|
||||
}
|
||||
name := src_of(f, vd.names[0])
|
||||
g.classes[strings.clone(name)] = strings.clone(cls)
|
||||
append(&g.class_decls, Class_Bind{strings.clone(name), strings.clone(cls)})
|
||||
}
|
||||
}
|
||||
|
||||
collect_bindings :: proc(g: ^Gen, f: ^ast.File) {
|
||||
for decl in f.decls {
|
||||
vd, ok := decl.derived.(^ast.Value_Decl)
|
||||
if !ok || len(vd.attributes) == 0 || len(vd.values) == 0 || len(vd.names) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pt: ^ast.Proc_Type
|
||||
#partial switch v in vd.values[0].derived {
|
||||
case ^ast.Proc_Type:
|
||||
pt = v
|
||||
case ^ast.Proc_Lit:
|
||||
pt = v.type
|
||||
case:
|
||||
continue
|
||||
}
|
||||
|
||||
attrs := attr_map(f, vd.attributes)
|
||||
defer delete(attrs)
|
||||
java_class, has_class := attrs["java_class"]
|
||||
if !has_class {
|
||||
continue
|
||||
}
|
||||
|
||||
b: Binding
|
||||
b.line = vd.pos.line
|
||||
b.odin_name = strings.clone(src_of(f, vd.names[0]))
|
||||
b.java_class = strings.clone(java_class)
|
||||
_, b.is_static = attrs["java_static"]
|
||||
|
||||
if name, has := attrs["java_name"]; has {
|
||||
b.java_name = strings.clone(name)
|
||||
} else {
|
||||
b.java_name = b.odin_name
|
||||
}
|
||||
|
||||
_, is_ctor := attrs["java_ctor"]
|
||||
_, is_field := attrs["java_field"]
|
||||
|
||||
params := parse_params(g, f, pt, b.line)
|
||||
results := parse_results(g, f, pt, b.line)
|
||||
|
||||
if len(results) > 1 {
|
||||
errorf(g, b.line, "'%s' declares %d results; bindings return at most one value", b.odin_name, len(results))
|
||||
continue
|
||||
}
|
||||
if len(results) == 1 {
|
||||
b.result = results[0]
|
||||
} else {
|
||||
b.result = Type_Ref{odin = "", sig = "V", of = .Void, call = "void"}
|
||||
}
|
||||
|
||||
switch {
|
||||
case is_ctor:
|
||||
b.kind = .Ctor
|
||||
b.java_name = "<init>"
|
||||
if b.result.of != .Object {
|
||||
errorf(g, b.line, "'%s' is a constructor so it must return a registered object type", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.params = params
|
||||
b.sig = build_sig(params, Type_Ref{sig = "V", of = .Void})
|
||||
case is_field:
|
||||
if b.result.of == .Void {
|
||||
b.kind = .Field_Set
|
||||
// instance setter: (self, value); static setter: (value)
|
||||
if !b.is_static {
|
||||
if len(params) != 2 {
|
||||
errorf(g, b.line, "'%s' is an instance field setter so it must take (self, value)", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.recv, b.has_recv = params[0], true
|
||||
b.params = params[1:]
|
||||
} else {
|
||||
if len(params) != 1 {
|
||||
errorf(g, b.line, "'%s' is a static field setter so it must take exactly (value)", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.params = params
|
||||
}
|
||||
b.sig = b.params[0].type.sig
|
||||
} else {
|
||||
b.kind = .Field_Get
|
||||
if !b.is_static {
|
||||
if len(params) != 1 {
|
||||
errorf(g, b.line, "'%s' is an instance field getter so it must take exactly (self)", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.recv, b.has_recv = params[0], true
|
||||
} else if len(params) != 0 {
|
||||
errorf(g, b.line, "'%s' is a static field getter so it must take no parameters", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.params = nil
|
||||
b.sig = b.result.sig
|
||||
}
|
||||
case:
|
||||
b.kind = .Method
|
||||
if !b.is_static {
|
||||
if len(params) == 0 {
|
||||
errorf(g, b.line, "'%s' is an instance method so its first parameter must be the receiver", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.recv, b.has_recv = params[0], true
|
||||
b.params = params[1:]
|
||||
} else {
|
||||
b.params = params
|
||||
}
|
||||
b.sig = build_sig(b.params, b.result)
|
||||
}
|
||||
|
||||
if override, has := attrs["java_sig"]; has {
|
||||
b.sig = strings.clone(override)
|
||||
}
|
||||
|
||||
append(&g.bindings, b)
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
parse_params :: proc(g: ^Gen, f: ^ast.File, pt: ^ast.Proc_Type, line: int) -> []Param {
|
||||
out := make([dynamic]Param)
|
||||
if pt.params == nil {
|
||||
return out[:]
|
||||
}
|
||||
for field in pt.params.list {
|
||||
t, ok := resolve_type(g, src_of(f, field.type), line)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// `a, b: i32` declares two params of one type.
|
||||
if len(field.names) == 0 {
|
||||
append(&out, Param{fmt.aprintf("_arg%d", len(out)), t})
|
||||
continue
|
||||
}
|
||||
for n in field.names {
|
||||
append(&out, Param{strings.clone(src_of(f, n)), t})
|
||||
}
|
||||
}
|
||||
return out[:]
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
parse_results :: proc(g: ^Gen, f: ^ast.File, pt: ^ast.Proc_Type, line: int) -> []Type_Ref {
|
||||
out := make([dynamic]Type_Ref)
|
||||
if pt.results == nil {
|
||||
return out[:]
|
||||
}
|
||||
for field in pt.results.list {
|
||||
t, ok := resolve_type(g, src_of(f, field.type), line)
|
||||
if ok {
|
||||
append(&out, t)
|
||||
}
|
||||
}
|
||||
return out[:]
|
||||
}
|
||||
|
||||
build_sig :: proc(params: []Param, result: Type_Ref) -> string {
|
||||
b := strings.builder_make()
|
||||
strings.write_byte(&b, '(')
|
||||
for p in params {
|
||||
strings.write_string(&b, p.type.sig)
|
||||
}
|
||||
strings.write_byte(&b, ')')
|
||||
strings.write_string(&b, result.sig)
|
||||
return strings.to_string(b)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package jnigen
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
Prim :: struct {
|
||||
odin: string,
|
||||
sig: string,
|
||||
call: string,
|
||||
jval: string,
|
||||
jcast: string,
|
||||
}
|
||||
|
||||
// Odin's u16 stands in for Java's char: both are unsigned 16-bit.
|
||||
prims := []Prim{
|
||||
{"bool", "Z", "boolean", "z", "android.jboolean"},
|
||||
{"i8", "B", "byte", "b", "android.jbyte"},
|
||||
{"u16", "C", "char", "c", "android.jchar"},
|
||||
{"i16", "S", "short", "s", "android.jshort"},
|
||||
{"i32", "I", "int", "i", "android.jint"},
|
||||
{"i64", "J", "long", "j", "android.jlong"},
|
||||
{"f32", "F", "float", "f", "android.jfloat"},
|
||||
{"f64", "D", "double", "d", "android.jdouble"},
|
||||
}
|
||||
|
||||
resolve_type :: proc(g: ^Gen, odin: string, line: int) -> (Type_Ref, bool) {
|
||||
src := strings.trim_space(odin)
|
||||
if src == "" {
|
||||
return Type_Ref{sig = "V", of = .Void, call = "void"}, true
|
||||
}
|
||||
|
||||
for p in prims {
|
||||
if src == p.odin {
|
||||
return Type_Ref{
|
||||
odin = src, sig = p.sig, of = .Prim,
|
||||
call = p.call, jval = p.jval, jcast = p.jcast,
|
||||
}, true
|
||||
}
|
||||
}
|
||||
|
||||
if src == "string" || src == "cstring" {
|
||||
return Type_Ref{
|
||||
odin = src, sig = "Ljava/lang/String;", of = .String,
|
||||
call = "object", jval = "l",
|
||||
}, true
|
||||
}
|
||||
|
||||
if strings.has_prefix(src, "[]") {
|
||||
elem := strings.trim_space(src[2:])
|
||||
et, ok := resolve_type(g, elem, line)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
if et.of == .Array {
|
||||
errorf(g, line, "nested slice '%s' is not supported; declare it as a jobject instead", src)
|
||||
return {}, false
|
||||
}
|
||||
return Type_Ref{
|
||||
odin = src, sig = fmt.aprintf("[%s", et.sig), of = .Array,
|
||||
call = "object", jval = "l", elem = elem,
|
||||
}, true
|
||||
}
|
||||
|
||||
// A raw jobject is untyped on the Java side, so it signs as Object.
|
||||
if src == "android.jobject" || src == "jobject" {
|
||||
return Type_Ref{
|
||||
odin = src, sig = "Ljava/lang/Object;", of = .Object,
|
||||
call = "object", jval = "l",
|
||||
}, true
|
||||
}
|
||||
|
||||
if cls, ok := g.classes[src]; ok {
|
||||
return Type_Ref{
|
||||
odin = src, sig = fmt.aprintf("L%s;", cls), of = .Object,
|
||||
call = "object", jval = "l",
|
||||
}, true
|
||||
}
|
||||
|
||||
errorf(g, line, "unknown type '%s'; register it with @(java_class=\"...\") or use a primitive", src)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
// Whether a returned value allocates and so needs an allocator parameter.
|
||||
allocates :: proc(t: Type_Ref) -> bool {
|
||||
if t.of == .String {
|
||||
return true
|
||||
}
|
||||
return t.of == .Array
|
||||
}
|
||||
Reference in New Issue
Block a user