ffi 2.2 green: parser collects #jni_class instance method body items

New `JniMethodDecl` AST struct (name, params, param_names,
return_type — no body, foreign declaration). `JniClassDecl.body`
becomes `methods: []const JniMethodDecl`. parseJniClassDecl loops
over body items, parsing each `name :: (self: *Self, args...) -> Ret;`
similarly to parseProtocolDecl but requiring `;` (no body brace).

`static`, fields, `#extends`, `#implements`, and the other six
directive forms land in 2.3–2.7. Sema/lower still treat the decl
as an opaque type alias — descriptor derivation arrives in 2.8+.

121/121 examples green.
This commit is contained in:
agra
2026-05-20 09:30:02 +03:00
parent f5da453af1
commit a2a2e83af0
4 changed files with 57 additions and 7 deletions

View File

@@ -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 {

View File

@@ -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),
} });
}

View File

@@ -1 +1 @@
1
0

View File

@@ -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