Skip to documentation
SLOP

tiny.smg.scan.python

Reference tiny.smg scan python

Defined in scan.

API (1)

Actions

Public operations.

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

Source

Called byCallsprivate; no linktools.smg.src.scan.pipeline.scanscanFileSourceprivate; no linktools.smg.src.scan.pythonscanLinesscan.pythonscan
Static calls · unresolved targets: 0 · external targets: 0.

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

Definitions2
Public names2
Members0
Version26.7.0
Revisiondaab053ee433