Skip to documentation
SLOP

tiny.smg.scan.chiclet

Reference tiny.smg scan chiclet

Defined in scan.

Structural Chiclet source scanner.

API (1)

Actions

Public operations.

No direct callersNo direct callsscanchiclet
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsprivate; no linktools.smg.src.scan.pipeline.scanscanFileSourceprivate; no linktools.smg.src.scan.chicletaddDeclarationprivate; no linktools.smg.src.scan.chicletcollectExportsprivate; no linktools.smg.src.scan.chicletdeclarationprivate; no linktools.smg.src.scan.chicletlistHeadprivate; no linktools.smg.src.scan.chicletmoduleDocumentationAlloc+2 morescan.chicletscan
Static calls · unresolved targets: 0 · external targets: 3.

Source: tools/smg/src/scan/chiclet.zig

zig
//! Structural Chiclet source scanner.const std = @import("std");const smg = @import("../root.zig");const api = smg.api;const graph_mod = smg.graph;const lines_mod = smg.lines;const model = smg.model;const scan_root = smg.scan;const core = scan_root.core;const source_metadata = scan_root.source_metadata;const SourceLines = lines_mod.SourceLines;const Stats = core.Stats;const Span = struct {    start: usize,    end: usize,};const Shape = struct {    name_index: usize = 1,    params_index: ?usize = null,    body_index: ?usize = null,    node_type: []const u8,    test_definition: bool = false,    method_definition: bool = false,};const Declaration = struct {    name: []const u8,    kind: []const u8,    node_type: []const u8,    form: Span,    params: ?Span,    body_owner: Span,    body_index: ?usize,    test_definition: bool,    method_definition: bool,};const ScanContext = struct {    allocator: std.mem.Allocator,    graph: *graph_mod.Graph,    module: []const u8,    path: []const u8,    source: []const u8,    source_lines: SourceLines,    closers: []u8,    exports: *const std.StringHashMap(void),    stats: *Stats,};const ItemIterator = struct {    source: []const u8,    closers: []u8,    index: usize,    limit: usize,    fn init(source: []const u8, aggregate: Span, closers: []u8) ?ItemIterator {        if (aggregate.end <= aggregate.start + 1) return null;        const open = source[aggregate.start];        if (open != '(' and open != '[' and open != '{') return null;        return .{            .source = source,            .closers = closers,            .index = aggregate.start + 1,            .limit = aggregate.end - 1,        };    }    fn next(self: *ItemIterator) ?Span {        skipTrivia(self.source, &self.index);        if (self.index >= self.limit) return null;        const start = self.index;        const end = expressionEnd(self.source, start, self.closers) orelse {            self.index = self.limit;            return null;        };        if (end > self.limit) {            self.index = self.limit;            return null;        }        self.index = end;        return .{ .start = start, .end = end };    }};pub fn scan(    allocator: std.mem.Allocator,    scratch: std.mem.Allocator,    graph: *graph_mod.Graph,    module: []const u8,    path: []const u8,    source: []const u8,    source_lines: SourceLines,    stats: *Stats,    limits: smg.ScanLimits,) !void {    const closers = try scratch.alloc(u8, limits.chiclet_nesting_depth);    if (try moduleDocumentationAlloc(allocator, source, closers)) |docstring| {        const module_node = graph_mod.getNode(graph, module) orelse unreachable;        try graph_mod.addNode(graph, .{            .name = module_node.name,            .type = module_node.type,            .docstring = docstring,        });    }    var exports = std.StringHashMap(void).init(scratch);    try collectExports(source, closers, &exports);    const context: ScanContext = .{        .allocator = allocator,        .graph = graph,        .module = module,        .path = path,        .source = source,        .source_lines = source_lines,        .closers = closers,        .exports = &exports,        .stats = stats,    };    var index: usize = 0;    while (nextTopLevel(source, &index, closers)) |form| {        const head = listHead(source, form, closers) orelse continue;        if (std.mem.eql(u8, head, "import")) {            try scanImport(context, form);        } else if (try declaration(source, form, head, closers)) |record| {            try addDeclaration(context, record);        }    }}fn moduleDocumentationAlloc(    allocator: std.mem.Allocator,    source: []const u8,    closers: []u8,) !?[]const u8 {    var index: usize = 0;    while (nextTopLevel(source, &index, closers)) |form| {        const head = listHead(source, form, closers) orelse continue;        if (!std.mem.eql(u8, head, "module-doc")) continue;        const documentation = listItem(source, form, 1, closers) orelse return null;        if (listItem(source, form, 2, closers) != null) return null;        if (try stringAlloc(allocator, source, documentation)) |description| {            return description;        }        return try mapDocumentationAlloc(allocator, source, documentation, closers);    }    return null;}fn collectExports(source: []const u8, closers: []u8, exports: *std.StringHashMap(void)) !void {    var index: usize = 0;    while (nextTopLevel(source, &index, closers)) |form| {        const head = listHead(source, form, closers) orelse continue;        if (!std.mem.eql(u8, head, "export")) continue;        var items = ItemIterator.init(source, form, closers) orelse continue;        _ = items.next();        while (items.next()) |item| try collectExportItem(source, item, closers, exports);    }}fn collectExportItem(    source: []const u8,    item: Span,    closers: []u8,    exports: *std.StringHashMap(void),) !void {    if (atom(source, item)) |name| {        try exports.put(name, {});        return;    }    var nested = ItemIterator.init(source, item, closers) orelse return;    const head = atom(source, nested.next() orelse return) orelse return;    if (!std.mem.eql(u8, head, "for-syntax")) return;    while (nested.next()) |child| {        if (atom(source, child)) |name| try exports.put(name, {});    }}fn scanImport(context: ScanContext, form: Span) !void {    var items = ItemIterator.init(context.source, form, context.closers) orelse return;    _ = items.next();    while (items.next()) |item| {        if (try stringAlloc(context.allocator, context.source, item)) |target| {            try core.addImportEdge(                context.allocator,                context.graph,                context.module,                try graphImportTargetAlloc(context.allocator, target),                &.{},                context.stats,            );            continue;        }        var nested = ItemIterator.init(context.source, item, context.closers) orelse continue;        _ = nested.next();        while (nested.next()) |child| {            const target = try stringAlloc(context.allocator, context.source, child) orelse                continue;            try core.addImportEdge(                context.allocator,                context.graph,                context.module,                try graphImportTargetAlloc(context.allocator, target),                &.{},                context.stats,            );        }    }}fn graphImportTargetAlloc(    allocator: std.mem.Allocator,    target: []const u8,) std.mem.Allocator.Error![]const u8 {    if (std.mem.indexOfScalar(u8, target, '/') == null) return target;    const normalized = try allocator.dupe(u8, target);    for (normalized) |*byte| {        if (byte.* == '/') byte.* = '.';    }    return normalized;}fn declaration(source: []const u8, form: Span, head: []const u8, closers: []u8) !?Declaration {    if (valueDeclaration(source, form, head, closers)) |record| return record;    const shape = declarationShape(head) orelse return null;    const name_span = listItem(source, form, shape.name_index, closers) orelse return null;    const name = declarationName(source, name_span, closers) orelse return null;    return .{        .name = name,        .kind = head,        .node_type = shape.node_type,        .form = form,        .params = if (shape.params_index) |index| listItem(source, form, index, closers) else null,        .body_owner = form,        .body_index = shape.body_index,        .test_definition = shape.test_definition,        .method_definition = shape.method_definition,    };}fn valueDeclaration(source: []const u8, form: Span, head: []const u8, closers: []u8) ?Declaration {    const value_form = std.mem.eql(u8, head, "define") or        std.mem.eql(u8, head, "define-for-syntax") or        std.mem.eql(u8, head, "define-syntax");    if (!value_form) return null;    const name = atom(source, listItem(source, form, 1, closers) orelse return null) orelse return null;    const value = listItem(source, form, 2, closers) orelse return null;    const lambda = std.mem.eql(u8, listHead(source, value, closers) orelse "", "lambda");    return .{        .name = name,        .kind = head,        .node_type = if (lambda) model.NodeType.function else model.NodeType.constant,        .form = form,        .params = if (lambda) listItem(source, value, 1, closers) else null,        .body_owner = if (lambda) value else form,        .body_index = if (lambda) 2 else null,        .test_definition = false,        .method_definition = false,    };}fn declarationShape(head: []const u8) ?Shape {    if (std.mem.eql(u8, head, "defsemantic")) return .{        .name_index = 2,        .params_index = 3,        .body_index = 4,        .node_type = model.NodeType.function,    };    if (std.mem.eql(u8, head, "defmethod")) return .{        .params_index = 3,        .body_index = 4,        .node_type = model.NodeType.method,        .method_definition = true,    };    if (std.mem.eql(u8, head, "deftest")) return .{        .body_index = 2,        .node_type = model.NodeType.function,        .test_definition = true,    };    if (std.mem.eql(u8, head, "defprotocol")) return .{        .body_index = 2,        .node_type = model.NodeType.interface,    };    if (std.mem.eql(u8, head, "defstruct") or        std.mem.eql(u8, head, "defrecord") or        std.mem.eql(u8, head, "define-condition")) return .{        .params_index = 2,        .body_index = 3,        .node_type = model.NodeType.class,    };    if (!definitionHead(head)) return null;    return .{        .params_index = 2,        .body_index = 3,        .node_type = model.NodeType.function,        .test_definition = std.mem.eql(u8, head, "deftest-support"),    };}fn definitionHead(head: []const u8) bool {    if (!std.mem.startsWith(u8, head, "def") or head.len <= "def".len) return false;    const non_definitions = [_][]const u8{ "default", "defaults", "definitions" };    for (non_definitions) |name| if (std.mem.eql(u8, head, name)) return false;    return true;}fn addDeclaration(context: ScanContext, record: Declaration) !void {    const line = lines_mod.lineForByte(context.source_lines, record.form.start);    const end_line = lines_mod.lineForByte(context.source_lines, record.form.end - 1);    const qualified = if (record.method_definition)        try std.fmt.allocPrint(            context.allocator,            "{s}.{s}#{d}",            .{ context.module, record.name, line },        )    else        try std.fmt.allocPrint(            context.allocator,            "{s}.{s}",            .{ context.module, record.name },        );    const existed = graph_mod.getNode(context.graph, qualified) != null;    const docstring = if (record.body_index) |body_index|        try documentationAlloc(            context.allocator,            context.source,            record.body_owner,            body_index,            context.closers,        )    else        null;    const metadata = try declarationMetadata(context, record);    try graph_mod.addNode(context.graph, .{        .name = qualified,        .type = record.node_type,        .file = context.path,        .line = line,        .end_line = end_line,        .docstring = docstring,        .metadata = metadata,    });    const edge_metadata = try model.sourcePair(context.allocator, "scan");    try core.addScanEdge(context.graph, context.stats, .{        .source = context.module,        .rel = model.RelType.contains,        .target = qualified,        .metadata = edge_metadata,    });    if (!existed) {        context.stats.nodes_added += 1;        try core.countType(context.stats, context.allocator, record.node_type);    }}fn declarationMetadata(context: ScanContext, record: Declaration) ![]const model.Pair {    const base = try source_metadata.spanMetadata(        context.allocator,        try model.sourcePair(context.allocator, "scan"),        context.source,        .{            .start_byte = @intCast(record.form.start),            .end_byte = @intCast(record.form.end),        },    );    var updates: std.ArrayList(model.Pair) = .empty;    const public = !record.method_definition and context.exports.contains(record.name);    try updates.append(context.allocator, try pair(        context.allocator,        api.visibility_key,        if (public) "public" else "private",    ));    try updates.append(context.allocator, try pair(        context.allocator,        "chiclet_kind",        record.kind,    ));    if (try signatureAlloc(context.allocator, context.source, record)) |signature| {        try updates.append(context.allocator, try pair(            context.allocator,            api.signature_key,            signature,        ));    }    if (record.test_definition) {        var test_pair = try pair(context.allocator, api.test_key, "true");        test_pair.json = true;        try updates.append(context.allocator, test_pair);    }    return try model.mergePairs(context.allocator, base, updates.items);}fn signatureAlloc(    allocator: std.mem.Allocator,    source: []const u8,    record: Declaration,) !?[]const u8 {    const params = record.params orelse return try allocator.dupe(u8, record.name);    const raw = std.mem.trim(u8, source[params.start..params.end], " \t\r\n");    return try std.fmt.allocPrint(allocator, "{s} {s}", .{ record.name, raw });}fn documentationAlloc(    allocator: std.mem.Allocator,    source: []const u8,    owner: Span,    body_index: usize,    closers: []u8,) !?[]const u8 {    var items = ItemIterator.init(source, owner, closers) orelse return null;    for (0..body_index) |_| _ = items.next() orelse return null;    const first = items.next() orelse return null;    _ = items.next() orelse return null;    if (try stringAlloc(allocator, source, first)) |description| return description;    return try mapDocumentationAlloc(allocator, source, first, closers);}fn mapDocumentationAlloc(    allocator: std.mem.Allocator,    source: []const u8,    map: Span,    closers: []u8,) !?[]const u8 {    if (source[map.start] != '{') return null;    var items = ItemIterator.init(source, map, closers) orelse return null;    while (items.next()) |key_span| {        const value_span = items.next() orelse return null;        const key = atom(source, key_span) orelse continue;        if (!documentationKey(key)) continue;        return try stringAlloc(allocator, source, value_span);    }    return null;}fn documentationKey(key: []const u8) bool {    return std.mem.eql(u8, key, ":summary") or        std.mem.eql(u8, key, ":description") or        std.mem.eql(u8, key, ":doc");}fn stringAlloc(    allocator: std.mem.Allocator,    source: []const u8,    span: Span,) !?[]const u8 {    if (span.end <= span.start + 1 or source[span.start] != '"' or        source[span.end - 1] != '"') return null;    const raw = source[span.start + 1 .. span.end - 1];    if (std.mem.indexOfScalar(u8, raw, '\\') == null) {        return try allocator.dupe(u8, raw);    }    var out: std.ArrayList(u8) = .empty;    var index: usize = 0;    while (index < raw.len) : (index += 1) {        if (raw[index] != '\\' or index + 1 == raw.len) {            try out.append(allocator, raw[index]);            continue;        }        index += 1;        try out.append(allocator, switch (raw[index]) {            'n' => '\n',            'r' => '\r',            't' => '\t',            else => raw[index],        });    }    return try out.toOwnedSlice(allocator);}fn pair(    allocator: std.mem.Allocator,    key: []const u8,    value: []const u8,) !model.Pair {    return .{        .key = try allocator.dupe(u8, key),        .value = try allocator.dupe(u8, value),    };}fn declarationName(source: []const u8, span: Span, closers: []u8) ?[]const u8 {    if (atom(source, span)) |name| return name;    var items = ItemIterator.init(source, span, closers) orelse return null;    return atom(source, items.next() orelse return null);}fn listHead(source: []const u8, span: Span, closers: []u8) ?[]const u8 {    var items = ItemIterator.init(source, span, closers) orelse return null;    return atom(source, items.next() orelse return null);}fn listItem(source: []const u8, span: Span, wanted: usize, closers: []u8) ?Span {    var items = ItemIterator.init(source, span, closers) orelse return null;    for (0..wanted) |_| _ = items.next() orelse return null;    return items.next();}fn atom(source: []const u8, span: Span) ?[]const u8 {    if (span.start >= span.end) return null;    const first = source[span.start];    if (first == '(' or first == '[' or first == '{' or first == '"' or        first == '\'' or first == '`' or first == ',') return null;    return source[span.start..span.end];}fn nextTopLevel(source: []const u8, index: *usize, closers: []u8) ?Span {    skipTrivia(source, index);    if (index.* >= source.len) return null;    const start = index.*;    const end = expressionEnd(source, start, closers) orelse {        index.* = source.len;        return null;    };    index.* = end;    return .{ .start = start, .end = end };}fn expressionEnd(source: []const u8, raw_start: usize, closers: []u8) ?usize {    var start = raw_start;    skipTrivia(source, &start);    if (start >= source.len) return null;    const first = source[start];    if (first == '\'' or first == '`' or first == ',') {        var operand = start + 1;        if (first == ',' and operand < source.len and source[operand] == '@') operand += 1;        return expressionEnd(source, operand, closers);    }    if (first == '"') return stringEnd(source, start);    if (first == '(' or first == '[' or first == '{') return aggregateEnd(source, start, closers);    if (first == ')' or first == ']' or first == '}') return null;    var end = start;    while (end < source.len and !delimiter(source[end])) end += 1;    return if (end == start) null else end;}fn aggregateEnd(source: []const u8, start: usize, closers: []u8) ?usize {    var depth: usize = 1;    closers[0] = closing(source[start]) orelse return null;    var index = start + 1;    while (index < source.len) : (index += 1) {        const byte = source[index];        if (byte == ';') {            while (index < source.len and source[index] != '\n') index += 1;            if (index >= source.len) return null;            continue;        }        if (byte == '"') {            index = (stringEnd(source, index) orelse return null) - 1;            continue;        }        if (closing(byte)) |closer| {            if (depth == closers.len) return null;            closers[depth] = closer;            depth += 1;            continue;        }        if (byte != ')' and byte != ']' and byte != '}') continue;        if (closers[depth - 1] != byte) return null;        depth -= 1;        if (depth == 0) return index + 1;    }    return null;}fn stringEnd(source: []const u8, start: usize) ?usize {    var index = start + 1;    while (index < source.len) : (index += 1) {        if (source[index] == '\\') {            index += 1;            if (index >= source.len) return null;            continue;        }        if (source[index] == '"') return index + 1;    }    return null;}fn closing(byte: u8) ?u8 {    return switch (byte) {        '(' => ')',        '[' => ']',        '{' => '}',        else => null,    };}fn delimiter(byte: u8) bool {    return byte == ' ' or byte == '\t' or byte == '\r' or byte == '\n' or        byte == '(' or byte == ')' or byte == '[' or byte == ']' or        byte == '{' or byte == '}' or byte == '"' or byte == ';' or        byte == '\'' or byte == '`' or byte == ',';}fn skipTrivia(source: []const u8, index: *usize) void {    while (index.* < source.len) {        const byte = source[index.*];        if (byte == ' ' or byte == '\t' or byte == '\r' or byte == '\n') {            index.* += 1;            continue;        }        if (byte != ';') return;        while (index.* < source.len and source[index.*] != '\n') index.* += 1;    }}

Source: tools/smg/src/scan/root.zig:7

zig
pub const chiclet = @import("chiclet.zig");

Complete call list for scan.chiclet.scan

7 direct calls.

Audit

Definitions2
Public names2
Members0
Version26.7.0
Revisiondaab053ee433