imports
This commit is contained in:
@@ -8,6 +8,7 @@ pub const Span = struct {
|
||||
pub const Node = struct {
|
||||
span: Span,
|
||||
data: Data,
|
||||
source_file: ?[]const u8 = null,
|
||||
|
||||
pub const Data = union(enum) {
|
||||
root: Root,
|
||||
|
||||
@@ -172,6 +172,10 @@ pub const CodeGen = struct {
|
||||
diagnostics: ?*errors.DiagnosticList = null,
|
||||
// Current source span (set at genExpr/genStmt/genExprAsType entry)
|
||||
current_span: Span = .{ .start = 0, .end = 0 },
|
||||
// Current source file path (for error reporting in imported files)
|
||||
current_source_file: ?[]const u8 = null,
|
||||
// Import source map (path → source text, for error reporting)
|
||||
import_sources: ?*const std.StringHashMap([:0]const u8) = null,
|
||||
// Loop context: break/continue target basic blocks (null when not in a loop)
|
||||
loop_break_bb: c.LLVMBasicBlockRef = null,
|
||||
loop_continue_bb: c.LLVMBasicBlockRef = null,
|
||||
@@ -238,6 +242,7 @@ pub const CodeGen = struct {
|
||||
fd: ast.FnDecl,
|
||||
name: []const u8, // qualified name (may differ from fd.name for namespaced functions)
|
||||
namespace: ?[]const u8 = null,
|
||||
source_file: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
const TypeCategory = enum {
|
||||
@@ -526,12 +531,18 @@ pub const CodeGen = struct {
|
||||
}
|
||||
|
||||
fn emitError(self: *CodeGen, msg: []const u8) error{CodeGenError} {
|
||||
if (self.diagnostics) |diags| diags.add(.err, msg, self.current_span);
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.current_source_file = self.current_source_file;
|
||||
diags.add(.err, msg, self.current_span);
|
||||
}
|
||||
return error.CodeGenError;
|
||||
}
|
||||
|
||||
fn emitErrorFmt(self: *CodeGen, comptime fmt: []const u8, args: anytype) error{CodeGenError} {
|
||||
if (self.diagnostics) |diags| diags.addFmt(.err, self.current_span, fmt, args);
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.current_source_file = self.current_source_file;
|
||||
diags.addFmt(.err, self.current_span, fmt, args);
|
||||
}
|
||||
return error.CodeGenError;
|
||||
}
|
||||
|
||||
@@ -1235,6 +1246,7 @@ pub const CodeGen = struct {
|
||||
|
||||
// Pre-scan: collect named library constants (handles forward references)
|
||||
for (root.data.root.decls) |decl| {
|
||||
self.current_source_file = decl.source_file;
|
||||
switch (decl.data) {
|
||||
.library_decl => |ld| {
|
||||
try self.library_constants.put(ld.name, ld.lib_name);
|
||||
@@ -1256,6 +1268,7 @@ pub const CodeGen = struct {
|
||||
|
||||
// Pass 1: Register all declarations (signatures only, no bodies)
|
||||
for (root.data.root.decls) |decl| {
|
||||
self.current_source_file = decl.source_file;
|
||||
switch (decl.data) {
|
||||
.fn_decl => |fd| {
|
||||
if (fd.body.data == .builtin_expr) {
|
||||
@@ -1392,13 +1405,14 @@ pub const CodeGen = struct {
|
||||
// Functions with Any parameters (like any_to_string) are deferred to Pass 3
|
||||
// so that all types are registered before their type-match expressions are compiled.
|
||||
for (root.data.root.decls) |decl| {
|
||||
self.current_source_file = decl.source_file;
|
||||
switch (decl.data) {
|
||||
.fn_decl => |fd| {
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) {
|
||||
// skip — no body to generate
|
||||
} else if (fd.type_params.len == 0) {
|
||||
if (shouldDeferFnBody(fd)) {
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = fd.name });
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = fd.name, .source_file = self.current_source_file });
|
||||
} else {
|
||||
try self.genFnBody(fd, fd.name);
|
||||
}
|
||||
@@ -1431,8 +1445,11 @@ pub const CodeGen = struct {
|
||||
// Pass 3: Compile deferred function bodies (after all types are registered)
|
||||
for (self.deferred_fn_bodies.items) |deferred| {
|
||||
const saved_ns = self.current_namespace;
|
||||
const saved_sf = self.current_source_file;
|
||||
self.current_namespace = deferred.namespace;
|
||||
self.current_source_file = deferred.source_file;
|
||||
defer self.current_namespace = saved_ns;
|
||||
defer self.current_source_file = saved_sf;
|
||||
try self.genFnBody(deferred.fd, deferred.name);
|
||||
}
|
||||
|
||||
@@ -2448,7 +2465,7 @@ pub const CodeGen = struct {
|
||||
} else if (fd.type_params.len == 0) {
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name });
|
||||
if (shouldDeferFnBody(fd)) {
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = ns.name });
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = ns.name, .source_file = self.current_source_file });
|
||||
} else {
|
||||
try self.genFnBody(fd, qualified);
|
||||
}
|
||||
@@ -2483,7 +2500,7 @@ pub const CodeGen = struct {
|
||||
if (fd.type_params.len > 0) continue; // generic methods instantiated on demand
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, fd.name });
|
||||
if (shouldDeferFnBody(fd)) {
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = sd.name });
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = sd.name, .source_file = self.current_source_file });
|
||||
} else {
|
||||
try self.genFnBody(fd, qualified);
|
||||
}
|
||||
@@ -5249,7 +5266,7 @@ pub const CodeGen = struct {
|
||||
if (fd.type_params.len > 0) continue; // generic methods instantiated on demand
|
||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ib.target_type, fd.name });
|
||||
if (shouldDeferFnBody(fd)) {
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = ib.target_type });
|
||||
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = ib.target_type, .source_file = self.current_source_file });
|
||||
} else {
|
||||
try self.genFnBody(fd, qualified);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,12 @@ pub const Compilation = struct {
|
||||
&self.diagnostics,
|
||||
) catch return error.CompileError;
|
||||
|
||||
// Store main file source in import_sources so error reporting can find it
|
||||
self.import_sources.put(self.file_path, self.source) catch {};
|
||||
|
||||
// Wire import_sources to diagnostics for file-aware error rendering
|
||||
self.diagnostics.import_sources = &self.import_sources;
|
||||
|
||||
// Build a root node from the resolved module's decls
|
||||
const new_root = try self.allocator.create(Node);
|
||||
new_root.* = .{
|
||||
@@ -90,6 +96,7 @@ pub const Compilation = struct {
|
||||
const root = self.resolved_root orelse self.root orelse return error.CompileError;
|
||||
var cg = codegen.CodeGen.init(self.allocator, "sx_module", self.target_config);
|
||||
cg.diagnostics = &self.diagnostics;
|
||||
cg.import_sources = &self.import_sources;
|
||||
if (self.sema_result) |*sr| {
|
||||
cg.sema_result = sr;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub const Diagnostic = struct {
|
||||
level: Level,
|
||||
message: []const u8,
|
||||
span: ?Span,
|
||||
source_file: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const DiagnosticList = struct {
|
||||
@@ -38,6 +39,8 @@ pub const DiagnosticList = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
source: []const u8,
|
||||
file_name: []const u8,
|
||||
current_source_file: ?[]const u8 = null,
|
||||
import_sources: ?*const std.StringHashMap([:0]const u8) = null,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, source: []const u8, file_name: []const u8) DiagnosticList {
|
||||
return .{
|
||||
@@ -64,6 +67,7 @@ pub const DiagnosticList = struct {
|
||||
.level = level,
|
||||
.message = message,
|
||||
.span = span,
|
||||
.source_file = self.current_source_file,
|
||||
}) catch {};
|
||||
}
|
||||
|
||||
@@ -79,6 +83,17 @@ pub const DiagnosticList = struct {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn resolveSourceAndFile(self: *const DiagnosticList, d: Diagnostic) struct { source: []const u8, file_name: []const u8 } {
|
||||
if (d.source_file) |sf| {
|
||||
if (self.import_sources) |is| {
|
||||
if (is.get(sf)) |src| {
|
||||
return .{ .source = src, .file_name = sf };
|
||||
}
|
||||
}
|
||||
}
|
||||
return .{ .source = self.source, .file_name = self.file_name };
|
||||
}
|
||||
|
||||
pub fn render(self: *const DiagnosticList, writer: anytype) !void {
|
||||
for (self.items.items) |d| {
|
||||
const level_str = switch (d.level) {
|
||||
@@ -87,8 +102,9 @@ pub const DiagnosticList = struct {
|
||||
.note => "note",
|
||||
};
|
||||
if (d.span) |span| {
|
||||
const loc = SourceLoc.compute(self.source, span.start);
|
||||
try writer.print("{s}:{d}:{d}: {s}: {s}\n", .{ self.file_name, loc.line, loc.col, level_str, d.message });
|
||||
const resolved = self.resolveSourceAndFile(d);
|
||||
const loc = SourceLoc.compute(resolved.source, span.start);
|
||||
try writer.print("{s}:{d}:{d}: {s}: {s}\n", .{ resolved.file_name, loc.line, loc.col, level_str, d.message });
|
||||
} else {
|
||||
try writer.print("{s}: {s}: {s}\n", .{ self.file_name, level_str, d.message });
|
||||
}
|
||||
@@ -103,8 +119,9 @@ pub const DiagnosticList = struct {
|
||||
.note => "note",
|
||||
};
|
||||
if (d.span) |span| {
|
||||
const loc = SourceLoc.compute(self.source, span.start);
|
||||
std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{ self.file_name, loc.line, loc.col, level_str, d.message });
|
||||
const resolved = self.resolveSourceAndFile(d);
|
||||
const loc = SourceLoc.compute(resolved.source, span.start);
|
||||
std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{ resolved.file_name, loc.line, loc.col, level_str, d.message });
|
||||
} else {
|
||||
std.debug.print("{s}: {s}: {s}\n", .{ self.file_name, level_str, d.message });
|
||||
}
|
||||
|
||||
@@ -121,28 +121,44 @@ pub fn resolveImports(
|
||||
.decls = try ns_decls.toOwnedSlice(allocator),
|
||||
} },
|
||||
};
|
||||
ns_node.source_file = file_path;
|
||||
try mod.scope.put(ns_name, {});
|
||||
try decl_list.append(allocator, ns_node);
|
||||
} else {
|
||||
// Flat: add fn_decls directly + keep c_import_decl
|
||||
for (result.fn_decls) |fd| {
|
||||
fd.source_file = file_path;
|
||||
_ = try mod.addDecl(allocator, &decl_list, fd);
|
||||
}
|
||||
decl.source_file = file_path;
|
||||
_ = try mod.addDecl(allocator, &decl_list, decl);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (decl.data != .import_decl) {
|
||||
decl.source_file = file_path;
|
||||
_ = 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 });
|
||||
// Resolve path: try relative to file dir first, then fall back to cwd-relative
|
||||
const resolved_path = resolvePath: {
|
||||
if (!std.mem.eql(u8, base_dir, ".")) {
|
||||
const rel_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ base_dir, imp.path });
|
||||
// Check if it exists as file or directory relative to base_dir
|
||||
if (std.Io.Dir.readFileAlloc(.cwd(), io, rel_path, allocator, .limited(10 * 1024 * 1024))) |_| {
|
||||
break :resolvePath rel_path;
|
||||
} else |_| {}
|
||||
// Try as directory
|
||||
if (std.Io.Dir.openDir(.cwd(), io, rel_path, .{})) |dir| {
|
||||
dir.close(io);
|
||||
break :resolvePath rel_path;
|
||||
} else |_| {}
|
||||
}
|
||||
// Fall back to raw path (cwd-relative)
|
||||
break :resolvePath imp.path;
|
||||
};
|
||||
|
||||
// Circular import check — only along the current chain
|
||||
if (chain.contains(resolved_path)) continue;
|
||||
|
||||
@@ -83,7 +83,7 @@ pub const Parser = struct {
|
||||
}
|
||||
|
||||
// All top-level declarations start with an identifier
|
||||
if (self.current.tag != .identifier and self.current.tag != .kw_Self) {
|
||||
if (!self.isIdentLike() and self.current.tag != .kw_Self) {
|
||||
return self.fail("expected identifier at top level");
|
||||
}
|
||||
const name = self.tokenSlice(self.current);
|
||||
@@ -426,7 +426,7 @@ pub const Parser = struct {
|
||||
}
|
||||
// Check for optional param name: `name: Type`
|
||||
// An identifier followed by `:` (not `::` or `:=`) is a param name
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon) {
|
||||
if (self.isIdentLike() and self.peekNext() == .colon) {
|
||||
const pname = self.tokenSlice(self.current);
|
||||
self.advance(); // skip name
|
||||
self.advance(); // skip ':'
|
||||
@@ -455,7 +455,7 @@ pub const Parser = struct {
|
||||
} });
|
||||
}
|
||||
|
||||
if (self.current.tag.isTypeKeyword() or self.current.tag == .identifier) {
|
||||
if (self.current.tag.isTypeKeyword() or self.isIdentLike()) {
|
||||
var name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
|
||||
@@ -465,7 +465,7 @@ pub const Parser = struct {
|
||||
const dot_current = self.current;
|
||||
const dot_prev_end = self.prev_end;
|
||||
self.advance();
|
||||
if (self.current.tag == .identifier or self.current.tag.isTypeKeyword()) {
|
||||
if (self.isIdentLike() or self.current.tag.isTypeKeyword()) {
|
||||
name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, self.tokenSlice(self.current) });
|
||||
self.advance();
|
||||
} else {
|
||||
@@ -1065,7 +1065,7 @@ pub const Parser = struct {
|
||||
is_ct_param = true;
|
||||
self.advance();
|
||||
}
|
||||
if (self.current.tag != .identifier) {
|
||||
if (!self.isIdentLike()) {
|
||||
return self.fail("expected parameter name");
|
||||
}
|
||||
const param_name = self.tokenSlice(self.current);
|
||||
@@ -1252,7 +1252,7 @@ pub const Parser = struct {
|
||||
|
||||
pub fn parseStmt(self: *Parser) anyerror!*Node {
|
||||
// Check if this is a declaration (IDENT followed by ::, :=, or : type)
|
||||
if (self.current.tag == .identifier) {
|
||||
if (self.isIdentLike()) {
|
||||
const saved_lexer = self.lexer;
|
||||
const saved_current = self.current;
|
||||
const saved_prev_end = self.prev_end;
|
||||
@@ -1721,10 +1721,11 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .identifier = .{ .name = name } });
|
||||
},
|
||||
.kw_closure => {
|
||||
// `closure` keyword used as identifier in expressions (closure intrinsic call)
|
||||
.kw_closure, .kw_protocol, .kw_impl, .kw_ufcs => {
|
||||
// Contextual keywords used as identifiers in expressions
|
||||
const name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .identifier = .{ .name = "closure" } });
|
||||
return try self.createNode(start, .{ .identifier = .{ .name = name } });
|
||||
},
|
||||
.dot => {
|
||||
self.advance();
|
||||
@@ -2277,6 +2278,16 @@ pub const Parser = struct {
|
||||
|
||||
// ---- Helpers ----
|
||||
|
||||
/// Returns true if the current token can be used as an identifier name.
|
||||
/// Includes actual identifiers plus contextual keywords that are only
|
||||
/// keywords in specific syntactic positions (e.g., `protocol`, `impl`).
|
||||
fn isIdentLike(self: *const Parser) bool {
|
||||
return switch (self.current.tag) {
|
||||
.identifier, .kw_protocol, .kw_impl, .kw_ufcs, .kw_closure => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn isFunctionDef(self: *Parser) bool {
|
||||
const tag = self.peekPastParens() orelse return false;
|
||||
return tag == .l_brace or tag == .arrow or tag == .hash_builtin or tag == .hash_foreign or tag == .fat_arrow;
|
||||
|
||||
Reference in New Issue
Block a user