289 lines
8.4 KiB
Odin
289 lines
8.4 KiB
Odin
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)
|
|
}
|