Skip to content

Commit

Permalink
feat(semantic): record symbol references (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
DonIsaac authored Nov 20, 2024
1 parent 54641e0 commit 1806792
Show file tree
Hide file tree
Showing 24 changed files with 1,195 additions and 41 deletions.
5 changes: 5 additions & 0 deletions src/printer/Printer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ pub fn pPropJson(self: *Printer, key: []const u8, value: anytype) !void {
try self.pIndent();
}

pub fn pJson(self: *Printer, value: anytype) !void {
const options: std.json.StringifyOptions = .{};
try stringify(value, options, self.writer);
}

/// Print an object property key with a trailing `:`, without printing a value.
pub fn pPropName(self: *Printer, key: []const u8) !void {
try self.pString(key);
Expand Down
60 changes: 59 additions & 1 deletion src/printer/SemanticPrinter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,58 @@ fn printSymbol(self: *SemanticPrinter, symbol: *const Semantic.Symbol, symbols:
try self.printer.pPropWithNamespacedValue("declNode", decl);
try self.printer.pProp("scope", "{d}", symbol.scope);
try self.printer.pPropJson("flags", symbol.flags);

{
try self.printer.pPropName("references");
try self.printer.pushArray();
defer {
self.printer.pop();
self.printer.pIndent() catch @panic("print failed");
}
for (symbol.references.items) |ref_id| {
try self.printReference(ref_id);
self.printer.pComma();
try self.printer.pIndent();
}
}

try self.printer.pPropJson("members", @as([]u32, @ptrCast(symbols.getMembers(symbol.id).items)));
try self.printer.pPropJson("exports", @as([]u32, @ptrCast(symbols.getExports(symbol.id).items)));
}

pub fn printUnresolvedReferences(self: *SemanticPrinter) !void {
const p = self.printer;
const symbols = &self.semantic.symbols;

if (symbols.unresolved_references.items.len == 0) {
try p.print("[],", .{});
return;
}

try p.pushArray();
defer p.pop();
for (symbols.unresolved_references.items) |ref_id| {
try self.printReference(ref_id);
self.printer.pComma();
try self.printer.pIndent();
}
}

fn printReference(self: *SemanticPrinter, ref_id: Reference.Id) !void {
const ref = self.semantic.symbols.getReference(ref_id);
const tags = self.semantic.ast.nodes.items(.tag);

const sid: ?Symbol.Id.Repr = if (ref.symbol.unwrap()) |id| id.int() else null;
const printable = PrintableReference{
.symbol = sid,
.scope = ref.scope.int(),
.node = tags[ref.node],
.identifier = ref.identifier,
.flags = ref.flags,
};
try self.printer.pJson(printable);
}

pub fn printScopeTree(self: *SemanticPrinter) !void {
return self.printScope(&self.semantic.scopes.getScope(Semantic.ROOT_SCOPE_ID));
}
Expand All @@ -54,7 +102,6 @@ fn printScope(self: *SemanticPrinter, scope: *const Semantic.Scope) !void {

try p.pProp("id", "{d}", scope.id);

// try p.pPropJson("flags", scope.flags);
{
const f = scope.flags;
try p.pPropName("flags");
Expand Down Expand Up @@ -113,6 +160,14 @@ fn printStrIf(p: *Printer, str: []const u8, cond: bool) !void {
try p.pIndent();
}

const PrintableReference = struct {
symbol: ?Symbol.Id.Repr,
scope: Scope.Id.Repr,
node: Node.Tag,
identifier: []const u8,
flags: Reference.Flags,
};

const SemanticPrinter = @This();

const std = @import("std");
Expand All @@ -123,3 +178,6 @@ const _semantic = @import("../semantic.zig");
const SemanticBuilder = _semantic.Builder;
const Semantic = _semantic.Semantic;
const Symbol = _semantic.Symbol;
const Scope = _semantic.Scope;
const Reference = _semantic.Reference;
const Node = std.zig.Ast.Node;
1 change: 1 addition & 0 deletions src/semantic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ pub const Symbol = @import("./semantic/Symbol.zig");
pub const SymbolTable = Symbol.SymbolTable;
pub const Scope = @import("./semantic/Scope.zig");
pub const ScopeTree = Scope.ScopeTree;
pub const Reference = @import("./semantic/Reference.zig");

const std = @import("std");
171 changes: 171 additions & 0 deletions src/semantic/Reference.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//! A reference on a symbol. Describes where and how a symbol is used.

pub const Reference = @This();

symbol: Symbol.Id.Optional,
scope: Scope.Id,
node: Node.Index,
identifier: []const u8,
flags: Flags,

pub const Id = NominalId(u32);

const FLAGS_REPR = u8;

/// Describes how a reference uses a symbol.
///
/// ## Chained references
/// ```zig
/// const x = foo.bar.baz()
/// ```
/// - `foo` is `member | read`
/// - `bar` is `member | call`
/// - `baz` is `call`
pub const Flags = packed struct(FLAGS_REPR) {
/// Reference is reading a symbol's value.
read: bool = false,
/// Reference is modifying the value stored in a symbol.
write: bool = false,
/// Reference is calling the symbol as a function.
call: bool = false,
/// Reference is using the symbol in a type annotation or function signature.
///
/// Does not include type parameters.
type: bool = false,
/// Reference is on a field member.
///
/// Mixed together with other flags to indicate how the member is being used.
/// Make sure this is `false` if you want to check that a symbol is, itself,
/// being read/written/etc.
member: bool = false,
// Padding.
_: u3 = 0,

pub fn eq(self: Flags, other: Flags) bool {
const a: FLAGS_REPR = @bitCast(self);
const b: FLAGS_REPR = @bitCast(other);
return a == b;
}

pub fn merge(self: Flags, other: Flags) Flags {
const a: FLAGS_REPR = @bitCast(self);
const b: FLAGS_REPR = @bitCast(other);

return @bitCast(a | b);
}

pub fn contains(self: Flags, other: Flags) bool {
const a: FLAGS_REPR = @bitCast(self);
const b: FLAGS_REPR = @bitCast(other);
return (a & b) == b;
}

pub fn intersects(self: Flags, other: Flags) bool {
const a: FLAGS_REPR = @bitCast(self);
const b: FLAGS_REPR = @bitCast(other);
return (a | b) > 0;
}

/// `true` if the referenced symbol is having its value read.
///
/// ```zig
/// const y = foo;
/// // ^^^
/// ```
pub inline fn isSelfRead(f: Flags) bool {
return !f.member and f.read;
}

/// `true` if the referenced symbol is having its value modified.
/// ```zig
/// const y = foo + 1;
/// // ^^^
/// ```
pub inline fn isSelfWrite(f: Flags) bool {
return !f.member and f.write;
}

/// `true` if the referenced symbol is being called.
///
/// ```zig
/// const y = foo();
/// // ^^^^^
/// ```
pub inline fn isSelfCall(f: Flags) bool {
return !f.member and f.call;
}

/// `true` if the referenced symbol is being used in a type annotation.
///
/// ```zig
/// const y: Foo = .{};
/// // ^^^
/// ```
pub inline fn isSelfType(f: Flags) bool {
return !f.member and f.type;
}

/// `true` if this symbol is having one of its members read.
///
/// ```zig
/// const y = foo.x;
/// // ^^^
/// ```
pub inline fn isMemberRead(f: Flags) bool {
return f.member and f.read;
}

/// `true` if this symbol is having one of its members modified.
///
/// ```zig
/// const y = foo.x + 1;
/// // ^^^
/// ```
pub inline fn isMemberWrite(f: Flags) bool {
return f.member and f.write;
}

/// `true` if this symbol is having one of its members called as a function.
///
/// ```zig
/// const y = foo.x();
/// // ^^^
/// ```
pub inline fn isMemberCall(f: Flags) bool {
return f.member and f.call;
}

/// `true` if this symbol is having one of its members used in a type annotation.
///
/// ```zig
/// const y: mod.Foo = .{};
/// // ^^^
/// ```
pub inline fn isMemberType(f: Flags) bool {
return f.member and f.type;
}
};

const std = @import("std");
const NominalId = @import("id.zig").NominalId;

const Node = std.zig.Ast.Node;
const Scope = @import("Scope.zig");
const Symbol = @import("Symbol.zig");

const t = std.testing;
test {
t.refAllDecls(@This());
t.refAllDecls(Reference);
}

test "Flags.isMemberRead" {
try t.expect(Flags.isMemberRead(.{ .member = true, .read = true }));
try t.expect(Flags.isMemberRead(.{ .member = true, .read = true, .write = true }));

// zig fmt: off
try t.expect(!Flags.isMemberRead(.{ .member = true, .write = true }));
try t.expect(!Flags.isMemberRead(.{ .member = false, .read = true }));
try t.expect(!Flags.isMemberRead(.{ .member = true, .read = false }));
// zig fmt: on
}
Loading

0 comments on commit 1806792

Please sign in to comment.