so... jai :D
This commit is contained in:
150
src/imports.zig
Normal file
150
src/imports.zig
Normal file
@@ -0,0 +1,150 @@
|
||||
const std = @import("std");
|
||||
const ast = @import("ast.zig");
|
||||
const parser = @import("parser.zig");
|
||||
const errors = @import("errors.zig");
|
||||
const Node = ast.Node;
|
||||
|
||||
pub fn dirName(path: []const u8) []const u8 {
|
||||
var last_sep: usize = 0;
|
||||
var found = false;
|
||||
for (path, 0..) |ch, i| {
|
||||
if (ch == '/') {
|
||||
last_sep = i;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return if (found) path[0..last_sep] else ".";
|
||||
}
|
||||
|
||||
/// A resolved module: the fully-resolved declarations of a single .sx file,
|
||||
/// with its own scope tracking which names are defined.
|
||||
pub const ResolvedModule = struct {
|
||||
path: []const u8,
|
||||
decls: []const *Node,
|
||||
scope: std.StringHashMap(void),
|
||||
|
||||
/// Try to add a declaration. Returns true if added, false if name already in scope.
|
||||
pub fn addDecl(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), decl: *Node) !bool {
|
||||
if (decl.data.declName()) |name| {
|
||||
if (self.scope.contains(name)) return false;
|
||||
try self.scope.put(name, {});
|
||||
}
|
||||
try list.append(allocator, decl);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Merge another module's decls as flat imports (skipping duplicates).
|
||||
pub fn mergeFlat(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), other: ResolvedModule) !void {
|
||||
for (other.decls) |decl| {
|
||||
_ = try self.addDecl(allocator, list, decl);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add another module as a namespaced import.
|
||||
pub fn addNamespace(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), name: []const u8, other: ResolvedModule, span: ast.Span) !void {
|
||||
const ns_node = try allocator.create(Node);
|
||||
ns_node.* = .{
|
||||
.span = span,
|
||||
.data = .{ .namespace_decl = .{
|
||||
.name = name,
|
||||
.decls = other.decls,
|
||||
} },
|
||||
};
|
||||
try self.scope.put(name, {});
|
||||
try list.append(allocator, ns_node);
|
||||
}
|
||||
|
||||
pub fn finalize(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node)) !void {
|
||||
self.decls = try list.toOwnedSlice(allocator);
|
||||
}
|
||||
};
|
||||
|
||||
/// Module cache: maps resolved file paths to their ResolvedModules.
|
||||
pub const ModuleCache = std.StringHashMap(ResolvedModule);
|
||||
|
||||
pub fn resolveImports(
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
root: *Node,
|
||||
base_dir: []const u8,
|
||||
file_path: []const u8,
|
||||
chain: *std.StringHashMap(void),
|
||||
cache: *ModuleCache,
|
||||
source_map: ?*std.StringHashMap([:0]const u8),
|
||||
diagnostics: ?*errors.DiagnosticList,
|
||||
) !ResolvedModule {
|
||||
var mod = ResolvedModule{
|
||||
.path = file_path,
|
||||
.decls = &.{},
|
||||
.scope = std.StringHashMap(void).init(allocator),
|
||||
};
|
||||
|
||||
if (root.data != .root) {
|
||||
mod.decls = &.{};
|
||||
return mod;
|
||||
}
|
||||
|
||||
var decl_list = std.ArrayList(*Node).empty;
|
||||
|
||||
for (root.data.root.decls) |decl| {
|
||||
if (decl.data != .import_decl) {
|
||||
_ = try mod.addDecl(allocator, &decl_list, decl);
|
||||
continue;
|
||||
}
|
||||
const imp = decl.data.import_decl;
|
||||
|
||||
// Resolve path relative to base_dir
|
||||
const resolved_path = if (std.mem.eql(u8, base_dir, "."))
|
||||
imp.path
|
||||
else
|
||||
try std.fmt.allocPrint(allocator, "{s}/{s}", .{ base_dir, imp.path });
|
||||
|
||||
// Circular import check — only along the current chain
|
||||
if (chain.contains(resolved_path)) continue;
|
||||
|
||||
// Resolve or retrieve the imported module
|
||||
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);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
if (imp.name) |ns_name| {
|
||||
try mod.addNamespace(allocator, &decl_list, ns_name, imported_mod, decl.span);
|
||||
} else {
|
||||
try mod.mergeFlat(allocator, &decl_list, imported_mod);
|
||||
}
|
||||
}
|
||||
|
||||
try mod.finalize(allocator, &decl_list);
|
||||
return mod;
|
||||
}
|
||||
Reference in New Issue
Block a user