diff --git a/src/ast.zig b/src/ast.zig index 1867aa4..e0ebec1 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -533,10 +533,17 @@ pub const ProtocolDecl = struct { type_params: []const StructTypeParam = &.{}, // for `protocol(Target: Type) { ... }` }; +pub const JniMethodDecl = struct { + name: []const u8, + params: []const *Node, // type_expr nodes — first is `*Self` for instance methods + param_names: []const []const u8, + return_type: ?*Node, // null = void +}; + pub const JniClassDecl = struct { name: []const u8, // sx-side alias (left of `::`) java_path: []const u8, // directive arg, e.g. "java/path/Foo" - body: []const *Node = &.{}, // body items (methods, fields, #extends, ...) — empty in 2.1 + methods: []const JniMethodDecl = &.{}, // instance methods (static/fields/extends land in later steps) }; pub const ImplBlock = struct { diff --git a/src/parser.zig b/src/parser.zig index 19c616e..93f9309 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1040,16 +1040,59 @@ pub const Parser = struct { try self.expect(.r_paren); try self.expect(.l_brace); - // Body items (methods, fields, #extends, #implements, ...) land in steps - // 2.2 onward. Step 2.1 accepts only the empty body (opaque forward decl). - if (self.current.tag != .r_brace) { - return self.fail("non-empty `#jni_class` body not yet supported (Phase 2.1 accepts only the empty/opaque form)"); + + var methods = std.ArrayList(ast.JniMethodDecl).empty; + while (self.current.tag != .r_brace and self.current.tag != .eof) { + // Instance method: name :: (self: *Self, args...) -> Ret; + // (`static`, fields, #extends, #implements land in later steps.) + if (self.current.tag != .identifier) { + return self.fail("expected method name in '#jni_class' body"); + } + const method_name = self.tokenSlice(self.current); + self.advance(); + try self.expect(.colon_colon); + try self.expect(.l_paren); + + var param_types = std.ArrayList(*Node).empty; + var param_names = std.ArrayList([]const u8).empty; + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (param_types.items.len > 0) { + try self.expect(.comma); + if (self.current.tag == .r_paren) break; + } + if (self.current.tag != .identifier and self.current.tag != .kw_Self) { + return self.fail("expected parameter name in '#jni_class' method"); + } + const pname = self.tokenSlice(self.current); + self.advance(); + try self.expect(.colon); + const ptype = try self.parseTypeExpr(); + try param_names.append(self.allocator, pname); + try param_types.append(self.allocator, ptype); + } + try self.expect(.r_paren); + + var return_type: ?*Node = null; + if (self.current.tag == .arrow) { + self.advance(); + return_type = try self.parseTypeExpr(); + } + + try self.expect(.semicolon); + + try methods.append(self.allocator, .{ + .name = method_name, + .params = try param_types.toOwnedSlice(self.allocator), + .param_names = try param_names.toOwnedSlice(self.allocator), + .return_type = return_type, + }); } try self.expect(.r_brace); return try self.createNode(start_pos, .{ .jni_class_decl = .{ .name = name, .java_path = java_path, + .methods = try methods.toOwnedSlice(self.allocator), } }); } diff --git a/tests/expected/ffi-jni-class-02-method.exit b/tests/expected/ffi-jni-class-02-method.exit index d00491f..573541a 100644 --- a/tests/expected/ffi-jni-class-02-method.exit +++ b/tests/expected/ffi-jni-class-02-method.exit @@ -1 +1 @@ -1 +0 diff --git a/tests/expected/ffi-jni-class-02-method.txt b/tests/expected/ffi-jni-class-02-method.txt index ea7929d..2ef3b99 100644 --- a/tests/expected/ffi-jni-class-02-method.txt +++ b/tests/expected/ffi-jni-class-02-method.txt @@ -1 +1 @@ -/Users/agra/projects/sx/examples/ffi-jni-class-02-method.sx:14:5: error: non-empty `#jni_class` body not yet supported (Phase 2.1 accepts only the empty/opaque form) +parse-only ok