so... jai :D

This commit is contained in:
agra
2026-02-04 01:34:30 +02:00
commit 55fc5790e4
60 changed files with 15876 additions and 0 deletions

150
src/imports.zig Normal file
View 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;
}