From 5956303366782f95c9c9035664163ccc5ee22b72 Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 20 Feb 2026 12:27:06 +0200 Subject: [PATCH] dir import --- examples/50-smoke.sx | 9 ++ examples/modules/testpkg/greet.sx | 1 + examples/modules/testpkg/math.sx | 2 + src/imports.zig | 151 ++++++++++++++++++++++++------ tests/expected/50-smoke.txt | 4 + 5 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 examples/modules/testpkg/greet.sx create mode 100644 examples/modules/testpkg/math.sx diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index 730ed2d..99a9f08 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -1,4 +1,5 @@ #import "modules/std.sx"; +pkg :: #import "modules/testpkg"; // ============================================================ // Comprehensive Smoke Test — exercises every spec feature @@ -1351,5 +1352,13 @@ END; print("{}\n", 5 in (1, 2, 3)); // false } + // --- Directory imports --- + { + print("--- directory imports ---\n"); + print("{}\n", pkg.add(3, 4)); // 7 + print("{}\n", pkg.mul(5, 6)); // 30 + print("{}\n", pkg.hello()); // hello from testpkg + } + print("=== DONE ===\n"); } diff --git a/examples/modules/testpkg/greet.sx b/examples/modules/testpkg/greet.sx new file mode 100644 index 0000000..f964029 --- /dev/null +++ b/examples/modules/testpkg/greet.sx @@ -0,0 +1 @@ +hello :: () -> string { "hello from testpkg"; } diff --git a/examples/modules/testpkg/math.sx b/examples/modules/testpkg/math.sx new file mode 100644 index 0000000..df3f315 --- /dev/null +++ b/examples/modules/testpkg/math.sx @@ -0,0 +1,2 @@ +add :: (a: s32, b: s32) -> s32 { a + b; } +mul :: (a: s32, b: s32) -> s32 { a * b; } diff --git a/src/imports.zig b/src/imports.zig index 3c444d7..a957d97 100644 --- a/src/imports.zig +++ b/src/imports.zig @@ -106,36 +106,42 @@ pub fn resolveImports( const imported_mod = if (cache.get(resolved_path)) |cached| cached else blk: { - // Read imported file - const imp_bytes = std.Io.Dir.readFileAlloc(.cwd(), io, resolved_path, allocator, .limited(10 * 1024 * 1024)) catch { - if (diagnostics) |diags| { - diags.addFmt(.err, decl.span, "cannot read import '{s}'", .{resolved_path}); - } - return error.ImportError; - }; - const imp_source = try allocator.dupeZ(u8, imp_bytes); + // Try as file first + if (std.Io.Dir.readFileAlloc(.cwd(), io, resolved_path, allocator, .limited(10 * 1024 * 1024))) |imp_bytes| { + const imp_source = try allocator.dupeZ(u8, imp_bytes); - if (source_map) |sm| { - sm.put(resolved_path, imp_source) catch {}; + if (source_map) |sm| { + sm.put(resolved_path, imp_source) catch {}; + } + + var p = parser.Parser.init(allocator, imp_source); + const imp_root = p.parse() catch { + if (diagnostics) |diags| { + diags.addFmt(.err, decl.span, "parse error in '{s}': {s}", .{ resolved_path, p.err_msg orelse "unknown" }); + } + return error.ImportError; + }; + + // Push onto chain before recursing, pop after + try chain.put(resolved_path, {}); + const imp_dir = dirName(resolved_path); + const result = try resolveImports(allocator, io, imp_root, imp_dir, resolved_path, chain, cache, source_map, diagnostics); + _ = chain.remove(resolved_path); + + // Cache + try cache.put(resolved_path, result); + break :blk result; + } else |_| { + // File read failed — try as directory import + const result = resolveDirectoryImport(allocator, io, resolved_path, chain, cache, source_map, diagnostics, decl.span) catch { + if (diagnostics) |diags| { + diags.addFmt(.err, decl.span, "cannot read import '{s}' (not a file or directory)", .{resolved_path}); + } + return error.ImportError; + }; + try cache.put(resolved_path, result); + break :blk result; } - - var p = parser.Parser.init(allocator, imp_source); - const imp_root = p.parse() catch { - if (diagnostics) |diags| { - diags.addFmt(.err, decl.span, "parse error in '{s}': {s}", .{ resolved_path, p.err_msg orelse "unknown" }); - } - return error.ImportError; - }; - - // Push onto chain before recursing, pop after - try chain.put(resolved_path, {}); - const imp_dir = dirName(resolved_path); - const result = try resolveImports(allocator, io, imp_root, imp_dir, resolved_path, chain, cache, source_map, diagnostics); - _ = chain.remove(resolved_path); - - // Cache - try cache.put(resolved_path, result); - break :blk result; }; if (imp.name) |ns_name| { @@ -148,3 +154,92 @@ pub fn resolveImports( try mod.finalize(allocator, &decl_list); return mod; } + +/// Resolve a directory import by aggregating all .sx files in the directory. +fn resolveDirectoryImport( + allocator: std.mem.Allocator, + io: std.Io, + dir_path: []const u8, + chain: *std.StringHashMap(void), + cache: *ModuleCache, + source_map: ?*std.StringHashMap([:0]const u8), + diagnostics: ?*errors.DiagnosticList, + span: ast.Span, +) anyerror!ResolvedModule { + // Open the directory with iteration capability + const dir = std.Io.Dir.openDir(.cwd(), io, dir_path, .{ .iterate = true }) catch { + return error.ImportError; + }; + defer dir.close(io); + + // Collect all .sx file names + var file_names = std.ArrayList([]const u8).empty; + var it = dir.iterate(); + while (it.next(io) catch null) |entry| { + if (entry.kind != .file) continue; + if (!std.mem.endsWith(u8, entry.name, ".sx")) continue; + const name_copy = try allocator.dupe(u8, entry.name); + try file_names.append(allocator, name_copy); + } + + // Sort alphabetically for deterministic ordering + std.mem.sort([]const u8, file_names.items, {}, struct { + fn lessThan(_: void, a: []const u8, b: []const u8) bool { + return std.mem.order(u8, a, b) == .lt; + } + }.lessThan); + + // Add directory to chain for circular import detection + try chain.put(dir_path, {}); + defer _ = chain.remove(dir_path); + + // Merge all files into a combined module + var combined = ResolvedModule{ + .path = dir_path, + .decls = &.{}, + .scope = std.StringHashMap(void).init(allocator), + }; + var decl_list = std.ArrayList(*Node).empty; + + for (file_names.items) |file_name| { + const file_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ dir_path, file_name }); + + if (chain.contains(file_path)) continue; + + const file_mod = if (cache.get(file_path)) |cached| + cached + else file_blk: { + const imp_bytes = std.Io.Dir.readFileAlloc(.cwd(), io, file_path, allocator, .limited(10 * 1024 * 1024)) catch { + if (diagnostics) |diags| { + diags.addFmt(.err, span, "cannot read '{s}' in directory import", .{file_path}); + } + return error.ImportError; + }; + const imp_source = try allocator.dupeZ(u8, imp_bytes); + + if (source_map) |sm| { + sm.put(file_path, imp_source) catch {}; + } + + var p = parser.Parser.init(allocator, imp_source); + const imp_root = p.parse() catch { + if (diagnostics) |diags| { + diags.addFmt(.err, span, "parse error in '{s}': {s}", .{ file_path, p.err_msg orelse "unknown" }); + } + return error.ImportError; + }; + + try chain.put(file_path, {}); + const result = try resolveImports(allocator, io, imp_root, dir_path, file_path, chain, cache, source_map, diagnostics); + _ = chain.remove(file_path); + + try cache.put(file_path, result); + break :file_blk result; + }; + + try combined.mergeFlat(allocator, &decl_list, file_mod); + } + + try combined.finalize(allocator, &decl_list); + return combined; +} diff --git a/tests/expected/50-smoke.txt b/tests/expected/50-smoke.txt index 0773876..6eb2826 100644 --- a/tests/expected/50-smoke.txt +++ b/tests/expected/50-smoke.txt @@ -352,4 +352,8 @@ true true true false +--- directory imports --- +7 +30 +hello from testpkg === DONE ===