commit 254edfc

xplshn  ·  2026-07-01 05:12:22 +0000 UTC
parent 1586f63
sh: qol change

Signed-off-by: xplshn <anto@xplshn.com.ar>
6 files changed,  +525, -8
+1, -0
1@@ -785,6 +785,7 @@ SHOBJ =\
2 	cmd/posix/sh/cd.o\
3 	cmd/posix/sh/echo.o\
4 	cmd/posix/sh/error.o\
5+	cmd/posix/sh/easter.o\
6 	cmd/posix/sh/eval.o\
7 	cmd/posix/sh/exec.o\
8 	cmd/posix/sh/expand.o\
+9, -8
 1@@ -35,9 +35,10 @@ posix)
 2     FEATURE_STAT_FILESYSTEM=0;        FEATURE_STAT_FORMAT=0
 3     FEATURE_SORT_BIG=0;               FEATURE_SORT_STABLE=0
 4     FEATURE_OD_ENDIAN=0;
 5-    FEATURE_SH_HISTEDIT=0;            FEATURE_SH_LOCAL=0
 6-    FEATURE_SH_LET=0;                 FEATURE_SH_ULIMIT=0
 7-    FEATURE_SH_SETVAR=0;              FEATURE_SH_WORDEXP=0
 8+    FEATURE_SH_EASTER=0;              FEATURE_SH_HISTEDIT=0;
 9+    FEATURE_SH_LOCAL=0                FEATURE_SH_LET=0;
