tiny.smg.zig
Defined in tiny.smg.
API (10)
Actions
Public operations.
declarationDocstring: Extracts the contiguous /// tokens attached before a declaration.declarationSourceSpan: Returns attached documentation and the complete declaration terminator.functionSignature: Copies a function prototype without its body.moduleDocstring: Extracts leading file documentation from //!nodeEndLinenodeLinetokenDocstring: Extracts the contiguous /// tokens attached before a source token.tokenLinevariableSignature: Copies a value declaration or the header of a container declaration.
Types and contracts
Public types and contracts.
SourceSpan: An exact half-open declaration range in parsed Zig source.
Source
Source: tools/smg/src/root.zig:39
zig
pub const zig = @import("zig.zig");Source: tools/smg/src/zig.zig
zig
const std = @import("std");const lines = @import("lines.zig");/// An exact half-open declaration range in parsed Zig source.pub const SourceSpan = struct { start_byte: u32, end_byte: u32,};pub fn tokenLine(source_lines: lines.SourceLines, ast: *const std.zig.Ast, token: std.zig.Ast.TokenIndex) i64 { return lines.lineForByte(source_lines, @intCast(ast.tokenStart(token)));}pub fn nodeLine(source_lines: lines.SourceLines, ast: *const std.zig.Ast, node: std.zig.Ast.Node.Index) i64 { return tokenLine(source_lines, ast, ast.firstToken(node));}pub fn nodeEndLine(source_lines: lines.SourceLines, ast: *const std.zig.Ast, node: std.zig.Ast.Node.Index) i64 { return tokenLine(source_lines, ast, ast.lastToken(node));}/// Extracts leading file documentation from //! tokens.pub fn moduleDocstring( allocator: std.mem.Allocator, ast: *const std.zig.Ast,) !?[]const u8 { var end: std.zig.Ast.TokenIndex = 0; while (end < ast.tokens.len and ast.tokenTag(end) == .container_doc_comment) { end += 1; } return try docstring(allocator, ast, 0, end, .container_doc_comment);}/// Extracts the contiguous /// tokens attached before a declaration.pub fn declarationDocstring( allocator: std.mem.Allocator, ast: *const std.zig.Ast, node: std.zig.Ast.Node.Index,) !?[]const u8 { var start = ast.firstToken(node); const end = start; while (start > 0 and ast.tokenTag(start - 1) == .doc_comment) start -= 1; return try docstring(allocator, ast, start, end, .doc_comment);}/// Returns attached documentation and the complete declaration terminator.pub fn declarationSourceSpan( ast: *const std.zig.Ast, node: std.zig.Ast.Node.Index,) SourceSpan { var first = ast.firstToken(node); while (first > 0 and ast.tokenTag(first - 1) == .doc_comment) first -= 1; const last = ast.lastToken(node); var end = ast.tokenStart(last) + ast.tokenSlice(last).len; if (last + 1 < ast.tokens.len and ast.tokenTag(last + 1) == .semicolon) { const terminator = last + 1; end = ast.tokenStart(terminator) + ast.tokenSlice(terminator).len; } const start = ast.tokenStart(first); std.debug.assert(start < end); std.debug.assert(end <= ast.source.len); return .{ .start_byte = @intCast(start), .end_byte = @intCast(end) };}/// Extracts the contiguous /// tokens attached before a source token.pub fn tokenDocstring( allocator: std.mem.Allocator, ast: *const std.zig.Ast, token: std.zig.Ast.TokenIndex,) !?[]const u8 { var start = token; while (start > 0 and ast.tokenTag(start - 1) == .doc_comment) start -= 1; return try docstring(allocator, ast, start, token, .doc_comment);}/// Copies a function prototype without its body.pub fn functionSignature( allocator: std.mem.Allocator, ast: *const std.zig.Ast, proto: std.zig.Ast.full.FnProto,) ![]const u8 { const node = proto.ast.proto_node; const last = ast.lastToken(node); const has_semicolon = last + 1 < ast.tokens.len and ast.tokenTag(last + 1) == .semicolon; return try copyTrimmedSignature( allocator, ast.getNodeSource(node), has_semicolon, );}/// Copies a value declaration or the header of a container declaration.pub fn variableSignature( allocator: std.mem.Allocator, ast: *const std.zig.Ast, node: std.zig.Ast.Node.Index, container: bool,) ![]const u8 { var raw = ast.getNodeSource(node); if (container) { const brace = std.mem.indexOfScalar(u8, raw, '{') orelse raw.len; raw = raw[0..brace]; return try copyTrimmed(allocator, raw); } const last = ast.lastToken(node); const has_semicolon = last + 1 < ast.tokens.len and ast.tokenTag(last + 1) == .semicolon; return try copyTrimmedSignature(allocator, raw, has_semicolon);}fn docstring( allocator: std.mem.Allocator, ast: *const std.zig.Ast, start: std.zig.Ast.TokenIndex, end: std.zig.Ast.TokenIndex, expected: std.zig.Token.Tag,) !?[]const u8 { if (start == end) return null; var size: usize = end - start - 1; for (start..end) |raw_index| { const index: std.zig.Ast.TokenIndex = @intCast(raw_index); std.debug.assert(ast.tokenTag(index) == expected); size = try std.math.add(usize, size, docLine(ast.tokenSlice(index)).len); } const out = try allocator.alloc(u8, size); var offset: usize = 0; for (start..end, 0..) |raw_index, line_index| { const index: std.zig.Ast.TokenIndex = @intCast(raw_index); const line = docLine(ast.tokenSlice(index)); if (line_index != 0) { out[offset] = '\n'; offset += 1; } @memcpy(out[offset .. offset + line.len], line); offset += line.len; } std.debug.assert(offset == out.len); return out;}fn docLine(raw: []const u8) []const u8 { std.debug.assert(raw.len >= 3); var line = raw[3..]; if (std.mem.startsWith(u8, line, " ")) line = line[1..]; return std.mem.trimEnd(u8, line, "\r");}fn copyTrimmed(allocator: std.mem.Allocator, raw: []const u8) ![]const u8 { return try allocator.dupe(u8, std.mem.trim(u8, raw, " \t\r\n"));}fn copyTrimmedSignature( allocator: std.mem.Allocator, raw: []const u8, semicolon: bool,) ![]const u8 { const trimmed = std.mem.trim(u8, raw, " \t\r\n"); const size = try std.math.add(usize, trimmed.len, @intFromBool(semicolon)); const out = try allocator.alloc(u8, size); @memcpy(out[0..trimmed.len], trimmed); if (semicolon) out[trimmed.len] = ';'; return out;}test "ast nodes map to source lines" { const source: [:0]const u8 = "const alpha = 1;\n\nfn beta() void {}\n"; var source_lines = try lines.SourceLines.init(std.testing.allocator, .{ .source = source }); defer source_lines.deinit(std.testing.allocator); source_lines.fill(); source_lines.activate(); var ast = try std.zig.Ast.parse(std.testing.allocator, source, .{}); defer ast.deinit(std.testing.allocator); const root_decls = ast.rootDecls(); try std.testing.expectEqual(@as(i64, 1), nodeLine(source_lines, &ast, root_decls[0])); try std.testing.expectEqual(@as(i64, 3), nodeLine(source_lines, &ast, root_decls[1])); try std.testing.expectEqual(@as(i64, 3), nodeEndLine(source_lines, &ast, root_decls[1]));}test "ast documentation and signatures preserve source meaning" { const source: [:0]const u8 = \\//! Module summary. \\//! \\//! More detail. \\/// Builds a value. \\pub fn build( \\ input: u32, \\) u32 { \\ return input; \\} \\/// Stores state. \\pub const State = struct { \\ value: u32, \\}; \\pub const default_state = State{ .value = 1 }; \\/// Calls an external implementation. \\pub extern fn external() void; \\ ; var ast = try std.zig.Ast.parse(std.testing.allocator, source, .{}); defer ast.deinit(std.testing.allocator); try std.testing.expectEqual(@as(usize, 0), ast.errors.len); const module_doc = (try moduleDocstring(std.testing.allocator, &ast)).?; defer std.testing.allocator.free(module_doc); try std.testing.expectEqualStrings("Module summary.\n\nMore detail.", module_doc); const declarations = ast.rootDecls(); var buffer: [1]std.zig.Ast.Node.Index = undefined; const proto = ast.fullFnProto(&buffer, declarations[0]).?; const function_doc = (try declarationDocstring(std.testing.allocator, &ast, declarations[0])).?; defer std.testing.allocator.free(function_doc); try std.testing.expectEqualStrings("Builds a value.", function_doc); const function_signature = try functionSignature(std.testing.allocator, &ast, proto); defer std.testing.allocator.free(function_signature); try std.testing.expectEqualStrings("pub fn build(\n input: u32,\n) u32", function_signature); const container_signature = try variableSignature(std.testing.allocator, &ast, declarations[1], true); defer std.testing.allocator.free(container_signature); try std.testing.expectEqualStrings("pub const State = struct", container_signature); const value_signature = try variableSignature(std.testing.allocator, &ast, declarations[2], false); defer std.testing.allocator.free(value_signature); try std.testing.expectEqualStrings("pub const default_state = State{ .value = 1 };", value_signature); const external_proto = ast.fullFnProto(&buffer, declarations[3]).?; const external_signature = try functionSignature(std.testing.allocator, &ast, external_proto); defer std.testing.allocator.free(external_signature); try std.testing.expectEqualStrings("pub extern fn external() void;", external_signature); const expected = [_][]const u8{ "/// Builds a value.\npub fn build(\n input: u32,\n) u32 {\n return input;\n}", "/// Stores state.\npub const State = struct {\n value: u32,\n};", "pub const default_state = State{ .value = 1 };", "/// Calls an external implementation.\npub extern fn external() void;", }; for (declarations, expected) |declaration, exact| { const span = declarationSourceSpan(&ast, declaration); try std.testing.expectEqualStrings( exact, source[span.start_byte..span.end_byte], ); }}test "declaration spans preserve every public declaration form" { const source: [:0]const u8 = \\/// Stores one value. \\pub const Box = struct { \\ value: u8, \\ /// Reads the value. \\ pub fn read(self: Box) u8 { \\ return self.value; \\ } \\}; \\/// Names the container. \\pub const Alias = Box; \\/// Builds a value. \\pub fn build() Box { \\ return .{ .value = 1 }; \\} \\/// Calls an external implementation. \\pub extern fn external() void; \\ ; var ast = try std.zig.Ast.parse(std.testing.allocator, source, .{}); defer ast.deinit(std.testing.allocator); try std.testing.expectEqual(@as(usize, 0), ast.errors.len); const declarations = ast.rootDecls(); const expected = [_][]const u8{ "/// Stores one value.\npub const Box = struct {\n value: u8,\n /// Reads the value.\n pub fn read(self: Box) u8 {\n return self.value;\n }\n};", "/// Names the container.\npub const Alias = Box;", "/// Builds a value.\npub fn build() Box {\n return .{ .value = 1 };\n}", "/// Calls an external implementation.\npub extern fn external() void;", }; for (declarations, expected) |declaration, exact| { try expectDeclarationBytes(&ast, declaration, exact); } var buffer: [2]std.zig.Ast.Node.Index = undefined; const variable = ast.fullVarDecl(declarations[0]).?; const initializer = variable.ast.init_node.unwrap().?; const container = ast.fullContainerDecl(&buffer, initializer).?; var method: ?std.zig.Ast.Node.Index = null; for (container.ast.members) |member| { var proto_buffer: [1]std.zig.Ast.Node.Index = undefined; if (ast.fullFnProto(&proto_buffer, member) != null) method = member; } try expectDeclarationBytes( &ast, method.?, "/// Reads the value.\n pub fn read(self: Box) u8 {\n return self.value;\n }", );}fn expectDeclarationBytes( ast: *const std.zig.Ast, declaration: std.zig.Ast.Node.Index, expected: []const u8,) !void { const span = declarationSourceSpan(ast, declaration); try std.testing.expectEqualStrings( expected, ast.source[span.start_byte..span.end_byte], );}Audit
| Definitions | 11 |
|---|---|
| Public names | 11 |
| Members | 2 |
| Version | 26.7.0 |
| Revision | daab053ee433 |