File Coverage

include/eshu_c.h
Criterion Covered Total %
statement 130 142 91.5
branch 100 124 80.6
condition n/a
subroutine n/a
pod n/a
total 230 266 86.4


line stmt bran cond sub pod time code
1             /*
2             * eshu_c.h — C language indentation scanner
3             *
4             * Tracks {} () [] nesting depth while skipping strings, comments,
5             * and preprocessor directives. Rewrites leading whitespace only.
6             */
7              
8             #ifndef ESHU_C_H
9             #define ESHU_C_H
10              
11             #include "eshu.h"
12              
13             /* ══════════════════════════════════════════════════════════════════
14             * Scanner context — persists across lines
15             * ══════════════════════════════════════════════════════════════════ */
16              
17             typedef struct {
18             int depth; /* brace nesting depth */
19             int pp_depth; /* preprocessor #if depth */
20             enum eshu_state state; /* current scanner state */
21             eshu_config_t cfg;
22             } eshu_ctx_t;
23              
24 105           static void eshu_ctx_init(eshu_ctx_t *ctx, const eshu_config_t *cfg) {
25 105           ctx->depth = 0;
26 105           ctx->pp_depth = 0;
27 105           ctx->state = ESHU_CODE;
28 105           ctx->cfg = *cfg;
29 105           }
30              
31             /* ══════════════════════════════════════════════════════════════════
32             * Classify first non-ws char for pre-indent adjustment
33             * ══════════════════════════════════════════════════════════════════ */
34              
35             /* Does the line's content (after stripping ws) start with a
36             * closing brace/bracket/paren? If so we dedent before emitting. */
37 3154           static int eshu_c_is_closing(char c) {
38 3154 100         return c == '}' || c == ')' || c == ']';
    100          
    50          
39             }
40              
41             /* Is this a preprocessor line? (first non-ws char is '#') */
42 3293           static int eshu_c_is_pp(const char *content) {
43 3293           return *content == '#';
44             }
45              
46             /* ══════════════════════════════════════════════════════════════════
47             * Classify preprocessor directive for pp_depth tracking
48             * Returns: +1 for #if/#ifdef/#ifndef, -1 for #endif, 0 otherwise
49             * Sets *is_else = 1 for #else/#elif
50             * ══════════════════════════════════════════════════════════════════ */
51              
52 139           static int eshu_c_pp_classify(const char *content, int *is_else) {
53 139           const char *p = content + 1; /* skip '#' */
54 139           *is_else = 0;
55              
56 139 50         while (*p == ' ' || *p == '\t') p++;
    50          
57              
58 139 100         if (strncmp(p, "if", 2) == 0 && !isalnum((unsigned char)p[2]) && p[2] != '_')
    100          
    50          
59 4           return 1;
60 135 100         if (strncmp(p, "ifdef", 5) == 0 && !isalnum((unsigned char)p[5]))
    50          
61 13           return 1;
62 122 100         if (strncmp(p, "ifndef", 6) == 0 && !isalnum((unsigned char)p[6]))
    50          
63 11           return 1;
64 111 100         if (strncmp(p, "endif", 5) == 0 && !isalnum((unsigned char)p[5]))
    50          
65 28           return -1;
66 83 100         if (strncmp(p, "else", 4) == 0 && !isalnum((unsigned char)p[4])) {
    50          
67 5           *is_else = 1;
68 5           return 0;
69             }
70 78 100         if (strncmp(p, "elif", 4) == 0 && !isalnum((unsigned char)p[4])) {
    50          
71 1           *is_else = 1;
72 1           return 0;
73             }
74              
75 77           return 0;
76             }
77              
78             /* ══════════════════════════════════════════════════════════════════
79             * Scan a line for nesting changes (in CODE state)
80             *
81             * Called AFTER the line has been emitted. Updates ctx->state
82             * and ctx->depth for the next line.
83             * ══════════════════════════════════════════════════════════════════ */
84              
85 3402           static void eshu_c_scan_line(eshu_ctx_t *ctx, const char *p, const char *end) {
86 107741 100         while (p < end) {
87 104342           char c = *p;
88              
89 104342           switch (ctx->state) {
90 58643           case ESHU_CODE:
91 58643 100         if (c == '{' || c == '(' || c == '[') {
    100          
    100          
92 1907           ctx->depth++;
93 56736 100         } else if (c == '}' || c == ')' || c == ']') {
    100          
    100          
94 1907           ctx->depth--;
95 1907 50         if (ctx->depth < 0) ctx->depth = 0;
96 54829 100         } else if (c == '"') {
97 77           ctx->state = ESHU_STRING_DQ;
98 54752 100         } else if (c == '\'') {
99 408           ctx->state = ESHU_CHAR_LIT;
100 54344 100         } else if (c == '/' && p + 1 < end && *(p + 1) == '/') {
    50          
    100          
101             /* line comment — skip rest of line */
102 3           return;
103 54341 100         } else if (c == '/' && p + 1 < end && *(p + 1) == '*') {
    50          
    100          
104 428           ctx->state = ESHU_COMMENT_BLOCK;
105 428           p++; /* skip the '*' */
106             }
107 58640           break;
108              
109 666           case ESHU_STRING_DQ:
110 666 100         if (c == '\\' && p + 1 < end) {
    50          
111 17           p++; /* skip escaped char */
112 649 100         } else if (c == '"') {
113 77           ctx->state = ESHU_CODE;
114             }
115 666           break;
116              
117 816           case ESHU_CHAR_LIT:
118 816 100         if (c == '\\' && p + 1 < end) {
    50          
119 141           p++; /* skip escaped char */
120 675 100         } else if (c == '\'') {
121 408           ctx->state = ESHU_CODE;
122             }
123 816           break;
124              
125 0           case ESHU_STRING_SQ:
126             /* C doesn't use SQ strings, but keep state complete */
127 0 0         if (c == '\\' && p + 1 < end) {
    0          
128 0           p++;
129 0 0         } else if (c == '\'') {
130 0           ctx->state = ESHU_CODE;
131             }
132 0           break;
133              
134 44217           case ESHU_COMMENT_BLOCK:
135 44217 100         if (c == '*' && p + 1 < end && *(p + 1) == '/') {
    100          
    100          
136 428           ctx->state = ESHU_CODE;
137 428           p++; /* skip the '/' */
138             }
139 44217           break;
140              
141 0           case ESHU_COMMENT_LINE:
142             /* shouldn't happen — we return early above */
143 0           return;
144              
145 0           case ESHU_PREPROCESSOR:
146             /* handled separately */
147 0           break;
148              
149             /* Perl-only states — not used in C scanner */
150 0           case ESHU_HEREDOC:
151             case ESHU_HEREDOC_INDENT:
152             case ESHU_REGEX:
153             case ESHU_QW:
154             case ESHU_QQ:
155             case ESHU_Q:
156             case ESHU_POD:
157 0           break;
158             }
159 104339           p++;
160             }
161             }
162              
163             /* ══════════════════════════════════════════════════════════════════
164             * Process a single line — decide indent, emit, scan
165             * ══════════════════════════════════════════════════════════════════ */
166              
167 4019           static void eshu_c_process_line(eshu_ctx_t *ctx, eshu_buf_t *out,
168             const char *line_start, const char *eol) {
169 4019           const char *content = eshu_skip_leading_ws(line_start);
170             int line_len;
171             int indent_depth;
172              
173             /* empty line — preserve it */
174 4019 100         if (content >= eol) {
175 478           eshu_buf_putc(out, '\n');
176 478           return;
177             }
178              
179 3541           line_len = (int)(eol - content);
180              
181             /* If we're in a block comment, pass through with current depth */
182 3541 100         if (ctx->state == ESHU_COMMENT_BLOCK) {
183             /* inside block comment: indent at current depth + 1
184             * (the comment was opened inside a code block) */
185 248           eshu_emit_indent(out, ctx->depth, &ctx->cfg);
186 248           eshu_buf_write_trimmed(out, content, line_len);
187 248           eshu_buf_putc(out, '\n');
188 248           eshu_c_scan_line(ctx, content, eol);
189 248           return;
190             }
191              
192             /* Preprocessor line */
193 3293 100         if (eshu_c_is_pp(content)) {
194 139           int is_else = 0;
195 139           int pp_dir = eshu_c_pp_classify(content, &is_else);
196              
197 139 100         if (ctx->cfg.indent_pp) {
198 5 100         if (pp_dir < 0) {
199             /* #endif — dedent first */
200 2           ctx->pp_depth--;
201 2 50         if (ctx->pp_depth < 0) ctx->pp_depth = 0;
202             }
203 5 100         if (is_else) {
204 1           eshu_emit_indent(out, ctx->pp_depth - 1, &ctx->cfg);
205             } else {
206 4           eshu_emit_indent(out, ctx->pp_depth, &ctx->cfg);
207             }
208 5 100         if (pp_dir > 0)
209 2           ctx->pp_depth++;
210             } else {
211             /* no pp indent — emit at column 0 */
212 134 100         if (pp_dir > 0)
213 26           ctx->pp_depth++;
214 108 100         else if (pp_dir < 0) {
215 26           ctx->pp_depth--;
216 26 50         if (ctx->pp_depth < 0) ctx->pp_depth = 0;
217             }
218             }
219              
220 139           eshu_buf_write_trimmed(out, content, line_len);
221 139           eshu_buf_putc(out, '\n');
222 139           return;
223             }
224              
225             /* Normal code line */
226 3154           indent_depth = ctx->depth;
227              
228             /* If line starts with closer, dedent this line */
229 3154 100         if (eshu_c_is_closing(*content)) {
230 535           indent_depth--;
231 535 50         if (indent_depth < 0) indent_depth = 0;
232             }
233              
234 3154           eshu_emit_indent(out, indent_depth, &ctx->cfg);
235 3154           eshu_buf_write_trimmed(out, content, line_len);
236 3154           eshu_buf_putc(out, '\n');
237              
238             /* Scan for nesting changes */
239 3154           eshu_c_scan_line(ctx, content, eol);
240             }
241              
242             /* ══════════════════════════════════════════════════════════════════
243             * Public API — indent a C source string
244             * ══════════════════════════════════════════════════════════════════ */
245              
246 72           static char * eshu_indent_c(const char *src, size_t src_len,
247             const eshu_config_t *cfg, size_t *out_len) {
248             eshu_ctx_t ctx;
249             eshu_buf_t out;
250 72           const char *p = src;
251 72           const char *end = src + src_len;
252             char *result;
253              
254 72           eshu_ctx_init(&ctx, cfg);
255 72           eshu_buf_init(&out, src_len + 256);
256              
257             {
258 72           int line_num = 1;
259 3972 100         while (p < end) {
260 3900           const char *eol = eshu_find_eol(p);
261              
262 3900 100         if (eshu_in_range(cfg, line_num)) {
263 3897           eshu_c_process_line(&ctx, &out, p, eol);
264             } else {
265             /* Outside range: scan for state, emit verbatim */
266 3           size_t saved = out.len;
267 3           eshu_c_process_line(&ctx, &out, p, eol);
268 3           out.len = saved;
269 3           eshu_buf_write_trimmed(&out, p, (int)(eol - p));
270 3           eshu_buf_putc(&out, '\n');
271             }
272              
273 3900           p = eol;
274 3900 50         if (*p == '\n') p++;
275 3900           line_num++;
276             }
277             }
278              
279             /* NUL-terminate */
280 72           eshu_buf_putc(&out, '\0');
281 72           out.len--; /* don't count NUL in length */
282              
283 72           *out_len = out.len;
284 72           result = out.data;
285             /* caller owns result, must free() it */
286 72           return result;
287             }
288              
289             #endif /* ESHU_C_H */