From 5fd8e0fbbee1a483edb5335e61e74f0f5296765e Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 20 May 2026 10:15:10 +0300 Subject: [PATCH] ffi 2.7 green: parser accepts all seven type-introducer directive forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six new lexer tokens (`hash_jni_interface`, `hash_objc_class`, `hash_objc_protocol`, `hash_swift_class`, `hash_swift_struct`, `hash_swift_protocol`) join the existing `hash_jni_class`. All seven share the body grammar from Phases 2.1–2.6. AST refactored: `JniClassDecl` → `ForeignClassDecl` with a `runtime: ForeignRuntime` enum discriminator; `JniMethodDecl` → `ForeignMethodDecl` (with `jni_descriptor_override` renamed for clarity since it's JNI-only); `JniFieldDecl` → `ForeignFieldDecl`; `JniClassMember` → `ForeignClassMember`. AST variant renamed `jni_class_decl` → `foreign_class_decl`. `parseForeignClassDecl` takes the runtime as a parameter; the `parseConstBinding` dispatch table now maps each of the seven directive tokens to its `ForeignRuntime` variant via `foreignRuntimeForCurrent`. No codegen yet — Phase 3 picks up Obj-C runtime, Phase 4 picks up Swift. Runtime-specific body items (fields, descriptor override) are validated at sema time in later steps. 126/126 examples green. --- src/ast.zig | 33 +++++++++++------ src/lexer.zig | 6 +++ src/lsp/server.zig | 6 +++ src/parser.zig | 37 +++++++++++++------ src/sema.zig | 6 +-- src/token.zig | 10 ++++- .../ffi-jni-class-07-all-runtimes.exit | 2 +- .../ffi-jni-class-07-all-runtimes.txt | 2 +- 8 files changed, 73 insertions(+), 29 deletions(-) diff --git a/src/ast.zig b/src/ast.zig index 67dd2eb..ea62f04 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -81,7 +81,7 @@ pub const Node = struct { protocol_decl: ProtocolDecl, impl_block: ImplBlock, ffi_intrinsic_call: FfiIntrinsicCall, - jni_class_decl: JniClassDecl, + foreign_class_decl: ForeignClassDecl, pub fn declName(self: Data) ?[]const u8 { return switch (self) { @@ -95,7 +95,7 @@ pub const Node = struct { .ufcs_alias => |d| d.name, .c_import_decl => |d| d.name, .protocol_decl => |d| d.name, - .jni_class_decl => |d| d.name, + .foreign_class_decl => |d| d.name, else => null, }; } @@ -533,31 +533,42 @@ pub const ProtocolDecl = struct { type_params: []const StructTypeParam = &.{}, // for `protocol(Target: Type) { ... }` }; -pub const JniMethodDecl = struct { +pub const ForeignRuntime = enum { + jni_class, + jni_interface, + objc_class, + objc_protocol, + swift_class, + swift_struct, + swift_protocol, +}; + +pub const ForeignMethodDecl = 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 is_static: bool = false, // true for `static name :: ...` - desc_override: ?[]const u8 = null, // `#desc("(Sig)Ret")` — skips auto-derivation in 2.8+ + jni_descriptor_override: ?[]const u8 = null, // `#jni_method_descriptor("(Sig)Ret")` — JNI runtime only }; -pub const JniFieldDecl = struct { +pub const ForeignFieldDecl = struct { name: []const u8, field_type: *Node, // type_expr node }; -pub const JniClassMember = union(enum) { - method: JniMethodDecl, - field: JniFieldDecl, +pub const ForeignClassMember = union(enum) { + method: ForeignMethodDecl, + field: ForeignFieldDecl, // JNI runtime only (sema-checked in later step) extends: []const u8, // sx-side alias name (right of `#extends`) implements: []const u8, // sx-side alias name (right of `#implements`) }; -pub const JniClassDecl = struct { +pub const ForeignClassDecl = struct { name: []const u8, // sx-side alias (left of `::`) - java_path: []const u8, // directive arg, e.g. "java/path/Foo" - members: []const JniClassMember = &.{}, // methods, #extends, #implements (fields/#desc land in 2.5+) + foreign_path: []const u8, // directive arg: "java/path/Foo" / "NSString" / "Foundation.URL" + runtime: ForeignRuntime, + members: []const ForeignClassMember = &.{}, }; pub const ImplBlock = struct { diff --git a/src/lexer.zig b/src/lexer.zig index a68e717..c5664b5 100644 --- a/src/lexer.zig +++ b/src/lexer.zig @@ -83,6 +83,12 @@ pub const Lexer = struct { .{ "#jni_call", Tag.hash_jni_call }, .{ "#jni_static_call", Tag.hash_jni_static_call }, .{ "#jni_class", Tag.hash_jni_class }, + .{ "#jni_interface", Tag.hash_jni_interface }, + .{ "#objc_class", Tag.hash_objc_class }, + .{ "#objc_protocol", Tag.hash_objc_protocol }, + .{ "#swift_class", Tag.hash_swift_class }, + .{ "#swift_struct", Tag.hash_swift_struct }, + .{ "#swift_protocol", Tag.hash_swift_protocol }, .{ "#extends", Tag.hash_extends }, .{ "#implements", Tag.hash_implements }, .{ "#jni_method_descriptor", Tag.hash_jni_method_descriptor }, diff --git a/src/lsp/server.zig b/src/lsp/server.zig index 828f699..807a9e3 100644 --- a/src/lsp/server.zig +++ b/src/lsp/server.zig @@ -1499,6 +1499,12 @@ pub const Server = struct { .hash_jni_call, .hash_jni_static_call, .hash_jni_class, + .hash_jni_interface, + .hash_objc_class, + .hash_objc_protocol, + .hash_swift_class, + .hash_swift_struct, + .hash_swift_protocol, .hash_extends, .hash_implements, .hash_jni_method_descriptor, diff --git a/src/parser.zig b/src/parser.zig index bbeec05..d9b0a8f 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -209,9 +209,10 @@ pub const Parser = struct { 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); + // Foreign-type binding: name :: #jni_class / #jni_interface / #objc_class / + // #objc_protocol / #swift_class / #swift_struct / #swift_protocol ("path") { body } + if (self.foreignRuntimeForCurrent()) |runtime| { + return self.parseForeignClassDecl(name, start_pos, runtime); } // C-style union declaration @@ -1027,21 +1028,34 @@ pub const Parser = struct { } }); } - fn parseJniClassDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { - self.advance(); // skip '#jni_class' + fn foreignRuntimeForCurrent(self: *Parser) ?ast.ForeignRuntime { + return switch (self.current.tag) { + .hash_jni_class => .jni_class, + .hash_jni_interface => .jni_interface, + .hash_objc_class => .objc_class, + .hash_objc_protocol => .objc_protocol, + .hash_swift_class => .swift_class, + .hash_swift_struct => .swift_struct, + .hash_swift_protocol => .swift_protocol, + else => null, + }; + } + + fn parseForeignClassDecl(self: *Parser, name: []const u8, start_pos: u32, runtime: ast.ForeignRuntime) anyerror!*Node { + self.advance(); // skip directive token try self.expect(.l_paren); if (self.current.tag != .string_literal) { - return self.fail("expected string literal Java class path after '#jni_class('"); + return self.fail("expected string literal foreign-type path after directive"); } const raw = self.tokenSlice(self.current); - const java_path = raw[1 .. raw.len - 1]; + const foreign_path = raw[1 .. raw.len - 1]; self.advance(); try self.expect(.r_paren); try self.expect(.l_brace); - var members = std.ArrayList(ast.JniClassMember).empty; + var members = std.ArrayList(ast.ForeignClassMember).empty; while (self.current.tag != .r_brace and self.current.tag != .eof) { // #extends Alias; or #implements Alias; if (self.current.tag == .hash_extends or self.current.tag == .hash_implements) { @@ -1143,14 +1157,15 @@ pub const Parser = struct { .param_names = try param_names.toOwnedSlice(self.allocator), .return_type = return_type, .is_static = is_static, - .desc_override = desc_override, + .jni_descriptor_override = desc_override, } }); } try self.expect(.r_brace); - return try self.createNode(start_pos, .{ .jni_class_decl = .{ + return try self.createNode(start_pos, .{ .foreign_class_decl = .{ .name = name, - .java_path = java_path, + .foreign_path = foreign_path, + .runtime = runtime, .members = try members.toOwnedSlice(self.allocator), } }); } diff --git a/src/sema.zig b/src/sema.zig index 8ae3482..f73aff4 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -907,8 +907,8 @@ pub const Analyzer = struct { } } }, - .jni_class_decl => |jd| { - try self.addSymbol(jd.name, .type_alias, null, node.span); + .foreign_class_decl => |fd| { + try self.addSymbol(fd.name, .type_alias, null, node.span); }, .impl_block => |ib| { // Each impl block gets its own scope so methods don't conflict across impls @@ -1309,7 +1309,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node { .tuple_type_expr, .ufcs_alias, .closure_type_expr, - .jni_class_decl, + .foreign_class_decl, => {}, .struct_decl => |sd| { for (sd.methods) |method_node| { diff --git a/src/token.zig b/src/token.zig index e2da850..2a1aea6 100644 --- a/src/token.zig +++ b/src/token.zig @@ -116,8 +116,14 @@ pub const Tag = enum { 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_class, // Foo :: #jni_class("java/path/Foo") { ...body... } - hash_extends, // `#extends Alias;` inside a #jni_class / #objc_class body - hash_implements, // `#implements Alias;` inside a #jni_class / #objc_class body + hash_jni_interface, // Foo :: #jni_interface("java/path/IFoo") { ...body... } + hash_objc_class, // Foo :: #objc_class("ObjcName") { ...body... } + hash_objc_protocol, // Foo :: #objc_protocol("ObjcProto") { ...body... } + hash_swift_class, // Foo :: #swift_class("Module.Type") { ...body... } + hash_swift_struct, // Foo :: #swift_struct("Module.Type") { ...body... } + hash_swift_protocol, // Foo :: #swift_protocol("Module.Proto") { ...body... } + hash_extends, // `#extends Alias;` inside a foreign-class body + hash_implements, // `#implements Alias;` inside a foreign-class body hash_jni_method_descriptor, // `#jni_method_descriptor("(Sig)Ret")` per-method JNI descriptor override triple_minus, // --- diff --git a/tests/expected/ffi-jni-class-07-all-runtimes.exit b/tests/expected/ffi-jni-class-07-all-runtimes.exit index d00491f..573541a 100644 --- a/tests/expected/ffi-jni-class-07-all-runtimes.exit +++ b/tests/expected/ffi-jni-class-07-all-runtimes.exit @@ -1 +1 @@ -1 +0 diff --git a/tests/expected/ffi-jni-class-07-all-runtimes.txt b/tests/expected/ffi-jni-class-07-all-runtimes.txt index 9a9558e..2ef3b99 100644 --- a/tests/expected/ffi-jni-class-07-all-runtimes.txt +++ b/tests/expected/ffi-jni-class-07-all-runtimes.txt @@ -1 +1 @@ -/Users/agra/projects/sx/examples/ffi-jni-class-07-all-runtimes.sx:20:9: error: unexpected token in expression +parse-only ok