tiny.smg.scan.python
Defined in scan.
API (1)
Actions
Public operations.
Source
Source: tools/smg/src/scan/python.zig
zig
const std = @import("std");const smg = @import("../root.zig");const graph_mod = smg.graph;const lines_mod = smg.lines;const model = smg.model;const scan_root = smg.scan;const text = smg.text;const core = scan_root.core;const metrics = scan_root.scan_metrics;const CallTarget = core.CallTarget;const SourceLines = lines_mod.SourceLines;const Stats = core.Stats;const identByte = metrics.identByte;const indentation = metrics.indentation;const pythonCommentLine = metrics.pythonCommentLine;const skipQuoted = metrics.skipQuoted;const textFunctionMetadata = metrics.textFunctionMetadata;const textSpanMetadata = metrics.textSpanMetadata;const Alias = struct { local: []const u8, target: []const u8,};const PythonLine = struct { raw: []const u8, trimmed: []const u8, indent: usize, number: i64,};const PythonBodyContext = struct { allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, parent: []const u8, path: []const u8, source: []const u8, source_lines: SourceLines, lines: []const PythonLine, parent_indent: usize, in_class: bool, aliases: []const Alias, stats: *Stats,};const PythonBodyLineAction = enum { scan, skip, stop,};const PythonBodyFrame = struct { parent: []const u8, start: usize, action_start: usize, end: usize, parent_indent: usize, in_class: bool,};const PythonBodyScan = struct { block_end: ?usize = null, child: ?PythonBodyFrame = null,};const PythonClassContext = struct { allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, parent: []const u8, path: []const u8, source: []const u8, source_lines: SourceLines, lines: []const PythonLine, aliases: []const Alias, stats: *Stats,};const PythonClassRecord = struct { qualified: []const u8, index: usize, block_end: usize, line: PythonLine, end_line: i64, decorators: []const []const u8,};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) !void { try scanLines(allocator, scratch, graph, module, path, source, source_lines, stats);}fn scanLines(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) !void { var lines: std.ArrayList(PythonLine) = .empty; var split = std.mem.splitScalar(u8, source, '\n'); var line_no: i64 = 0; while (split.next()) |raw_line| { line_no += 1; const trimmed = text.trim(raw_line); try lines.append(scratch, .{ .raw = raw_line, .trimmed = trimmed, .indent = indentation(raw_line), .number = line_no }); } var aliases: std.ArrayList(Alias) = .empty; try extractImportsText(allocator, scratch, graph, module, lines.items, stats, &aliases); try walkTextBody(allocator, scratch, graph, module, path, source, source_lines, lines.items, 0, lines.items.len, 0, false, aliases.items, stats); try extractDynamicImportsText(allocator, graph, module, lines.items, stats);}fn extractImportsText(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, lines: []const PythonLine, stats: *Stats, aliases: *std.ArrayList(Alias)) !void { var index = lines.len; while (index > 0) { index -= 1; const line = lines[index]; if (line.trimmed.len == 0 or pythonCommentLine(line.trimmed)) continue; const metadata = if (line.indent == 0) &.{} else try core.boolMetadata(allocator, "deferred"); if (std.mem.startsWith(u8, line.trimmed, "import ")) { try extractImportText(allocator, scratch, graph, module, line.trimmed["import ".len..], metadata, stats, aliases); } else if (std.mem.startsWith(u8, line.trimmed, "from ")) { try extractFromImportText(allocator, scratch, graph, module, line.trimmed["from ".len..], metadata, stats, aliases); } }}fn extractImportText(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, raw: []const u8, metadata: []const model.Pair, stats: *Stats, aliases: *std.ArrayList(Alias)) !void { var imports: std.ArrayList([]const u8) = .empty; var split = std.mem.splitScalar(u8, raw, ','); while (split.next()) |part| { const item = text.trim(part); if (item.len != 0) try imports.append(scratch, item); } var index = imports.items.len; while (index > 0) { index -= 1; const item = imports.items[index]; const as_index = std.mem.indexOf(u8, item, " as "); const target_raw = text.trim(if (as_index) |pos| item[0..pos] else item); if (!dottedName(target_raw)) continue; const target = try allocator.dupe(u8, target_raw); try core.addImportEdge(allocator, graph, module, target, metadata, stats); const local = if (as_index) |pos| text.trim(item[pos + " as ".len ..]) else firstDottedPart(target); if (local.len != 0 and identifier(local)) try aliases.append(scratch, .{ .local = local, .target = target }); }}fn extractFromImportText(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, raw: []const u8, metadata: []const model.Pair, stats: *Stats, aliases: *std.ArrayList(Alias)) !void { const import_index = std.mem.indexOf(u8, raw, " import ") orelse return; const raw_module = text.trim(raw[0..import_index]); if (raw_module.len == 0) return; const target = try resolveImportTarget(allocator, scratch, module, raw_module); try core.addImportEdge(allocator, graph, module, target, metadata, stats); const imports_raw = text.trim(raw[import_index + " import ".len ..]); var imports: std.ArrayList([]const u8) = .empty; var split = std.mem.splitScalar(u8, imports_raw, ','); while (split.next()) |part| { const item = text.trim(part); if (item.len != 0) try imports.append(scratch, item); } var index = imports.items.len; while (index > 0) { index -= 1; const item = imports.items[index]; if (std.mem.eql(u8, item, "*")) continue; const as_index = std.mem.indexOf(u8, item, " as "); const imported_raw = text.trim(if (as_index) |pos| item[0..pos] else item); if (!dottedName(imported_raw)) continue; const local = if (as_index) |pos| text.trim(item[pos + " as ".len ..]) else firstDottedPart(imported_raw); if (local.len == 0 or !identifier(local)) continue; try aliases.append(scratch, .{ .local = local, .target = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ target, imported_raw }), }); }}fn walkTextBody(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, parent: []const u8, path: []const u8, source: []const u8, source_lines: SourceLines, lines: []const PythonLine, start: usize, end: usize, parent_indent: usize, in_class: bool, aliases: []const Alias, stats: *Stats) anyerror!void { const context: PythonBodyContext = .{ .allocator = allocator, .scratch = scratch, .graph = graph, .parent = parent, .path = path, .source = source, .source_lines = source_lines, .lines = lines, .parent_indent = parent_indent, .in_class = in_class, .aliases = aliases, .stats = stats }; try walkTextBodyContext(context, start, end);}fn walkTextBodyContext(context: PythonBodyContext, start: usize, end: usize) anyerror!void { var frames: std.ArrayList(PythonBodyFrame) = .empty; try frames.append(context.scratch, .{ .parent = context.parent, .start = start, .action_start = start, .end = end, .parent_indent = context.parent_indent, .in_class = context.in_class }); while (frames.pop()) |frame| { var frame_context = context; frame_context.parent = frame.parent; frame_context.parent_indent = frame.parent_indent; frame_context.in_class = frame.in_class; var index = frame.start; while (index < frame.end) : (index += 1) { const line = context.lines[index]; switch (bodyLineAction(frame_context, frame.action_start, line)) { .skip => continue, .stop => break, .scan => {}, } const result = try scanBodyLine(frame_context, index, frame.end); if (result.child) |child| { if (result.block_end) |block_end| { if (block_end + 1 < frame.end) try frames.append(context.scratch, .{ .parent = frame.parent, .start = block_end + 1, .action_start = frame.action_start, .end = frame.end, .parent_indent = frame.parent_indent, .in_class = frame.in_class }); } try frames.append(context.scratch, child); break; } if (result.block_end) |block_end| index = block_end; } }}fn bodyLineAction(context: PythonBodyContext, start: usize, line: PythonLine) PythonBodyLineAction { if (line.trimmed.len == 0 or pythonCommentLine(line.trimmed)) return .skip; if (line.indent < context.parent_indent) return .stop; if (start != 0 and line.indent <= context.parent_indent) return .stop; if (line.indent != context.parent_indent and start == 0) return .skip; if (start != 0 and line.indent != context.parent_indent + 4 and line.indent <= context.parent_indent) return .skip; return .scan;}fn scanBodyLine(context: PythonBodyContext, index: usize, end: usize) anyerror!PythonBodyScan { const line = context.lines[index]; var decorators: std.ArrayList([]const u8) = .empty; var definition_index = index; if (std.mem.startsWith(u8, line.trimmed, "@")) { definition_index = try collectDecorators(context.allocator, context.scratch, context.lines, index, end, &decorators); if (definition_index == index) return .{}; } if (try scanClassDefinition(context, definition_index, decorators.items)) |result| return result; if (try scanFunctionDefinition(context, definition_index, decorators.items)) |block_end| return .{ .block_end = block_end }; if (decorators.items.len == 0) try extractAssignmentText(context.allocator, context.graph, context.parent, context.path, context.source, line, context.stats); return .{};}fn scanClassDefinition(context: PythonBodyContext, definition_index: usize, decorators: []const []const u8) anyerror!?PythonBodyScan { const definition_line = context.lines[definition_index]; const name = className(definition_line.trimmed) orelse return null; const block_end = blockEnd(context.lines, definition_index); const child = try extractClassText(context.allocator, context.scratch, context.graph, context.parent, context.path, context.source, context.source_lines, context.lines, definition_index, block_end, name, decorators, context.aliases, context.stats); return .{ .block_end = block_end, .child = child };}fn scanFunctionDefinition(context: PythonBodyContext, definition_index: usize, decorators: []const []const u8) anyerror!?usize { const definition_line = context.lines[definition_index]; const name = functionName(definition_line.trimmed) orelse return null; const block_end = blockEnd(context.lines, definition_index); try extractFunctionText(context.allocator, context.scratch, context.graph, context.parent, context.path, context.source_lines, context.lines, definition_index, block_end, name, context.in_class, decorators, context.aliases, context.stats); return block_end;}fn collectDecorators(allocator: std.mem.Allocator, scratch: std.mem.Allocator, lines: []const PythonLine, start: usize, end: usize, decorators: *std.ArrayList([]const u8)) !usize { const indent = lines[start].indent; var index = start; while (index < end) : (index += 1) { const line = lines[index]; if (line.trimmed.len == 0) continue; if (line.indent != indent or !std.mem.startsWith(u8, line.trimmed, "@")) break; if (decoratorNameText(line.trimmed)) |name| try decorators.append(scratch, try allocator.dupe(u8, name)); } return index;}fn extractClassText(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, parent: []const u8, path: []const u8, source: []const u8, source_lines: SourceLines, lines: []const PythonLine, index: usize, block_end: usize, name: []const u8, decorators: []const []const u8, aliases: []const Alias, stats: *Stats) !?PythonBodyFrame { const context: PythonClassContext = .{ .allocator = allocator, .scratch = scratch, .graph = graph, .parent = parent, .path = path, .source = source, .source_lines = source_lines, .lines = lines, .aliases = aliases, .stats = stats }; const class_record = try classRecord(context, index, block_end, name, decorators); try addClassNode(context, class_record); try addClassRelations(context, class_record); return classBodyFrame(class_record);}fn classRecord(context: PythonClassContext, index: usize, block_end: usize, name: []const u8, decorators: []const []const u8) !PythonClassRecord { return .{ .qualified = try std.fmt.allocPrint(context.allocator, "{s}.{s}", .{ context.parent, name }), .index = index, .block_end = block_end, .line = context.lines[index], .end_line = context.lines[block_end].number, .decorators = decorators, };}fn addClassNode(context: PythonClassContext, class_record: PythonClassRecord) !void { try graph_mod.addNode(context.graph, .{ .name = class_record.qualified, .type = model.NodeType.class, .file = context.path, .line = class_record.line.number, .end_line = class_record.end_line, .docstring = try docstring(context.allocator, context.scratch, context.lines, class_record.index, class_record.block_end), .metadata = try textSpanMetadata(context.allocator, context.source_lines, class_record.line.number, class_record.end_line, null), });}fn addClassRelations(context: PythonClassContext, class_record: PythonClassRecord) !void { try core.addScanEdge(context.graph, context.stats, .{ .source = context.parent, .rel = model.RelType.contains, .target = class_record.qualified, .metadata = try model.sourcePair(context.allocator, "scan") }); context.stats.nodes_added += 1; try core.countType(context.stats, context.allocator, model.NodeType.class); try extractSuperText(context.allocator, context.scratch, context.graph, context.parent, class_record.qualified, class_record.line.trimmed, context.aliases, context.stats); try recordDecoratorSkipsText(context.stats, class_record.decorators, class_record.qualified);}fn classBodyFrame(class_record: PythonClassRecord) ?PythonBodyFrame { if (class_record.block_end <= class_record.index) return null; return .{ .parent = class_record.qualified, .start = class_record.index + 1, .action_start = class_record.index + 1, .end = class_record.block_end + 1, .parent_indent = class_record.line.indent, .in_class = true };}fn extractFunctionText(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, parent: []const u8, path: []const u8, source_lines: SourceLines, lines: []const PythonLine, index: usize, block_end: usize, name: []const u8, in_class: bool, decorators: []const []const u8, aliases: []const Alias, stats: *Stats) !void { const line = lines[index]; const qualified = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ parent, name }); const node_type = if (in_class) model.NodeType.method else model.NodeType.function; try graph_mod.addNode(graph, .{ .name = qualified, .type = node_type, .file = path, .line = line.number, .end_line = lines[block_end].number, .docstring = try docstring(allocator, scratch, lines, index, block_end), .metadata = try textFunctionMetadata(allocator, stats.metricInputAllocator(allocator), source_lines, line.number, lines[block_end].number, .python), }); try core.addScanEdge(graph, stats, .{ .source = parent, .rel = model.RelType.contains, .target = qualified, .metadata = try model.sourcePair(allocator, "scan") }); stats.nodes_added += 1; try core.countType(stats, allocator, node_type); try recordDecoratorSkipsText(stats, decorators, qualified); try extractCallsText(allocator, scratch, graph, qualified, if (in_class) parent else null, lines, index + 1, block_end + 1, aliases, stats);}fn extractAssignmentText(allocator: std.mem.Allocator, graph: *graph_mod.Graph, parent: []const u8, path: []const u8, source: []const u8, line: PythonLine, stats: *Stats) !void { _ = source; const eq = std.mem.indexOfScalar(u8, line.trimmed, '=') orelse return; const name = text.trim(line.trimmed[0..eq]); if (!identifier(name) or !core.isUpper(name)) return; const qualified = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ parent, name }); try graph_mod.addNode(graph, .{ .name = qualified, .type = model.NodeType.constant, .file = path, .line = line.number, .end_line = line.number, .metadata = try model.sourcePair(allocator, "scan"), }); try core.addScanEdge(graph, stats, .{ .source = parent, .rel = model.RelType.contains, .target = qualified, .metadata = try model.sourcePair(allocator, "scan") }); stats.nodes_added += 1; try core.countType(stats, allocator, model.NodeType.constant);}fn extractSuperText(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, parent: []const u8, qualified: []const u8, line: []const u8, aliases: []const Alias, stats: *Stats) !void { const open = std.mem.indexOfScalar(u8, line, '(') orelse return; const close = std.mem.lastIndexOfScalar(u8, line, ')') orelse return; if (close <= open + 1) return; var split = std.mem.splitScalar(u8, line[open + 1 .. close], ','); while (split.next()) |raw_item| { const item = text.trim(raw_item); if (!dottedName(item)) continue; const target = aliasTarget(aliases, item) orelse item; if (try resolveLocalType(scratch, graph.*, parent, target)) |resolved| { try core.addScanEdge(graph, stats, .{ .source = qualified, .rel = model.RelType.inherits, .target = resolved, .metadata = try model.sourcePair(allocator, "scan") }); } else { try stats.deferred.append(.{ .source = qualified, .rel = model.RelType.inherits, .target = try allocator.dupe(u8, target) }); } }}fn extractCallsText(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, caller: []const u8, class_name: ?[]const u8, lines: []const PythonLine, start: usize, end: usize, aliases: []const Alias, stats: *Stats) !void { var calls: std.ArrayList(CallTarget) = .empty; var index = start; while (index < end) : (index += 1) { const line = lines[index]; if (line.trimmed.len == 0 or pythonCommentLine(line.trimmed)) continue; try collectCallsInLine(allocator, scratch, line.trimmed, class_name, aliases, &calls); } var call_index = calls.items.len; while (call_index > 0) { call_index -= 1; try core.addCallEdge(allocator, graph, caller, calls.items[call_index], stats); }}fn collectCallsInLine(allocator: std.mem.Allocator, scratch: std.mem.Allocator, line: []const u8, class_name: ?[]const u8, aliases: []const Alias, calls: *std.ArrayList(CallTarget)) !void { var index: usize = 0; while (index < line.len) : (index += 1) { const byte = line[index]; if (byte == '"' or byte == '\'') { index = skipQuoted(line, index); if (index >= line.len) break; continue; } if (byte == '#') break; if (byte != '(') continue; const raw = targetBeforeParen(line, index) orelse continue; if (try callTargetFromText(allocator, raw, class_name, aliases)) |target| try calls.append(scratch, target); }}fn callTargetFromText(allocator: std.mem.Allocator, raw: []const u8, class_name: ?[]const u8, aliases: []const Alias) !?CallTarget { if (std.mem.eql(u8, raw, "def") or std.mem.eql(u8, raw, "class") or builtin(raw)) return null; const first_dot = std.mem.indexOfScalar(u8, raw, '.'); if (first_dot == null) { if (aliasTarget(aliases, raw)) |target| return .{ .name = target, .resolved = false }; return .{ .name = try allocator.dupe(u8, raw), .resolved = false }; } const root = raw[0..first_dot.?]; const suffix = raw[first_dot.? + 1 ..]; if ((std.mem.eql(u8, root, "self") or std.mem.eql(u8, root, "cls")) and class_name != null) { return .{ .name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ class_name.?, suffix }), .resolved = true }; } if (aliasTarget(aliases, root)) |target_prefix| { return .{ .name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ target_prefix, suffix }), .resolved = false }; } return .{ .name = try allocator.dupe(u8, raw), .resolved = false };}fn extractDynamicImportsText(allocator: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, lines: []const PythonLine, stats: *Stats) !void { for (lines) |line| { if (line.trimmed.len == 0 or pythonCommentLine(line.trimmed)) continue; var index: usize = 0; while (index < line.trimmed.len) : (index += 1) { const byte = line.trimmed[index]; if (byte == '"' or byte == '\'') { index = skipQuoted(line.trimmed, index); if (index >= line.trimmed.len) break; continue; } if (byte == '#') break; if (byte != '(') continue; const raw = targetBeforeParen(line.trimmed, index) orelse continue; if (!std.mem.eql(u8, raw, "importlib.import_module") and !std.mem.eql(u8, raw, "__import__")) continue; const target = firstTextStringArgument(line.trimmed[index + 1 ..]) orelse continue; if (!std.mem.startsWith(u8, target, ".")) try core.addImportEdge(allocator, graph, module, try allocator.dupe(u8, target), try core.boolMetadata(allocator, "dynamic"), stats); } }}fn blockEnd(lines: []const PythonLine, start: usize) usize { const base_indent = lines[start].indent; var last = start; var index = start + 1; while (index < lines.len) : (index += 1) { const line = lines[index]; if (line.trimmed.len == 0 or pythonCommentLine(line.trimmed)) continue; if (line.indent <= base_indent) break; last = index; } return last;}fn className(line: []const u8) ?[]const u8 { if (!std.mem.startsWith(u8, line, "class ")) return null; const rest = text.trim(line["class ".len..]); return core.leadingIdentifier(rest);}fn functionName(line: []const u8) ?[]const u8 { const rest = if (std.mem.startsWith(u8, line, "def ")) line["def ".len..] else if (std.mem.startsWith(u8, line, "async def ")) line["async def ".len..] else return null; return core.leadingIdentifier(text.trim(rest));}fn decoratorNameText(line: []const u8) ?[]const u8 { if (!std.mem.startsWith(u8, line, "@")) return null; const rest = line[1..]; var end: usize = 0; while (end < rest.len and (identByte(rest[end]) or rest[end] == '.')) end += 1; if (end == 0) return null; return rest[0..end];}fn recordDecoratorSkipsText(stats: *Stats, decorators: []const []const u8, target: []const u8) !void { for (decorators) |decorator| { try stats.deferred.append(.{ .source = decorator, .rel = model.RelType.decorates, .target = target }); }}fn targetBeforeParen(line: []const u8, paren: usize) ?[]const u8 { if (paren == 0) return null; var end = paren; while (end > 0 and std.ascii.isWhitespace(line[end - 1])) end -= 1; var start = end; while (start > 0 and (identByte(line[start - 1]) or line[start - 1] == '.')) start -= 1; if (start == end) return null; const raw = line[start..end]; if (raw[0] == '.' or raw[raw.len - 1] == '.' or std.mem.indexOf(u8, raw, "..") != null) return null; return raw;}fn firstTextStringArgument(raw: []const u8) ?[]const u8 { var rest = text.trim(raw); if (rest.len == 0) return null; if (rest[0] != '"' and rest[0] != '\'') return null; const quote = rest[0]; rest = rest[1..]; var end: usize = 0; while (end < rest.len) : (end += 1) { if (rest[end] == '\\') { if (end + 1 < rest.len) end += 1; continue; } if (rest[end] == quote) return rest[0..end]; } return null;}fn docstring(allocator: std.mem.Allocator, scratch: std.mem.Allocator, lines: []const PythonLine, definition_index: usize, block_end: usize) !?[]const u8 { if (definition_index >= block_end) return null; const def_indent = lines[definition_index].indent; var index = definition_index + 1; while (index <= block_end) : (index += 1) { const line = lines[index]; if (line.trimmed.len == 0 or pythonCommentLine(line.trimmed)) continue; if (line.indent <= def_indent) return null; return try docstringFromLine(allocator, scratch, lines, index, block_end); } return null;}fn docstringFromLine(allocator: std.mem.Allocator, scratch: std.mem.Allocator, lines: []const PythonLine, start: usize, block_end: usize) !?[]const u8 { const first = lines[start].trimmed; const quote = if (std.mem.startsWith(u8, first, "\"\"\"")) "\"\"\"" else if (std.mem.startsWith(u8, first, "'''")) "'''" else if (first.len >= 2 and (first[0] == '"' or first[0] == '\'')) first[0..1] else return null; var content = first[quote.len..]; if (std.mem.indexOf(u8, content, quote)) |end| { const doc = text.trim(content[0..end]); return if (doc.len == 0) null else try allocator.dupe(u8, doc); } var out: std.Io.Writer.Allocating = .init(scratch); errdefer out.deinit(); if (content.len != 0) try out.writer.writeAll(content); var index = start + 1; while (index <= block_end) : (index += 1) { const line = text.trim(lines[index].raw); if (std.mem.indexOf(u8, line, quote)) |end| { if (out.written().len != 0) try out.writer.writeByte('\n'); try out.writer.writeAll(line[0..end]); break; } if (out.written().len != 0) try out.writer.writeByte('\n'); try out.writer.writeAll(line); } const raw_doc = try out.toOwnedSlice(); const doc = text.trim(raw_doc); return if (doc.len == 0) null else try allocator.dupe(u8, doc);}fn dottedName(value: []const u8) bool { if (value.len == 0) return false; var split = std.mem.splitScalar(u8, value, '.'); while (split.next()) |part| { if (!identifier(part)) return false; } return true;}fn identifier(value: []const u8) bool { if (value.len == 0 or (!std.ascii.isAlphabetic(value[0]) and value[0] != '_')) return false; for (value[1..]) |byte| if (!identByte(byte)) return false; return true;}fn aliasTarget(aliases: []const Alias, local: []const u8) ?[]const u8 { for (aliases) |alias| { if (std.mem.eql(u8, alias.local, local)) return alias.target; } return null;}fn resolveLocalType(scratch: std.mem.Allocator, graph: graph_mod.Graph, parent: []const u8, raw: []const u8) !?[]const u8 { if (graph_mod.getNode(&graph, raw)) |node| return node.name; const local = try std.fmt.allocPrint(scratch, "{s}.{s}", .{ parent, raw }); if (graph_mod.getNode(&graph, local)) |node| return node.name; return null;}fn firstDottedPart(value: []const u8) []const u8 { const end = std.mem.indexOfScalar(u8, value, '.') orelse value.len; return value[0..end];}fn resolveImportTarget(allocator: std.mem.Allocator, scratch: std.mem.Allocator, module: []const u8, raw: []const u8) ![]const u8 { if (!std.mem.startsWith(u8, raw, ".")) return try allocator.dupe(u8, raw); var dots: usize = 0; while (dots < raw.len and raw[dots] == '.') dots += 1; var parts: std.ArrayList([]const u8) = .empty; var split = std.mem.splitScalar(u8, module, '.'); while (split.next()) |part| try parts.append(scratch, part); const keep = if (dots > parts.items.len) 0 else parts.items.len - dots; const base = if (keep == 0) "" else try core.joinDotted(allocator, parts.items[0..keep]); const suffix = raw[dots..]; if (base.len == 0) return try allocator.dupe(u8, suffix); if (suffix.len == 0) return base; return try std.fmt.allocPrint(allocator, "{s}.{s}", .{ base, suffix });}fn builtin(name: []const u8) bool { const builtins = [_][]const u8{ "print", "len", "range", "enumerate", "zip", "map", "filter", "isinstance", "issubclass", "hasattr", "getattr", "setattr", "delattr", "type", "id", "hash", "repr", "str", "int", "float", "bool", "bytes", "callable", "chr", "list", "dict", "dir", "set", "tuple", "frozenset", "sorted", "reversed", "min", "max", "sum", "abs", "round", "open", "ord", "iter", "next", "any", "all", "__import__", "cls", "super", "property", "staticmethod", "classmethod", "ValueError", "TypeError", "KeyError", "AttributeError", "RuntimeError", "Exception", "NotImplementedError", "StopIteration", "AssertionError", "OSError", "IOError", "FileNotFoundError", "ImportError" }; for (builtins) |item| if (std.mem.eql(u8, name, item)) return true; return false;}Source: tools/smg/src/scan/root.zig:9
zig
pub const python = @import("python.zig");Audit
| Definitions | 2 |
|---|---|
| Public names | 2 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |