tiny.smg.scan.chiclet
Defined in scan.
Structural Chiclet source scanner.
API (1)
Actions
Public operations.
Source
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.
tools.smg.src.scan.chiclet.addDeclaration[function] — private; no exact target attools/smg/src/scan/chiclet.zig:311in nearest public ownertiny.smg.scan.chiclettools.smg.src.scan.chiclet.collectExports[function] — private; no exact target attools/smg/src/scan/chiclet.zig:154in nearest public ownertiny.smg.scan.chiclettools.smg.src.scan.chiclet.declaration[function] — private; no exact target attools/smg/src/scan/chiclet.zig:227in nearest public ownertiny.smg.scan.chiclettools.smg.src.scan.chiclet.listHead[function] — private; no exact target attools/smg/src/scan/chiclet.zig:491in nearest public ownertiny.smg.scan.chiclettools.smg.src.scan.chiclet.moduleDocumentationAlloc[function] — private; no exact target attools/smg/src/scan/chiclet.zig:135in nearest public ownertiny.smg.scan.chiclettools.smg.src.scan.chiclet.nextTopLevel[function] — private; no exact target attools/smg/src/scan/chiclet.zig:510in nearest public ownertiny.smg.scan.chiclettools.smg.src.scan.chiclet.scanImport[function] — private; no exact target attools/smg/src/scan/chiclet.zig:183in nearest public ownertiny.smg.scan.chiclet
Audit
| Definitions | 2 |
|---|---|
| Public names | 2 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |