File Coverage

include/eshu_pl.h
Criterion Covered Total %
statement 327 346 94.5
branch 282 358 78.7
condition n/a
subroutine n/a
pod n/a
total 609 704 86.5


line stmt bran cond sub pod time code
1             /*
2             * eshu_pl.h — Perl language indentation scanner
3             *
4             * Tracks {} () [] nesting depth while skipping strings, heredocs,
5             * regex, qw/qq/q constructs, pod sections, and comments.
6             * Rewrites leading whitespace only.
7             */
8              
9             #ifndef ESHU_PL_H
10             #define ESHU_PL_H
11              
12             #include "eshu.h"
13              
14             #define ESHU_PL_HEREDOC_MAX 64 /* max length of heredoc terminator */
15             #define ESHU_PL_QDEPTH_MAX 8 /* max nesting depth for paired delims */
16              
17             /* ══════════════════════════════════════════════════════════════════
18             * Scanner context — persists across lines
19             * ══════════════════════════════════════════════════════════════════ */
20              
21             typedef struct {
22             int depth; /* brace/paren/bracket nesting */
23             enum eshu_state state; /* current scanner state */
24             eshu_config_t cfg;
25              
26             /* Heredoc tracking */
27             char heredoc_tag[ESHU_PL_HEREDOC_MAX];
28             int heredoc_tag_len;
29             int heredoc_indented; /* <<~ variant */
30             int heredoc_pending; /* heredoc detected, body starts next line */
31              
32             /* Quoted construct tracking (qw, qq, q, s, tr, y, m) */
33             char q_open; /* opening delimiter */
34             char q_close; /* closing delimiter (0 for non-paired) */
35             int q_depth; /* nesting depth for paired delimiters */
36             int q_sections; /* remaining sections (2 for s///, 1 for others) */
37              
38             /* Regex tracking */
39             char rx_delim; /* regex delimiter char */
40              
41             /* Track whether last significant token could precede division */
42             int last_was_value; /* 1 if last token was var/number/)/] */
43             } eshu_pl_ctx_t;
44              
45 100           static void eshu_pl_ctx_init(eshu_pl_ctx_t *ctx, const eshu_config_t *cfg) {
46 100           ctx->depth = 0;
47 100           ctx->state = ESHU_CODE;
48 100           ctx->cfg = *cfg;
49 100           ctx->heredoc_tag[0] = '\0';
50 100           ctx->heredoc_tag_len = 0;
51 100           ctx->heredoc_indented = 0;
52 100           ctx->heredoc_pending = 0;
53 100           ctx->q_open = 0;
54 100           ctx->q_close = 0;
55 100           ctx->q_depth = 0;
56 100           ctx->q_sections = 0;
57 100           ctx->rx_delim = 0;
58 100           ctx->last_was_value = 0;
59 100           }
60              
61             /* ══════════════════════════════════════════════════════════════════
62             * Delimiter helpers
63             * ══════════════════════════════════════════════════════════════════ */
64              
65 25           static char eshu_pl_matching_close(char open) {
66 25           switch (open) {
67 8           case '(': return ')';
68 15           case '{': return '}';
69 1           case '[': return ']';
70 1           case '<': return '>';
71 0           default: return 0; /* non-paired: same char closes */
72             }
73             }
74              
75 30           static int eshu_pl_is_paired(char c) {
76 30 100         return c == '(' || c == '{' || c == '[' || c == '<';
    100          
    100          
    100          
77             }
78              
79             /* ══════════════════════════════════════════════════════════════════
80             * Heredoc detection
81             *
82             * Recognises: <
83             * Sets heredoc_pending=1 so the NEXT line enters heredoc state.
84             * ══════════════════════════════════════════════════════════════════ */
85              
86 26           static int eshu_pl_detect_heredoc(eshu_pl_ctx_t *ctx,
87             const char *p, const char *end) {
88             const char *start;
89 26           int indented = 0;
90 26           char quote = 0;
91             int len;
92              
93             /* p points to the first '<' — we need "<<" */
94 26 50         if (p + 1 >= end || *(p + 1) != '<')
    100          
95 15           return 0;
96 11           p += 2;
97              
98             /* optional ~ for indented heredoc */
99 11 50         if (p < end && *p == '~') {
    100          
100 1           indented = 1;
101 1           p++;
102             }
103              
104             /* optional quote */
105 11 50         if (p < end && (*p == '\'' || *p == '"' || *p == '`')) {
    100          
    100          
    50          
106 5           quote = *p;
107 5           p++;
108             }
109              
110             /* identifier */
111 11           start = p;
112 55 50         while (p < end && (isalnum((unsigned char)*p) || *p == '_'))
    100          
    50          
113 44           p++;
114 11           len = (int)(p - start);
115 11 50         if (len == 0 || len >= ESHU_PL_HEREDOC_MAX)
    50          
116 0           return 0;
117              
118             /* closing quote must match */
119 11 100         if (quote && (p >= end || *p != quote))
    50          
    50          
120 0           return 0;
121              
122 11           memcpy(ctx->heredoc_tag, start, len);
123 11           ctx->heredoc_tag[len] = '\0';
124 11           ctx->heredoc_tag_len = len;
125 11           ctx->heredoc_indented = indented;
126 11           ctx->heredoc_pending = 1;
127              
128 11           return 1;
129             }
130              
131             /* ══════════════════════════════════════════════════════════════════
132             * Check if line is a heredoc terminator
133             * ══════════════════════════════════════════════════════════════════ */
134              
135 41           static int eshu_pl_is_heredoc_end(const eshu_pl_ctx_t *ctx,
136             const char *line, const char *eol) {
137 41           const char *p = line;
138             int len;
139              
140             /* for <<~ the terminator may be indented */
141 41 100         if (ctx->heredoc_indented) {
142 15 50         while (p < eol && (*p == ' ' || *p == '\t'))
    100          
    50          
143 12           p++;
144             }
145              
146 41           len = (int)(eol - p);
147             /* terminator may have trailing ; or whitespace but we'll be strict:
148             the line content (trimmed) must exactly match the tag */
149 41 50         if (len < ctx->heredoc_tag_len)
150 0           return 0;
151              
152 41 100         if (memcmp(p, ctx->heredoc_tag, ctx->heredoc_tag_len) != 0)
153 30           return 0;
154              
155             /* rest of line must be empty or just whitespace/semicolons */
156 11           p += ctx->heredoc_tag_len;
157 11 50         while (p < eol) {
158 0 0         if (*p != ' ' && *p != '\t' && *p != ';')
    0          
    0          
159 0           return 0;
160 0           p++;
161             }
162 11           return 1;
163             }
164              
165             /* ══════════════════════════════════════════════════════════════════
166             * Pod detection — "=word" at start of line
167             * ══════════════════════════════════════════════════════════════════ */
168              
169 546           static int eshu_pl_is_pod_start(const char *content, const char *eol) {
170 546 100         if (*content != '=')
171 537           return 0;
172             /* must be followed by a letter */
173 9 50         if (content + 1 >= eol || !isalpha((unsigned char)content[1]))
    50          
174 0           return 0;
175             /* must NOT be =cut */
176 9 50         if (eol - content >= 4 && memcmp(content, "=cut", 4) == 0)
    50          
177 0           return 0;
178 9           return 1;
179             }
180              
181 227           static int eshu_pl_is_pod_end(const char *content, const char *eol) {
182 227           int len = (int)(eol - content);
183 227 100         if (len < 4) return 0;
184 224 100         if (memcmp(content, "=cut", 4) != 0) return 0;
185             /* rest should be whitespace or EOL */
186 8 50         if (len > 4 && content[4] != ' ' && content[4] != '\t')
    0          
    0          
187 0           return 0;
188 8           return 1;
189             }
190              
191             /* ══════════════════════════════════════════════════════════════════
192             * Classify whether a preceding context expects regex or division
193             *
194             * Returns 1 if the next '/' should be treated as regex opening.
195             * ══════════════════════════════════════════════════════════════════ */
196              
197 25           static int eshu_pl_expects_regex(const eshu_pl_ctx_t *ctx) {
198             /*
199             * If last token was a "value" (variable, number, closing bracket/paren)
200             * then / is division. Otherwise it's regex.
201             */
202 25           return !ctx->last_was_value;
203             }
204              
205             /* ══════════════════════════════════════════════════════════════════
206             * Enter a quoted construct (q/qq/qw/qx/s/tr/y/m)
207             *
208             * p points to the character AFTER the keyword letter(s).
209             * Returns the number of chars consumed for the delimiter.
210             * ══════════════════════════════════════════════════════════════════ */
211              
212 30           static int eshu_pl_enter_quoted(eshu_pl_ctx_t *ctx, char delim,
213             int sections, enum eshu_state state) {
214 30           ctx->q_open = delim;
215 30           ctx->q_sections = sections;
216 30 100         if (eshu_pl_is_paired(delim)) {
217 25           ctx->q_close = eshu_pl_matching_close(delim);
218 25           ctx->q_depth = 1;
219             } else {
220 5           ctx->q_close = delim;
221 5           ctx->q_depth = 0; /* non-paired don't nest */
222             }
223 30           ctx->state = state;
224 30           return 1; /* consumed the delimiter char */
225             }
226              
227             /* ══════════════════════════════════════════════════════════════════
228             * Try to detect q/qq/qw/qx/s/tr/y/m constructs
229             *
230             * p points to a char that may be q, s, t, m, y.
231             * Returns chars consumed (including keyword + delimiter) or 0.
232             * ══════════════════════════════════════════════════════════════════ */
233              
234 572           static int eshu_pl_try_q_construct(eshu_pl_ctx_t *ctx,
235             const char *p, const char *end) {
236 572           const char *start = p;
237 572           char c = *p;
238 572           int sections = 1;
239 572           enum eshu_state st = ESHU_Q;
240              
241 572 100         if (c == 'q') {
242 15           p++;
243 15 50         if (p < end && *(p) == 'w') {
    100          
244 11           p++; st = ESHU_QW;
245 4 50         } else if (p < end && *(p) == 'q') {
    100          
246 1           p++; st = ESHU_QQ;
247 3 50         } else if (p < end && *(p) == 'x') {
    50          
248 0           p++; st = ESHU_QQ; /* qx behaves like qq */
249 3 50         } else if (p < end && *(p) == 'r') {
    100          
250 2           p++; st = ESHU_QQ; /* qr// behaves like qq for scanning */
251             } else {
252 1           st = ESHU_Q;
253             }
254 557 100         } else if (c == 's') {
255 246           p++;
256 246           sections = 2;
257 246           st = ESHU_QQ; /* s/// — two sections, interpolates */
258 311 100         } else if (c == 't' && p + 1 < end && *(p + 1) == 'r') {
    50          
    50          
259 1           p += 2;
260 1           sections = 2;
261 1           st = ESHU_QQ;
262 310 100         } else if (c == 'y') {
263 3           p++;
264 3           sections = 2;
265 3           st = ESHU_QQ;
266 307 50         } else if (c == 'm') {
267 307           p++;
268 307           st = ESHU_QQ; /* m// uses same delimiter tracking as qq */
269             } else {
270 0           return 0;
271             }
272              
273             /* Must be followed by a non-alnum delimiter */
274 572 50         if (p >= end)
275 0           return 0;
276 572 100         if (isalnum((unsigned char)*p) || *p == '_')
    50          
277 543           return 0;
278 29 100         if (*p == ' ' || *p == '\t' || *p == '\n')
    50          
    50          
279 4           return 0;
280              
281 25           eshu_pl_enter_quoted(ctx, *p, sections, st);
282 25           p++;
283 25           return (int)(p - start);
284             }
285              
286             /* Check if the character before position p is a word character
287             * (to prevent matching 'eq' as 'q' construct, etc.) */
288 851           static int eshu_pl_preceded_by_word(const char *line_start, const char *p) {
289 851 100         if (p <= line_start)
290 365           return 0;
291 486 100         return isalnum((unsigned char)*(p - 1)) || *(p - 1) == '_';
    50          
292             }
293              
294             /* ══════════════════════════════════════════════════════════════════
295             * Scan a line for nesting changes (Perl-aware)
296             *
297             * Called AFTER the line has been emitted. Updates ctx->state
298             * and ctx->depth for the next line.
299             * ══════════════════════════════════════════════════════════════════ */
300              
301 1238           static void eshu_pl_scan_line(eshu_pl_ctx_t *ctx,
302             const char *p, const char *end) {
303 1238           const char *line_start = p;
304              
305 13257 100         while (p < end) {
306 12061           char c = *p;
307              
308 12061           switch (ctx->state) {
309 10261           case ESHU_CODE:
310 10261 100         if (c == '{' || c == '(' || c == '[') {
    100          
    100          
311 860           ctx->depth++;
312 860           ctx->last_was_value = 0;
313 9401 100         } else if (c == '}' || c == ')' || c == ']') {
    100          
    100          
314 860           ctx->depth--;
315 860 50         if (ctx->depth < 0) ctx->depth = 0;
316 860           ctx->last_was_value = 1;
317 8541 100         } else if (c == '"') {
318 39           ctx->state = ESHU_STRING_DQ;
319 39           ctx->last_was_value = 0;
320 8502 100         } else if (c == '\'') {
321 91           ctx->state = ESHU_STRING_SQ;
322 91           ctx->last_was_value = 0;
323 8411 100         } else if (c == '#') {
324             /* line comment — skip rest */
325 42           ctx->last_was_value = 0;
326 42           return;
327 8369 100         } else if (c == '<' && eshu_pl_detect_heredoc(ctx, p, end)) {
    100          
328             /* heredoc_pending is set; continue scanning rest of line */
329             /* skip past the heredoc operator to avoid re-matching */
330 11           p += 2; /* skip << */
331 11 50         if (p < end && *p == '~') p++;
    100          
332 16 50         if (p < end && (*p == '\'' || *p == '"' || *p == '`')) {
    100          
    100          
    50          
333 5           char hq = *p; p++;
334 23 50         while (p < end && *p != hq) p++;
    100          
335 5 50         if (p < end) p++;
336             } else {
337 32 50         while (p < end && (isalnum((unsigned char)*p) || *p == '_'))
    100          
    50          
338 26           p++;
339             }
340 11           ctx->last_was_value = 1;
341 11           continue;
342 8358 100         } else if (c == '/' && eshu_pl_expects_regex(ctx)) {
    100          
343             /* regex literal */
344 15           ctx->rx_delim = '/';
345 15           ctx->state = ESHU_REGEX;
346 15           ctx->last_was_value = 0;
347 8343 100         } else if ((c == 'q' || c == 'm' || c == 's' || c == 'y' ||
    100          
    100          
    100          
    100          
348 927 50         (c == 't' && p + 1 < end && *(p + 1) == 'r')) &&
349 1398           !eshu_pl_preceded_by_word(line_start, p)) {
350 572           int consumed = eshu_pl_try_q_construct(ctx, p, end);
351 572 100         if (consumed > 0) {
352 25           p += consumed;
353 25           ctx->last_was_value = 0;
354 25           continue;
355             }
356             /* not a q-construct, fall through */
357 547 50         if (isalnum((unsigned char)c))
358 547           ctx->last_was_value = 0;
359 7771 100         } else if (c == '/' && !eshu_pl_expects_regex(ctx)) {
    50          
360             /* division operator */
361 5           ctx->last_was_value = 0;
362 7766 100         } else if (c == '$' || c == '@' || c == '%') {
    100          
    100          
363             /* variable sigil — skip the variable name */
364 1134           ctx->last_was_value = 1;
365 1134           p++;
366 5699 50         while (p < end && (isalnum((unsigned char)*p) || *p == '_' || *p == ':'))
    100          
    100          
    100          
367 4565           p++;
368 1134           continue;
369 6632 100         } else if (isdigit((unsigned char)c)) {
370 187           ctx->last_was_value = 1;
371 437 50         while (p < end && (isalnum((unsigned char)*p) || *p == '.' || *p == '_'))
    100          
    100          
    100          
372 250           p++;
373 187           continue;
374 6445 100         } else if (c == '=' && p + 1 < end && *(p + 1) == '~') {
    50          
    100          
375             /* =~ forces next / to be regex */
376 26           ctx->last_was_value = 0;
377 26           p += 2;
378 26           continue;
379 6419 100         } else if (c == '!' && p + 1 < end && *(p + 1) == '~') {
    50          
    50          
380 0           ctx->last_was_value = 0;
381 0           p += 2;
382 0           continue;
383 6419 100         } else if (c == ' ' || c == '\t') {
    50          
384             /* whitespace — don't change last_was_value */
385 3576 100         } else if (isalpha((unsigned char)c) || c == '_') {
    100          
386             /* keyword or bareword — skip it */
387 1466           const char *ws = p;
388 7226 100         while (p < end && (isalnum((unsigned char)*p) || *p == '_'))
    100          
    100          
389 5760           p++;
390             /* barewords ending in a value context: could be function call */
391 1466           ctx->last_was_value = 0;
392 1466           continue;
393             } else {
394             /* operator chars: = + - * etc */
395 2110           ctx->last_was_value = 0;
396             }
397 7370           break;
398              
399 530           case ESHU_STRING_DQ:
400 530 100         if (c == '\\' && p + 1 < end) {
    50          
401 27           p++; /* skip escaped char */
402 503 100         } else if (c == '"') {
403 39           ctx->state = ESHU_CODE;
404 39           ctx->last_was_value = 1;
405             }
406 530           break;
407              
408 730           case ESHU_STRING_SQ:
409 730 100         if (c == '\\' && p + 1 < end) {
    50          
410 1           p++; /* skip escaped char */
411 729 100         } else if (c == '\'') {
412 91           ctx->state = ESHU_CODE;
413 91           ctx->last_was_value = 1;
414             }
415 730           break;
416              
417 53           case ESHU_REGEX:
418 53 100         if (c == '\\' && p + 1 < end) {
    50          
419 11           p++; /* skip escaped char */
420 42 100         } else if (c == ctx->rx_delim) {
421 15           ctx->state = ESHU_CODE;
422 15           ctx->last_was_value = 1;
423             /* skip optional flags */
424 15           p++;
425 19 50         while (p < end && isalpha((unsigned char)*p))
    100          
426 4           p++;
427 15           continue;
428             }
429 38           break;
430              
431 487           case ESHU_QW:
432             case ESHU_QQ:
433             case ESHU_Q:
434 487 100         if (c == '\\' && p + 1 < end && ctx->state != ESHU_QW) {
    50          
    50          
435 4           p++; /* skip escaped char (not in qw) */
436 483 100         } else if (ctx->q_close != ctx->q_open) {
437             /* paired delimiters — track nesting */
438 445 100         if (c == ctx->q_open) {
439 4           ctx->q_depth++;
440 441 100         } else if (c == ctx->q_close) {
441 29           ctx->q_depth--;
442 29 100         if (ctx->q_depth == 0) {
443 25           ctx->q_sections--;
444 25 100         if (ctx->q_sections <= 0) {
445 20           ctx->state = ESHU_CODE;
446 20           ctx->last_was_value = 1;
447             /* skip optional flags */
448 20           p++;
449 29 50         while (p < end && isalpha((unsigned char)*p))
    100          
450 9           p++;
451 20           continue;
452             } else {
453             /* next section: find new delimiter */
454 5           p++;
455             /* skip whitespace between sections */
456 5 50         while (p < end && (*p == ' ' || *p == '\t'))
    50          
    50          
457 0           p++;
458 5 50         if (p < end) {
459 5           eshu_pl_enter_quoted(ctx, *p,
460             ctx->q_sections, ctx->state);
461 5           p++; /* skip past opening delimiter */
462             }
463 5           continue;
464             }
465             }
466             }
467             } else {
468             /* non-paired: same char opens and closes */
469 38 100         if (c == ctx->q_close) {
470 9           ctx->q_sections--;
471 9 100         if (ctx->q_sections <= 0) {
472 5           ctx->state = ESHU_CODE;
473 5           ctx->last_was_value = 1;
474             /* skip optional flags */
475 5           p++;
476 8 50         while (p < end && isalpha((unsigned char)*p))
    100          
477 3           p++;
478 5           continue;
479             }
480             /* next section uses same delimiter, just continue */
481             }
482             }
483 457           break;
484              
485             /* These states are handled at the line level, not char level */
486 0           case ESHU_HEREDOC:
487             case ESHU_HEREDOC_INDENT:
488             case ESHU_POD:
489             case ESHU_CHAR_LIT:
490             case ESHU_COMMENT_LINE:
491             case ESHU_COMMENT_BLOCK:
492             case ESHU_PREPROCESSOR:
493 0           return;
494             }
495 9125           p++;
496             }
497             }
498              
499             /* ══════════════════════════════════════════════════════════════════
500             * Process a single Perl line — decide indent, emit, scan
501             * ══════════════════════════════════════════════════════════════════ */
502              
503 1821           static void eshu_pl_process_line(eshu_pl_ctx_t *ctx, eshu_buf_t *out,
504             const char *line_start, const char *eol) {
505 1821           const char *content = eshu_skip_leading_ws(line_start);
506             int line_len;
507             int indent_depth;
508              
509             /* empty line — preserve it */
510 1821 100         if (content >= eol) {
511 306           eshu_buf_putc(out, '\n');
512 306           return;
513             }
514              
515 1515           line_len = (int)(eol - content);
516              
517             /* ── Heredoc body: pass through verbatim ── */
518 1515 100         if (ctx->state == ESHU_HEREDOC || ctx->state == ESHU_HEREDOC_INDENT) {
    100          
519             /* emit line exactly as-is */
520 41           eshu_buf_write_trimmed(out, line_start, (int)(eol - line_start));
521 41           eshu_buf_putc(out, '\n');
522              
523             /* check for terminator */
524 41 100         if (eshu_pl_is_heredoc_end(ctx, line_start, eol)) {
525 11           ctx->state = ESHU_CODE;
526 11           ctx->heredoc_tag[0] = '\0';
527 11           ctx->heredoc_tag_len = 0;
528             }
529 41           return;
530             }
531              
532             /* ── Pod section: pass through verbatim ── */
533 1474 100         if (ctx->state == ESHU_POD) {
534 227           eshu_buf_write_trimmed(out, line_start, (int)(eol - line_start));
535 227           eshu_buf_putc(out, '\n');
536              
537 227 100         if (eshu_pl_is_pod_end(content, eol))
538 8           ctx->state = ESHU_CODE;
539 227           return;
540             }
541              
542             /* ── Pod start detection (= at column 0) ── */
543 1247 100         if (content == line_start && eshu_pl_is_pod_start(content, eol)) {
    100          
544 9           ctx->state = ESHU_POD;
545 9           eshu_buf_write_trimmed(out, content, line_len);
546 9           eshu_buf_putc(out, '\n');
547 9           return;
548             }
549              
550             /* ── Inside multi-line q/qq/qw/regex: indent at current depth+1 ── */
551 1238 100         if (ctx->state == ESHU_QW || ctx->state == ESHU_QQ ||
    100          
552 1216 50         ctx->state == ESHU_Q || ctx->state == ESHU_REGEX) {
    50          
553 22           int qdepth = ctx->depth + 1;
554             /* closing delimiter line gets same depth as opening line */
555 22 50         if (ctx->q_close && *content == ctx->q_close)
    100          
556 7           qdepth = ctx->depth;
557 22           eshu_emit_indent(out, qdepth, &ctx->cfg);
558 22           eshu_buf_write_trimmed(out, content, line_len);
559 22           eshu_buf_putc(out, '\n');
560 22           eshu_pl_scan_line(ctx, content, eol);
561 22           return;
562             }
563              
564             /* ── Normal Perl code ── */
565 1216           indent_depth = ctx->depth;
566              
567             /* If line starts with closer, dedent this line */
568 1216 100         if (*content == '}' || *content == ')' || *content == ']') {
    100          
    100          
569 278           indent_depth--;
570 278 50         if (indent_depth < 0) indent_depth = 0;
571             }
572              
573 1216           eshu_emit_indent(out, indent_depth, &ctx->cfg);
574 1216           eshu_buf_write_trimmed(out, content, line_len);
575 1216           eshu_buf_putc(out, '\n');
576              
577             /* scan for nesting changes */
578 1216           eshu_pl_scan_line(ctx, content, eol);
579              
580             /* If a heredoc was detected on this line, enter heredoc state now */
581 1216 100         if (ctx->heredoc_pending) {
582 11           ctx->heredoc_pending = 0;
583 11 100         ctx->state = ctx->heredoc_indented ? ESHU_HEREDOC_INDENT : ESHU_HEREDOC;
584             }
585             }
586              
587             /* ══════════════════════════════════════════════════════════════════
588             * Public API — indent a Perl source string
589             * ══════════════════════════════════════════════════════════════════ */
590              
591 100           static char * eshu_indent_pl(const char *src, size_t src_len,
592             const eshu_config_t *cfg, size_t *out_len) {
593             eshu_pl_ctx_t ctx;
594             eshu_buf_t out;
595 100           const char *p = src;
596 100           const char *end = src + src_len;
597             char *result;
598              
599 100           eshu_pl_ctx_init(&ctx, cfg);
600 100           eshu_buf_init(&out, src_len + 256);
601              
602             {
603 100           int line_num = 1;
604 1921 100         while (p < end) {
605 1821           const char *eol = eshu_find_eol(p);
606              
607 1821 100         if (eshu_in_range(cfg, line_num)) {
608 1818           eshu_pl_process_line(&ctx, &out, p, eol);
609             } else {
610 3           size_t saved = out.len;
611 3           eshu_pl_process_line(&ctx, &out, p, eol);
612 3           out.len = saved;
613 3           eshu_buf_write_trimmed(&out, p, (int)(eol - p));
614 3           eshu_buf_putc(&out, '\n');
615             }
616              
617 1821           p = eol;
618 1821 50         if (*p == '\n') p++;
619 1821           line_num++;
620             }
621             }
622              
623             /* NUL-terminate */
624 100           eshu_buf_putc(&out, '\0');
625 100           out.len--;
626              
627 100           *out_len = out.len;
628 100           result = out.data;
629 100           return result;
630             }
631              
632             #endif /* ESHU_PL_H */