diff --git a/src/ast.zig b/src/ast.zig index ea62f04..09a34f4 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -82,6 +82,7 @@ pub const Node = struct { impl_block: ImplBlock, ffi_intrinsic_call: FfiIntrinsicCall, foreign_class_decl: ForeignClassDecl, + jni_env_block: JniEnvBlock, pub fn declName(self: Data) ?[]const u8 { return switch (self) { @@ -571,6 +572,11 @@ pub const ForeignClassDecl = struct { members: []const ForeignClassMember = &.{}, }; +pub const JniEnvBlock = struct { + env: *Node, // expression yielding the *JNIEnv for this scope + body: *Node, // block (or expression) — runs with `env` scoped via TL push/pop +}; + pub const ImplBlock = struct { protocol_name: []const u8, target_type: []const u8, diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 34e2921..b2db0e0 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -1050,6 +1050,13 @@ pub const Lowering = struct { .destructure_decl => |dd| self.lowerDestructureDecl(&dd), .insert_expr => |ins| self.lowerInsertExpr(ins.expr), .block => self.lowerBlock(node), + .jni_env_block => |eb| { + // 2.16a: evaluate env for side effects, lower body as a normal block. + // TL push/pop semantics land in 2.16b; until then `#jni_env` is a + // syntactic marker that doesn't affect codegen. + _ = self.lowerExpr(eb.env); + self.lowerBlock(eb.body); + }, // Block-local type declarations .struct_decl => |sd| self.registerStructDecl(&sd), .enum_decl, .union_decl => { diff --git a/src/lexer.zig b/src/lexer.zig index c5664b5..ccaf21e 100644 --- a/src/lexer.zig +++ b/src/lexer.zig @@ -92,6 +92,7 @@ pub const Lexer = struct { .{ "#extends", Tag.hash_extends }, .{ "#implements", Tag.hash_implements }, .{ "#jni_method_descriptor", Tag.hash_jni_method_descriptor }, + .{ "#jni_env", Tag.hash_jni_env }, }; inline for (directives) |d| { const keyword = d[0]; diff --git a/src/lsp/server.zig b/src/lsp/server.zig index 807a9e3..9f835e3 100644 --- a/src/lsp/server.zig +++ b/src/lsp/server.zig @@ -1508,6 +1508,7 @@ pub const Server = struct { .hash_extends, .hash_implements, .hash_jni_method_descriptor, + .hash_jni_env, => ST.keyword, .kw_f32, .kw_f64, .kw_Type, .kw_Self => ST.type_, diff --git a/src/parser.zig b/src/parser.zig index d9b0a8f..af849be 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1558,6 +1558,7 @@ pub const Parser = struct { .while_expr => false, .for_expr => false, .block => false, + .jni_env_block => false, else => true, }; if (needs_semi) { @@ -2187,6 +2188,9 @@ pub const Parser = struct { .hash_objc_call, .hash_jni_call, .hash_jni_static_call => { return try self.parseFfiIntrinsicCall(); }, + .hash_jni_env => { + return try self.parseJniEnvBlock(); + }, else => { return self.fail("unexpected token in expression"); }, @@ -2198,6 +2202,27 @@ pub const Parser = struct { /// `#jni_static_call(T)(class, "name", "(Sig)R", args...)`. The /// return type sits in the first parens; the actual call args /// follow in the second. + fn parseJniEnvBlock(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + self.advance(); // skip `#jni_env` + + try self.expect(.l_paren); + const env_expr = try self.parseExpr(); + try self.expect(.r_paren); + + // Body is a brace-delimited block. The `-> ?T` annotation for + // exception bubbling lands with step 2.15 / 2.16 follow-ups. + if (self.current.tag != .l_brace) { + return self.fail("expected '{' after '#jni_env(env)'"); + } + const body = try self.parseBlock(); + + return try self.createNode(start, .{ .jni_env_block = .{ + .env = env_expr, + .body = body, + } }); + } + fn parseFfiIntrinsicCall(self: *Parser) anyerror!*Node { const start = self.current.loc.start; const kind: ast.FfiIntrinsicKind = switch (self.current.tag) { diff --git a/src/sema.zig b/src/sema.zig index f73aff4..ee95223 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -910,6 +910,12 @@ pub const Analyzer = struct { .foreign_class_decl => |fd| { try self.addSymbol(fd.name, .type_alias, null, node.span); }, + .jni_env_block => |eb| { + try self.analyzeNode(eb.env); + try self.pushScope(); + try self.analyzeNode(eb.body); + self.popScope(); + }, .impl_block => |ib| { // Each impl block gets its own scope so methods don't conflict across impls try self.pushScope(); @@ -1311,6 +1317,10 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node { .closure_type_expr, .foreign_class_decl, => {}, + .jni_env_block => |eb| { + if (findNodeAtOffset(eb.env, offset)) |found| return found; + if (findNodeAtOffset(eb.body, offset)) |found| return found; + }, .struct_decl => |sd| { for (sd.methods) |method_node| { if (findNodeAtOffset(method_node, offset)) |found| return found; diff --git a/src/token.zig b/src/token.zig index 2a1aea6..5e312e5 100644 --- a/src/token.zig +++ b/src/token.zig @@ -125,6 +125,7 @@ pub const Tag = enum { 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 + hash_jni_env, // `#jni_env(env) { body }` block-form env-scoping intrinsic triple_minus, // --- // Special diff --git a/tests/expected/ffi-jni-env-01-block.exit b/tests/expected/ffi-jni-env-01-block.exit index d00491f..573541a 100644 --- a/tests/expected/ffi-jni-env-01-block.exit +++ b/tests/expected/ffi-jni-env-01-block.exit @@ -1 +1 @@ -1 +0 diff --git a/tests/expected/ffi-jni-env-01-block.txt b/tests/expected/ffi-jni-env-01-block.txt index 3a3c7e4..5b22336 100644 --- a/tests/expected/ffi-jni-env-01-block.txt +++ b/tests/expected/ffi-jni-env-01-block.txt @@ -1 +1 @@ -/Users/agra/projects/sx/examples/ffi-jni-env-01-block.sx:17:5: error: unexpected token in expression +inside #jni_env scope