ffi 2.1: parser accepts Foo :: #jni_class("path") { } opaque form
New `hash_jni_class` token + lexer entry, `JniClassDecl` AST node
(alias + java path; body deferred to 2.2+), `parseJniClassDecl`
consuming `("...") { }` and rejecting non-empty bodies for now.
Sema registers the alias as a type_alias symbol; LSP classifies
the directive as a keyword. The 2.0 xfail snapshot flips to
`parse-only ok`, exit 0.
120/120 examples green; zig test clean.
This commit is contained in:
@@ -81,6 +81,7 @@ pub const Node = struct {
|
|||||||
protocol_decl: ProtocolDecl,
|
protocol_decl: ProtocolDecl,
|
||||||
impl_block: ImplBlock,
|
impl_block: ImplBlock,
|
||||||
ffi_intrinsic_call: FfiIntrinsicCall,
|
ffi_intrinsic_call: FfiIntrinsicCall,
|
||||||
|
jni_class_decl: JniClassDecl,
|
||||||
|
|
||||||
pub fn declName(self: Data) ?[]const u8 {
|
pub fn declName(self: Data) ?[]const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
@@ -94,6 +95,7 @@ pub const Node = struct {
|
|||||||
.ufcs_alias => |d| d.name,
|
.ufcs_alias => |d| d.name,
|
||||||
.c_import_decl => |d| d.name,
|
.c_import_decl => |d| d.name,
|
||||||
.protocol_decl => |d| d.name,
|
.protocol_decl => |d| d.name,
|
||||||
|
.jni_class_decl => |d| d.name,
|
||||||
else => null,
|
else => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -531,6 +533,12 @@ pub const ProtocolDecl = struct {
|
|||||||
type_params: []const StructTypeParam = &.{}, // for `protocol(Target: Type) { ... }`
|
type_params: []const StructTypeParam = &.{}, // for `protocol(Target: Type) { ... }`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
pub const ImplBlock = struct {
|
pub const ImplBlock = struct {
|
||||||
protocol_name: []const u8,
|
protocol_name: []const u8,
|
||||||
target_type: []const u8,
|
target_type: []const u8,
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ pub const Lexer = struct {
|
|||||||
.{ "#objc_call", Tag.hash_objc_call },
|
.{ "#objc_call", Tag.hash_objc_call },
|
||||||
.{ "#jni_call", Tag.hash_jni_call },
|
.{ "#jni_call", Tag.hash_jni_call },
|
||||||
.{ "#jni_static_call", Tag.hash_jni_static_call },
|
.{ "#jni_static_call", Tag.hash_jni_static_call },
|
||||||
|
.{ "#jni_class", Tag.hash_jni_class },
|
||||||
};
|
};
|
||||||
inline for (directives) |d| {
|
inline for (directives) |d| {
|
||||||
const keyword = d[0];
|
const keyword = d[0];
|
||||||
|
|||||||
@@ -1498,6 +1498,7 @@ pub const Server = struct {
|
|||||||
.hash_objc_call,
|
.hash_objc_call,
|
||||||
.hash_jni_call,
|
.hash_jni_call,
|
||||||
.hash_jni_static_call,
|
.hash_jni_static_call,
|
||||||
|
.hash_jni_class,
|
||||||
=> ST.keyword,
|
=> ST.keyword,
|
||||||
|
|
||||||
.kw_f32, .kw_f64, .kw_Type, .kw_Self => ST.type_,
|
.kw_f32, .kw_f64, .kw_Type, .kw_Self => ST.type_,
|
||||||
|
|||||||
@@ -209,6 +209,11 @@ pub const Parser = struct {
|
|||||||
return self.parseProtocolDecl(name, start_pos);
|
return self.parseProtocolDecl(name, start_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JNI class binding: name :: #jni_class("java/path/Foo") { ...body... }
|
||||||
|
if (self.current.tag == .hash_jni_class) {
|
||||||
|
return self.parseJniClassDecl(name, start_pos);
|
||||||
|
}
|
||||||
|
|
||||||
// C-style union declaration
|
// C-style union declaration
|
||||||
if (self.current.tag == .kw_union) {
|
if (self.current.tag == .kw_union) {
|
||||||
return self.parseUnionDecl(name, start_pos);
|
return self.parseUnionDecl(name, start_pos);
|
||||||
@@ -1022,6 +1027,32 @@ pub const Parser = struct {
|
|||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parseJniClassDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
||||||
|
self.advance(); // skip '#jni_class'
|
||||||
|
|
||||||
|
try self.expect(.l_paren);
|
||||||
|
if (self.current.tag != .string_literal) {
|
||||||
|
return self.fail("expected string literal Java class path after '#jni_class('");
|
||||||
|
}
|
||||||
|
const raw = self.tokenSlice(self.current);
|
||||||
|
const java_path = raw[1 .. raw.len - 1];
|
||||||
|
self.advance();
|
||||||
|
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)");
|
||||||
|
}
|
||||||
|
try self.expect(.r_brace);
|
||||||
|
|
||||||
|
return try self.createNode(start_pos, .{ .jni_class_decl = .{
|
||||||
|
.name = name,
|
||||||
|
.java_path = java_path,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
fn parseImplBlock(self: *Parser, start_pos: u32) anyerror!*Node {
|
fn parseImplBlock(self: *Parser, start_pos: u32) anyerror!*Node {
|
||||||
self.advance(); // skip 'impl'
|
self.advance(); // skip 'impl'
|
||||||
|
|
||||||
|
|||||||
@@ -907,6 +907,9 @@ pub const Analyzer = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
.jni_class_decl => |jd| {
|
||||||
|
try self.addSymbol(jd.name, .type_alias, null, node.span);
|
||||||
|
},
|
||||||
.impl_block => |ib| {
|
.impl_block => |ib| {
|
||||||
// Each impl block gets its own scope so methods don't conflict across impls
|
// Each impl block gets its own scope so methods don't conflict across impls
|
||||||
try self.pushScope();
|
try self.pushScope();
|
||||||
@@ -1306,6 +1309,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
|||||||
.tuple_type_expr,
|
.tuple_type_expr,
|
||||||
.ufcs_alias,
|
.ufcs_alias,
|
||||||
.closure_type_expr,
|
.closure_type_expr,
|
||||||
|
.jni_class_decl,
|
||||||
=> {},
|
=> {},
|
||||||
.struct_decl => |sd| {
|
.struct_decl => |sd| {
|
||||||
for (sd.methods) |method_node| {
|
for (sd.methods) |method_node| {
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ pub const Tag = enum {
|
|||||||
hash_objc_call, // #objc_call(T)(recv, "sel:", args...)
|
hash_objc_call, // #objc_call(T)(recv, "sel:", args...)
|
||||||
hash_jni_call, // #jni_call(T)(env, target, "name", "(Sig)R", args...)
|
hash_jni_call, // #jni_call(T)(env, target, "name", "(Sig)R", args...)
|
||||||
hash_jni_static_call, // #jni_static_call(T)(class, "name", "(Sig)R", args...)
|
hash_jni_static_call, // #jni_static_call(T)(class, "name", "(Sig)R", args...)
|
||||||
|
hash_jni_class, // Foo :: #jni_class("java/path/Foo") { ...body... }
|
||||||
triple_minus, // ---
|
triple_minus, // ---
|
||||||
|
|
||||||
// Special
|
// Special
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1
|
0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
/Users/agra/projects/sx/examples/ffi-jni-class-01-empty.sx:13:8: error: unexpected token in expression
|
parse-only ok
|
||||||
|
|||||||
Reference in New Issue
Block a user