commit 805b3a3
xplshn
·
2026-05-17 18:52:06 +0000 UTC
parent 49f886f
Gating mechanism Signed-off-by: xplshn <anto@xplshn.com.ar>
12 files changed,
+181,
-73
M
Makefile
+4,
-1
1@@ -44,9 +44,12 @@ HELPERSRCS = tests/runner/testhelp/main.go
2
3 CC = cc
4 CFLAGS = -O2 -Wall -Wextra -pedantic
5-CPPFLAGS = -Isrc -Ibackends
6+CPPFLAGS = -Isrc -Ibackends -DSHIN_WITH_GNU=$(SHIN_WITH_GNU)
7 LDFLAGS = -static
8
9+# set to 0 to exclude gnu mode from the runtime -M flag
10+SHIN_WITH_GNU = 1
11+
12 .PHONY: all fmt test runner helper install clean
13
14 all: $(BIN)
+23,
-2
1@@ -13,7 +13,7 @@
2 static void
3 usage(FILE *fp, const char *argv0)
4 {
5- fprintf(fp, "usage: %s [-age] [-G ninja|dot|compdb] [-C dir] [-f file] [target ...]\n", argv0);
6+ fprintf(fp, "usage: %s [-age] [-G ninja|dot|compdb] [-M posix2024|posix2008|gnu] [-C dir] [-f file] [target ...]\n", argv0);
7 }
8
9 static int
10@@ -35,6 +35,8 @@ assignopname(enum AssignOp op)
11 return "=";
12 case ASSIGN_PLUS_EQ:
13 return "+=";
14+ case ASSIGN_DCOLON_EQ:
15+ return "::=";
16 case ASSIGN_COLON_EQ:
17 return ":=";
18 case ASSIGN_COLON3_EQ:
19@@ -226,6 +228,7 @@ main(int argc, char **argv)
20 size_t nassigns;
21 size_t ngoals;
22 enum Generator gen;
23+ enum ShinMode mode;
24 struct Ast ast;
25 struct RuleSet rs;
26 struct SubGraph sg;
27@@ -240,6 +243,7 @@ main(int argc, char **argv)
28 dump_graph = 0;
29 env_override = 0;
30 gen = GEN_NINJA;
31+ mode = MODE_GNU;
32 assigns = 0;
33 nassigns = 0;
34 goals = 0;
35@@ -274,6 +278,22 @@ main(int argc, char **argv)
36 free(goals);
37 return 2;
38 }
39+ } else if (strcmp(argv[i], "-M") == 0) {
40+ if (i + 1 >= argc) {
41+ fprintf(stderr, "specify a mode\n\n");
42+ usage(stderr, argv[0]);
43+ free(assigns);
44+ free(goals);
45+ return 2;
46+ }
47+ ++i;
48+ if (shinmode_parse(argv[i], &mode) < 0) {
49+ fprintf(stderr, "unknown mode: %s\n\n", argv[i]);
50+ usage(stderr, argv[0]);
51+ free(assigns);
52+ free(goals);
53+ return 2;
54+ }
55 } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
56 usage(stdout, argv[0]);
57 free(assigns);
58@@ -384,7 +404,7 @@ main(int argc, char **argv)
59 }
60 }
61 }
62- if (eval(path, &ast, 0, env_override, &rs) < 0) {
63+ if (eval(path, &ast, 0, env_override, mode, &rs) < 0) {
64 free(assigns);
65 free(goals);
66 freeast(&ast);
67@@ -402,6 +422,7 @@ main(int argc, char **argv)
68 sg.makefile = xstrdup(path);
69 if (env_override)
70 addstr(&sg.flags, "-e");
71+ sg.mode = mode;
72 free(assigns);
73 free(goals);
74 assigns = 0;
+32,
-28
1@@ -443,6 +443,8 @@ enum FuncMode {
2 FNCTX,
3 };
4
5+/* posix2008 and posix2024 both have no builtin functions, gnu has all of them */
6+
7 struct func {
8 const char *name;
9 enum FuncMode mode;
10@@ -498,36 +500,36 @@ static const char *const allfuncs[] = {
11 };
12
13 static const struct func funcs[] = {
14- {"wildcard", FNEXP1, {.f1 = fnwildcard}},
15- {"shell", FNEXP1, {.f1 = fnshell}},
16- {"sort", FNEXP1, {.f1 = fnsort}},
17- {"info", FNCTX, {.ctx = fninfo}},
18- {"origin", FNCTX, {.ctx = fnorigin}},
19- {"notdir", FNEXP1, {.f1 = fnnotdir}},
20- {"dir", FNEXP1, {.f1 = fndir}},
21- {"basename", FNEXP1, {.f1 = fnbasename}},
22+ {"wildcard", FNEXP1, {.f1 = fnwildcard}},
23+ {"shell", FNEXP1, {.f1 = fnshell}},
24+ {"sort", FNEXP1, {.f1 = fnsort}},
25+ {"info", FNCTX, {.ctx = fninfo}},
26+ {"origin", FNCTX, {.ctx = fnorigin}},
27+ {"notdir", FNEXP1, {.f1 = fnnotdir}},
28+ {"dir", FNEXP1, {.f1 = fndir}},
29+ {"basename", FNEXP1, {.f1 = fnbasename}},
30 {"filter-out", FNEXP2, {.f2 = fnfilterout}},
31- {"filter", FNEXP2, {.f2 = fnfilter}},
32+ {"filter", FNEXP2, {.f2 = fnfilter}},
33 {"findstring", FNEXP2, {.f2 = fnfindstring}},
34- {"addprefix", FNEXP2, {.f2 = fnaddprefix}},
35- {"addsuffix", FNEXP2, {.f2 = fnaddsuffix}},
36- {"join", FNEXP2, {.f2 = fnjoin}},
37- {"strip", FNEXP1, {.f1 = fnstrip}},
38- {"subst", FNEXP3, {.f3 = fnsubst}},
39- {"patsubst", FNEXP3, {.f3 = fnpatsubst}},
40- {"if", FNEXP3, {.f3 = fnif}},
41- {"call", FNCTX, {.ctx = fncall}},
42- {"foreach", FNCTX, {.ctx = fnforeach}},
43- {"eval", FNCTX, {.ctx = fneval}},
44- {"value", FNCTX, {.ctx = fnvalue}},
45- {"words", FNEXP1, {.f1 = fnwords}},
46- {"word", FNEXP2, {.f2 = fnword}},
47- {"wordlist", FNEXP3, {.f3 = fnwordlist}},
48- {"firstword", FNEXP1, {.f1 = fnfirstword}},
49- {"lastword", FNEXP1, {.f1 = fnlastword}},
50- {"realpath", FNEXP1, {.f1 = fnrealpath}},
51- {"abspath", FNEXP1, {.f1 = fnabspath}},
52- {0, FNEXP1, {.f1 = 0}},
53+ {"addprefix", FNEXP2, {.f2 = fnaddprefix}},
54+ {"addsuffix", FNEXP2, {.f2 = fnaddsuffix}},
55+ {"join", FNEXP2, {.f2 = fnjoin}},
56+ {"strip", FNEXP1, {.f1 = fnstrip}},
57+ {"subst", FNEXP3, {.f3 = fnsubst}},
58+ {"patsubst", FNEXP3, {.f3 = fnpatsubst}},
59+ {"if", FNEXP3, {.f3 = fnif}},
60+ {"call", FNCTX, {.ctx = fncall}},
61+ {"foreach", FNCTX, {.ctx = fnforeach}},
62+ {"eval", FNCTX, {.ctx = fneval}},
63+ {"value", FNCTX, {.ctx = fnvalue}},
64+ {"words", FNEXP1, {.f1 = fnwords}},
65+ {"word", FNEXP2, {.f2 = fnword}},
66+ {"wordlist", FNEXP3, {.f3 = fnwordlist}},
67+ {"firstword", FNEXP1, {.f1 = fnfirstword}},
68+ {"lastword", FNEXP1, {.f1 = fnlastword}},
69+ {"realpath", FNEXP1, {.f1 = fnrealpath}},
70+ {"abspath", FNEXP1, {.f1 = fnabspath}},
71+ {0, FNEXP1, {.f1 = 0}},
72 };
73
74 static char *
75@@ -537,6 +539,8 @@ funcref(struct EvalCtx *ctx, const char *s, size_t n)
76 const struct func *f;
77 char *val;
78
79+ if (ctx->mode != MODE_GNU)
80+ return 0;
81 for (i = 0; funcs[i].name; i++) {
82 f = &funcs[i];
83 namelen = strlen(f->name);
+19,
-17
1@@ -175,19 +175,21 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
2 case ASSIGN_EQ:
3 envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
4 break;
5+ case ASSIGN_DCOLON_EQ:
6+ envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
7+ break;
8 case ASSIGN_COLON_EQ:
9+ /* := is a gnu extension. posix uses ::= for simple expansion.
10+ * bsd := is different again, equivalent to posix :::= */
11+ if (ctx->mode != MODE_GNU) {
12+ dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in posix mode, use '::='", 0);
13+ ctx->errors++;
14+ break;
15+ }
16 envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
17 break;
18 case ASSIGN_COLON3_EQ:
19- /* :::= expands immediately and then it stores a recursive value with dollars escaped
20- * so when you expand later you get literal text.
21- *
22- * TODO
23- * in gnu make, := and ::= are equivalent, and :::= is different.
24- * in posix, := is rejected, and ::= and :::= are different.
25- * in bsd make, := and :::= are equivalent. i don't know if ::= is handled.
26- * when we add feature gating, we need to honor these behaviours. right now,
27- * we handle it like gnu make.*/
28+ /* expand now, escape the result so re-expansion on use gives back the same value */
29 rhs = expandstr(ctx, in->rhs);
30 joined = escapedollars(rhs);
31 free(rhs);
32@@ -329,7 +331,8 @@ addrulesetassign(struct AssignNode **vec, size_t *n, const struct Node *src, str
33 dst->origin = src->data.assign.origin;
34 dst->exported = assignexported(ctx, &src->data.assign);
35 dst->tspec = src->data.assign.tspec;
36- if (src->data.assign.op == ASSIGN_COLON_EQ ||
37+ if (src->data.assign.op == ASSIGN_DCOLON_EQ ||
38+ src->data.assign.op == ASSIGN_COLON_EQ ||
39 src->data.assign.op == ASSIGN_COLON3_EQ) {
40 char *rhs;
41
42@@ -419,12 +422,10 @@ evalinclude(struct EvalCtx *ctx, const struct IncludeNode *inc)
43 word = paths.v[i];
44 nmatch = 0;
45 grc = 0;
46- if (hasglobmeta(word))
47+ /* glob in include paths is a gnu extension */
48+ if (ctx->mode == MODE_GNU && hasglobmeta(word)) {
49 grc = glob(word, 0, 0, &g);
50- if (hasglobmeta(word) && grc == 0 && g.gl_pathc > 0) {
51- nmatch = g.gl_pathc;
52- } else if (hasglobmeta(word)) {
53- nmatch = 0;
54+ nmatch = (grc == 0 && g.gl_pathc > 0) ? g.gl_pathc : 0;
55 } else {
56 nmatch = 1;
57 single = xstrdup(word);
58@@ -615,7 +616,7 @@ evalsnippet(struct EvalCtx *ctx, const char *path, const char *src)
59 }
60
61 int
62-eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, struct RuleSet *out)
63+eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, enum ShinMode mode, struct RuleSet *out)
64 {
65 struct Env env;
66 struct EvalCtx ctx;
67@@ -625,10 +626,11 @@ eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envover
68 out->envoverride = envoverride;
69 memset(&env, 0, sizeof(env));
70 memset(&ctx, 0, sizeof(ctx));
71- seedenv(&env, 0, out->envoverride);
72+ seedenv(&env, 0, out->envoverride, mode);
73 ctx.env = &env;
74 ctx.out = out;
75 ctx.cur_path = path;
76+ ctx.mode = mode;
77 if (pre) {
78 rc = evalnodes((const struct NodeList *)pre, out, &ctx);
79 if (rc < 0) {
+15,
-7
1@@ -30,6 +30,7 @@ struct GraphState {
2 struct TAssign *tas;
3 size_t ntas;
4 int saw_suffixes;
5+ enum ShinMode mode;
6 };
7
8 static void
9@@ -250,7 +251,7 @@ instdefaultrule(const struct GraphState *gs, struct Target *t, struct EvalCtx *c
10 * then expand placeholder targets until all prereqs
11 * we can find are in the graph. */
12 int
13-buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph)
14+buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph, enum ShinMode mode)
15 {
16 size_t i, j;
17 struct GraphState gs;
18@@ -262,11 +263,13 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
19 gs.graph = graph;
20 gs.phony = &ruleset->phony;
21 gs.defaultrule = &ruleset->defaultrule;
22- seedenv(&gs.env, ruleset->posix, ruleset->envoverride);
23+ seedenv(&gs.env, ruleset->posix, ruleset->envoverride, mode);
24 gs.rules = ruleset->rules;
25 gs.nrules = ruleset->nrules;
26+ gs.mode = mode;
27 ctx.env = &gs.env;
28- imprules(&gs.sufs, ruleset->posix);
29+ ctx.mode = mode;
30+ imprules(&gs.sufs, ruleset->posix, mode);
31
32 for (i = 0; i < ruleset->nvars; i++)
33 evalassign(&ctx, &ruleset->vars[i]);
34@@ -296,7 +299,9 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
35 continue;
36 if (collectsufrule(&gs.sufs, &gs.rules[i]))
37 continue;
38- collectpat(&gs.patterns, &gs.rules[i]);
39+ /* pattern rules (%.o: %.c) are a gnu extension */
40+ if (gs.mode == MODE_GNU)
41+ collectpat(&gs.patterns, &gs.rules[i]);
42 for (k = 0; k < gs.rules[i].targets.n; k++) {
43 if (!ispat(gs.rules[i].targets.v[k]))
44 addrule(&gs, gs.rules[i].targets.v[k], &gs.rules[i]);
45@@ -358,8 +363,10 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
46
47 memset(&targetctx, 0, sizeof(targetctx));
48 targetctx.env = &env;
49+ targetctx.mode = gs.mode;
50 targetenv(&gs, &targetctx, &t->env, t->name);
51- instpatrule(&gs.patterns, graph, t, &targetctx);
52+ if (gs.mode == MODE_GNU)
53+ instpatrule(&gs.patterns, graph, t, &targetctx);
54 matched = t->recipes.n > 0;
55 if (!matched) {
56 instsufrule(&gs.sufs, graph, t, &targetctx);
57@@ -414,7 +421,7 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
58 }
59
60 int
61-expandgraph(struct Graph *graph)
62+expandgraph(struct Graph *graph, enum ShinMode mode)
63 {
64 size_t i, k, j;
65
66@@ -432,6 +439,7 @@ expandgraph(struct Graph *graph)
67 memset(&side_effects, 0, sizeof(side_effects));
68 memset(&new_recipes, 0, sizeof(new_recipes));
69 ctx.env = &t->env;
70+ ctx.mode = mode;
71 ctx.auto_target = t->name;
72 {
73 struct StrList allprereqs;
74@@ -507,7 +515,7 @@ expandgraph(struct Graph *graph)
75 }
76
77 for (i = 0; i < graph->nsubs; i++) {
78- if (expandgraph(&graph->subs[i].graph) < 0)
79+ if (expandgraph(&graph->subs[i].graph, mode) < 0)
80 return -1;
81 }
82 return 0;
+4,
-3
1@@ -519,6 +519,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
2 memset(&child, 0, sizeof(child));
3 child.cwd = r->sm.dir ? joinpath(sg->cwd, r->sm.dir) : xstrdup(sg->cwd);
4 child.prefix = joinprefix(sg->prefix, r->sm.dir);
5+ child.mode = sg->mode;
6 addstr(&child.parents, tname);
7 if (r->sm.makefile)
8 child.makefile = xstrdup(r->sm.makefile);
9@@ -660,13 +661,13 @@ buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
10 rc = parse(path, src, &ast);
11 if (rc < 0)
12 goto out;
13- rc = eval(path, &ast, sg->assigns.n ? &pre : 0, envoverride, &rs);
14+ rc = eval(path, &ast, sg->assigns.n ? &pre : 0, envoverride, sg->mode, &rs);
15 if (rc < 0)
16 goto out;
17- rc = buildgraph(&rs, &sg->goals, &sg->graph);
18+ rc = buildgraph(&rs, &sg->goals, &sg->graph, sg->mode);
19 if (rc < 0)
20 goto out;
21- rc = expandgraph(&sg->graph);
22+ rc = expandgraph(&sg->graph, sg->mode);
23 if (rc < 0)
24 goto out;
25 scopegraph(&sg->graph, sg->prefix);
+5,
-1
1@@ -24,6 +24,7 @@ struct EvalCtx {
2 struct StrList exports;
3 struct StrList unexports;
4 int export_all;
5+ enum ShinMode mode;
6 };
7
8 struct SpecialTargets {
9@@ -82,7 +83,7 @@ int handlespecialrule(struct SpecialTargets *targets, const struct RuleNode *rul
10
11 struct Var *findvar(struct Env *env, const char *name);
12 char *expandstr(struct EvalCtx *ctx, const char *s);
13-void seedenv(struct Env *env, int posix, int envoverride);
14+void seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode);
15 void freeenv(struct Env *env);
16 void copyenv(struct Env *dst, const struct Env *src);
17 void evalassign(struct EvalCtx *ctx, const struct AssignNode *in);
18@@ -119,4 +120,7 @@ char *fnforeach(struct EvalCtx *ctx, const char *args);
19 char *fneval(struct EvalCtx *ctx, const char *args);
20 int evalsnippet(struct EvalCtx *ctx, const char *path, const char *src);
21
22+const char *shinmode_name(enum ShinMode mode);
23+int shinmode_parse(const char *s, enum ShinMode *out);
24+
25 #endif
+1,
-1
1@@ -239,7 +239,7 @@ findassign(const char *s, size_t n, size_t start)
2 if (i + 2 < n && s[i] == ':' && s[i + 1] == ':' && s[i + 2] == '=') {
3 out.pos = i;
4 out.len = 3;
5- out.op = ASSIGN_COLON_EQ;
6+ out.op = ASSIGN_DCOLON_EQ;
7 out.ok = 1;
8 return out;
9 }
+27,
-8
1@@ -215,12 +215,10 @@ expandauto(const char *s, const struct Target *t, const char *stem)
2 return out;
3 }
4
5+/* rules shared by all dialects except the .c.o rule, which differs */
6 #define IMPRULES_COMMON \
7- ".SUFFIXES: .o .c .f .y .l .sh\n" \
8 ".c:\n" \
9 "\t$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<\n" \
10- ".f:\n" \
11- "\t$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \
12 ".sh:\n" \
13 "\tcp $< $@\n" \
14 "\tchmod a+x $@\n" \
15@@ -241,15 +239,28 @@ expandauto(const char *s, const struct Target *t, const char *stem)
16 "\t$(LEX) $(LFLAGS) $<\n" \
17 "\tmv lex.yy.c $@\n"
18
19-#define IMPRULES_POSIX \
20+/* posix 2008 includes fortran; posix 2024 dropped it */
21+#define IMPRULES_POSIX_2008 \
22+ ".SUFFIXES: .o .c .f .y .l .sh\n" \
23 IMPRULES_COMMON \
24+ ".f:\n" \
25+ "\t$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \
26 ".c.o:\n" \
27 "\t$(CC) $(CFLAGS) -c $<\n" \
28 ".f.o:\n" \
29 "\t$(FC) $(FFLAGS) -c $<\n"
30
31+#define IMPRULES_POSIX_2024 \
32+ ".SUFFIXES: .o .c .y .l .sh\n" \
33+ IMPRULES_COMMON \
34+ ".c.o:\n" \
35+ "\t$(CC) $(CFLAGS) -c $<\n"
36+
37 #define IMPRULES_GNU \
38+ ".SUFFIXES: .o .c .f .y .l .sh\n" \
39 IMPRULES_COMMON \
40+ ".f:\n" \
41+ "\t$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \
42 ".c.o:\n" \
43 "\t$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<\n" \
44 ".f.o:\n" \
45@@ -281,7 +292,7 @@ loadimprules(struct SufRules *rules, const char *src)
46 }
47
48 void
49-seedenv(struct Env *env, int posix, int envoverride)
50+seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode)
51 {
52 char *cwd;
53 size_t i;
54@@ -314,7 +325,7 @@ seedenv(struct Env *env, int posix, int envoverride)
55 * unsure about behaviour under directory change condition or
56 * in submakes. should work for most simple cases. */
57 envsetvar(env, "CURDIR", cwd, 1, ORIGIN_FILE, 0);
58- if (posix) {
59+ if (posix || mode != MODE_GNU) {
60 envsetvar(env, "CC", xstrdup("c99"), 1, ORIGIN_DEFAULT, 0);
61 envsetvar(env, "CFLAGS", xstrdup("-O1"), 1, ORIGIN_DEFAULT, 0);
62 envsetvar(env, "FFLAGS", xstrdup("-O1"), 1, ORIGIN_DEFAULT, 0);
63@@ -334,9 +345,17 @@ seedenv(struct Env *env, int posix, int envoverride)
64 }
65
66 void
67-imprules(struct SufRules *rules, int posix)
68+imprules(struct SufRules *rules, int posix, enum ShinMode mode)
69 {
70- if (loadimprules(rules, posix ? IMPRULES_POSIX : IMPRULES_GNU) < 0) {
71+ const char *src;
72+
73+ if (mode == MODE_POSIX_2024)
74+ src = IMPRULES_POSIX_2024;
75+ else if (mode == MODE_POSIX_2008 || posix)
76+ src = IMPRULES_POSIX_2008;
77+ else
78+ src = IMPRULES_GNU;
79+ if (loadimprules(rules, src) < 0) {
80 fprintf(stderr, "failed to load built-in implicit rules\n");
81 exit(1);
82 }
+2,
-2
1@@ -35,8 +35,8 @@ struct SufRules {
2
3 int issinglesuf(const char *s, char **from);
4 int issuf(const char *s, char **from, char **to);
5-void seedenv(struct Env *env, int posix, int envoverride);
6-void imprules(struct SufRules *rules, int posix);
7+void seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode);
8+void imprules(struct SufRules *rules, int posix, enum ShinMode mode);
9 int collectsufrule(struct SufRules *rules, const struct RuleNode *rule);
10 int instsufrule(const struct SufRules *rules,
11 const struct Graph *graph,
+12,
-3
1@@ -3,6 +3,13 @@
2
3 #include <stddef.h>
4
5+/* -M flag: controls the makefile dialect accepted at eval time */
6+enum ShinMode {
7+ MODE_GNU = 0,
8+ MODE_POSIX_2024,
9+ MODE_POSIX_2008,
10+};
11+
12 /*
13 * types
14 * PreLine: one preprocessed line with source location
15@@ -32,6 +39,7 @@ enum NodeKind {
16 enum AssignOp {
17 ASSIGN_EQ,
18 ASSIGN_PLUS_EQ,
19+ ASSIGN_DCOLON_EQ,
20 ASSIGN_COLON_EQ,
21 ASSIGN_COLON3_EQ,
22 ASSIGN_QMARK_EQ,
23@@ -234,17 +242,18 @@ struct SubGraph {
24 struct StrList flags;
25 struct StrList goals;
26 struct Graph graph;
27+ enum ShinMode mode;
28 };
29
30 int preproc(const char *path, struct Pre *pre);
31 void freepre(struct Pre *pre);
32 int buildast(const char *path, const struct Pre *pre, struct Ast *ast);
33-int eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, struct RuleSet *out);
34+int eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, enum ShinMode mode, struct RuleSet *out);
35 int parse(const char *path, const char *src, struct Ast *ast);
36 void freeast(struct Ast *ast);
37 void freeruleset(struct RuleSet *ruleset);
38-int buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph);
39-int expandgraph(struct Graph *graph);
40+int buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph, enum ShinMode mode);
41+int expandgraph(struct Graph *graph, enum ShinMode mode);
42 void freegraph(struct Graph *graph);
43 int buildsubgraph(struct SubGraph *sg);
44 void freesubgraph(struct SubGraph *sg);
+37,
-0
1@@ -8,6 +8,43 @@
2
3 /* shared utility functions */
4
5+const char *
6+shinmode_name(enum ShinMode mode)
7+{
8+ switch (mode) {
9+ case MODE_GNU:
10+ return "gnu";
11+ case MODE_POSIX_2024:
12+ return "posix2024";
13+ case MODE_POSIX_2008:
14+ return "posix2008";
15+ }
16+ return "gnu";
17+}
18+
19+int
20+shinmode_parse(const char *s, enum ShinMode *out)
21+{
22+ if (strcmp(s, "gnu") == 0) {
23+#if SHIN_WITH_GNU
24+ *out = MODE_GNU;
25+ return 0;
26+#else
27+ /* gnu support was not compiled in */
28+ return -1;
29+#endif
30+ }
31+ if (strcmp(s, "posix2024") == 0 || strcmp(s, "posix") == 0) {
32+ *out = MODE_POSIX_2024;
33+ return 0;
34+ }
35+ if (strcmp(s, "posix2008") == 0) {
36+ *out = MODE_POSIX_2008;
37+ return 0;
38+ }
39+ return -1;
40+}
41+
42 static const char *progname = "shin";
43
44 void