mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
Refactor zig fmt indentation. Remove indent from rendering code and have a stream handle automatic indentation
This commit is contained in:
parent
3750cc06fd
commit
a72b9d403d
@ -169,6 +169,15 @@ pub const BitOutStream = BitWriter;
|
||||
/// Deprecated: use `bitWriter`
|
||||
pub const bitOutStream = bitWriter;
|
||||
|
||||
pub const AutoIndentingStream = @import("io/auto_indenting_stream.zig").AutoIndentingStream;
|
||||
pub const autoIndentingStream = @import("io/auto_indenting_stream.zig").autoIndentingStream;
|
||||
|
||||
pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream;
|
||||
pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream;
|
||||
|
||||
pub const FindByteOutStream = @import("io/find_byte_out_stream.zig").FindByteOutStream;
|
||||
pub const findByteOutStream = @import("io/find_byte_out_stream.zig").findByteOutStream;
|
||||
|
||||
pub const Packing = @import("io/serialization.zig").Packing;
|
||||
|
||||
pub const Serializer = @import("io/serialization.zig").Serializer;
|
||||
@ -182,10 +191,10 @@ pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAt
|
||||
pub const StreamSource = @import("io/stream_source.zig").StreamSource;
|
||||
|
||||
/// A Writer that doesn't write to anything.
|
||||
pub const null_writer = @as(NullWriter, .{ .context = {} });
|
||||
pub var null_writer = @as(NullWriter, .{ .context = {} });
|
||||
|
||||
/// Deprecated: use `null_writer`
|
||||
pub const null_out_stream = null_writer;
|
||||
pub var null_out_stream = null_writer;
|
||||
|
||||
const NullWriter = Writer(void, error{}, dummyWrite);
|
||||
/// Deprecated: use NullWriter
|
||||
|
||||
135
lib/std/io/auto_indenting_stream.zig
Normal file
135
lib/std/io/auto_indenting_stream.zig
Normal file
@ -0,0 +1,135 @@
|
||||
const std = @import("../std.zig");
|
||||
const io = std.io;
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub fn AutoIndentingStream(comptime indent_delta: u8, comptime OutStreamType: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
pub const Error = OutStreamType.Error;
|
||||
pub const OutStream = io.Writer(*Self, Error, write);
|
||||
|
||||
out_stream: *OutStreamType,
|
||||
current_line_empty: bool = true,
|
||||
indent_stack: [255]u8 = undefined,
|
||||
indent_stack_top: u8 = 0,
|
||||
indent_one_shot_count: u8 = 0, // automatically popped when applied
|
||||
applied_indent: u8 = 0, // the most recently applied indent
|
||||
indent_next_line: u8 = 0, // not used until the next line
|
||||
|
||||
pub fn init(out_stream: *OutStreamType) Self {
|
||||
return Self{ .out_stream = out_stream };
|
||||
}
|
||||
|
||||
pub fn writer(self: *Self) OutStream {
|
||||
return .{ .context = self };
|
||||
}
|
||||
|
||||
pub fn write(self: *Self, bytes: []const u8) Error!usize {
|
||||
if (bytes.len == 0)
|
||||
return @as(usize, 0);
|
||||
|
||||
try self.applyIndent();
|
||||
return self.writeNoIndent(bytes);
|
||||
}
|
||||
|
||||
fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize {
|
||||
try self.out_stream.outStream().writeAll(bytes);
|
||||
if (bytes[bytes.len - 1] == '\n')
|
||||
self.resetLine();
|
||||
return bytes.len;
|
||||
}
|
||||
|
||||
pub fn insertNewline(self: *Self) Error!void {
|
||||
_ = try self.writeNoIndent("\n");
|
||||
}
|
||||
|
||||
fn resetLine(self: *Self) void {
|
||||
self.current_line_empty = true;
|
||||
self.indent_next_line = 0;
|
||||
}
|
||||
|
||||
/// Insert a newline unless the current line is blank
|
||||
pub fn maybeInsertNewline(self: *Self) Error!void {
|
||||
if (!self.current_line_empty)
|
||||
try self.insertNewline();
|
||||
}
|
||||
|
||||
/// Push default indentation
|
||||
pub fn pushIndent(self: *Self) void {
|
||||
// Doesn't actually write any indentation. Just primes the stream to be able to write the correct indentation if it needs to.
|
||||
self.pushIndentN(indent_delta);
|
||||
}
|
||||
|
||||
/// Push an indent of arbitrary width
|
||||
pub fn pushIndentN(self: *Self, n: u8) void {
|
||||
assert(self.indent_stack_top < std.math.maxInt(u8));
|
||||
self.indent_stack[self.indent_stack_top] = n;
|
||||
self.indent_stack_top += 1;
|
||||
}
|
||||
|
||||
/// Push an indent that is automatically popped after being applied
|
||||
pub fn pushIndentOneShot(self: *Self) void {
|
||||
self.indent_one_shot_count += 1;
|
||||
self.pushIndent();
|
||||
}
|
||||
|
||||
/// Turns all one-shot indents into regular indents
|
||||
/// Returns number of indents that must now be manually popped
|
||||
pub fn lockOneShotIndent(self: *Self) u8 {
|
||||
var locked_count = self.indent_one_shot_count;
|
||||
self.indent_one_shot_count = 0;
|
||||
return locked_count;
|
||||
}
|
||||
|
||||
/// Push an indent that should not take effect until the next line
|
||||
pub fn pushIndentNextLine(self: *Self) void {
|
||||
self.indent_next_line += 1;
|
||||
self.pushIndent();
|
||||
}
|
||||
|
||||
pub fn popIndent(self: *Self) void {
|
||||
assert(self.indent_stack_top != 0);
|
||||
self.indent_stack_top -= 1;
|
||||
self.indent_next_line = std.math.min(self.indent_stack_top, self.indent_next_line); // Tentative indent may have been popped before there was a newline
|
||||
}
|
||||
|
||||
/// Writes ' ' bytes if the current line is empty
|
||||
fn applyIndent(self: *Self) Error!void {
|
||||
const current_indent = self.currentIndent();
|
||||
if (self.current_line_empty and current_indent > 0) {
|
||||
try self.out_stream.outStream().writeByteNTimes(' ', current_indent);
|
||||
self.applied_indent = current_indent;
|
||||
}
|
||||
|
||||
self.indent_stack_top -= self.indent_one_shot_count;
|
||||
self.indent_one_shot_count = 0;
|
||||
self.current_line_empty = false;
|
||||
}
|
||||
|
||||
/// Checks to see if the most recent indentation exceeds the currently pushed indents
|
||||
pub fn isLineOverIndented(self: *Self) bool {
|
||||
if (self.current_line_empty) return false;
|
||||
return self.applied_indent > self.currentIndent();
|
||||
}
|
||||
|
||||
fn currentIndent(self: *Self) u8 {
|
||||
var indent_current: u8 = 0;
|
||||
if (self.indent_stack_top > 0) {
|
||||
const stack_top = self.indent_stack_top - self.indent_next_line;
|
||||
for (self.indent_stack[0..stack_top]) |indent| {
|
||||
indent_current += indent;
|
||||
}
|
||||
}
|
||||
return indent_current;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn autoIndentingStream(
|
||||
comptime indent_delta: u8,
|
||||
underlying_stream: anytype,
|
||||
) AutoIndentingStream(indent_delta, @TypeOf(underlying_stream).Child) {
|
||||
comptime assert(@typeInfo(@TypeOf(underlying_stream)) == .Pointer);
|
||||
return AutoIndentingStream(indent_delta, @TypeOf(underlying_stream).Child).init(underlying_stream);
|
||||
}
|
||||
58
lib/std/io/change_detection_stream.zig
Normal file
58
lib/std/io/change_detection_stream.zig
Normal file
@ -0,0 +1,58 @@
|
||||
const std = @import("../std.zig");
|
||||
const io = std.io;
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub fn ChangeDetectionStream(comptime OutStreamType: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
pub const Error = OutStreamType.Error;
|
||||
pub const OutStream = io.OutStream(*Self, Error, write);
|
||||
|
||||
anything_changed: bool = false,
|
||||
out_stream: *OutStreamType,
|
||||
source_index: usize,
|
||||
source: []const u8,
|
||||
|
||||
pub fn init(source: []const u8, out_stream: *OutStreamType) Self {
|
||||
return Self{
|
||||
.out_stream = out_stream,
|
||||
.source_index = 0,
|
||||
.source = source,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn outStream(self: *Self) OutStream {
|
||||
return .{ .context = self };
|
||||
}
|
||||
|
||||
fn write(self: *Self, bytes: []const u8) Error!usize {
|
||||
if (!self.anything_changed) {
|
||||
const end = self.source_index + bytes.len;
|
||||
if (end > self.source.len) {
|
||||
self.anything_changed = true;
|
||||
} else {
|
||||
const src_slice = self.source[self.source_index..end];
|
||||
self.source_index += bytes.len;
|
||||
if (!mem.eql(u8, bytes, src_slice)) {
|
||||
self.anything_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.out_stream.write(bytes);
|
||||
}
|
||||
|
||||
pub fn changeDetected(self: *Self) bool {
|
||||
return self.anything_changed or (self.source_index != self.source.len);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn changeDetectionStream(
|
||||
source: []const u8,
|
||||
underlying_stream: anytype,
|
||||
) ChangeDetectionStream(@TypeOf(underlying_stream).Child) {
|
||||
comptime assert(@typeInfo(@TypeOf(underlying_stream)) == .Pointer);
|
||||
return ChangeDetectionStream(@TypeOf(underlying_stream).Child).init(source, underlying_stream);
|
||||
}
|
||||
44
lib/std/io/find_byte_out_stream.zig
Normal file
44
lib/std/io/find_byte_out_stream.zig
Normal file
@ -0,0 +1,44 @@
|
||||
const std = @import("../std.zig");
|
||||
const io = std.io;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
// An OutStream that returns whether the given character has been written to it.
|
||||
// The contents are not written to anything.
|
||||
pub fn FindByteOutStream(comptime OutStreamType: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
pub const Error = OutStreamType.Error;
|
||||
pub const OutStream = io.OutStream(*Self, Error, write);
|
||||
|
||||
out_stream: *OutStreamType,
|
||||
byte_found: bool,
|
||||
byte: u8,
|
||||
|
||||
pub fn init(byte: u8, out_stream: *OutStreamType) Self {
|
||||
return Self{
|
||||
.out_stream = out_stream,
|
||||
.byte = byte,
|
||||
.byte_found = false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn outStream(self: *Self) OutStream {
|
||||
return .{ .context = self };
|
||||
}
|
||||
|
||||
fn write(self: *Self, bytes: []const u8) Error!usize {
|
||||
if (!self.byte_found) {
|
||||
self.byte_found = blk: {
|
||||
for (bytes) |b|
|
||||
if (b == self.byte) break :blk true;
|
||||
break :blk false;
|
||||
};
|
||||
}
|
||||
return self.out_stream.writer().write(bytes);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub fn findByteOutStream(byte: u8, underlying_stream: anytype) FindByteOutStream(@TypeOf(underlying_stream).Child) {
|
||||
comptime assert(@typeInfo(@TypeOf(underlying_stream)) == .Pointer);
|
||||
return FindByteOutStream(@TypeOf(underlying_stream).Child).init(byte, underlying_stream);
|
||||
}
|
||||
@ -18,6 +18,10 @@ pub fn Writer(
|
||||
const Self = @This();
|
||||
pub const Error = WriteError;
|
||||
|
||||
pub fn writer(self: *const Self) Self {
|
||||
return self.*;
|
||||
}
|
||||
|
||||
pub fn write(self: Self, bytes: []const u8) Error!usize {
|
||||
return writeFn(self.context, bytes);
|
||||
}
|
||||
|
||||
@ -615,6 +615,17 @@ test "zig fmt: infix operator and then multiline string literal" {
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: infix operator and then multiline string literal" {
|
||||
try testCanonical(
|
||||
\\const x = "" ++
|
||||
\\ \\ hi0
|
||||
\\ \\ hi1
|
||||
\\ \\ hi2
|
||||
\\;
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: C pointers" {
|
||||
try testCanonical(
|
||||
\\const Ptr = [*c]i32;
|
||||
@ -885,6 +896,28 @@ test "zig fmt: 2nd arg multiline string" {
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: 2nd arg multiline string many args" {
|
||||
try testCanonical(
|
||||
\\comptime {
|
||||
\\ cases.addAsm("hello world linux x86_64",
|
||||
\\ \\.text
|
||||
\\ , "Hello, world!\n", "Hello, world!\n");
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: final arg multiline string" {
|
||||
try testCanonical(
|
||||
\\comptime {
|
||||
\\ cases.addAsm("hello world linux x86_64", "Hello, world!\n",
|
||||
\\ \\.text
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: if condition wraps" {
|
||||
try testTransform(
|
||||
\\comptime {
|
||||
@ -915,6 +948,11 @@ test "zig fmt: if condition wraps" {
|
||||
\\ var a = if (a) |*f| x: {
|
||||
\\ break :x &a.b;
|
||||
\\ } else |err| err;
|
||||
\\ var a = if (cond and
|
||||
\\ cond) |*f|
|
||||
\\ x: {
|
||||
\\ break :x &a.b;
|
||||
\\ } else |err| err;
|
||||
\\}
|
||||
,
|
||||
\\comptime {
|
||||
@ -951,6 +989,35 @@ test "zig fmt: if condition wraps" {
|
||||
\\ var a = if (a) |*f| x: {
|
||||
\\ break :x &a.b;
|
||||
\\ } else |err| err;
|
||||
\\ var a = if (cond and
|
||||
\\ cond) |*f|
|
||||
\\ x: {
|
||||
\\ break :x &a.b;
|
||||
\\ } else |err| err;
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: if condition has line break but must not wrap" {
|
||||
try testCanonical(
|
||||
\\comptime {
|
||||
\\ if (self.user_input_options.put(
|
||||
\\ name,
|
||||
\\ UserInputOption{
|
||||
\\ .name = name,
|
||||
\\ .used = false,
|
||||
\\ },
|
||||
\\ ) catch unreachable) |*prev_value| {
|
||||
\\ foo();
|
||||
\\ bar();
|
||||
\\ }
|
||||
\\ if (put(
|
||||
\\ a,
|
||||
\\ b,
|
||||
\\ )) {
|
||||
\\ foo();
|
||||
\\ }
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
@ -977,6 +1044,18 @@ test "zig fmt: if condition has line break but must not wrap" {
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: function call with multiline argument" {
|
||||
try testCanonical(
|
||||
\\comptime {
|
||||
\\ self.user_input_options.put(name, UserInputOption{
|
||||
\\ .name = name,
|
||||
\\ .used = false,
|
||||
\\ });
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: same-line doc comment on variable declaration" {
|
||||
try testTransform(
|
||||
\\pub const MAP_ANONYMOUS = 0x1000; /// allocated from memory, swap space
|
||||
@ -1228,7 +1307,7 @@ test "zig fmt: array literal with hint" {
|
||||
\\const a = []u8{
|
||||
\\ 1, 2,
|
||||
\\ 3, //
|
||||
\\ 4,
|
||||
\\ 4,
|
||||
\\ 5, 6,
|
||||
\\ 7,
|
||||
\\};
|
||||
@ -1293,7 +1372,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" {
|
||||
\\ \\ZIG_C_HEADER_FILES {}
|
||||
\\ \\ZIG_DIA_GUIDS_LIB {}
|
||||
\\ \\
|
||||
\\ ,
|
||||
\\ ,
|
||||
\\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR),
|
||||
\\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER),
|
||||
\\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB),
|
||||
@ -2885,20 +2964,20 @@ test "zig fmt: multiline string in array" {
|
||||
try testCanonical(
|
||||
\\const Foo = [][]const u8{
|
||||
\\ \\aaa
|
||||
\\,
|
||||
\\ ,
|
||||
\\ \\bbb
|
||||
\\};
|
||||
\\
|
||||
\\fn bar() void {
|
||||
\\ const Foo = [][]const u8{
|
||||
\\ \\aaa
|
||||
\\ ,
|
||||
\\ ,
|
||||
\\ \\bbb
|
||||
\\ };
|
||||
\\ const Bar = [][]const u8{ // comment here
|
||||
\\ \\aaa
|
||||
\\ \\
|
||||
\\ , // and another comment can go here
|
||||
\\ , // and another comment can go here
|
||||
\\ \\bbb
|
||||
\\ };
|
||||
\\}
|
||||
@ -3214,6 +3293,23 @@ test "zig fmt: C var args" {
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: Only indent multiline string literals in function calls" {
|
||||
try testCanonical(
|
||||
\\test "zig fmt:" {
|
||||
\\ try testTransform(
|
||||
\\ \\const X = struct {
|
||||
\\ \\ foo: i32, bar: i8 };
|
||||
\\ ,
|
||||
\\ \\const X = struct {
|
||||
\\ \\ foo: i32, bar: i8
|
||||
\\ \\};
|
||||
\\ \\
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const warn = std.debug.warn;
|
||||
@ -3256,7 +3352,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b
|
||||
var buffer = std.ArrayList(u8).init(allocator);
|
||||
errdefer buffer.deinit();
|
||||
|
||||
anything_changed.* = try std.zig.render(allocator, buffer.outStream(), tree);
|
||||
const outStream = buffer.outStream();
|
||||
anything_changed.* = try std.zig.render(allocator, &outStream, tree);
|
||||
return buffer.toOwnedSlice();
|
||||
}
|
||||
fn testTransform(source: []const u8, expected_source: []const u8) !void {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -682,13 +682,13 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
|
||||
process.exit(1);
|
||||
}
|
||||
if (check_flag) {
|
||||
const anything_changed = try std.zig.render(gpa, io.null_out_stream, tree);
|
||||
const anything_changed = try std.zig.render(gpa, &io.null_out_stream, tree);
|
||||
const code = if (anything_changed) @as(u8, 1) else @as(u8, 0);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
const stdout = io.getStdOut().outStream();
|
||||
_ = try std.zig.render(gpa, stdout, tree);
|
||||
_ = try std.zig.render(gpa, &stdout, tree);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -830,7 +830,7 @@ fn fmtPathFile(
|
||||
}
|
||||
|
||||
if (check_mode) {
|
||||
const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree);
|
||||
const anything_changed = try std.zig.render(fmt.gpa, &io.null_out_stream, tree);
|
||||
if (anything_changed) {
|
||||
std.debug.print("{}\n", .{file_path});
|
||||
fmt.any_error = true;
|
||||
@ -839,7 +839,8 @@ fn fmtPathFile(
|
||||
// As a heuristic, we make enough capacity for the same as the input source.
|
||||
try fmt.out_buffer.ensureCapacity(source_code.len);
|
||||
fmt.out_buffer.items.len = 0;
|
||||
const anything_changed = try std.zig.render(fmt.gpa, fmt.out_buffer.writer(), tree);
|
||||
const writer = fmt.out_buffer.writer();
|
||||
const anything_changed = try std.zig.render(fmt.gpa, &writer, tree);
|
||||
if (!anything_changed)
|
||||
return; // Good thing we didn't waste any file system access on this.
|
||||
|
||||
|
||||
@ -151,7 +151,7 @@ export fn stage2_free_clang_errors(errors_ptr: [*]translate_c.ClangErrMsg, error
|
||||
|
||||
export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
|
||||
const c_out_stream = std.io.cOutStream(output_file);
|
||||
_ = std.zig.render(std.heap.c_allocator, c_out_stream, tree) catch |e| switch (e) {
|
||||
_ = std.zig.render(std.heap.c_allocator, &c_out_stream, tree) catch |e| switch (e) {
|
||||
error.WouldBlock => unreachable, // stage1 opens stuff in exclusively blocking mode
|
||||
error.NotOpenForWriting => unreachable,
|
||||
error.SystemResources => return .SystemResources,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user