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 = "" 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) }