10+    FEATURE_SH_ULIMIT=0               FEATURE_SH_SETVAR=0;
11+    FEATURE_SH_WORDEXP=0
12     FEATURE_CAL_EXT=0
13     FEATURE_LS_COLOR=0
14     FEATURE_DIFF3_MERGE=0;            FEATURE_DIFF3_EDSCRIPT=0
15@@ -70,9 +71,9 @@ common)
16     FEATURE_STAT_FILESYSTEM=1;        FEATURE_STAT_FORMAT=1
17     FEATURE_SORT_BIG=0;               FEATURE_SORT_STABLE=1
18     FEATURE_OD_ENDIAN=0;
19-    FEATURE_SH_HISTEDIT=0;            FEATURE_SH_LOCAL=1
20-    FEATURE_SH_LET=1;                 FEATURE_SH_ULIMIT=1
21-    FEATURE_SH_SETVAR=0;              FEATURE_SH_WORDEXP=0
22+    FEATURE_SH_EASTER=0;              FEATURE_SH_HISTEDIT=0;
23+    FEATURE_SH_LOCAL=1                FEATURE_SH_LET=1;
24+    FEATURE_SH_ULIMIT=1               FEATURE_SH_SETVAR=0;
25     FEATURE_CAL_EXT=1
26     FEATURE_LS_COLOR=1
27     FEATURE_DIFF3_MERGE=0;            FEATURE_DIFF3_EDSCRIPT=1
28@@ -105,8 +106,8 @@ all)
29     FEATURE_STAT_FILESYSTEM=1;        FEATURE_STAT_FORMAT=1
30     FEATURE_SORT_BIG=1;               FEATURE_SORT_STABLE=1
31     FEATURE_OD_ENDIAN=1;
32-    FEATURE_SH_HISTEDIT=1
33-    FEATURE_INPROC_EXEC=0             FEATURE_SH_LOCAL=1
34+    FEATURE_SH_EASTER=0;              FEATURE_SH_HISTEDIT=1
35+    FEATURE_INPROC_EXEC=0;            FEATURE_SH_LOCAL=1
36     FEATURE_SH_LET=1;                 FEATURE_SH_ULIMIT=1
37     FEATURE_SH_SETVAR=1;              FEATURE_SH_WORDEXP=1
38     FEATURE_CAL_EXT=1
+2, -0
1@@ -89,3 +89,5 @@ unaliascmd	unalias
2 unsetcmd	-s unset
3 waitcmd		wait
4 wordexpcmd	wordexp	#FEATURE_SH_WORDEXP
5+bogometercmd -n	bogometer #FEATURE_SH_EASTER
6+boguscmd -n	bogus #FEATURE_SH_EASTER
+509, -0
  1@@ -0,0 +1,509 @@
  2+/* See LICENSE file for copyright and license details. */
  3+#include "bltin.h"
  4+#include "fs.h"
  5+#include "util.h"
  6+
  7+#include <sys/stat.h>
  8+#include <sys/types.h>
  9+
 10+#include <fcntl.h>
 11+#include <string.h>
 12+#include <unistd.h>
 13+
 14+enum {
 15+  COMMENT_C,       /* line // and block /* */
 16+  COMMENT_HASH,    /* line # only */
 17+  COMMENT_PERCENT, /* line % only */
 18+  COMMENT_SEMI,    /* line ; only */
 19+  COMMENT_DASH,    /* line -- only */
 20+  COMMENT_HASKELL, /* line -- and block {- -} */
 21+  COMMENT_PAREN,   /* block (* *) only, no line comment */
 22+};
 23+
 24+enum {
 25+  LANG_C,
 26+  LANG_CPP,
 27+  LANG_SH,
 28+  LANG_AWK,
 29+  LANG_MAKE,
 30+  LANG_M4,
 31+  LANG_LUA,
 32+  LANG_PERL,
 33+  LANG_PYTHON,
 34+  LANG_RUBY,
 35+  LANG_PHP,
 36+  LANG_JS,
 37+  LANG_TS,
 38+  LANG_JAVA,
 39+  LANG_KOTLIN,
 40+  LANG_SCALA,
 41+  LANG_CS,
 42+  LANG_GO,
 43+  LANG_SWIFT,
 44+  LANG_ZIG,
 45+  LANG_DART,
 46+  LANG_ELIXIR,
 47+  LANG_ERLANG,
 48+  LANG_HASKELL,
 49+  LANG_OCAML,
 50+  LANG_CLOJURE,
 51+  LANG_RUST,
 52+  LANG_ADA,
 53+  LANG_R,
 54+  LANG_COUNT
 55+};
 56+
 57+struct CommentSpec {
 58+  const char *line;
 59+  const char *bopen;
 60+  const char *bclose;
 61+};
 62+
 63+static const struct CommentSpec comment_specs[] = {
 64+    [COMMENT_C]       = {"//", "/*", "*/"},
 65+    [COMMENT_HASH]    = {"#",  NULL, NULL},
 66+    [COMMENT_PERCENT] = {"%",  NULL, NULL},
 67+    [COMMENT_SEMI]    = {";",  NULL, NULL},
 68+    [COMMENT_DASH]    = {"--", NULL, NULL},
 69+    [COMMENT_HASKELL] = {"--", "{-", "-}"},
 70+    [COMMENT_PAREN]   = {NULL, "(*", "*)"},
 71+};
 72+
 73+/* weight is this languages contribution per percent of the codebase */
 74+struct LangDef {
 75+  const char *name;
 76+  int         weight;
 77+  int         cstyle;
 78+};
 79+
 80+static const struct LangDef lang_defs[LANG_COUNT] = {
 81+    [LANG_C]       = {"C",       1,    COMMENT_C      },
 82+    [LANG_CPP]     = {"C++",     300,  COMMENT_C      },
 83+    [LANG_SH]      = {"Shell",   6,    COMMENT_HASH   },
 84+    [LANG_AWK]     = {"Awk",     6,    COMMENT_HASH   },
 85+    [LANG_MAKE]    = {"Make",    10,    COMMENT_HASH   },
 86+    [LANG_M4]      = {"M4",      25,   COMMENT_HASH   },
 87+    [LANG_LUA]     = {"Lua",     25,   COMMENT_DASH   },
 88+    [LANG_PERL]    = {"Perl",    500,  COMMENT_HASH   },
 89+    [LANG_PYTHON]  = {"Python",  80,   COMMENT_HASH   },
 90+    [LANG_RUBY]    = {"Ruby",    90,   COMMENT_HASH   },
 91+    [LANG_PHP]     = {"PHP",     110,  COMMENT_C      },
 92+    [LANG_JS]      = {"JS",      100,  COMMENT_C      },
 93+    [LANG_TS]      = {"TS",      140,  COMMENT_C      },
 94+    [LANG_JAVA]    = {"Java",    150,  COMMENT_C      },
 95+    [LANG_KOTLIN]  = {"Kotlin",  160,  COMMENT_C      },
 96+    [LANG_SCALA]   = {"Scala",   180,  COMMENT_C      },
 97+    [LANG_CS]      = {"C#",      140,  COMMENT_C      },
 98+    [LANG_GO]      = {"Go",      3,   COMMENT_C      },
 99+    [LANG_SWIFT]   = {"Swift",   70,   COMMENT_C      },
100+    [LANG_ZIG]     = {"Zig",     65,   COMMENT_C      },
101+    [LANG_DART]    = {"Dart",    55,   COMMENT_C      },
102+    [LANG_ELIXIR]  = {"Elixir",  50,   COMMENT_HASH   },
103+    [LANG_ERLANG]  = {"Erlang",  55,   COMMENT_PERCENT},
104+    [LANG_HASKELL] = {"Haskell", 200,  COMMENT_HASKELL},
105+    [LANG_OCAML]   = {"OCaml",   45,   COMMENT_PAREN  },
106+    [LANG_CLOJURE] = {"Clojure", 85,   COMMENT_SEMI   },
107+    [LANG_RUST]    = {"Rust",    3000, COMMENT_C      },
108+    [LANG_ADA]     = {"Ada",     15,   COMMENT_DASH   },
109+    [LANG_R]       = {"R",       40,   COMMENT_HASH   },
110+};
111+
112+struct ExtMap {
113+  const char *ext;
114+  int         lang;
115+};
116+
117+static const struct ExtMap ext_map[] = {
118+    {".c",     LANG_C      },
119+    {".cc",    LANG_CPP    },
120+    {".cpp",   LANG_CPP    },
121+    {".cxx",   LANG_CPP    },
122+    {".hpp",   LANG_CPP    },
123+    {".sh",    LANG_SH     },
124+    {".awk",   LANG_AWK    },
125+    {".mk",    LANG_MAKE   },
126+    {".m4",    LANG_M4     },
127+    {".lua",   LANG_LUA    },
128+    {".pl",    LANG_PERL   },
129+    {".pm",    LANG_PERL   },
130+    {".py",    LANG_PYTHON },
131+    {".pyw",   LANG_PYTHON },
132+    {".rb",    LANG_RUBY   },
133+    {".php",   LANG_PHP    },
134+    {".js",    LANG_JS     },
135+    {".mjs",   LANG_JS     },
136+    {".cjs",   LANG_JS     },
137+    {".ts",    LANG_TS     },
138+    {".tsx",   LANG_TS     },
139+    {".java",  LANG_JAVA   },
140+    {".kt",    LANG_KOTLIN },
141+    {".kts",   LANG_KOTLIN },
142+    {".scala", LANG_SCALA  },
143+    {".cs",    LANG_CS     },
144+    {".go",    LANG_GO     },
145+    {".swift", LANG_SWIFT  },
146+    {".zig",   LANG_ZIG    },
147+    {".dart",  LANG_DART   },
148+    {".ex",    LANG_ELIXIR },
149+    {".exs",   LANG_ELIXIR },
150+    {".erl",   LANG_ERLANG },
151+    {".hrl",   LANG_ERLANG },
152+    {".hs",    LANG_HASKELL},
153+    {".lhs",   LANG_HASKELL},
154+    {".ml",    LANG_OCAML  },
155+    {".mli",   LANG_OCAML  },
156+    {".clj",   LANG_CLOJURE},
157+    {".cljs",  LANG_CLOJURE},
158+    {".rs",    LANG_RUST   },
159+    {".adb",   LANG_ADA    },
160+    {".ads",   LANG_ADA    },
161+    {".r",     LANG_R      },
162+    {".R",     LANG_R      },
163+};
164+
165+struct NameMap {
166+  const char *name;
167+  int         lang;
168+};
169+
170+static const struct NameMap name_map[] = {
171+    {"Makefile",       LANG_MAKE},
172+    {"GNUmakefile",    LANG_MAKE},
173+    {".profile",       LANG_SH  },
174+    {"profile",        LANG_SH  },
175+    {".bashrc",        LANG_SH  },
176+    {".bash_profile",  LANG_SH  },
177+    {".shrc",          LANG_SH  },
178+    {".kshrc",         LANG_SH  },
179+    {".zshrc",         LANG_SH  },
180+    {".cshrc",         LANG_SH  },
181+    {".login",         LANG_SH  },
182+};
183+
184+/* not scanned for lines, but still checked against markers below */
185+static const char *const skip_dirs[] = {
186+    ".git", ".hg", ".svn", "node_modules", "vendor", "__pycache__",
187+    ".venv", "venv", "target", "build", "dist", ".cache",
188+    ".next", ".idea", ".vscode", NULL,
189+};
190+
191+struct Marker {
192+  const char *name;
193+  int         is_dir;
194+  double      penalty;
195+  const char *remark;
196+};
197+
198+static const struct Marker markers[] = {
199+    {"node_modules",   1, 150.0, "skipping bullshit / malware"                          },
200+    {"Cargo.toml",     0, 80.0,  "I challenge you to bootstrapping this shitty lang"    },
201+    {"package.json",   0, 60.0,  "js. Self-explanatory"                                 },
202+    {"go.mod",         0, 20.0,  "Not C. But still good. Minor penalty"                 },
203+    {"Dockerfile",     0, 20.0,  "containerization crutch"                              },
204+    {"Gemfile",        0, 55.0,  "shitty language baseline"                             },
205+    {"CMakeLists.txt", 0, 55.0,  "shitty build system"                                  },
206+    {"tsconfig.json",  0, 60.0,  "a language with a configuration file. Degenerate."    },
207+};
208+
209+struct ComboPenalty {
210+  int         lang_a;
211+  int         lang_b;
212+  double      penalty;
213+  const char *remark;
214+};
215+
216+static const struct ComboPenalty combos[] = {
217+    {LANG_RUST, LANG_CPP, 4000.0, "MAXIMUM BOGUSNESS, rust and c++ sharing a tree"},
218+    {LANG_RUST, LANG_C,   400.0,  "comical bogusness, rust apologizing to the c it links against"},
219+    {LANG_JS,   LANG_TS,  150.0,  "half the codebase does not trust the other half"},
220+    {LANG_C,    LANG_CPP, 120.0,  "abi boundary between two compilers of the same language"},
221+};
222+
223+struct LangStat {
224+  long files;
225+  long blank;
226+  long comment;
227+  long code;
228+};
229+
230+struct ScanState {
231+  struct LangStat stats[LANG_COUNT];
232+  double          bonus;
233+  int             marker_seen[LEN(markers)];
234+};
235+
236+#define BABEL_THRESHOLD 3
237+#define BABEL_MULT      1.5
238+
239+static int
240+is_classic(int lang)
241+{
242+  return lang == LANG_C || lang == LANG_SH || lang == LANG_AWK;
243+}
244+
245+static int
246+is_skip_dir(const char *name)
247+{
248+  int i;
249+
250+  for (i = 0; skip_dirs[i]; i++)
251+    if (!strcmp(name, skip_dirs[i]))
252+      return 1;
253+  return 0;
254+}
255+
256+/* checks a 1 or 2 char marker at p without reading past bufend */
257+static int
258+marker_at(const char *p, const char *bufend, const char *marker)
259+{
260+  if (!marker)
261+    return 0;
262+  if (!marker[1])
263+    return *p == marker[0];
264+  return p + 1 < bufend && p[0] == marker[0] && p[1] == marker[1];
265+}
266+
267+static int
268+detect_lang(const char *name)
269+{
270+  const char *ext;
271+  size_t      i, nlen;
272+
273+  for (i = 0; i < LEN(name_map); i++)
274+    if (!strcmp(name, name_map[i].name))
275+      return name_map[i].lang;
276+
277+  ext = strrchr(name, '.');
278+  if (ext) {
279+    for (i = 0; i < LEN(ext_map); i++)
280+      if (!strcmp(ext, ext_map[i].ext))
281+        return ext_map[i].lang;
282+  }
283+
284+  /*
285+   * anchored to the end of the name, so arch.txt (which contains "rc"
286+   * but does not end in it) is never mistaken for a shell rc file
287+   */
288+  nlen = strlen(name);
289+  if (nlen >= 2 && !strcmp(name + nlen - 2, "rc"))
290+    return LANG_SH;
291+  if (nlen >= 7 && !strcmp(name + nlen - 7, "profile"))
292+    return LANG_SH;
293+
294+  return -1;
295+}
296+
297+static void
298+check_marker(const char *name, int is_dir, struct ScanState *st)
299+{
300+  size_t i;
301+
302+  for (i = 0; i < LEN(markers); i++) {
303+    if (markers[i].is_dir != is_dir || strcmp(name, markers[i].name) != 0)
304+      continue;
305+    if (st->marker_seen[i])
306+      return;
307+    st->marker_seen[i] = 1;
308+    st->bonus += markers[i].penalty;
309+    fprintf(stderr, "note: %s: %s\n", markers[i].name, markers[i].remark);
310+    return;
311+  }
312+}
313+
314+static void
315+scan_file(int dirfd, const char *name, int lang, struct LangStat *lp)
316+{
317+  const struct CommentSpec *cs;
318+  char                       buf[8192];
319+  char                      *p, *bufend;
320+  ssize_t                    nread;
321+  int fd, in_block, leading_ws, checked, line_has_content, line_is_comment;
322+
323+  fd = openat(dirfd, name, O_RDONLY);
324+  if (fd < 0)
325+    return;
326+
327+  lp->files++;
328+  cs               = &comment_specs[lang_defs[lang].cstyle];
329+  in_block         = 0;
330+  leading_ws       = 1;
331+  checked          = 0;
332+  line_has_content = 0;
333+  line_is_comment  = 0;
334+
335+  while ((nread = read(fd, buf, sizeof(buf))) > 0) {
336+    for (p = buf, bufend = buf + nread; p < bufend; p++) {
337+      if (*p == '\n') {
338+        if (!line_has_content)
339+          lp->blank++;
340+        else if (line_is_comment)
341+          lp->comment++;
342+        else
343+          lp->code++;
344+        leading_ws       = 1;
345+        checked          = 0;
346+        line_has_content = 0;
347+        line_is_comment  = 0;
348+        continue;
349+      }
350+
351+      if (leading_ws) {
352+        if (*p == ' ' || *p == '\t' || *p == '\r')
353+          continue;
354+        leading_ws       = 0;
355+        line_has_content = 1;
356+      }
357+
358+      /* only the first token of a line decides if the line starts a comment */
359+      if (!checked) {
360+        checked = 1;
361+        if (in_block) {
362+          line_is_comment = 1;
363+          if (marker_at(p, bufend, cs->bclose))
364+            in_block = 0;
365+        } else if (marker_at(p, bufend, cs->line)) {
366+          line_is_comment = 1;
367+        } else if (marker_at(p, bufend, cs->bopen)) {
368+          line_is_comment = 1;
369+          in_block        = 1;
370+        }
371+      } else if (in_block && marker_at(p, bufend, cs->bclose)) {
372+        in_block = 0;
373+      }
374+    }
375+  }
376+
377+  /* files that dont end in a newline still have one line left to count */
378+  if (line_has_content) {
379+    if (line_is_comment)
380+      lp->comment++;
381+    else
382+      lp->code++;
383+  }
384+
385+  close(fd);
386+}
387+
388+static void
389+scan(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r)
390+{
391+  struct ScanState *scanst = data;
392+  int                lang;
393+
394+  check_marker(name, S_ISDIR(st->st_mode), scanst);
395+
396+  if (S_ISDIR(st->st_mode)) {
397+    if (is_skip_dir(name))
398+      return;
399+    recurse(dirfd, name, data, r);
400+    return;
401+  }
402+
403+  if (!S_ISREG(st->st_mode))
404+    return;
405+
406+  lang = detect_lang(name);
407+  if (lang < 0)
408+    return;
409+
410+  scan_file(dirfd, name, lang, &scanst->stats[lang]);
411+}
412+
413+int
414+bogometercmd(int argc, char **argv)
415+{
416+  struct recursor  r  = {.fn = scan, .follow = 'P', .flags = SILENT};
417+  struct ScanState st = {0};
418+  const char      *target;
419+  double           pct, score;
420+  long             total_code;
421+  int              i, modern_langs;
422+
423+  target     = (argc > 1) ? argv[1] : ".";
424+  total_code = 0;
425+  score      = 0.0;
426+
427+  recurse(AT_FDCWD, target, &st, &r);
428+
429+  printf("%-10s %10s %10s %10s %10s\n", "Language", "Files", "Blank", "Comment", "Code");
430+  printf("---------------------------------------------------------\n");
431+
432+  for (i = 0; i < LANG_COUNT; i++) {
433+    if (st.stats[i].files == 0)
434+      continue;
435+    printf("%-10s %10ld %10ld %10ld %10ld\n",
436+        lang_defs[i].name, st.stats[i].files, st.stats[i].blank, st.stats[i].comment,
437+        st.stats[i].code);
438+    total_code += st.stats[i].code;
439+  }
440+
441+  if (total_code == 0) {
442+    printf("\nNo targeted code found. System is pure.\n");
443+    return 0;
444+  }
445+
446+  printf("---------------------------------------------------------\n");
447+    modern_langs = 0;
448+    for (i = 0; i < LANG_COUNT; i++) {
449+      if (st.stats[i].code == 0)
450+        continue;
451+      pct = ((double)st.stats[i].code / total_code) * 100.0;
452+      score += pct * lang_defs[i].weight;
453+      printf("Distribution: %-6s is %5.2f%% of targeted language lines\n", lang_defs[i].name, pct);
454+      if (!is_classic(i))
455+        modern_langs++;
456+    }
457+
458+    if (modern_langs >= BABEL_THRESHOLD) {
459+    score *= BABEL_MULT;
460+    fprintf(stderr,
461+        "note: %d languages in one tree: babel tower, no two files agree on what tongue they speak\n",
462+        modern_langs);
463+  }
464+
465+  fflush(stdout);
466+    for (i = 0; i < (int)LEN(combos); i++) {
467+      if (st.stats[combos[i].lang_a].code > 0 && st.stats[combos[i].lang_b].code > 0) {
468+        score += combos[i].penalty;
469+        fprintf(stderr, "note: %s + %s: %s\n", lang_defs[combos[i].lang_a].name,
470+            lang_defs[combos[i].lang_b].name, combos[i].remark);
471+        fflush(stderr);
472+      }
473+    }
474+
475+  if (st.stats[LANG_RUST].code > 0 && st.stats[LANG_CPP].code == 0 && st.stats[LANG_C].code == 0)
476+    fprintf(stderr, "note: rust: enormous magnitudes of bogus\n");
477+
478+  score += st.bonus;
479+
480+  if (modern_langs == 0 && st.stats[LANG_C].code > 0 && st.stats[LANG_SH].code > 0 &&
481+      st.stats[LANG_AWK].code > 0)
482+    printf("note: sh + awk + c only: peak codebase\n");
483+
484+  printf("---------------------------------------------------------\n");
485+  printf("TOTAL CODE LINES: %ld\n", total_code);
486+  printf("AGGREGATE BOGOSITY INDEX: %5.2f uL\n", score);
487+
488+  if (score > 5000.0)
489+    fprintf(stderr, "note: bogosity index over 5000: critical\n");
490+  else if (st.stats[LANG_C].code > 0 && score < 200.0)
491+    printf("note: bogosity index under 200 with c present: clean architecture\n");
492+
493+  return 0;
494+}
495+
496+int
497+boguscmd(int argc __unused, char **argv __unused)
498+{
499+  printf(
500+      "BOGOSITY n. The degree to which something is BOGUS. Measured with a\n"
501+      "bogometer, in units of the microLenat (uL).\n"
502+      "\n"
503+      "BOGUS adj. 1. Non-functional. \"Your patches are bogus.\"\n"
504+      "2. Useless. \"OPCON is a bogus program.\"\n"
505+      "3. False. \"Your arguments are bogus.\"\n"
506+      "4. Incorrect. \"That algorithm is bogus.\"\n"
507+      "5. Silly. \"Stop writing those bogus sagas.\"\n"
508+  );
509+  return 0;
510+}
+3, -0
 1@@ -44,6 +44,7 @@ feature_let=1
 2 feature_ulimit=1
 3 feature_setvar=1
 4 feature_wordexp=1
 5+feature_easter=1
 6 
 7 if [ -f ../../config.mk ]; then
 8 	feature_histedit=$(grep '^[[:space:]]*FEATURE_SH_HISTEDIT[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
 9@@ -52,6 +53,7 @@ if [ -f ../../config.mk ]; then
10 	feature_ulimit=$(grep '^[[:space:]]*FEATURE_SH_ULIMIT[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
11 	feature_setvar=$(grep '^[[:space:]]*FEATURE_SH_SETVAR[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
12 	feature_wordexp=$(grep '^[[:space:]]*FEATURE_SH_WORDEXP[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
13+	feature_easter=$(grep '^[[:space:]]*FEATURE_SH_EASTER[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
14 fi
15 
16 exec > builtins.c
17@@ -72,6 +74,7 @@ awk '/^[^#]/ {
18 	if ($0 ~ /#FEATURE_SH_ULIMIT/ && '$feature_ulimit' != 1) next;
19 	if ($0 ~ /#FEATURE_SH_SETVAR/ && '$feature_setvar' != 1) next;
20 	if ($0 ~ /#FEATURE_SH_WORDEXP/ && '$feature_wordexp' != 1) next;
21+	if ($0 ~ /#FEATURE_SH_EASTER/ && '$feature_easter' != 1) next;
22 
23 	if ('$havejobs' || $2 != "-j") {
24 		sub(/#[A-Z0-9_]*/, "");
+1, -0
1@@ -223,6 +223,7 @@ FEATURE_STAT_FORMAT     = 1
2 FEATURE_SORT_BIG        = 1
3 FEATURE_SORT_STABLE     = 1
4 FEATURE_OD_ENDIAN       = 1
5+FEATURE_SH_EASTER       = 1
6 FEATURE_SH_HISTEDIT     = 1
7 FEATURE_SH_LOCAL        = 1
8 FEATURE_SH_LET          = 1