| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | | const std = @import("../std.zig"); |
| 1 | const std = @import("../../std.zig"); |
| 2 | 2 | const assert = std.debug.assert; |
| 3 | 3 | const mem = std.mem; |
| 4 | 4 | const Allocator = std.mem.Allocator; |
| ... | ... | @@ -6,13 +6,24 @@ const meta = std.meta; |
| 6 | 6 | const Ast = std.zig.Ast; |
| 7 | 7 | const Token = std.zig.Token; |
| 8 | 8 | const primitives = std.zig.primitives; |
| 9 | const Writer = std.io.Writer; |
| 10 | |
| 11 | const Render = @This(); |
| 12 | |
| 13 | gpa: Allocator, |
| 14 | ais: *AutoIndentingStream, |
| 15 | tree: Ast, |
| 16 | fixups: Fixups, |
| 9 | 17 | |
| 10 | 18 | const indent_delta = 4; |
| 11 | 19 | const asm_indent_delta = 2; |
| 12 | 20 | |
| 13 | | pub const Error = Ast.RenderError; |
| 14 | | |
| 15 | | const Ais = AutoIndentingStream(std.ArrayList(u8).Writer); |
| 21 | pub const Error = error{ |
| 22 | /// Ran out of memory allocating call stack frames to complete rendering. |
| 23 | OutOfMemory, |
| 24 | /// Transitive failure from |
| 25 | WriteFailed, |
| 26 | }; |
| 16 | 27 | |
| 17 | 28 | pub const Fixups = struct { |
| 18 | 29 | /// The key is the mut token (`var`/`const`) of the variable declaration |
| ... | ... | @@ -72,19 +83,12 @@ pub const Fixups = struct { |
| 72 | 83 | } |
| 73 | 84 | }; |
| 74 | 85 | |
| 75 | | const Render = struct { |
| 76 | | gpa: Allocator, |
| 77 | | ais: *Ais, |
| 78 | | tree: Ast, |
| 79 | | fixups: Fixups, |
| 80 | | }; |
| 81 | | |
| 82 | | pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!void { |
| 86 | pub fn renderTree(gpa: Allocator, w: *Writer, tree: Ast, fixups: Fixups) Error!void { |
| 83 | 87 | assert(tree.errors.len == 0); // Cannot render an invalid tree. |
| 84 | | var auto_indenting_stream = Ais.init(buffer, indent_delta); |
| 88 | var auto_indenting_stream: AutoIndentingStream = .init(gpa, w, indent_delta); |
| 85 | 89 | defer auto_indenting_stream.deinit(); |
| 86 | 90 | var r: Render = .{ |
| 87 | | .gpa = buffer.allocator, |
| 91 | .gpa = gpa, |
| 88 | 92 | .ais = &auto_indenting_stream, |
| 89 | 93 | .tree = tree, |
| 90 | 94 | .fixups = fixups, |
| ... | ... | @@ -186,7 +190,7 @@ fn renderMember( |
| 186 | 190 | if (opt_callconv_expr.unwrap()) |callconv_expr| { |
| 187 | 191 | if (tree.nodeTag(callconv_expr) == .enum_literal) { |
| 188 | 192 | if (mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)))) { |
| 189 | | try ais.writer().writeAll("inline "); |
| 193 | try ais.underlying_writer.writeAll("inline "); |
| 190 | 194 | } |
| 191 | 195 | } |
| 192 | 196 | } |
| ... | ... | @@ -200,7 +204,7 @@ fn renderMember( |
| 200 | 204 | const lbrace = tree.nodeMainToken(body_node); |
| 201 | 205 | try renderToken(r, lbrace, .newline); |
| 202 | 206 | try discardAllParams(r, fn_proto); |
| 203 | | try ais.writer().writeAll("@trap();"); |
| 207 | try ais.writeAll("@trap();"); |
| 204 | 208 | ais.popIndent(); |
| 205 | 209 | try ais.insertNewline(); |
| 206 | 210 | try renderToken(r, tree.lastToken(body_node), space); // rbrace |
| ... | ... | @@ -216,10 +220,9 @@ fn renderMember( |
| 216 | 220 | const name_ident = param.name_token.?; |
| 217 | 221 | assert(tree.tokenTag(name_ident) == .identifier); |
| 218 | 222 | if (r.fixups.unused_var_decls.contains(name_ident)) { |
| 219 | | const w = ais.writer(); |
| 220 | | try w.writeAll("_ = "); |
| 221 | | try w.writeAll(tokenSliceForRender(r.tree, name_ident)); |
| 222 | | try w.writeAll(";\n"); |
| 223 | try ais.writeAll("_ = "); |
| 224 | try ais.writeAll(tokenSliceForRender(r.tree, name_ident)); |
| 225 | try ais.writeAll(";\n"); |
| 223 | 226 | } |
| 224 | 227 | } |
| 225 | 228 | var statements_buf: [2]Ast.Node.Index = undefined; |
| ... | ... | @@ -312,7 +315,7 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { |
| 312 | 315 | const tree = r.tree; |
| 313 | 316 | const ais = r.ais; |
| 314 | 317 | if (r.fixups.replace_nodes_with_string.get(node)) |replacement| { |
| 315 | | try ais.writer().writeAll(replacement); |
| 318 | try ais.writeAll(replacement); |
| 316 | 319 | try renderOnlySpace(r, space); |
| 317 | 320 | return; |
| 318 | 321 | } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| { |
| ... | ... | @@ -881,7 +884,7 @@ fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!v |
| 881 | 884 | const ais = r.ais; |
| 882 | 885 | try renderExpression(r, node, space); |
| 883 | 886 | if (r.fixups.append_string_after_node.get(node)) |bytes| { |
| 884 | | try ais.writer().writeAll(bytes); |
| 887 | try ais.writeAll(bytes); |
| 885 | 888 | } |
| 886 | 889 | } |
| 887 | 890 | |
| ... | ... | @@ -1086,10 +1089,10 @@ fn renderVarDecl( |
| 1086 | 1089 | try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space); |
| 1087 | 1090 | if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) { |
| 1088 | 1091 | // Discard the variable like this: `_ = foo;` |
| 1089 | | const w = r.ais.writer(); |
| 1090 | | try w.writeAll("_ = "); |
| 1091 | | try w.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1)); |
| 1092 | | try w.writeAll(";\n"); |
| 1092 | const ais = r.ais; |
| 1093 | try ais.writeAll("_ = "); |
| 1094 | try ais.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1)); |
| 1095 | try ais.writeAll(";\n"); |
| 1093 | 1096 | } |
| 1094 | 1097 | } |
| 1095 | 1098 | |
| ... | ... | @@ -1567,7 +1570,7 @@ fn renderBuiltinCall( |
| 1567 | 1570 | defer r.gpa.free(new_string); |
| 1568 | 1571 | |
| 1569 | 1572 | try renderToken(r, builtin_token + 1, .none); // ( |
| 1570 | | try ais.writer().print("\"{f}\"", .{std.zig.fmtString(new_string)}); |
| 1573 | try ais.print("\"{f}\"", .{std.zig.fmtString(new_string)}); |
| 1571 | 1574 | return renderToken(r, str_lit_token + 1, space); // ) |
| 1572 | 1575 | } |
| 1573 | 1576 | } |
| ... | ... | @@ -2125,13 +2128,13 @@ fn renderArrayInit( |
| 2125 | 2128 | |
| 2126 | 2129 | const section_exprs = row_exprs[0..section_end]; |
| 2127 | 2130 | |
| 2128 | | var sub_expr_buffer = std.ArrayList(u8).init(gpa); |
| 2131 | var sub_expr_buffer: std.io.Writer.Allocating = .init(gpa); |
| 2129 | 2132 | defer sub_expr_buffer.deinit(); |
| 2130 | 2133 | |
| 2131 | 2134 | const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1); |
| 2132 | 2135 | defer gpa.free(sub_expr_buffer_starts); |
| 2133 | 2136 | |
| 2134 | | var auto_indenting_stream = Ais.init(&sub_expr_buffer, indent_delta); |
| 2137 | var auto_indenting_stream: AutoIndentingStream = .init(gpa, &sub_expr_buffer.writer, indent_delta); |
| 2135 | 2138 | defer auto_indenting_stream.deinit(); |
| 2136 | 2139 | var sub_render: Render = .{ |
| 2137 | 2140 | .gpa = r.gpa, |
| ... | ... | @@ -2145,13 +2148,14 @@ fn renderArrayInit( |
| 2145 | 2148 | var single_line = true; |
| 2146 | 2149 | var contains_newline = false; |
| 2147 | 2150 | for (section_exprs, 0..) |expr, i| { |
| 2148 | | const start = sub_expr_buffer.items.len; |
| 2151 | const start = sub_expr_buffer.getWritten().len; |
| 2149 | 2152 | sub_expr_buffer_starts[i] = start; |
| 2150 | 2153 | |
| 2151 | 2154 | if (i + 1 < section_exprs.len) { |
| 2152 | 2155 | try renderExpression(&sub_render, expr, .none); |
| 2153 | | const width = sub_expr_buffer.items.len - start; |
| 2154 | | const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start..], '\n') != null; |
| 2156 | const written = sub_expr_buffer.getWritten(); |
| 2157 | const width = written.len - start; |
| 2158 | const this_contains_newline = mem.indexOfScalar(u8, written[start..], '\n') != null; |
| 2155 | 2159 | contains_newline = contains_newline or this_contains_newline; |
| 2156 | 2160 | expr_widths[i] = width; |
| 2157 | 2161 | expr_newlines[i] = this_contains_newline; |
| ... | ... | @@ -2173,8 +2177,9 @@ fn renderArrayInit( |
| 2173 | 2177 | try renderExpression(&sub_render, expr, .comma); |
| 2174 | 2178 | ais.popSpace(); |
| 2175 | 2179 | |
| 2176 | | const width = sub_expr_buffer.items.len - start - 2; |
| 2177 | | const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start .. sub_expr_buffer.items.len - 1], '\n') != null; |
| 2180 | const written = sub_expr_buffer.getWritten(); |
| 2181 | const width = written.len - start - 2; |
| 2182 | const this_contains_newline = mem.indexOfScalar(u8, written[start .. written.len - 1], '\n') != null; |
| 2178 | 2183 | contains_newline = contains_newline or this_contains_newline; |
| 2179 | 2184 | expr_widths[i] = width; |
| 2180 | 2185 | expr_newlines[i] = contains_newline; |
| ... | ... | @@ -2185,20 +2190,20 @@ fn renderArrayInit( |
| 2185 | 2190 | } |
| 2186 | 2191 | } |
| 2187 | 2192 | } |
| 2188 | | sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.items.len; |
| 2193 | sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.getWritten().len; |
| 2189 | 2194 | |
| 2190 | 2195 | // Render exprs in current section. |
| 2191 | 2196 | column_counter = 0; |
| 2192 | 2197 | for (section_exprs, 0..) |expr, i| { |
| 2193 | 2198 | const start = sub_expr_buffer_starts[i]; |
| 2194 | 2199 | const end = sub_expr_buffer_starts[i + 1]; |
| 2195 | | const expr_text = sub_expr_buffer.items[start..end]; |
| 2200 | const expr_text = sub_expr_buffer.getWritten()[start..end]; |
| 2196 | 2201 | if (!expr_newlines[i]) { |
| 2197 | | try ais.writer().writeAll(expr_text); |
| 2202 | try ais.writeAll(expr_text); |
| 2198 | 2203 | } else { |
| 2199 | 2204 | var by_line = std.mem.splitScalar(u8, expr_text, '\n'); |
| 2200 | 2205 | var last_line_was_empty = false; |
| 2201 | | try ais.writer().writeAll(by_line.first()); |
| 2206 | try ais.writeAll(by_line.first()); |
| 2202 | 2207 | while (by_line.next()) |line| { |
| 2203 | 2208 | if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) { |
| 2204 | 2209 | try ais.insertNewline(); |
| ... | ... | @@ -2206,7 +2211,7 @@ fn renderArrayInit( |
| 2206 | 2211 | try ais.maybeInsertNewline(); |
| 2207 | 2212 | } |
| 2208 | 2213 | last_line_was_empty = (line.len == 0); |
| 2209 | | try ais.writer().writeAll(line); |
| 2214 | try ais.writeAll(line); |
| 2210 | 2215 | } |
| 2211 | 2216 | } |
| 2212 | 2217 | |
| ... | ... | @@ -2220,7 +2225,7 @@ fn renderArrayInit( |
| 2220 | 2225 | try renderToken(r, comma, .space); // , |
| 2221 | 2226 | assert(column_widths[column_counter % row_size] >= expr_widths[i]); |
| 2222 | 2227 | const padding = column_widths[column_counter % row_size] - expr_widths[i]; |
| 2223 | | try ais.writer().writeByteNTimes(' ', padding); |
| 2228 | try ais.splatByteAll(' ', padding); |
| 2224 | 2229 | |
| 2225 | 2230 | column_counter += 1; |
| 2226 | 2231 | continue; |
| ... | ... | @@ -2799,7 +2804,7 @@ fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void |
| 2799 | 2804 | const tree = r.tree; |
| 2800 | 2805 | const ais = r.ais; |
| 2801 | 2806 | const lexeme = tokenSliceForRender(tree, token_index); |
| 2802 | | try ais.writer().writeAll(lexeme); |
| 2807 | try ais.writeAll(lexeme); |
| 2803 | 2808 | try renderSpace(r, token_index, lexeme.len, space); |
| 2804 | 2809 | } |
| 2805 | 2810 | |
| ... | ... | @@ -2807,7 +2812,7 @@ fn renderTokenOverrideSpaceMode(r: *Render, token_index: Ast.TokenIndex, space: |
| 2807 | 2812 | const tree = r.tree; |
| 2808 | 2813 | const ais = r.ais; |
| 2809 | 2814 | const lexeme = tokenSliceForRender(tree, token_index); |
| 2810 | | try ais.writer().writeAll(lexeme); |
| 2815 | try ais.writeAll(lexeme); |
| 2811 | 2816 | ais.enableSpaceMode(override_space); |
| 2812 | 2817 | defer ais.disableSpaceMode(); |
| 2813 | 2818 | try renderSpace(r, token_index, lexeme.len, space); |
| ... | ... | @@ -2822,7 +2827,7 @@ fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space |
| 2822 | 2827 | if (space == .skip) return; |
| 2823 | 2828 | |
| 2824 | 2829 | if (space == .comma and next_token_tag != .comma) { |
| 2825 | | try ais.writer().writeByte(','); |
| 2830 | try ais.writeByte(','); |
| 2826 | 2831 | } |
| 2827 | 2832 | if (space == .semicolon or space == .comma) ais.enableSpaceMode(space); |
| 2828 | 2833 | defer ais.disableSpaceMode(); |
| ... | ... | @@ -2833,7 +2838,7 @@ fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space |
| 2833 | 2838 | ); |
| 2834 | 2839 | switch (space) { |
| 2835 | 2840 | .none => {}, |
| 2836 | | .space => if (!comment) try ais.writer().writeByte(' '), |
| 2841 | .space => if (!comment) try ais.writeByte(' '), |
| 2837 | 2842 | .newline => if (!comment) try ais.insertNewline(), |
| 2838 | 2843 | |
| 2839 | 2844 | .comma => if (next_token_tag == .comma) { |
| ... | ... | @@ -2845,7 +2850,7 @@ fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space |
| 2845 | 2850 | .comma_space => if (next_token_tag == .comma) { |
| 2846 | 2851 | try renderToken(r, token_index + 1, .space); |
| 2847 | 2852 | } else if (!comment) { |
| 2848 | | try ais.writer().writeByte(' '); |
| 2853 | try ais.writeByte(' '); |
| 2849 | 2854 | }, |
| 2850 | 2855 | |
| 2851 | 2856 | .semicolon => if (next_token_tag == .semicolon) { |
| ... | ... | @@ -2862,11 +2867,11 @@ fn renderOnlySpace(r: *Render, space: Space) Error!void { |
| 2862 | 2867 | const ais = r.ais; |
| 2863 | 2868 | switch (space) { |
| 2864 | 2869 | .none => {}, |
| 2865 | | .space => try ais.writer().writeByte(' '), |
| 2870 | .space => try ais.writeByte(' '), |
| 2866 | 2871 | .newline => try ais.insertNewline(), |
| 2867 | | .comma => try ais.writer().writeAll(",\n"), |
| 2868 | | .comma_space => try ais.writer().writeAll(", "), |
| 2869 | | .semicolon => try ais.writer().writeAll(";\n"), |
| 2872 | .comma => try ais.writeAll(",\n"), |
| 2873 | .comma_space => try ais.writeAll(", "), |
| 2874 | .semicolon => try ais.writeAll(";\n"), |
| 2870 | 2875 | .skip => unreachable, |
| 2871 | 2876 | } |
| 2872 | 2877 | } |
| ... | ... | @@ -2883,7 +2888,7 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote |
| 2883 | 2888 | const lexeme = tokenSliceForRender(tree, token_index); |
| 2884 | 2889 | |
| 2885 | 2890 | if (r.fixups.rename_identifiers.get(lexeme)) |mangled| { |
| 2886 | | try r.ais.writer().writeAll(mangled); |
| 2891 | try r.ais.writeAll(mangled); |
| 2887 | 2892 | try renderSpace(r, token_index, lexeme.len, space); |
| 2888 | 2893 | return; |
| 2889 | 2894 | } |
| ... | ... | @@ -2992,15 +2997,15 @@ fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, |
| 2992 | 2997 | const lexeme = tokenSliceForRender(tree, token_index); |
| 2993 | 2998 | assert(lexeme.len >= 3 and lexeme[0] == '@'); |
| 2994 | 2999 | |
| 2995 | | if (!unquote) try ais.writer().writeAll("@\""); |
| 3000 | if (!unquote) try ais.writeAll("@\""); |
| 2996 | 3001 | const contents = lexeme[2 .. lexeme.len - 1]; |
| 2997 | | try renderIdentifierContents(ais.writer(), contents); |
| 2998 | | if (!unquote) try ais.writer().writeByte('\"'); |
| 3002 | try renderIdentifierContents(ais, contents); |
| 3003 | if (!unquote) try ais.writeByte('\"'); |
| 2999 | 3004 | |
| 3000 | 3005 | try renderSpace(r, token_index, lexeme.len, space); |
| 3001 | 3006 | } |
| 3002 | 3007 | |
| 3003 | | fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void { |
| 3008 | fn renderIdentifierContents(ais: *AutoIndentingStream, bytes: []const u8) !void { |
| 3004 | 3009 | var pos: usize = 0; |
| 3005 | 3010 | while (pos < bytes.len) { |
| 3006 | 3011 | const byte = bytes[pos]; |
| ... | ... | @@ -3013,23 +3018,23 @@ fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void { |
| 3013 | 3018 | .success => |codepoint| { |
| 3014 | 3019 | if (codepoint <= 0x7f) { |
| 3015 | 3020 | const buf = [1]u8{@as(u8, @intCast(codepoint))}; |
| 3016 | | try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)}); |
| 3021 | try ais.print("{f}", .{std.zig.fmtString(&buf)}); |
| 3017 | 3022 | } else { |
| 3018 | | try writer.writeAll(escape_sequence); |
| 3023 | try ais.writeAll(escape_sequence); |
| 3019 | 3024 | } |
| 3020 | 3025 | }, |
| 3021 | 3026 | .failure => { |
| 3022 | | try writer.writeAll(escape_sequence); |
| 3027 | try ais.writeAll(escape_sequence); |
| 3023 | 3028 | }, |
| 3024 | 3029 | } |
| 3025 | 3030 | }, |
| 3026 | 3031 | 0x00...('\\' - 1), ('\\' + 1)...0x7f => { |
| 3027 | 3032 | const buf = [1]u8{byte}; |
| 3028 | | try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)}); |
| 3033 | try ais.print("{f}", .{std.zig.fmtString(&buf)}); |
| 3029 | 3034 | pos += 1; |
| 3030 | 3035 | }, |
| 3031 | 3036 | 0x80...0xff => { |
| 3032 | | try writer.writeByte(byte); |
| 3037 | try ais.writeByte(byte); |
| 3033 | 3038 | pos += 1; |
| 3034 | 3039 | }, |
| 3035 | 3040 | } |
| ... | ... | @@ -3091,7 +3096,7 @@ fn renderComments(r: *Render, start: usize, end: usize) Error!bool { |
| 3091 | 3096 | } else if (index == start) { |
| 3092 | 3097 | // Otherwise if the first comment is on the same line as |
| 3093 | 3098 | // the token before it, prefix it with a single space. |
| 3094 | | try ais.writer().writeByte(' '); |
| 3099 | try ais.writeByte(' '); |
| 3095 | 3100 | } |
| 3096 | 3101 | } |
| 3097 | 3102 | |
| ... | ... | @@ -3108,11 +3113,11 @@ fn renderComments(r: *Render, start: usize, end: usize) Error!bool { |
| 3108 | 3113 | ais.disabled_offset = null; |
| 3109 | 3114 | } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) { |
| 3110 | 3115 | // Write with the canonical single space. |
| 3111 | | try ais.writer().writeAll("// zig fmt: off\n"); |
| 3116 | try ais.writeAll("// zig fmt: off\n"); |
| 3112 | 3117 | ais.disabled_offset = index; |
| 3113 | 3118 | } else { |
| 3114 | 3119 | // Write the comment minus trailing whitespace. |
| 3115 | | try ais.writer().print("{s}\n", .{trimmed_comment}); |
| 3120 | try ais.print("{s}\n", .{trimmed_comment}); |
| 3116 | 3121 | } |
| 3117 | 3122 | } |
| 3118 | 3123 | |
| ... | ... | @@ -3213,10 +3218,9 @@ fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void { |
| 3213 | 3218 | while (it.next()) |param| { |
| 3214 | 3219 | const name_ident = param.name_token.?; |
| 3215 | 3220 | assert(tree.tokenTag(name_ident) == .identifier); |
| 3216 | | const w = ais.writer(); |
| 3217 | | try w.writeAll("_ = "); |
| 3218 | | try w.writeAll(tokenSliceForRender(r.tree, name_ident)); |
| 3219 | | try w.writeAll(";\n"); |
| 3221 | try ais.writeAll("_ = "); |
| 3222 | try ais.writeAll(tokenSliceForRender(r.tree, name_ident)); |
| 3223 | try ais.writeAll(";\n"); |
| 3220 | 3224 | } |
| 3221 | 3225 | } |
| 3222 | 3226 | |
| ... | ... | @@ -3269,11 +3273,11 @@ fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenI |
| 3269 | 3273 | return false; |
| 3270 | 3274 | } |
| 3271 | 3275 | |
| 3272 | | fn writeFixingWhitespace(writer: std.ArrayList(u8).Writer, slice: []const u8) Error!void { |
| 3276 | fn writeFixingWhitespace(w: *Writer, slice: []const u8) Error!void { |
| 3273 | 3277 | for (slice) |byte| switch (byte) { |
| 3274 | | '\t' => try writer.writeAll(" " ** indent_delta), |
| 3278 | '\t' => try w.splatByteAll(' ', indent_delta), |
| 3275 | 3279 | '\r' => {}, |
| 3276 | | else => try writer.writeByte(byte), |
| 3280 | else => try w.writeByte(byte), |
| 3277 | 3281 | }; |
| 3278 | 3282 | } |
| 3279 | 3283 | |
| ... | ... | @@ -3398,224 +3402,244 @@ fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usi |
| 3398 | 3402 | /// of the appropriate indentation level for them with pushSpace/popSpace. |
| 3399 | 3403 | /// This should be done whenever a scope that ends in a .semicolon or a |
| 3400 | 3404 | /// .comma is introduced. |
| 3401 | | fn AutoIndentingStream(comptime UnderlyingWriter: type) type { |
| 3402 | | return struct { |
| 3403 | | const Self = @This(); |
| 3404 | | pub const WriteError = UnderlyingWriter.Error; |
| 3405 | | pub const Writer = std.io.GenericWriter(*Self, WriteError, write); |
| 3406 | | |
| 3407 | | pub const IndentType = enum { |
| 3408 | | normal, |
| 3409 | | after_equals, |
| 3410 | | binop, |
| 3411 | | field_access, |
| 3412 | | }; |
| 3413 | | const StackElem = struct { |
| 3414 | | indent_type: IndentType, |
| 3415 | | realized: bool, |
| 3416 | | }; |
| 3417 | | const SpaceElem = struct { |
| 3418 | | space: Space, |
| 3419 | | indent_count: usize, |
| 3405 | const AutoIndentingStream = struct { |
| 3406 | underlying_writer: *Writer, |
| 3407 | |
| 3408 | /// Offset into the source at which formatting has been disabled with |
| 3409 | /// a `zig fmt: off` comment. |
| 3410 | /// |
| 3411 | /// If non-null, the AutoIndentingStream will not write any bytes |
| 3412 | /// to the underlying writer. It will however continue to track the |
| 3413 | /// indentation level. |
| 3414 | disabled_offset: ?usize = null, |
| 3415 | |
| 3416 | indent_count: usize = 0, |
| 3417 | indent_delta: usize, |
| 3418 | indent_stack: std.ArrayList(StackElem), |
| 3419 | space_stack: std.ArrayList(SpaceElem), |
| 3420 | space_mode: ?usize = null, |
| 3421 | disable_indent_committing: usize = 0, |
| 3422 | current_line_empty: bool = true, |
| 3423 | /// the most recently applied indent |
| 3424 | applied_indent: usize = 0, |
| 3425 | |
| 3426 | pub const IndentType = enum { |
| 3427 | normal, |
| 3428 | after_equals, |
| 3429 | binop, |
| 3430 | field_access, |
| 3431 | }; |
| 3432 | const StackElem = struct { |
| 3433 | indent_type: IndentType, |
| 3434 | realized: bool, |
| 3435 | }; |
| 3436 | const SpaceElem = struct { |
| 3437 | space: Space, |
| 3438 | indent_count: usize, |
| 3439 | }; |
| 3440 | |
| 3441 | pub fn init(gpa: Allocator, w: *Writer, starting_indent_delta: usize) AutoIndentingStream { |
| 3442 | return .{ |
| 3443 | .underlying_writer = w, |
| 3444 | .indent_delta = starting_indent_delta, |
| 3445 | .indent_stack = .init(gpa), |
| 3446 | .space_stack = .init(gpa), |
| 3420 | 3447 | }; |
| 3448 | } |
| 3421 | 3449 | |
| 3422 | | underlying_writer: UnderlyingWriter, |
| 3423 | | |
| 3424 | | /// Offset into the source at which formatting has been disabled with |
| 3425 | | /// a `zig fmt: off` comment. |
| 3426 | | /// |
| 3427 | | /// If non-null, the AutoIndentingStream will not write any bytes |
| 3428 | | /// to the underlying writer. It will however continue to track the |
| 3429 | | /// indentation level. |
| 3430 | | disabled_offset: ?usize = null, |
| 3431 | | |
| 3432 | | indent_count: usize = 0, |
| 3433 | | indent_delta: usize, |
| 3434 | | indent_stack: std.ArrayList(StackElem), |
| 3435 | | space_stack: std.ArrayList(SpaceElem), |
| 3436 | | space_mode: ?usize = null, |
| 3437 | | disable_indent_committing: usize = 0, |
| 3438 | | current_line_empty: bool = true, |
| 3439 | | /// the most recently applied indent |
| 3440 | | applied_indent: usize = 0, |
| 3441 | | |
| 3442 | | pub fn init(buffer: *std.ArrayList(u8), indent_delta_: usize) Self { |
| 3443 | | return .{ |
| 3444 | | .underlying_writer = buffer.writer(), |
| 3445 | | .indent_delta = indent_delta_, |
| 3446 | | .indent_stack = std.ArrayList(StackElem).init(buffer.allocator), |
| 3447 | | .space_stack = std.ArrayList(SpaceElem).init(buffer.allocator), |
| 3448 | | }; |
| 3449 | | } |
| 3450 | pub fn deinit(self: *AutoIndentingStream) void { |
| 3451 | self.indent_stack.deinit(); |
| 3452 | self.space_stack.deinit(); |
| 3453 | } |
| 3450 | 3454 | |
| 3451 | | pub fn deinit(self: *Self) void { |
| 3452 | | self.indent_stack.deinit(); |
| 3453 | | self.space_stack.deinit(); |
| 3454 | | } |
| 3455 | pub fn writeAll(ais: *AutoIndentingStream, bytes: []const u8) Error!void { |
| 3456 | if (bytes.len == 0) return; |
| 3457 | try ais.applyIndent(); |
| 3458 | if (ais.disabled_offset == null) try ais.underlying_writer.writeAll(bytes); |
| 3459 | if (bytes[bytes.len - 1] == '\n') ais.resetLine(); |
| 3460 | } |
| 3455 | 3461 | |
| 3456 | | pub fn writer(self: *Self) Writer { |
| 3457 | | return .{ .context = self }; |
| 3458 | | } |
| 3462 | /// Assumes that if the printed data ends with a newline, it is directly |
| 3463 | /// contained in the format string. |
| 3464 | pub fn print(ais: *AutoIndentingStream, comptime format: []const u8, args: anytype) Error!void { |
| 3465 | try ais.applyIndent(); |
| 3466 | if (ais.disabled_offset == null) try ais.underlying_writer.print(format, args); |
| 3467 | if (format[format.len - 1] == '\n') ais.resetLine(); |
| 3468 | } |
| 3459 | 3469 | |
| 3460 | | pub fn write(self: *Self, bytes: []const u8) WriteError!usize { |
| 3461 | | if (bytes.len == 0) |
| 3462 | | return @as(usize, 0); |
| 3470 | pub fn writeByte(ais: *AutoIndentingStream, byte: u8) Error!void { |
| 3471 | try ais.applyIndent(); |
| 3472 | if (ais.disabled_offset == null) try ais.underlying_writer.writeByte(byte); |
| 3473 | assert(byte != '\n'); |
| 3474 | } |
| 3463 | 3475 | |
| 3464 | | try self.applyIndent(); |
| 3465 | | return self.writeNoIndent(bytes); |
| 3466 | | } |
| 3476 | pub fn splatByteAll(ais: *AutoIndentingStream, byte: u8, n: usize) Error!void { |
| 3477 | assert(byte != '\n'); |
| 3478 | try ais.applyIndent(); |
| 3479 | if (ais.disabled_offset == null) try ais.underlying_writer.splatByteAll(byte, n); |
| 3480 | } |
| 3467 | 3481 | |
| 3468 | | // Change the indent delta without changing the final indentation level |
| 3469 | | pub fn setIndentDelta(self: *Self, new_indent_delta: usize) void { |
| 3470 | | if (self.indent_delta == new_indent_delta) { |
| 3471 | | return; |
| 3472 | | } else if (self.indent_delta > new_indent_delta) { |
| 3473 | | assert(self.indent_delta % new_indent_delta == 0); |
| 3474 | | self.indent_count = self.indent_count * (self.indent_delta / new_indent_delta); |
| 3475 | | } else { |
| 3476 | | // assert that the current indentation (in spaces) in a multiple of the new delta |
| 3477 | | assert((self.indent_count * self.indent_delta) % new_indent_delta == 0); |
| 3478 | | self.indent_count = self.indent_count / (new_indent_delta / self.indent_delta); |
| 3479 | | } |
| 3480 | | self.indent_delta = new_indent_delta; |
| 3482 | // Change the indent delta without changing the final indentation level |
| 3483 | pub fn setIndentDelta(ais: *AutoIndentingStream, new_indent_delta: usize) void { |
| 3484 | if (ais.indent_delta == new_indent_delta) { |
| 3485 | return; |
| 3486 | } else if (ais.indent_delta > new_indent_delta) { |
| 3487 | assert(ais.indent_delta % new_indent_delta == 0); |
| 3488 | ais.indent_count = ais.indent_count * (ais.indent_delta / new_indent_delta); |
| 3489 | } else { |
| 3490 | // assert that the current indentation (in spaces) in a multiple of the new delta |
| 3491 | assert((ais.indent_count * ais.indent_delta) % new_indent_delta == 0); |
| 3492 | ais.indent_count = ais.indent_count / (new_indent_delta / ais.indent_delta); |
| 3481 | 3493 | } |
| 3494 | ais.indent_delta = new_indent_delta; |
| 3495 | } |
| 3482 | 3496 | |
| 3483 | | fn writeNoIndent(self: *Self, bytes: []const u8) WriteError!usize { |
| 3484 | | if (bytes.len == 0) |
| 3485 | | return @as(usize, 0); |
| 3497 | pub fn insertNewline(ais: *AutoIndentingStream) Error!void { |
| 3498 | if (ais.disabled_offset == null) try ais.underlying_writer.writeByte('\n'); |
| 3499 | ais.resetLine(); |
| 3500 | } |
| 3486 | 3501 | |
| 3487 | | if (self.disabled_offset == null) try self.underlying_writer.writeAll(bytes); |
| 3488 | | if (bytes[bytes.len - 1] == '\n') |
| 3489 | | self.resetLine(); |
| 3490 | | return bytes.len; |
| 3491 | | } |
| 3502 | /// Insert a newline unless the current line is blank |
| 3503 | pub fn maybeInsertNewline(ais: *AutoIndentingStream) Error!void { |
| 3504 | if (!ais.current_line_empty) |
| 3505 | try ais.insertNewline(); |
| 3506 | } |
| 3492 | 3507 | |
| 3493 | | pub fn insertNewline(self: *Self) WriteError!void { |
| 3494 | | _ = try self.writeNoIndent("\n"); |
| 3495 | | } |
| 3508 | /// Push an indent that is automatically popped after being applied |
| 3509 | pub fn pushIndentOneShot(ais: *AutoIndentingStream) void { |
| 3510 | ais.indent_one_shot_count += 1; |
| 3511 | ais.pushIndent(); |
| 3512 | } |
| 3496 | 3513 | |
| 3497 | | fn resetLine(self: *Self) void { |
| 3498 | | self.current_line_empty = true; |
| 3514 | /// Turns all one-shot indents into regular indents |
| 3515 | /// Returns number of indents that must now be manually popped |
| 3516 | pub fn lockOneShotIndent(ais: *AutoIndentingStream) usize { |
| 3517 | const locked_count = ais.indent_one_shot_count; |
| 3518 | ais.indent_one_shot_count = 0; |
| 3519 | return locked_count; |
| 3520 | } |
| 3499 | 3521 | |
| 3500 | | if (self.disable_indent_committing > 0) return; |
| 3522 | /// Push an indent that should not take effect until the next line |
| 3523 | pub fn pushIndentNextLine(ais: *AutoIndentingStream) void { |
| 3524 | ais.indent_next_line += 1; |
| 3525 | ais.pushIndent(); |
| 3526 | } |
| 3501 | 3527 | |
| 3502 | | if (self.indent_stack.items.len > 0) { |
| 3503 | | // By default, we realize the most recent indentation scope. |
| 3504 | | var to_realize = self.indent_stack.items.len - 1; |
| 3528 | /// Checks to see if the most recent indentation exceeds the currently pushed indents |
| 3529 | pub fn isLineOverIndented(ais: *AutoIndentingStream) bool { |
| 3530 | if (ais.current_line_empty) return false; |
| 3531 | return ais.applied_indent > ais.currentIndent(); |
| 3532 | } |
| 3505 | 3533 | |
| 3506 | | if (self.indent_stack.items.len >= 2 and |
| 3507 | | self.indent_stack.items[to_realize - 1].indent_type == .after_equals and |
| 3508 | | self.indent_stack.items[to_realize - 1].realized and |
| 3509 | | self.indent_stack.items[to_realize].indent_type == .binop) |
| 3510 | | { |
| 3511 | | // If we are in a .binop scope and our direct parent is .after_equals, don't indent. |
| 3512 | | // This ensures correct indentation in the below example: |
| 3513 | | // |
| 3514 | | // const foo = |
| 3515 | | // (x >= 'a' and x <= 'z') or //<-- we are here |
| 3516 | | // (x >= 'A' and x <= 'Z'); |
| 3517 | | // |
| 3518 | | return; |
| 3519 | | } |
| 3534 | fn resetLine(ais: *AutoIndentingStream) void { |
| 3535 | ais.current_line_empty = true; |
| 3520 | 3536 | |
| 3521 | | if (self.indent_stack.items[to_realize].indent_type == .field_access) { |
| 3522 | | // Only realize the top-most field_access in a chain. |
| 3523 | | while (to_realize > 0 and self.indent_stack.items[to_realize - 1].indent_type == .field_access) |
| 3524 | | to_realize -= 1; |
| 3525 | | } |
| 3537 | if (ais.disable_indent_committing > 0) return; |
| 3526 | 3538 | |
| 3527 | | if (self.indent_stack.items[to_realize].realized) return; |
| 3528 | | self.indent_stack.items[to_realize].realized = true; |
| 3529 | | self.indent_count += 1; |
| 3539 | if (ais.indent_stack.items.len > 0) { |
| 3540 | // By default, we realize the most recent indentation scope. |
| 3541 | var to_realize = ais.indent_stack.items.len - 1; |
| 3542 | |
| 3543 | if (ais.indent_stack.items.len >= 2 and |
| 3544 | ais.indent_stack.items[to_realize - 1].indent_type == .after_equals and |
| 3545 | ais.indent_stack.items[to_realize - 1].realized and |
| 3546 | ais.indent_stack.items[to_realize].indent_type == .binop) |
| 3547 | { |
| 3548 | // If we are in a .binop scope and our direct parent is .after_equals, don't indent. |
| 3549 | // This ensures correct indentation in the below example: |
| 3550 | // |
| 3551 | // const foo = |
| 3552 | // (x >= 'a' and x <= 'z') or //<-- we are here |
| 3553 | // (x >= 'A' and x <= 'Z'); |
| 3554 | // |
| 3555 | return; |
| 3530 | 3556 | } |
| 3531 | | } |
| 3532 | 3557 | |
| 3533 | | /// Disables indentation level changes during the next newlines until re-enabled. |
| 3534 | | pub fn disableIndentCommitting(self: *Self) void { |
| 3535 | | self.disable_indent_committing += 1; |
| 3536 | | } |
| 3558 | if (ais.indent_stack.items[to_realize].indent_type == .field_access) { |
| 3559 | // Only realize the top-most field_access in a chain. |
| 3560 | while (to_realize > 0 and ais.indent_stack.items[to_realize - 1].indent_type == .field_access) |
| 3561 | to_realize -= 1; |
| 3562 | } |
| 3537 | 3563 | |
| 3538 | | pub fn enableIndentCommitting(self: *Self) void { |
| 3539 | | assert(self.disable_indent_committing > 0); |
| 3540 | | self.disable_indent_committing -= 1; |
| 3564 | if (ais.indent_stack.items[to_realize].realized) return; |
| 3565 | ais.indent_stack.items[to_realize].realized = true; |
| 3566 | ais.indent_count += 1; |
| 3541 | 3567 | } |
| 3568 | } |
| 3542 | 3569 | |
| 3543 | | pub fn pushSpace(self: *Self, space: Space) !void { |
| 3544 | | try self.space_stack.append(.{ .space = space, .indent_count = self.indent_count }); |
| 3545 | | } |
| 3570 | /// Disables indentation level changes during the next newlines until re-enabled. |
| 3571 | pub fn disableIndentCommitting(ais: *AutoIndentingStream) void { |
| 3572 | ais.disable_indent_committing += 1; |
| 3573 | } |
| 3546 | 3574 | |
| 3547 | | pub fn popSpace(self: *Self) void { |
| 3548 | | _ = self.space_stack.pop(); |
| 3549 | | } |
| 3575 | pub fn enableIndentCommitting(ais: *AutoIndentingStream) void { |
| 3576 | assert(ais.disable_indent_committing > 0); |
| 3577 | ais.disable_indent_committing -= 1; |
| 3578 | } |
| 3550 | 3579 | |
| 3551 | | /// Sets current indentation level to be the same as that of the last pushSpace. |
| 3552 | | pub fn enableSpaceMode(self: *Self, space: Space) void { |
| 3553 | | if (self.space_stack.items.len == 0) return; |
| 3554 | | const curr = self.space_stack.getLast(); |
| 3555 | | if (curr.space != space) return; |
| 3556 | | self.space_mode = curr.indent_count; |
| 3557 | | } |
| 3580 | pub fn pushSpace(ais: *AutoIndentingStream, space: Space) !void { |
| 3581 | try ais.space_stack.append(.{ .space = space, .indent_count = ais.indent_count }); |
| 3582 | } |
| 3558 | 3583 | |
| 3559 | | pub fn disableSpaceMode(self: *Self) void { |
| 3560 | | self.space_mode = null; |
| 3561 | | } |
| 3584 | pub fn popSpace(ais: *AutoIndentingStream) void { |
| 3585 | _ = ais.space_stack.pop(); |
| 3586 | } |
| 3562 | 3587 | |
| 3563 | | pub fn lastSpaceModeIndent(self: *Self) usize { |
| 3564 | | if (self.space_stack.items.len == 0) return 0; |
| 3565 | | return self.space_stack.getLast().indent_count * self.indent_delta; |
| 3566 | | } |
| 3588 | /// Sets current indentation level to be the same as that of the last pushSpace. |
| 3589 | pub fn enableSpaceMode(ais: *AutoIndentingStream, space: Space) void { |
| 3590 | if (ais.space_stack.items.len == 0) return; |
| 3591 | const curr = ais.space_stack.getLast(); |
| 3592 | if (curr.space != space) return; |
| 3593 | ais.space_mode = curr.indent_count; |
| 3594 | } |
| 3567 | 3595 | |
| 3568 | | /// Insert a newline unless the current line is blank |
| 3569 | | pub fn maybeInsertNewline(self: *Self) WriteError!void { |
| 3570 | | if (!self.current_line_empty) |
| 3571 | | try self.insertNewline(); |
| 3572 | | } |
| 3596 | pub fn disableSpaceMode(ais: *AutoIndentingStream) void { |
| 3597 | ais.space_mode = null; |
| 3598 | } |
| 3573 | 3599 | |
| 3574 | | /// Push default indentation |
| 3575 | | /// Doesn't actually write any indentation. |
| 3576 | | /// Just primes the stream to be able to write the correct indentation if it needs to. |
| 3577 | | pub fn pushIndent(self: *Self, indent_type: IndentType) !void { |
| 3578 | | try self.indent_stack.append(.{ .indent_type = indent_type, .realized = false }); |
| 3579 | | } |
| 3600 | pub fn lastSpaceModeIndent(ais: *AutoIndentingStream) usize { |
| 3601 | if (ais.space_stack.items.len == 0) return 0; |
| 3602 | return ais.space_stack.getLast().indent_count * ais.indent_delta; |
| 3603 | } |
| 3580 | 3604 | |
| 3581 | | /// Forces an indentation level to be realized. |
| 3582 | | pub fn forcePushIndent(self: *Self, indent_type: IndentType) !void { |
| 3583 | | try self.indent_stack.append(.{ .indent_type = indent_type, .realized = true }); |
| 3584 | | self.indent_count += 1; |
| 3585 | | } |
| 3605 | /// Push default indentation |
| 3606 | /// Doesn't actually write any indentation. |
| 3607 | /// Just primes the stream to be able to write the correct indentation if it needs to. |
| 3608 | pub fn pushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void { |
| 3609 | try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = false }); |
| 3610 | } |
| 3586 | 3611 | |
| 3587 | | pub fn popIndent(self: *Self) void { |
| 3588 | | if (self.indent_stack.pop().?.realized) { |
| 3589 | | assert(self.indent_count > 0); |
| 3590 | | self.indent_count -= 1; |
| 3591 | | } |
| 3592 | | } |
| 3612 | /// Forces an indentation level to be realized. |
| 3613 | pub fn forcePushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void { |
| 3614 | try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = true }); |
| 3615 | ais.indent_count += 1; |
| 3616 | } |
| 3593 | 3617 | |
| 3594 | | pub fn indentStackEmpty(self: *Self) bool { |
| 3595 | | return self.indent_stack.items.len == 0; |
| 3618 | pub fn popIndent(ais: *AutoIndentingStream) void { |
| 3619 | if (ais.indent_stack.pop().?.realized) { |
| 3620 | assert(ais.indent_count > 0); |
| 3621 | ais.indent_count -= 1; |
| 3596 | 3622 | } |
| 3623 | } |
| 3597 | 3624 | |
| 3598 | | /// Writes ' ' bytes if the current line is empty |
| 3599 | | fn applyIndent(self: *Self) WriteError!void { |
| 3600 | | const current_indent = self.currentIndent(); |
| 3601 | | if (self.current_line_empty and current_indent > 0) { |
| 3602 | | if (self.disabled_offset == null) { |
| 3603 | | try self.underlying_writer.writeByteNTimes(' ', current_indent); |
| 3604 | | } |
| 3605 | | self.applied_indent = current_indent; |
| 3606 | | } |
| 3607 | | self.current_line_empty = false; |
| 3608 | | } |
| 3625 | pub fn indentStackEmpty(ais: *AutoIndentingStream) bool { |
| 3626 | return ais.indent_stack.items.len == 0; |
| 3627 | } |
| 3609 | 3628 | |
| 3610 | | /// Checks to see if the most recent indentation exceeds the currently pushed indents |
| 3611 | | pub fn isLineOverIndented(self: *Self) bool { |
| 3612 | | if (self.current_line_empty) return false; |
| 3613 | | return self.applied_indent > self.currentIndent(); |
| 3629 | /// Writes ' ' bytes if the current line is empty |
| 3630 | fn applyIndent(ais: *AutoIndentingStream) Error!void { |
| 3631 | const current_indent = ais.currentIndent(); |
| 3632 | if (ais.current_line_empty and current_indent > 0) { |
| 3633 | if (ais.disabled_offset == null) { |
| 3634 | try ais.underlying_writer.splatByteAll(' ', current_indent); |
| 3635 | } |
| 3636 | ais.applied_indent = current_indent; |
| 3614 | 3637 | } |
| 3638 | ais.current_line_empty = false; |
| 3639 | } |
| 3615 | 3640 | |
| 3616 | | fn currentIndent(self: *Self) usize { |
| 3617 | | const indent_count = self.space_mode orelse self.indent_count; |
| 3618 | | return indent_count * self.indent_delta; |
| 3619 | | } |
| 3620 | | }; |
| 3621 | | } |
| 3641 | fn currentIndent(ais: *AutoIndentingStream) usize { |
| 3642 | const indent_count = ais.space_mode orelse ais.indent_count; |
| 3643 | return indent_count * ais.indent_delta; |
| 3644 | } |
| 3645 | }; |