strings
This commit is contained in:
@@ -1,48 +1,207 @@
|
||||
const std = @import("std");
|
||||
const sx = struct {
|
||||
pub const ast = @import("../ast.zig");
|
||||
pub const parser = @import("../parser.zig");
|
||||
pub const sema = @import("../sema.zig");
|
||||
pub const imports = @import("../imports.zig");
|
||||
};
|
||||
|
||||
pub const Import = struct {
|
||||
/// Namespace name. null for flat imports.
|
||||
ns: ?[]const u8,
|
||||
/// Resolved absolute file path.
|
||||
path: []const u8,
|
||||
};
|
||||
|
||||
pub const Document = struct {
|
||||
/// Resolved absolute file path.
|
||||
path: []const u8,
|
||||
/// Source text of this file.
|
||||
source: [:0]const u8,
|
||||
/// LSP version (from didOpen/didChange), -1 for disk-loaded imports.
|
||||
version: i64,
|
||||
/// AST root for this file only (not merged).
|
||||
root: ?*sx.ast.Node,
|
||||
/// Sema results for this file (references are relative to this source).
|
||||
sema: ?sx.sema.SemaResult,
|
||||
/// Import declarations parsed from this file.
|
||||
imports: []const Import,
|
||||
|
||||
pub fn topLevelSymbols(self: *const Document) []const sx.sema.Symbol {
|
||||
const sr = self.sema orelse return &.{};
|
||||
return sr.symbols;
|
||||
}
|
||||
};
|
||||
|
||||
pub const DocumentStore = struct {
|
||||
documents: std.StringHashMap(Document),
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
/// All loaded documents keyed by resolved file path.
|
||||
by_path: std.StringHashMap(*Document),
|
||||
|
||||
pub const Document = struct {
|
||||
uri: []const u8,
|
||||
text: []const u8,
|
||||
version: i64,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) DocumentStore {
|
||||
pub fn init(allocator: std.mem.Allocator, io: std.Io) DocumentStore {
|
||||
return .{
|
||||
.documents = std.StringHashMap(Document).init(allocator),
|
||||
.allocator = allocator,
|
||||
.io = io,
|
||||
.by_path = std.StringHashMap(*Document).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn open(self: *DocumentStore, uri: []const u8, text: []const u8, version: i64) !void {
|
||||
const uri_copy = try self.allocator.dupe(u8, uri);
|
||||
const text_copy = try self.allocator.dupe(u8, text);
|
||||
try self.documents.put(uri_copy, .{
|
||||
.uri = uri_copy,
|
||||
.text = text_copy,
|
||||
.version = version,
|
||||
});
|
||||
/// Get or create a document for the given file path. Reads from disk if not yet loaded.
|
||||
pub fn getOrLoad(self: *DocumentStore, path: []const u8) !*Document {
|
||||
if (self.by_path.get(path)) |doc| return doc;
|
||||
|
||||
const bytes = std.Io.Dir.readFileAlloc(.cwd(), self.io, path, self.allocator, .limited(10 * 1024 * 1024)) catch {
|
||||
return error.FileNotFound;
|
||||
};
|
||||
const source = try self.allocator.dupeZ(u8, bytes);
|
||||
return self.createDocument(path, source, -1);
|
||||
}
|
||||
|
||||
pub fn update(self: *DocumentStore, uri: []const u8, text: []const u8, version: i64) !void {
|
||||
if (self.documents.getPtr(uri)) |doc| {
|
||||
self.allocator.free(doc.text);
|
||||
doc.text = try self.allocator.dupe(u8, text);
|
||||
/// Create or update a document with editor-provided source (for didOpen/didChange).
|
||||
pub fn openOrUpdate(self: *DocumentStore, path: []const u8, source: [:0]const u8, version: i64) !*Document {
|
||||
if (self.by_path.get(path)) |doc| {
|
||||
doc.source = source;
|
||||
doc.version = version;
|
||||
// Invalidate analysis
|
||||
doc.root = null;
|
||||
doc.sema = null;
|
||||
doc.imports = &.{};
|
||||
return doc;
|
||||
}
|
||||
return self.createDocument(path, source, version);
|
||||
}
|
||||
|
||||
pub fn close(self: *DocumentStore, uri: []const u8) void {
|
||||
if (self.documents.fetchRemove(uri)) |kv| {
|
||||
self.allocator.free(kv.value.text);
|
||||
self.allocator.free(kv.key);
|
||||
}
|
||||
fn createDocument(self: *DocumentStore, path: []const u8, source: [:0]const u8, version: i64) !*Document {
|
||||
const doc = try self.allocator.create(Document);
|
||||
const path_owned = try self.allocator.dupe(u8, path);
|
||||
doc.* = .{
|
||||
.path = path_owned,
|
||||
.source = source,
|
||||
.version = version,
|
||||
.root = null,
|
||||
.sema = null,
|
||||
.imports = &.{},
|
||||
};
|
||||
try self.by_path.put(path_owned, doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
pub fn get(self: *const DocumentStore, uri: []const u8) ?*const Document {
|
||||
return self.documents.getPtr(uri);
|
||||
/// Analyze a document: parse, resolve imports, run sema with imported symbols pre-registered.
|
||||
pub fn analyzeDocument(self: *DocumentStore, doc: *Document) !void {
|
||||
// Parse if needed
|
||||
if (doc.root == null) {
|
||||
var p = sx.parser.Parser.init(self.allocator, doc.source);
|
||||
doc.root = p.parse() catch return;
|
||||
}
|
||||
const root = doc.root orelse return;
|
||||
|
||||
// Extract imports from AST
|
||||
var import_list = std.ArrayList(Import).empty;
|
||||
const base_dir = sx.imports.dirName(doc.path);
|
||||
if (root.data == .root) {
|
||||
for (root.data.root.decls) |decl| {
|
||||
if (decl.data != .import_decl) continue;
|
||||
const imp = decl.data.import_decl;
|
||||
const resolved_path = if (std.mem.eql(u8, base_dir, "."))
|
||||
imp.path
|
||||
else
|
||||
try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ base_dir, imp.path });
|
||||
try import_list.append(self.allocator, .{
|
||||
.ns = imp.name,
|
||||
.path = resolved_path,
|
||||
});
|
||||
}
|
||||
}
|
||||
doc.imports = try import_list.toOwnedSlice(self.allocator);
|
||||
|
||||
// Recursively analyze imported documents and pre-register their symbols
|
||||
var analyzer = sx.sema.Analyzer.init(self.allocator);
|
||||
|
||||
// Track in-progress documents to detect cycles
|
||||
var cycle_guard = std.StringHashMap(void).init(self.allocator);
|
||||
try cycle_guard.put(doc.path, {});
|
||||
|
||||
for (doc.imports) |imp| {
|
||||
const imp_doc = self.getOrLoad(imp.path) catch continue;
|
||||
|
||||
// Cycle detection
|
||||
if (cycle_guard.contains(imp.path)) continue;
|
||||
|
||||
// Ensure imported doc is analyzed
|
||||
if (imp_doc.sema == null) {
|
||||
try cycle_guard.put(imp.path, {});
|
||||
self.analyzeDocument(imp_doc) catch {};
|
||||
_ = cycle_guard.remove(imp.path);
|
||||
}
|
||||
|
||||
const imp_sema = imp_doc.sema orelse continue;
|
||||
|
||||
if (imp.ns) |ns_name| {
|
||||
// Namespaced import: register one namespace symbol
|
||||
try analyzer.preRegisterSymbol(.{
|
||||
.name = ns_name,
|
||||
.kind = .namespace,
|
||||
.ty = null,
|
||||
.def_span = .{ .start = 0, .end = 0 },
|
||||
.scope_depth = 0,
|
||||
.origin = imp.path,
|
||||
});
|
||||
// Copy fn_signatures with namespace prefix
|
||||
var sig_it = imp_sema.fn_signatures.iterator();
|
||||
while (sig_it.next()) |entry| {
|
||||
const prefixed = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, entry.key_ptr.* });
|
||||
try analyzer.fn_signatures.put(prefixed, entry.value_ptr.*);
|
||||
}
|
||||
// Copy struct_types with namespace prefix
|
||||
var struct_it = imp_sema.struct_types.iterator();
|
||||
while (struct_it.next()) |entry| {
|
||||
const prefixed = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, entry.key_ptr.* });
|
||||
try analyzer.struct_types.put(prefixed, entry.value_ptr.*);
|
||||
}
|
||||
// Copy enum_types with namespace prefix
|
||||
var enum_it = imp_sema.enum_types.iterator();
|
||||
while (enum_it.next()) |entry| {
|
||||
const prefixed = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, entry.key_ptr.* });
|
||||
try analyzer.enum_types.put(prefixed, entry.value_ptr.*);
|
||||
}
|
||||
} else {
|
||||
// Flat import: pre-register all top-level symbols with origin set
|
||||
for (imp_sema.symbols) |sym| {
|
||||
if (sym.scope_depth == 0) {
|
||||
try analyzer.preRegisterSymbol(.{
|
||||
.name = sym.name,
|
||||
.kind = sym.kind,
|
||||
.ty = sym.ty,
|
||||
.def_span = sym.def_span,
|
||||
.scope_depth = 0,
|
||||
.origin = imp.path,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Copy fn_signatures as-is
|
||||
var sig_it = imp_sema.fn_signatures.iterator();
|
||||
while (sig_it.next()) |entry| {
|
||||
try analyzer.fn_signatures.put(entry.key_ptr.*, entry.value_ptr.*);
|
||||
}
|
||||
// Copy struct_types
|
||||
var struct_it = imp_sema.struct_types.iterator();
|
||||
while (struct_it.next()) |entry| {
|
||||
try analyzer.struct_types.put(entry.key_ptr.*, entry.value_ptr.*);
|
||||
}
|
||||
// Copy enum_types
|
||||
var enum_it = imp_sema.enum_types.iterator();
|
||||
while (enum_it.next()) |entry| {
|
||||
try analyzer.enum_types.put(entry.key_ptr.*, entry.value_ptr.*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run sema on this file's own AST
|
||||
doc.sema = analyzer.analyze(root) catch null;
|
||||
}
|
||||
|
||||
pub fn get(self: *const DocumentStore, path: []const u8) ?*Document {
|
||||
return self.by_path.get(path);
|
||||
}
|
||||
};
|
||||
|
||||
1380
src/lsp/server.zig
1380
src/lsp/server.zig
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user