| 1 | //! `Format` converts a parsed Ast back into source code. This uses |
| 2 | //! the fact that `Ast` contains the original source code slice in |
| 3 | //! order to preserve comments despite not representing them as nodes. |
| 4 | ast: Ast, |
| 5 | writer: AnyWriter, |
| 6 | ident: usize = 0, |
| 7 | |
| 8 | // Formatting options such as tab size or character are intentionally |
| 9 | // not exposed as configuration options. This makes all source files |
| 10 | // follow the same style guide. |
| 11 | const spaces_per_level = 3; |
| 12 | |
| 13 | pub fn format(ast: Ast, writer: AnyWriter) !void { |
| 14 | var state: Format = .{ .ast = ast, .writer = writer }; |
| 15 | |
| 16 | // Print the top-level documentation comment |
| 17 | { |
| 18 | var i: u32 = 0; |
| 19 | while (state.ast.tokenTag(i) == .module_doc_comment) { |
| 20 | i += 1; |
| 21 | } |
| 22 | if (i > 0) { |
| 23 | const start = state.ast.tokenOffset(0); |
| 24 | const end = (mem.lastIndexOf( |
| 25 | u8, |
| 26 | state.ast.utf8_source[0..state.ast.tokenOffset(i)], |
| 27 | "\n", |
| 28 | ) orelse unreachable) + 1; |
| 29 | try state.writer.writeAll(state.ast.utf8_source[start..end]); |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | try state.formatNode(0); |
| 34 | |
| 35 | assert(state.ident == 0); |
| 36 | } |
| 37 | |
| 38 | fn formatNode(state: Format, node: NodeIndex) !void { |
| 39 | switch (state.ast.nodeTag(node)) { |
| 40 | .root => { |
| 41 | for (state.ast.nodeData(node).list.slice(&state.ast)) |child| { |
| 42 | try state.formatNode(child); |
| 43 | } |
| 44 | }, |
| 45 | .fn_decl => { |
| 46 | const decl = state.ast.fullFnDecl(node); |
| 47 | |
| 48 | if (decl.public) { |
| 49 | try state.writer.writeAll("pub "); |
| 50 | } |
| 51 | try state.writer.writeAll("fn "); |
| 52 | try state.rewriteIdentifier(decl.id); |
| 53 | try state.writer.writeAll("("); |
| 54 | try state.writer.writeAll(")"); |
| 55 | |
| 56 | if (decl.return_type) |return_type| { |
| 57 | try state.writer.writeAll(" -> "); |
| 58 | try state.formatNode(return_type); |
| 59 | } |
| 60 | |
| 61 | try state.writer.writeAll(" "); |
| 62 | |
| 63 | try state.formatNode(decl.body); |
| 64 | |
| 65 | try state.writer.writeAll("\n"); |
| 66 | }, |
| 67 | else => |unhandled_tag| std.debug.panic("TODO: unhandled tag .{s}", .{@tagName(unhandled_tag)}), |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | fn rewriteIdentifier(state: Format, id: []const u8) !void { |
| 72 | try state.writer.writeAll(id); |
| 73 | } |
| 74 | |
| 75 | const Format = @This(); |
| 76 | |
| 77 | const std = @import("std"); |
| 78 | const mem = std.mem; |
| 79 | const assert = std.debug.assert; |
| 80 | const AnyWriter = std.io.AnyWriter; |
| 81 | |
| 82 | const core = @import("core"); |
| 83 | const Ast = core.Ast; |
| 84 | const NodeIndex = Ast.NodeIndex; |