代码格式化是现代编辑器的核心功能之一。良好的格式化功能可以让代码保持一致的风格,提升可读性。本文将介绍代码编辑器中格式化器的设计与实现。
格式化器概述
一个完整的代码格式化器通常包含以下功能:
- 缩进处理:根据语法结构添加或修正缩进
- 换行处理:控制单行代码的最大长度,超长自动换行
- 空格处理:运算符周围添加空格、移除多余空格
- 对齐处理:对齐赋值号、参数、数组元素等
- 括号处理:控制花括号位置、换行风格
解析器设计
格式化之前,需要先解析代码生成抽象语法树(AST)。这里以简化版为例:
/* formatter_ast.h - 语法树定义 */
#ifndef FORMATTER_AST_H
#define FORMATTER_AST_H
typedef enum {
NODE_PROGRAM,
NODE_FUNCTION,
NODE_BLOCK,
NODE_IF,
NODE_FOR,
NODE_WHILE,
NODE_RETURN,
NODE_EXPRESSION,
NODE_ASSIGNMENT,
NODE_BINARY_OP,
NODE_UNARY_OP,
NODE_CALL,
NODE_VARIABLE,
NODE_LITERAL
} NodeType;
typedef struct ASTNode {
NodeType type;
char *value;
struct ASTNode **children;
size_t child_count;
int line;
int column;
} ASTNode;
/* 解析源代码生成 AST */
ASTNode *parse(const char *source);
/* 释放 AST */
void ast_free(ASTNode *node);
#endif
格式化选项配置
允许用户自定义格式化行为:
/* formatter_options.h - 格式化选项 */
#ifndef FORMATTER_OPTIONS_H
#define FORMATTER_OPTIONS_H
typedef struct {
/* 缩进设置 */
int indent_size; /* 缩进宽度,默认 4 */
int tab_size; /* Tab 宽度,默认 4 */
int use_spaces; /* 使用空格缩进,默认 1 */
int indent_with_tabs; /* 使用 Tab 缩进,默认 0 */
/* 行宽设置 */
int print_width; /* 最大行宽,默认 80 */
/* 空格设置 */
int spaces_around_operators; /* 运算符周围空格 */
int spaces_around_brackets; /* 括号周围空格 */
int space_before_paren; /* 函数圆括号前空格 */
int align_var_decl; /* 变量声明对齐 */
/* 括号风格 */
int brace_style; /* 0: K&R, 1: Allman, 2: GNU */
/* 其他 */
int insert_final_newline; /* 文件末尾插入新行 */
int trim_trailing_whitespace; /* 去除末尾空白 */
} FormatterOptions;
void options_init(FormatterOptions *opts);
int options_load_from_file(FormatterOptions *opts, const char *path);
#endif
核心格式化逻辑
/* formatter.c - 格式化器核心实现 */
#include
#include
#include
#include
#include "formatter_options.h"
#include "formatter_ast.h"
typedef struct FormatterContext {
FormatterOptions *opts;
char *output;
size_t output_size;
size_t output_capacity;
int current_indent;
int current_column;
} FormatterContext;
/* 输出格式化文本 */
static void format_append(FormatterContext *ctx, const char *fmt, ...) {
va_list args;
char buffer[1024];
va_start(args, fmt);
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
/* 扩容检查 */
while (ctx->output_size + len + 1 > ctx->output_capacity) {
ctx->output_capacity *= 2;
ctx->output = realloc(ctx->output, ctx->output_capacity);
}
memcpy(ctx->output + ctx->output_size, buffer, len);
ctx->output_size += len;
ctx->output[ctx->output_size] = '\0';
ctx->current_column += len;
}
/* 输出缩进 */
static void format_indent(FormatterContext *ctx) {
int indent = ctx->current_indent;
if (ctx->opts->use_spaces) {
for (int i = 0; i < indent * ctx->opts->indent_size; i++) {
format_append(ctx, " ");
}
} else {
format_append(ctx, "\t");
}
}
处理花括号风格
/* 处理花括号换行风格 */
static void format_open_brace(FormatterContext *ctx, int *need_newline) {
switch (ctx->opts->brace_style) {
case 0: /* K&R 风格 - 同行 */
format_append(ctx, " {\n");
ctx->current_indent++;
ctx->current_column = 0;
*need_newline = 0;
break;
case 1: /* Allman 风格 - 独立行 */
format_append(ctx, "\n");
format_indent(ctx);
format_append(ctx, "{\n");
ctx->current_indent++;
ctx->current_column = 0;
*need_newline = 0;
break;
case 2: /* GNU 风格 - 独立行 + 缩进 */
format_append(ctx, " {\n");
format_indent(ctx);
ctx->current_indent++;
ctx->current_column = 0;
*need_newline = 0;
break;
}
}
static void format_close_brace(FormatterContext *ctx) {
ctx->current_indent--;
format_append(ctx, "\n");
format_indent(ctx);
format_append(ctx, "}");
ctx->current_column = 1;
}
运算符空格处理
/* 格式化二元运算符 */
static void format_binary_op(FormatterContext *ctx, const char *op) {
if (ctx->opts->spaces_around_operators) {
format_append(ctx, " %s ", op);
ctx->current_column += strlen(op) + 2;
} else {
format_append(ctx, "%s", op);
ctx->current_column += strlen(op);
}
}
/* 检查是否需要换行 */
static int should_break_line(FormatterContext *ctx, int estimated_len) {
if (ctx->current_column + estimated_len > ctx->opts->print_width) {
return 1;
}
return 0;
}
/* 换行并缩进 */
static void format_break_line(FormatterContext *ctx) {
format_append(ctx, "\n");
format_indent(ctx);
ctx->current_column = ctx->current_indent * ctx->opts->indent_size;
}
主格式化入口
/* 格式化整个程序 */
char *format_code(const char *source, FormatterOptions *opts) {
/* 1. 解析源代码为 AST */
ASTNode *ast = parse(source);
if (!ast) return NULL;
/* 2. 初始化格式化上下文 */
FormatterContext ctx;
ctx.opts = opts;
ctx.current_indent = 0;
ctx.current_column = 0;
ctx.output_capacity = 4096;
ctx.output_size = 0;
ctx.output = malloc(ctx.output_capacity);
ctx.output[0] = '\0';
/* 3. 遍历 AST 生成格式化后的代码 */
format_node(&ctx, ast);
/* 4. 处理末尾 */
if (opts->insert_final_newline && ctx.output_size > 0) {
if (ctx.output[ctx.output_size - 1] != '\n') {
format_append(&ctx, "\n");
}
}
/* 5. 清理 */
ast_free(ast);
return ctx.output;
}
/* 格式化单个节点 */
static void format_node(FormatterContext *ctx, ASTNode *node) {
if (!node) return;
switch (node->type) {
case NODE_BLOCK:
format_block(ctx, node);
break;
case NODE_IF:
format_if(ctx, node);
break;
case NODE_FOR:
case NODE_WHILE:
format_loop(ctx, node);
break;
case NODE_FUNCTION:
format_function(ctx, node);
break;
/* ... 其他节点类型 ... */
}
}
格式化 API 设计
/* formatter_api.h - 对外 API */
#ifndef FORMATTER_API_H
#define FORMATTER_API_H
#include "formatter_options.h"
/* 格式化代码字符串 */
char *format(const char *source, FormatterOptions *opts);
/* 格式化文件 */
int format_file(const char *input_path,
const char *output_path,
FormatterOptions *opts);
/* 原地格式化(修改原文件)*/
int format_file_inplace(const char *path,
FormatterOptions *opts);
#endif
实现建议
- 使用增量解析,只重新格式化修改的部分
- 支持格式化前后的 diff 预览
- 提供 "保存时格式化" 选项
- 支持针对不同语言使用不同的格式化规则
- 考虑集成 Prettier、clang-format 等成熟工具
总结
代码格式化器是编辑器中复杂度较高的功能模块。它需要先解析代码为语法树,然后根据用户配置逐个处理节点,最后输出格式化的代码。本文介绍的设计方案涵盖了核心的数据结构、配置选项和处理逻辑,可以作为实现格式化器的基础。