kconfig: Sync with upstream v4.16

This commit introduces the following upstream changes:

5ae6fcc4bb82 kconfig: fix line number in recursive inclusion error message
1a90ce36c6ef kconfig: Update ncurses package names for menuconfig
bf0bbdcf1003 kconfig: Don't leak choice names during parsing
f4bc1eefc160 kconfig: set SYMBOL_AUTO to the symbol marked with defconfig_list
cd81fc82b93f kconfig: add xstrdup() helper
523ca58b7db2 kconfig: remove const qualifier from sym_expand_string_value()
d717f24d8c68 kconfig: add xrealloc() helper
9e3e10c72536 kconfig: send error messages to stderr
f3ff6fb5db68 kconfig: echo stdin to stdout if either is redirected
d2a04648a5db kconfig: remove check_stdin()
cd58a91def2a kconfig: remove 'config*' pattern from .gitignnore
4f208f392103 kconfig: show '?' prompt even if no help text is available
cb67ab2cd2b8 kconfig: do not write choice values when their dependency becomes n
1b9eda2e4892 kconfig: Warn if help text is blank
cedd55d49dee kconfig: Remove silentoldconfig from help and docs; fix kconfig/conf's help
1ccb27143360 kconfig: make "Selected by:" and "Implied by:" readable
312ee68752fa kconfig: announce removal of oldnoconfig if used
d0fd0428ecf0 kconfig: fix make xconfig when gettext is missing
b53688014e33 kconfig: Clarify menu and 'if' dependency propagation
9d1a9e8bc18b kconfig: Document 'if' flattening logic
d3465af60f44 kconfig: Clarify choice dependency propagation
3e41ba05b6d6 kconfig: Document SYMBOL_OPTIONAL logic
765f4cdef6f8 kconfig: use default 'yy' prefix for lexer and parser
84dd95d4f87a kconfig: make conf_unsaved a local variable of conf_read()
5a3dc717b3c7 kconfig: make xfgets() really static
52e58a3caeba kconfig: make input_mode static
6479f327dea6 kconfig: Warn if there is more than one help text
b92d804a5179 kconfig: drop 'boolean' keyword
df60f4b92d3d kconfig: Remove menu_end_entry()
0735f7e5def2 kconfig: Document important expression functions
05cccce58045 kconfig: Document automatic submenu creation code
7cf33f88e294 kconfig: Fix choice symbol expression leak
5b1374b3b3c2 kconfig: Fix expr_free() E_NOT leak
ae7440ef0c80 kconfig: Fix automatic menu creation mem leak
0724a7c32a54 kconfig: Don't leak main menus during parsing
bc28fe1d5ede kconfig: Don't leak 'option' arguments during parsing
24161a6711c9 kconfig: Don't leak 'source' filenames during parsing
26e47a3c11a2 kconfig: Don't leak symbol names during parsing
29c833061c1d kconfig: generate lexer and parser during build instead of shipping
e3b03bf29d6b kconfig: display recursive dependency resolution hint just once
f77850d3fe0c kconfig: Clean up modules handling and fix crash
fa8cedaef814 kconfig: Clarify expression rewriting
9a826842ff2f kconfig: Rename menu_check_dep() to rewrite_m()
c873443430eb kconfig: Sync zconf.y with zconf.tab.c_shipped
52aede4ba5ef kconfig: Document the 'symbol' struct
33ca1a248663 kconfig: Document the 'menu' struct
2c37e08464a8 kconfig: Warn if choice default is not in choice

Signed-off-by: Chris Packham <judge.packham@gmail.com>
This commit is contained in:
Chris Packham 2020-12-10 20:33:11 +13:00
parent 51cb8939f8
commit 07ae8dd48d
17 changed files with 575 additions and 168 deletions

20
kconfig/.gitignore vendored
View File

@ -1,9 +1,15 @@
conf #
?conf # Generated files
*.o #
*.lex.c
*.tab.c
*.tab.h
.deps .deps
zconf.c
zconf.lex.c
*.exe
Makefile Makefile
.dirstamp
#
# configuration programs
#
conf
mconf
nconf

0
kconfig/check.sh Normal file → Executable file
View File

View File

@ -20,7 +20,6 @@
static void conf(struct menu *menu); static void conf(struct menu *menu);
static void check_conf(struct menu *menu); static void check_conf(struct menu *menu);
static void xfgets(char *str, int size, FILE *in);
enum input_mode { enum input_mode {
oldaskconfig, oldaskconfig,
@ -35,11 +34,11 @@ enum input_mode {
savedefconfig, savedefconfig,
listnewconfig, listnewconfig,
olddefconfig, olddefconfig,
} input_mode = oldaskconfig; };
static enum input_mode input_mode = oldaskconfig;
static int indent = 1; static int indent = 1;
static int tty_stdio; static int tty_stdio;
static int valid_stdin = 1;
static int sync_kconfig; static int sync_kconfig;
static int conf_cnt; static int conf_cnt;
static char line[PATH_MAX]; static char line[PATH_MAX];
@ -72,14 +71,14 @@ static void strip(char *str)
*p-- = 0; *p-- = 0;
} }
static void check_stdin(void) /* Helper function to facilitate fgets() by Jean Sacren. */
static void xfgets(char *str, int size, FILE *in)
{ {
if (!valid_stdin) { if (!fgets(str, size, in))
/* For crosstool-NG, we don't care if stdin/stdout got redirected. fprintf(stderr, "\nError in reading or end of file.\n");
* In this case, just printf a cariage return, for pretty output.
*/ if (!tty_stdio)
printf("\n"); printf("%s", str);
}
} }
static int conf_askvalue(struct symbol *sym, const char *def) static int conf_askvalue(struct symbol *sym, const char *def)
@ -106,13 +105,10 @@ static int conf_askvalue(struct symbol *sym, const char *def)
printf("%s\n", def); printf("%s\n", def);
return 0; return 0;
} }
check_stdin();
/* fall through */ /* fall through */
case oldaskconfig: case oldaskconfig:
fflush(stdout); fflush(stdout);
xfgets(line, sizeof(line), stdin); xfgets(line, sizeof(line), stdin);
if (!tty_stdio)
printf("\n");
return 1; return 1;
default: default:
break; break;
@ -192,9 +188,7 @@ static int conf_sym(struct menu *menu)
printf("/m"); printf("/m");
if (oldval != yes && sym_tristate_within_range(sym, yes)) if (oldval != yes && sym_tristate_within_range(sym, yes))
printf("/y"); printf("/y");
if (menu_has_help(menu)) printf("/?] ");
printf("/?");
printf("] ");
if (!conf_askvalue(sym, sym_get_string_value(sym))) if (!conf_askvalue(sym, sym_get_string_value(sym)))
return 0; return 0;
strip(line); strip(line);
@ -296,10 +290,7 @@ static int conf_choice(struct menu *menu)
printf("[1]: 1\n"); printf("[1]: 1\n");
goto conf_childs; goto conf_childs;
} }
printf("[1-%d", cnt); printf("[1-%d?]: ", cnt);
if (menu_has_help(menu))
printf("?");
printf("]: ");
switch (input_mode) { switch (input_mode) {
case oldconfig: case oldconfig:
case silentoldconfig: case silentoldconfig:
@ -308,7 +299,6 @@ static int conf_choice(struct menu *menu)
printf("%d\n", cnt); printf("%d\n", cnt);
break; break;
} }
check_stdin();
/* fall through */ /* fall through */
case oldaskconfig: case oldaskconfig:
fflush(stdout); fflush(stdout);
@ -480,7 +470,7 @@ int main(int ac, char **av)
bindtextdomain(PACKAGE, LOCALEDIR); bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE); textdomain(PACKAGE);
tty_stdio = isatty(0) && isatty(1) && isatty(2); tty_stdio = isatty(0) && isatty(1);
while ((opt = getopt_long(ac, av, "s", long_opts, NULL)) != -1) { while ((opt = getopt_long(ac, av, "s", long_opts, NULL)) != -1) {
if (opt == 's') { if (opt == 's') {
@ -537,7 +527,7 @@ int main(int ac, char **av)
} }
} }
if (ac == optind) { if (ac == optind) {
printf(_("%s: Kconfig file missing\n"), av[0]); fprintf(stderr, _("%s: Kconfig file missing\n"), av[0]);
fprintf(stderr, _("See README for usage info\n")); fprintf(stderr, _("See README for usage info\n"));
exit(1); exit(1);
} }
@ -562,9 +552,11 @@ int main(int ac, char **av)
if (!defconfig_file) if (!defconfig_file)
defconfig_file = conf_get_default_confname(); defconfig_file = conf_get_default_confname();
if (conf_read(defconfig_file)) { if (conf_read(defconfig_file)) {
printf(_("***\n" fprintf(stderr,
"*** Can't find default configuration \"%s\"!\n" _("***\n"
"***\n"), defconfig_file); "*** Can't find default configuration \"%s\"!\n"
"***\n"),
defconfig_file);
exit(1); exit(1);
} }
break; break;
@ -622,7 +614,6 @@ int main(int ac, char **av)
return 1; return 1;
} }
} }
valid_stdin = tty_stdio;
} }
switch (input_mode) { switch (input_mode) {
@ -693,12 +684,3 @@ int main(int ac, char **av)
} }
return 0; return 0;
} }
/*
* Helper function to facilitate fgets() by Jean Sacren.
*/
void xfgets(char *str, int size, FILE *in)
{
if (fgets(str, size, in) == NULL)
fprintf(stderr, "\nError in reading or end of file.\n");
}

View File

@ -28,7 +28,7 @@ static void conf_message(const char *fmt, ...)
__attribute__ ((format (printf, 1, 2))); __attribute__ ((format (printf, 1, 2)));
static const char *conf_filename; static const char *conf_filename;
static int conf_lineno, conf_warnings, conf_unsaved; static int conf_lineno, conf_warnings;
const char conf_defname[] = "arch/$ARCH/defconfig"; const char conf_defname[] = "arch/$ARCH/defconfig";
@ -178,7 +178,7 @@ static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
case S_HEX: case S_HEX:
done: done:
if (sym_string_valid(sym, p)) { if (sym_string_valid(sym, p)) {
sym->def[def].val = strdup(p); sym->def[def].val = xstrdup(p);
sym->flags |= def_flags; sym->flags |= def_flags;
} else { } else {
if (def != S_DEF_AUTO) if (def != S_DEF_AUTO)
@ -201,7 +201,7 @@ static int add_byte(int c, char **lineptr, size_t slen, size_t *n)
if (new_size > *n) { if (new_size > *n) {
new_size += LINE_GROWTH - 1; new_size += LINE_GROWTH - 1;
new_size *= 2; new_size *= 2;
nline = realloc(*lineptr, new_size); nline = xrealloc(*lineptr, new_size);
if (!nline) if (!nline)
return -1; return -1;
@ -290,7 +290,6 @@ load:
conf_filename = name; conf_filename = name;
conf_lineno = 0; conf_lineno = 0;
conf_warnings = 0; conf_warnings = 0;
conf_unsaved = 0;
def_flags = SYMBOL_DEF << def; def_flags = SYMBOL_DEF << def;
for_all_symbols(i, sym) { for_all_symbols(i, sym) {
@ -409,6 +408,7 @@ setsym:
int conf_read(const char *name) int conf_read(const char *name)
{ {
struct symbol *sym; struct symbol *sym;
int conf_unsaved = 0;
int i; int i;
sym_set_change_count(0); sym_set_change_count(0);
@ -1123,7 +1123,7 @@ void set_all_choice_values(struct symbol *csym)
bool conf_set_all_new_symbols(enum conf_def_mode mode) bool conf_set_all_new_symbols(enum conf_def_mode mode)
{ {
struct symbol *sym, *csym; struct symbol *sym, *csym;
int i, cnt, pby, pty, ptm; /* pby: probability of boolean = y int i, cnt, pby, pty, ptm; /* pby: probability of bool = y
* pty: probability of tristate = y * pty: probability of tristate = y
* ptm: probability of tristate = m * ptm: probability of tristate = m
*/ */

View File

@ -94,7 +94,7 @@ struct expr *expr_copy(const struct expr *org)
e->right.expr = expr_copy(org->right.expr); e->right.expr = expr_copy(org->right.expr);
break; break;
default: default:
printf("can't copy type %d\n", e->type); fprintf(stderr, "can't copy type %d\n", e->type);
free(e); free(e);
e = NULL; e = NULL;
break; break;
@ -113,7 +113,7 @@ void expr_free(struct expr *e)
break; break;
case E_NOT: case E_NOT:
expr_free(e->left.expr); expr_free(e->left.expr);
return; break;
case E_EQUAL: case E_EQUAL:
case E_GEQ: case E_GEQ:
case E_GTH: case E_GTH:
@ -127,7 +127,7 @@ void expr_free(struct expr *e)
expr_free(e->right.expr); expr_free(e->right.expr);
break; break;
default: default:
printf("how to free type %d?\n", e->type); fprintf(stderr, "how to free type %d?\n", e->type);
break; break;
} }
free(e); free(e);
@ -138,8 +138,18 @@ static int trans_count;
#define e1 (*ep1) #define e1 (*ep1)
#define e2 (*ep2) #define e2 (*ep2)
/*
* expr_eliminate_eq() helper.
*
* Walks the two expression trees given in 'ep1' and 'ep2'. Any node that does
* not have type 'type' (E_OR/E_AND) is considered a leaf, and is compared
* against all other leaves. Two equal leaves are both replaced with either 'y'
* or 'n' as appropriate for 'type', to be eliminated later.
*/
static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2) static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2)
{ {
/* Recurse down to leaves */
if (e1->type == type) { if (e1->type == type) {
__expr_eliminate_eq(type, &e1->left.expr, &e2); __expr_eliminate_eq(type, &e1->left.expr, &e2);
__expr_eliminate_eq(type, &e1->right.expr, &e2); __expr_eliminate_eq(type, &e1->right.expr, &e2);
@ -150,12 +160,18 @@ static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct e
__expr_eliminate_eq(type, &e1, &e2->right.expr); __expr_eliminate_eq(type, &e1, &e2->right.expr);
return; return;
} }
/* e1 and e2 are leaves. Compare them. */
if (e1->type == E_SYMBOL && e2->type == E_SYMBOL && if (e1->type == E_SYMBOL && e2->type == E_SYMBOL &&
e1->left.sym == e2->left.sym && e1->left.sym == e2->left.sym &&
(e1->left.sym == &symbol_yes || e1->left.sym == &symbol_no)) (e1->left.sym == &symbol_yes || e1->left.sym == &symbol_no))
return; return;
if (!expr_eq(e1, e2)) if (!expr_eq(e1, e2))
return; return;
/* e1 and e2 are equal leaves. Prepare them for elimination. */
trans_count++; trans_count++;
expr_free(e1); expr_free(e2); expr_free(e1); expr_free(e2);
switch (type) { switch (type) {
@ -172,6 +188,35 @@ static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct e
} }
} }
/*
* Rewrites the expressions 'ep1' and 'ep2' to remove operands common to both.
* Example reductions:
*
* ep1: A && B -> ep1: y
* ep2: A && B && C -> ep2: C
*
* ep1: A || B -> ep1: n
* ep2: A || B || C -> ep2: C
*
* ep1: A && (B && FOO) -> ep1: FOO
* ep2: (BAR && B) && A -> ep2: BAR
*
* ep1: A && (B || C) -> ep1: y
* ep2: (C || B) && A -> ep2: y
*
* Comparisons are done between all operands at the same "level" of && or ||.
* For example, in the expression 'e1 && (e2 || e3) && (e4 || e5)', the
* following operands will be compared:
*
* - 'e1', 'e2 || e3', and 'e4 || e5', against each other
* - e2 against e3
* - e4 against e5
*
* Parentheses are irrelevant within a single level. 'e1 && (e2 && e3)' and
* '(e1 && e2) && e3' are both a single level.
*
* See __expr_eliminate_eq() as well.
*/
void expr_eliminate_eq(struct expr **ep1, struct expr **ep2) void expr_eliminate_eq(struct expr **ep1, struct expr **ep2)
{ {
if (!e1 || !e2) if (!e1 || !e2)
@ -197,6 +242,12 @@ void expr_eliminate_eq(struct expr **ep1, struct expr **ep2)
#undef e1 #undef e1
#undef e2 #undef e2
/*
* Returns true if 'e1' and 'e2' are equal, after minor simplification. Two
* &&/|| expressions are considered equal if every operand in one expression
* equals some operand in the other (operands do not need to appear in the same
* order), recursively.
*/
static int expr_eq(struct expr *e1, struct expr *e2) static int expr_eq(struct expr *e1, struct expr *e2)
{ {
int res, old_count; int res, old_count;
@ -243,6 +294,17 @@ static int expr_eq(struct expr *e1, struct expr *e2)
return 0; return 0;
} }
/*
* Recursively performs the following simplifications in-place (as well as the
* corresponding simplifications with swapped operands):
*
* expr && n -> n
* expr && y -> expr
* expr || n -> expr
* expr || y -> y
*
* Returns the optimized expression.
*/
static struct expr *expr_eliminate_yn(struct expr *e) static struct expr *expr_eliminate_yn(struct expr *e)
{ {
struct expr *tmp; struct expr *tmp;
@ -516,12 +578,21 @@ static struct expr *expr_join_and(struct expr *e1, struct expr *e2)
return NULL; return NULL;
} }
/*
* expr_eliminate_dups() helper.
*
* Walks the two expression trees given in 'ep1' and 'ep2'. Any node that does
* not have type 'type' (E_OR/E_AND) is considered a leaf, and is compared
* against all other leaves to look for simplifications.
*/
static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2) static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2)
{ {
#define e1 (*ep1) #define e1 (*ep1)
#define e2 (*ep2) #define e2 (*ep2)
struct expr *tmp; struct expr *tmp;
/* Recurse down to leaves */
if (e1->type == type) { if (e1->type == type) {
expr_eliminate_dups1(type, &e1->left.expr, &e2); expr_eliminate_dups1(type, &e1->left.expr, &e2);
expr_eliminate_dups1(type, &e1->right.expr, &e2); expr_eliminate_dups1(type, &e1->right.expr, &e2);
@ -532,6 +603,9 @@ static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct
expr_eliminate_dups1(type, &e1, &e2->right.expr); expr_eliminate_dups1(type, &e1, &e2->right.expr);
return; return;
} }
/* e1 and e2 are leaves. Compare and process them. */
if (e1 == e2) if (e1 == e2)
return; return;
@ -568,6 +642,17 @@ static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct
#undef e2 #undef e2
} }
/*
* Rewrites 'e' in-place to remove ("join") duplicate and other redundant
* operands.
*
* Example simplifications:
*
* A || B || A -> A || B
* A && B && A=y -> A=y && B
*
* Returns the deduplicated expression.
*/
struct expr *expr_eliminate_dups(struct expr *e) struct expr *expr_eliminate_dups(struct expr *e)
{ {
int oldcount; int oldcount;
@ -584,6 +669,7 @@ struct expr *expr_eliminate_dups(struct expr *e)
; ;
} }
if (!trans_count) if (!trans_count)
/* No simplifications done in this pass. We're done */
break; break;
e = expr_eliminate_yn(e); e = expr_eliminate_yn(e);
} }
@ -591,6 +677,12 @@ struct expr *expr_eliminate_dups(struct expr *e)
return e; return e;
} }
/*
* Performs various simplifications involving logical operators and
* comparisons.
*
* Allocates and returns a new expression.
*/
struct expr *expr_transform(struct expr *e) struct expr *expr_transform(struct expr *e)
{ {
struct expr *tmp; struct expr *tmp;
@ -805,6 +897,20 @@ bool expr_depends_symbol(struct expr *dep, struct symbol *sym)
return false; return false;
} }
/*
* Inserts explicit comparisons of type 'type' to symbol 'sym' into the
* expression 'e'.
*
* Examples transformations for type == E_UNEQUAL, sym == &symbol_no:
*
* A -> A!=n
* !A -> A=n
* A && B -> !(A=n || B=n)
* A || B -> !(A=n && B=n)
* A && (B || C) -> !(A=n || (B=n && C=n))
*
* Allocates and returns a new expression.
*/
struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym) struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym)
{ {
struct expr *e1, *e2; struct expr *e1, *e2;
@ -1073,7 +1179,7 @@ struct expr *expr_simplify_unmet_dep(struct expr *e1, struct expr *e2)
return expr_get_leftmost_symbol(ret); return expr_get_leftmost_symbol(ret);
} }
void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken) static void __expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken, bool revdep)
{ {
if (!e) { if (!e) {
fn(data, NULL, "y"); fn(data, NULL, "y");
@ -1128,9 +1234,14 @@ void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *
fn(data, e->right.sym, e->right.sym->name); fn(data, e->right.sym, e->right.sym->name);
break; break;
case E_OR: case E_OR:
expr_print(e->left.expr, fn, data, E_OR); if (revdep && e->left.expr->type != E_OR)
fn(data, NULL, " || "); fn(data, NULL, "\n - ");
expr_print(e->right.expr, fn, data, E_OR); __expr_print(e->left.expr, fn, data, E_OR, revdep);
if (revdep)
fn(data, NULL, "\n - ");
else
fn(data, NULL, " || ");
__expr_print(e->right.expr, fn, data, E_OR, revdep);
break; break;
case E_AND: case E_AND:
expr_print(e->left.expr, fn, data, E_AND); expr_print(e->left.expr, fn, data, E_AND);
@ -1163,6 +1274,11 @@ void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *
fn(data, NULL, ")"); fn(data, NULL, ")");
} }
void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken)
{
__expr_print(e, fn, data, prevtoken, false);
}
static void expr_print_file_helper(void *data, struct symbol *sym, const char *str) static void expr_print_file_helper(void *data, struct symbol *sym, const char *str)
{ {
xfwrite(str, strlen(str), 1, data); xfwrite(str, strlen(str), 1, data);
@ -1207,3 +1323,13 @@ void expr_gstr_print(struct expr *e, struct gstr *gs)
{ {
expr_print(e, expr_print_gstr_helper, gs, E_NONE); expr_print(e, expr_print_gstr_helper, gs, E_NONE);
} }
/*
* Transform the top level "||" tokens into newlines and prepend each
* line with a minus. This makes expressions much easier to read.
* Suitable for reverse dependency expressions.
*/
void expr_gstr_print_revdep(struct expr *e, struct gstr *gs)
{
__expr_print(e, expr_print_gstr_helper, gs, E_NONE, true);
}

View File

@ -74,17 +74,60 @@ enum {
S_DEF_COUNT S_DEF_COUNT
}; };
/*
* Represents a configuration symbol.
*
* Choices are represented as a special kind of symbol and have the
* SYMBOL_CHOICE bit set in 'flags'.
*/
struct symbol { struct symbol {
/* The next symbol in the same bucket in the symbol hash table */
struct symbol *next; struct symbol *next;
/* The name of the symbol, e.g. "FOO" for 'config FOO' */
char *name; char *name;
/* S_BOOLEAN, S_TRISTATE, ... */
enum symbol_type type; enum symbol_type type;
/*
* The calculated value of the symbol. The SYMBOL_VALID bit is set in
* 'flags' when this is up to date. Note that this value might differ
* from the user value set in e.g. a .config file, due to visibility.
*/
struct symbol_value curr; struct symbol_value curr;
/*
* Values for the symbol provided from outside. def[S_DEF_USER] holds
* the .config value.
*/
struct symbol_value def[S_DEF_COUNT]; struct symbol_value def[S_DEF_COUNT];
/*
* An upper bound on the tristate value the user can set for the symbol
* if it is a boolean or tristate. Calculated from prompt dependencies,
* which also inherit dependencies from enclosing menus, choices, and
* ifs. If 'n', the user value will be ignored.
*
* Symbols lacking prompts always have visibility 'n'.
*/
tristate visible; tristate visible;
/* SYMBOL_* flags */
int flags; int flags;
/* List of properties. See prop_type. */
struct property *prop; struct property *prop;
/* Dependencies from enclosing menus, choices, and ifs */
struct expr_value dir_dep; struct expr_value dir_dep;
/* Reverse dependencies through being selected by other symbols */
struct expr_value rev_dep; struct expr_value rev_dep;
/*
* "Weak" reverse dependencies through being implied by other symbols
*/
struct expr_value implied; struct expr_value implied;
}; };
@ -133,7 +176,7 @@ enum prop_type {
P_UNKNOWN, P_UNKNOWN,
P_PROMPT, /* prompt "foo prompt" or "BAZ Value" */ P_PROMPT, /* prompt "foo prompt" or "BAZ Value" */
P_COMMENT, /* text associated with a comment */ P_COMMENT, /* text associated with a comment */
P_MENU, /* prompt associated with a menuconfig option */ P_MENU, /* prompt associated with a menu or menuconfig symbol */
P_DEFAULT, /* default y */ P_DEFAULT, /* default y */
P_CHOICE, /* choice value */ P_CHOICE, /* choice value */
P_SELECT, /* select BAR */ P_SELECT, /* select BAR */
@ -166,22 +209,67 @@ struct property {
for (st = sym->prop; st; st = st->next) \ for (st = sym->prop; st; st = st->next) \
if (st->text) if (st->text)
/*
* Represents a node in the menu tree, as seen in e.g. menuconfig (though used
* for all front ends). Each symbol, menu, etc. defined in the Kconfig files
* gets a node. A symbol defined in multiple locations gets one node at each
* location.
*/
struct menu { struct menu {
/* The next menu node at the same level */
struct menu *next; struct menu *next;
/* The parent menu node, corresponding to e.g. a menu or choice */
struct menu *parent; struct menu *parent;
/* The first child menu node, for e.g. menus and choices */
struct menu *list; struct menu *list;
/*
* The symbol associated with the menu node. Choices are implemented as
* a special kind of symbol. NULL for menus, comments, and ifs.
*/
struct symbol *sym; struct symbol *sym;
/*
* The prompt associated with the node. This holds the prompt for a
* symbol as well as the text for a menu or comment, along with the
* type (P_PROMPT, P_MENU, etc.)
*/
struct property *prompt; struct property *prompt;
/*
* 'visible if' dependencies. If more than one is given, they will be
* ANDed together.
*/
struct expr *visibility; struct expr *visibility;
/*
* Ordinary dependencies from e.g. 'depends on' and 'if', ANDed
* together
*/
struct expr *dep; struct expr *dep;
/* MENU_* flags */
unsigned int flags; unsigned int flags;
/* Any help text associated with the node */
char *help; char *help;
/* The location where the menu node appears in the Kconfig files */
struct file *file; struct file *file;
int lineno; int lineno;
/* For use by front ends that need to store auxiliary data */
void *data; void *data;
}; };
/*
* Set on a menu node when the corresponding symbol changes state in some way.
* Can be checked by front ends.
*/
#define MENU_CHANGED 0x0001 #define MENU_CHANGED 0x0001
#define MENU_ROOT 0x0002 #define MENU_ROOT 0x0002
struct jump_key { struct jump_key {
@ -222,6 +310,7 @@ struct expr *expr_simplify_unmet_dep(struct expr *e1, struct expr *e2);
void expr_fprint(struct expr *e, FILE *out); void expr_fprint(struct expr *e, FILE *out);
struct gstr; /* forward */ struct gstr; /* forward */
void expr_gstr_print(struct expr *e, struct gstr *gs); void expr_gstr_print(struct expr *e, struct gstr *gs);
void expr_gstr_print_revdep(struct expr *e, struct gstr *gs);
static inline int expr_is_yes(struct expr *e) static inline int expr_is_yes(struct expr *e)
{ {

View File

@ -20,7 +20,6 @@ static struct kconf_id kconf_id_array[] = {
{ "tristate", T_TYPE, TF_COMMAND, S_TRISTATE }, { "tristate", T_TYPE, TF_COMMAND, S_TRISTATE },
{ "def_tristate", T_DEFAULT, TF_COMMAND, S_TRISTATE }, { "def_tristate", T_DEFAULT, TF_COMMAND, S_TRISTATE },
{ "bool", T_TYPE, TF_COMMAND, S_BOOLEAN }, { "bool", T_TYPE, TF_COMMAND, S_BOOLEAN },
{ "boolean", T_TYPE, TF_COMMAND, S_BOOLEAN },
{ "def_bool", T_DEFAULT, TF_COMMAND, S_BOOLEAN }, { "def_bool", T_DEFAULT, TF_COMMAND, S_BOOLEAN },
{ "int", T_TYPE, TF_COMMAND, S_INT }, { "int", T_TYPE, TF_COMMAND, S_INT },
{ "hex", T_TYPE, TF_COMMAND, S_HEX }, { "hex", T_TYPE, TF_COMMAND, S_HEX },

View File

@ -100,7 +100,6 @@ void menu_warn(struct menu *menu, const char *fmt, ...);
struct menu *menu_add_menu(void); struct menu *menu_add_menu(void);
void menu_end_menu(void); void menu_end_menu(void);
void menu_add_entry(struct symbol *sym); void menu_add_entry(struct symbol *sym);
void menu_end_entry(void);
void menu_add_dep(struct expr *dep); void menu_add_dep(struct expr *dep);
void menu_add_visibility(struct expr *dep); void menu_add_visibility(struct expr *dep);
struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep); struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep);
@ -115,6 +114,8 @@ struct file *file_lookup(const char *name);
int file_write_dep(const char *name); int file_write_dep(const char *name);
void *xmalloc(size_t size); void *xmalloc(size_t size);
void *xcalloc(size_t nmemb, size_t size); void *xcalloc(size_t nmemb, size_t size);
void *xrealloc(void *p, size_t size);
char *xstrdup(const char *s);
struct gstr { struct gstr {
size_t len; size_t len;

View File

@ -31,7 +31,7 @@ extern struct symbol * symbol_hash[SYMBOL_HASHSIZE];
struct symbol * sym_lookup(const char *name, int flags); struct symbol * sym_lookup(const char *name, int flags);
struct symbol * sym_find(const char *name); struct symbol * sym_find(const char *name);
const char * sym_expand_string_value(const char *in); char *sym_expand_string_value(const char *in);
const char * sym_escape_string_value(const char *in); const char * sym_escape_string_value(const char *in);
struct symbol ** sym_re_search(const char *pattern); struct symbol ** sym_re_search(const char *pattern);
const char * sym_type_name(enum symbol_type type); const char * sym_type_name(enum symbol_type type);

3
kconfig/lxdialog/check-lxdialog.sh Normal file → Executable file
View File

@ -55,7 +55,8 @@ EOF
echo " *** required header files." 1>&2 echo " *** required header files." 1>&2
echo " *** 'make menuconfig' requires the ncurses libraries." 1>&2 echo " *** 'make menuconfig' requires the ncurses libraries." 1>&2
echo " *** " 1>&2 echo " *** " 1>&2
echo " *** Install ncurses (ncurses-devel) and try again." 1>&2 echo " *** Install ncurses (ncurses-devel or libncurses-dev " 1>&2
echo " *** depending on your distribution) and try again." 1>&2
echo " *** " 1>&2 echo " *** " 1>&2
exit 1 exit 1
fi fi

View File

@ -246,7 +246,7 @@ search_help[] = N_(
" Selected by: BAR [=n]\n" " Selected by: BAR [=n]\n"
"-----------------------------------------------------------------\n" "-----------------------------------------------------------------\n"
"o The line 'Type:' shows the type of the configuration option for\n" "o The line 'Type:' shows the type of the configuration option for\n"
" this symbol (boolean, tristate, string, ...)\n" " this symbol (bool, tristate, string, ...)\n"
"o The line 'Prompt:' shows the text used in the menu structure for\n" "o The line 'Prompt:' shows the text used in the menu structure for\n"
" this symbol\n" " this symbol\n"
"o The 'Defined at' line tells at what file / line number the symbol\n" "o The 'Defined at' line tells at what file / line number the symbol\n"

View File

@ -62,13 +62,8 @@ void menu_add_entry(struct symbol *sym)
menu_add_symbol(P_SYMBOL, sym, NULL); menu_add_symbol(P_SYMBOL, sym, NULL);
} }
void menu_end_entry(void)
{
}
struct menu *menu_add_menu(void) struct menu *menu_add_menu(void)
{ {
menu_end_entry();
last_entry_ptr = &current_entry->list; last_entry_ptr = &current_entry->list;
return current_menu = current_entry; return current_menu = current_entry;
} }
@ -79,19 +74,23 @@ void menu_end_menu(void)
current_menu = current_menu->parent; current_menu = current_menu->parent;
} }
static struct expr *menu_check_dep(struct expr *e) /*
* Rewrites 'm' to 'm' && MODULES, so that it evaluates to 'n' when running
* without modules
*/
static struct expr *rewrite_m(struct expr *e)
{ {
if (!e) if (!e)
return e; return e;
switch (e->type) { switch (e->type) {
case E_NOT: case E_NOT:
e->left.expr = menu_check_dep(e->left.expr); e->left.expr = rewrite_m(e->left.expr);
break; break;
case E_OR: case E_OR:
case E_AND: case E_AND:
e->left.expr = menu_check_dep(e->left.expr); e->left.expr = rewrite_m(e->left.expr);
e->right.expr = menu_check_dep(e->right.expr); e->right.expr = rewrite_m(e->right.expr);
break; break;
case E_SYMBOL: case E_SYMBOL:
/* change 'm' into 'm' && MODULES */ /* change 'm' into 'm' && MODULES */
@ -106,7 +105,7 @@ static struct expr *menu_check_dep(struct expr *e)
void menu_add_dep(struct expr *dep) void menu_add_dep(struct expr *dep)
{ {
current_entry->dep = expr_alloc_and(current_entry->dep, menu_check_dep(dep)); current_entry->dep = expr_alloc_and(current_entry->dep, dep);
} }
void menu_set_type(int type) void menu_set_type(int type)
@ -131,7 +130,7 @@ static struct property *menu_add_prop(enum prop_type type, char *prompt, struct
prop->menu = current_entry; prop->menu = current_entry;
prop->expr = expr; prop->expr = expr;
prop->visible.expr = menu_check_dep(dep); prop->visible.expr = dep;
if (prompt) { if (prompt) {
/* For crostool-NG, a leading pipe followed with spaces /* For crostool-NG, a leading pipe followed with spaces
@ -219,6 +218,7 @@ void menu_add_option(int token, char *arg)
sym_defconfig_list = current_entry->sym; sym_defconfig_list = current_entry->sym;
else if (sym_defconfig_list != current_entry->sym) else if (sym_defconfig_list != current_entry->sym)
zconf_error("trying to redefine defconfig symbol"); zconf_error("trying to redefine defconfig symbol");
sym_defconfig_list->flags |= SYMBOL_AUTO;
break; break;
case T_OPT_ENV: case T_OPT_ENV:
prop_add_env(arg); prop_add_env(arg);
@ -258,6 +258,16 @@ static void sym_check_prop(struct symbol *sym)
"'%s': number is invalid", "'%s': number is invalid",
sym->name); sym->name);
} }
if (sym_is_choice(sym)) {
struct property *choice_prop =
sym_get_choice_prop(sym2);
if (!choice_prop ||
prop_get_symbol(choice_prop) != sym)
prop_warn(prop,
"choice default symbol '%s' is not contained in the choice",
sym2->name);
}
break; break;
case P_SELECT: case P_SELECT:
case P_IMPLY: case P_IMPLY:
@ -266,13 +276,13 @@ static void sym_check_prop(struct symbol *sym)
if (sym->type != S_BOOLEAN && sym->type != S_TRISTATE) if (sym->type != S_BOOLEAN && sym->type != S_TRISTATE)
prop_warn(prop, prop_warn(prop,
"config symbol '%s' uses %s, but is " "config symbol '%s' uses %s, but is "
"not boolean or tristate", sym->name, use); "not bool or tristate", sym->name, use);
else if (sym2->type != S_UNKNOWN && else if (sym2->type != S_UNKNOWN &&
sym2->type != S_BOOLEAN && sym2->type != S_BOOLEAN &&
sym2->type != S_TRISTATE) sym2->type != S_TRISTATE)
prop_warn(prop, prop_warn(prop,
"'%s' has wrong type. '%s' only " "'%s' has wrong type. '%s' only "
"accept arguments of boolean and " "accept arguments of bool and "
"tristate type", sym2->name, use); "tristate type", sym2->name, use);
break; break;
case P_RANGE: case P_RANGE:
@ -298,6 +308,11 @@ void menu_finalize(struct menu *parent)
sym = parent->sym; sym = parent->sym;
if (parent->list) { if (parent->list) {
/*
* This menu node has children. We (recursively) process them
* and propagate parent dependencies before moving on.
*/
if (sym && sym_is_choice(sym)) { if (sym && sym_is_choice(sym)) {
if (sym->type == S_UNKNOWN) { if (sym->type == S_UNKNOWN) {
/* find the first choice value to find out choice type */ /* find the first choice value to find out choice type */
@ -315,30 +330,83 @@ void menu_finalize(struct menu *parent)
if (menu->sym && menu->sym->type == S_UNKNOWN) if (menu->sym && menu->sym->type == S_UNKNOWN)
menu_set_type(sym->type); menu_set_type(sym->type);
} }
/*
* Use the choice itself as the parent dependency of
* the contained items. This turns the mode of the
* choice into an upper bound on the visibility of the
* choice value symbols.
*/
parentdep = expr_alloc_symbol(sym); parentdep = expr_alloc_symbol(sym);
} else if (parent->prompt) } else if (parent->prompt)
/* Menu node for 'menu' */
parentdep = parent->prompt->visible.expr; parentdep = parent->prompt->visible.expr;
else else
/* Menu node for 'if' */
parentdep = parent->dep; parentdep = parent->dep;
/* For each child menu node... */
for (menu = parent->list; menu; menu = menu->next) { for (menu = parent->list; menu; menu = menu->next) {
basedep = expr_transform(menu->dep); /*
* Propagate parent dependencies to the child menu
* node, also rewriting and simplifying expressions
*/
basedep = rewrite_m(menu->dep);
basedep = expr_transform(basedep);
basedep = expr_alloc_and(expr_copy(parentdep), basedep); basedep = expr_alloc_and(expr_copy(parentdep), basedep);
basedep = expr_eliminate_dups(basedep); basedep = expr_eliminate_dups(basedep);
menu->dep = basedep; menu->dep = basedep;
if (menu->sym) if (menu->sym)
/*
* Note: For symbols, all prompts are included
* too in the symbol's own property list
*/
prop = menu->sym->prop; prop = menu->sym->prop;
else else
/*
* For non-symbol menu nodes, we just need to
* handle the prompt
*/
prop = menu->prompt; prop = menu->prompt;
/* For each property... */
for (; prop; prop = prop->next) { for (; prop; prop = prop->next) {
if (prop->menu != menu) if (prop->menu != menu)
/*
* Two possibilities:
*
* 1. The property lacks dependencies
* and so isn't location-specific,
* e.g. an 'option'
*
* 2. The property belongs to a symbol
* defined in multiple locations and
* is from some other location. It
* will be handled there in that
* case.
*
* Skip the property.
*/
continue; continue;
dep = expr_transform(prop->visible.expr);
/*
* Propagate parent dependencies to the
* property's condition, rewriting and
* simplifying expressions at the same time
*/
dep = rewrite_m(prop->visible.expr);
dep = expr_transform(dep);
dep = expr_alloc_and(expr_copy(basedep), dep); dep = expr_alloc_and(expr_copy(basedep), dep);
dep = expr_eliminate_dups(dep); dep = expr_eliminate_dups(dep);
if (menu->sym && menu->sym->type != S_TRISTATE) if (menu->sym && menu->sym->type != S_TRISTATE)
dep = expr_trans_bool(dep); dep = expr_trans_bool(dep);
prop->visible.expr = dep; prop->visible.expr = dep;
/*
* Handle selects and implies, which modify the
* dependencies of the selected/implied symbol
*/
if (prop->type == P_SELECT) { if (prop->type == P_SELECT) {
struct symbol *es = prop_get_symbol(prop); struct symbol *es = prop_get_symbol(prop);
es->rev_dep.expr = expr_alloc_or(es->rev_dep.expr, es->rev_dep.expr = expr_alloc_or(es->rev_dep.expr,
@ -350,34 +418,81 @@ void menu_finalize(struct menu *parent)
} }
} }
} }
if (sym && sym_is_choice(sym))
expr_free(parentdep);
/*
* Recursively process children in the same fashion before
* moving on
*/
for (menu = parent->list; menu; menu = menu->next) for (menu = parent->list; menu; menu = menu->next)
menu_finalize(menu); menu_finalize(menu);
} else if (sym) { } else if (sym) {
/*
* Automatic submenu creation. If sym is a symbol and A, B, C,
* ... are consecutive items (symbols, menus, ifs, etc.) that
* all depend on sym, then the following menu structure is
* created:
*
* sym
* +-A
* +-B
* +-C
* ...
*
* This also works recursively, giving the following structure
* if A is a symbol and B depends on A:
*
* sym
* +-A
* | +-B
* +-C
* ...
*/
basedep = parent->prompt ? parent->prompt->visible.expr : NULL; basedep = parent->prompt ? parent->prompt->visible.expr : NULL;
basedep = expr_trans_compare(basedep, E_UNEQUAL, &symbol_no); basedep = expr_trans_compare(basedep, E_UNEQUAL, &symbol_no);
basedep = expr_eliminate_dups(expr_transform(basedep)); basedep = expr_eliminate_dups(expr_transform(basedep));
/* Examine consecutive elements after sym */
last_menu = NULL; last_menu = NULL;
for (menu = parent->next; menu; menu = menu->next) { for (menu = parent->next; menu; menu = menu->next) {
dep = menu->prompt ? menu->prompt->visible.expr : menu->dep; dep = menu->prompt ? menu->prompt->visible.expr : menu->dep;
if (!expr_contains_symbol(dep, sym)) if (!expr_contains_symbol(dep, sym))
/* No dependency, quit */
break; break;
if (expr_depends_symbol(dep, sym)) if (expr_depends_symbol(dep, sym))
/* Absolute dependency, put in submenu */
goto next; goto next;
/*
* Also consider it a dependency on sym if our
* dependencies contain sym and are a "superset" of
* sym's dependencies, e.g. '(sym || Q) && R' when sym
* depends on R.
*
* Note that 'R' might be from an enclosing menu or if,
* making this a more common case than it might seem.
*/
dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no); dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no);
dep = expr_eliminate_dups(expr_transform(dep)); dep = expr_eliminate_dups(expr_transform(dep));
dep2 = expr_copy(basedep); dep2 = expr_copy(basedep);
expr_eliminate_eq(&dep, &dep2); expr_eliminate_eq(&dep, &dep2);
expr_free(dep); expr_free(dep);
if (!expr_is_yes(dep2)) { if (!expr_is_yes(dep2)) {
/* Not superset, quit */
expr_free(dep2); expr_free(dep2);
break; break;
} }
/* Superset, put in submenu */
expr_free(dep2); expr_free(dep2);
next: next:
menu_finalize(menu); menu_finalize(menu);
menu->parent = parent; menu->parent = parent;
last_menu = menu; last_menu = menu;
} }
expr_free(basedep);
if (last_menu) { if (last_menu) {
parent->list = parent->next; parent->list = parent->next;
parent->next = last_menu->next; parent->next = last_menu->next;
@ -426,6 +541,35 @@ void menu_finalize(struct menu *parent)
*ep = expr_alloc_one(E_LIST, NULL); *ep = expr_alloc_one(E_LIST, NULL);
(*ep)->right.sym = menu->sym; (*ep)->right.sym = menu->sym;
} }
/*
* This code serves two purposes:
*
* (1) Flattening 'if' blocks, which do not specify a submenu
* and only add dependencies.
*
* (Automatic submenu creation might still create a submenu
* from an 'if' before this code runs.)
*
* (2) "Undoing" any automatic submenus created earlier below
* promptless symbols.
*
* Before:
*
* A
* if ... (or promptless symbol)
* +-B
* +-C
* D
*
* After:
*
* A
* if ... (or promptless symbol)
* B
* C
* D
*/
if (menu->list && (!menu->prompt || !menu->prompt->text)) { if (menu->list && (!menu->prompt || !menu->prompt->text)) {
for (last_menu = menu->list; ; last_menu = last_menu->next) { for (last_menu = menu->list; ; last_menu = last_menu->next) {
last_menu->parent = parent; last_menu->parent = parent;
@ -450,6 +594,15 @@ void menu_finalize(struct menu *parent)
sym->flags |= SYMBOL_WARNED; sym->flags |= SYMBOL_WARNED;
} }
/*
* For non-optional choices, add a reverse dependency (corresponding to
* a select) of '<visibility> && m'. This prevents the user from
* setting the choice mode to 'n' when the choice is visible.
*
* This would also work for non-choice symbols, but only non-optional
* choices clear SYMBOL_OPTIONAL as of writing. Choices are implemented
* as a type of symbol.
*/
if (sym && !sym_is_optional(sym) && parent->prompt) { if (sym && !sym_is_optional(sym) && parent->prompt) {
sym->rev_dep.expr = expr_alloc_or(sym->rev_dep.expr, sym->rev_dep.expr = expr_alloc_or(sym->rev_dep.expr,
expr_alloc_and(parent->prompt->visible.expr, expr_alloc_and(parent->prompt->visible.expr,
@ -682,14 +835,14 @@ static void get_symbol_str(struct gstr *r, struct symbol *sym,
get_symbol_props_str(r, sym, P_SELECT, _(" Selects: ")); get_symbol_props_str(r, sym, P_SELECT, _(" Selects: "));
if (sym->rev_dep.expr) { if (sym->rev_dep.expr) {
str_append(r, _(" Selected by: ")); str_append(r, _(" Selected by: "));
expr_gstr_print(sym->rev_dep.expr, r); expr_gstr_print_revdep(sym->rev_dep.expr, r);
str_append(r, "\n"); str_append(r, "\n");
} }
get_symbol_props_str(r, sym, P_IMPLY, _(" Implies: ")); get_symbol_props_str(r, sym, P_IMPLY, _(" Implies: "));
if (sym->implied.expr) { if (sym->implied.expr) {
str_append(r, _(" Implied by: ")); str_append(r, _(" Implied by: "));
expr_gstr_print(sym->implied.expr, r); expr_gstr_print_revdep(sym->implied.expr, r);
str_append(r, "\n"); str_append(r, "\n");
} }

View File

@ -6,6 +6,7 @@
* *
*/ */
#include "nconf.h" #include "nconf.h"
#include "lkc.h"
/* a list of all the different widgets we use */ /* a list of all the different widgets we use */
attributes_t attributes[ATTR_MAX+1] = {0}; attributes_t attributes[ATTR_MAX+1] = {0};
@ -374,7 +375,7 @@ int dialog_inputbox(WINDOW *main_window,
if (strlen(init)+1 > *result_len) { if (strlen(init)+1 > *result_len) {
*result_len = strlen(init)+1; *result_len = strlen(init)+1;
*resultp = result = realloc(result, *result_len); *resultp = result = xrealloc(result, *result_len);
} }
/* find the widest line of msg: */ /* find the widest line of msg: */

View File

@ -77,7 +77,7 @@ const char *sym_type_name(enum symbol_type type)
{ {
switch (type) { switch (type) {
case S_BOOLEAN: case S_BOOLEAN:
return "boolean"; return "bool";
case S_TRISTATE: case S_TRISTATE:
return "tristate"; return "tristate";
case S_INT: case S_INT:
@ -183,7 +183,7 @@ static void sym_validate_range(struct symbol *sym)
sprintf(str, "%lld", val2); sprintf(str, "%lld", val2);
else else
sprintf(str, "0x%llx", val2); sprintf(str, "0x%llx", val2);
sym->curr.val = strdup(str); sym->curr.val = xstrdup(str);
} }
static void sym_set_changed(struct symbol *sym) static void sym_set_changed(struct symbol *sym)
@ -371,11 +371,13 @@ void sym_calc_value(struct symbol *sym)
sym->curr.tri = no; sym->curr.tri = no;
return; return;
} }
if (!sym_is_choice_value(sym)) sym->flags &= ~SYMBOL_WRITE;
sym->flags &= ~SYMBOL_WRITE;
sym_calc_visibility(sym); sym_calc_visibility(sym);
if (sym->visible != no)
sym->flags |= SYMBOL_WRITE;
/* set default if recursively called */ /* set default if recursively called */
sym->curr = newval; sym->curr = newval;
@ -390,7 +392,6 @@ void sym_calc_value(struct symbol *sym)
/* if the symbol is visible use the user value /* if the symbol is visible use the user value
* if available, otherwise try the default value * if available, otherwise try the default value
*/ */
sym->flags |= SYMBOL_WRITE;
if (sym_has_value(sym)) { if (sym_has_value(sym)) {
newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
sym->visible); sym->visible);
@ -433,12 +434,9 @@ void sym_calc_value(struct symbol *sym)
case S_STRING: case S_STRING:
case S_HEX: case S_HEX:
case S_INT: case S_INT:
if (sym->visible != no) { if (sym->visible != no && sym_has_value(sym)) {
sym->flags |= SYMBOL_WRITE; newval.val = sym->def[S_DEF_USER].val;
if (sym_has_value(sym)) { break;
newval.val = sym->def[S_DEF_USER].val;
break;
}
} }
prop = sym_get_default_prop(sym); prop = sym_get_default_prop(sym);
if (prop) { if (prop) {
@ -851,7 +849,7 @@ struct symbol *sym_lookup(const char *name, int flags)
: !(symbol->flags & (SYMBOL_CONST|SYMBOL_CHOICE)))) : !(symbol->flags & (SYMBOL_CONST|SYMBOL_CHOICE))))
return symbol; return symbol;
} }
new_name = strdup(name); new_name = xstrdup(name);
} else { } else {
new_name = NULL; new_name = NULL;
hash = 0; hash = 0;
@ -901,12 +899,16 @@ struct symbol *sym_find(const char *name)
* name to be expanded shall be prefixed by a '$'. Unknown symbol expands to * name to be expanded shall be prefixed by a '$'. Unknown symbol expands to
* the empty string. * the empty string.
*/ */
const char *sym_expand_string_value(const char *in) char *sym_expand_string_value(const char *in)
{ {
const char *src; const char *src;
char *res; char *res;
size_t reslen; size_t reslen;
/*
* Note: 'in' might come from a token that's about to be
* freed, so make sure to always allocate a new string
*/
reslen = strlen(in) + 1; reslen = strlen(in) + 1;
res = xmalloc(reslen); res = xmalloc(reslen);
res[0] = '\0'; res[0] = '\0';
@ -934,7 +936,7 @@ const char *sym_expand_string_value(const char *in)
newlen = strlen(res) + strlen(symval) + strlen(src) + 1; newlen = strlen(res) + strlen(symval) + strlen(src) + 1;
if (newlen > reslen) { if (newlen > reslen) {
reslen = newlen; reslen = newlen;
res = realloc(res, reslen); res = xrealloc(res, reslen);
} }
strcat(res, symval); strcat(res, symval);
@ -1219,7 +1221,7 @@ static struct symbol *sym_check_expr_deps(struct expr *e)
default: default:
break; break;
} }
printf("Oops! How to check %d?\n", e->type); fprintf(stderr, "Oops! How to check %d?\n", e->type);
return NULL; return NULL;
} }

View File

@ -14,11 +14,11 @@
struct file *file_lookup(const char *name) struct file *file_lookup(const char *name)
{ {
struct file *file; struct file *file;
const char *file_name = sym_expand_string_value(name); char *file_name = sym_expand_string_value(name);
for (file = file_list; file; file = file->next) { for (file = file_list; file; file = file->next) {
if (!strcmp(name, file->name)) { if (!strcmp(name, file->name)) {
free((void *)file_name); free(file_name);
return file; return file;
} }
} }
@ -104,7 +104,7 @@ void str_append(struct gstr *gs, const char *s)
if (s) { if (s) {
l = strlen(gs->s) + strlen(s) + 1; l = strlen(gs->s) + strlen(s) + 1;
if (l > gs->len) { if (l > gs->len) {
gs->s = realloc(gs->s, l); gs->s = xrealloc(gs->s, l);
gs->len = l; gs->len = l;
} }
strcat(gs->s, s); strcat(gs->s, s);
@ -145,3 +145,23 @@ void *xcalloc(size_t nmemb, size_t size)
fprintf(stderr, "Out of memory.\n"); fprintf(stderr, "Out of memory.\n");
exit(1); exit(1);
} }
void *xrealloc(void *p, size_t size)
{
p = realloc(p, size);
if (p)
return p;
fprintf(stderr, "Out of memory.\n");
exit(1);
}
char *xstrdup(const char *s)
{
char *p;
p = strdup(s);
if (p)
return p;
fprintf(stderr, "Out of memory.\n");
exit(1);
}

View File

@ -52,7 +52,7 @@ static void append_string(const char *str, int size)
if (new_size > text_asize) { if (new_size > text_asize) {
new_size += START_STRSIZE - 1; new_size += START_STRSIZE - 1;
new_size &= -START_STRSIZE; new_size &= -START_STRSIZE;
text = realloc(text, new_size); text = xrealloc(text, new_size);
text_asize = new_size; text_asize = new_size;
} }
memcpy(text + text_size, str, size); memcpy(text + text_size, str, size);
@ -106,11 +106,11 @@ n [A-Za-z0-9_-]
current_pos.file = current_file; current_pos.file = current_file;
current_pos.lineno = current_file->lineno; current_pos.lineno = current_file->lineno;
if (id && id->flags & TF_COMMAND) { if (id && id->flags & TF_COMMAND) {
zconflval.id = id; yylval.id = id;
return id->token; return id->token;
} }
alloc_string(yytext, yyleng); alloc_string(yytext, yyleng);
zconflval.string = text; yylval.string = text;
return T_WORD; return T_WORD;
} }
. warn_ignored_character(*yytext); . warn_ignored_character(*yytext);
@ -142,11 +142,11 @@ n [A-Za-z0-9_-]
({n}|[/.])+ { ({n}|[/.])+ {
const struct kconf_id *id = kconf_id_lookup(yytext, yyleng); const struct kconf_id *id = kconf_id_lookup(yytext, yyleng);
if (id && id->flags & TF_PARAM) { if (id && id->flags & TF_PARAM) {
zconflval.id = id; yylval.id = id;
return id->token; return id->token;
} }
alloc_string(yytext, yyleng); alloc_string(yytext, yyleng);
zconflval.string = text; yylval.string = text;
return T_WORD; return T_WORD;
} }
#.* /* comment */ #.* /* comment */
@ -161,7 +161,7 @@ n [A-Za-z0-9_-]
<STRING>{ <STRING>{
[^'"\\\n]+/\n { [^'"\\\n]+/\n {
append_string(yytext, yyleng); append_string(yytext, yyleng);
zconflval.string = text; yylval.string = text;
return T_WORD_QUOTE; return T_WORD_QUOTE;
} }
[^'"\\\n]+ { [^'"\\\n]+ {
@ -169,7 +169,7 @@ n [A-Za-z0-9_-]
} }
\\.?/\n { \\.?/\n {
append_string(yytext + 1, yyleng - 1); append_string(yytext + 1, yyleng - 1);
zconflval.string = text; yylval.string = text;
return T_WORD_QUOTE; return T_WORD_QUOTE;
} }
\\.? { \\.? {
@ -178,13 +178,15 @@ n [A-Za-z0-9_-]
\'|\" { \'|\" {
if (str == yytext[0]) { if (str == yytext[0]) {
BEGIN(PARAM); BEGIN(PARAM);
zconflval.string = text; yylval.string = text;
return T_WORD_QUOTE; return T_WORD_QUOTE;
} else } else
append_string(yytext, 1); append_string(yytext, 1);
} }
\n { \n {
printf("%s:%d:warning: multi-line strings not supported\n", zconf_curname(), zconf_lineno()); fprintf(stderr,
"%s:%d:warning: multi-line strings not supported\n",
zconf_curname(), zconf_lineno());
current_file->lineno++; current_file->lineno++;
BEGIN(INITIAL); BEGIN(INITIAL);
return T_EOL; return T_EOL;
@ -261,7 +263,7 @@ void zconf_starthelp(void)
static void zconf_endhelp(void) static void zconf_endhelp(void)
{ {
zconflval.string = text; yylval.string = text;
BEGIN(INITIAL); BEGIN(INITIAL);
} }
@ -294,7 +296,7 @@ void zconf_initscan(const char *name)
{ {
yyin = zconf_fopen(name); yyin = zconf_fopen(name);
if (!yyin) { if (!yyin) {
printf("can't find file %s\n", name); fprintf(stderr, "can't find file %s\n", name);
exit(1); exit(1);
} }
@ -315,8 +317,8 @@ void zconf_nextfile(const char *name)
current_buf->state = YY_CURRENT_BUFFER; current_buf->state = YY_CURRENT_BUFFER;
yyin = zconf_fopen(file->name); yyin = zconf_fopen(file->name);
if (!yyin) { if (!yyin) {
printf("%s:%d: can't open file \"%s\"\n", fprintf(stderr, "%s:%d: can't open file \"%s\"\n",
zconf_curname(), zconf_lineno(), file->name); zconf_curname(), zconf_lineno(), file->name);
exit(1); exit(1);
} }
yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
@ -325,20 +327,17 @@ void zconf_nextfile(const char *name)
for (iter = current_file->parent; iter; iter = iter->parent ) { for (iter = current_file->parent; iter; iter = iter->parent ) {
if (!strcmp(current_file->name,iter->name) ) { if (!strcmp(current_file->name,iter->name) ) {
printf("%s:%d: recursive inclusion detected. " fprintf(stderr,
"Inclusion path:\n current file : '%s'\n", "%s:%d: recursive inclusion detected. "
zconf_curname(), zconf_lineno(), "Inclusion path:\n current file : '%s'\n",
zconf_curname()); zconf_curname(), zconf_lineno(),
iter = current_file->parent; zconf_curname());
while (iter && \ iter = current_file;
strcmp(iter->name,current_file->name)) { do {
printf(" included from: '%s:%d'\n",
iter->name, iter->lineno-1);
iter = iter->parent; iter = iter->parent;
} fprintf(stderr, " included from: '%s:%d'\n",
if (iter) iter->name, iter->lineno - 1);
printf(" included from: '%s:%d'\n", } while (strcmp(iter->name, current_file->name));
iter->name, iter->lineno+1);
exit(1); exit(1);
} }
} }

View File

@ -20,10 +20,10 @@
int cdebug = PRINTD; int cdebug = PRINTD;
extern int zconflex(void); int yylex(void);
static void yyerror(const char *err);
static void zconfprint(const char *err, ...); static void zconfprint(const char *err, ...);
static void zconf_error(const char *err, ...); static void zconf_error(const char *err, ...);
static void zconferror(const char *err);
static bool zconf_endtoken(const struct kconf_id *id, int starttoken, int endtoken); static bool zconf_endtoken(const struct kconf_id *id, int starttoken, int endtoken);
struct symbol *symbol_hash[SYMBOL_HASHSIZE]; struct symbol *symbol_hash[SYMBOL_HASHSIZE];
@ -85,6 +85,7 @@ static struct menu *current_menu, *current_entry;
%nonassoc T_NOT %nonassoc T_NOT
%type <string> prompt %type <string> prompt
%type <symbol> nonconst_symbol
%type <symbol> symbol %type <symbol> symbol
%type <expr> expr %type <expr> expr
%type <expr> if_expr %type <expr> if_expr
@ -101,14 +102,34 @@ static struct menu *current_menu, *current_entry;
} if_entry menu_entry choice_entry } if_entry menu_entry choice_entry
%{ %{
/* Include zconf_id.c here so it can see the token constants. */ /* Include kconf_id.c here so it can see the token constants. */
#include "kconf_id.c" #include "kconf_id.c"
%} %}
%% %%
input: nl start | start; input: nl start | start;
start: mainmenu_stmt stmt_list | stmt_list; start: mainmenu_stmt stmt_list | no_mainmenu_stmt stmt_list;
/* mainmenu entry */
mainmenu_stmt: T_MAINMENU prompt nl
{
menu_add_prompt(P_MENU, $2, NULL);
};
/* Default main menu, if there's no mainmenu entry */
no_mainmenu_stmt: /* empty */
{
/*
* Hack: Keep the main menu title on the heap so we can safely free it
* later regardless of whether it comes from the 'prompt' in
* mainmenu_stmt or here
*/
menu_add_prompt(P_MENU, xstrdup("Linux Kernel Configuration"), NULL);
};
stmt_list: stmt_list:
/* empty */ /* empty */
@ -145,26 +166,23 @@ option_error:
/* config/menuconfig entry */ /* config/menuconfig entry */
config_entry_start: T_CONFIG T_WORD T_EOL config_entry_start: T_CONFIG nonconst_symbol T_EOL
{ {
struct symbol *sym = sym_lookup($2, 0); $2->flags |= SYMBOL_OPTIONAL;
sym->flags |= SYMBOL_OPTIONAL; menu_add_entry($2);
menu_add_entry(sym); printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), $2->name);
printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), $2);
}; };
config_stmt: config_entry_start config_option_list config_stmt: config_entry_start config_option_list
{ {
menu_end_entry();
printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno());
}; };
menuconfig_entry_start: T_MENUCONFIG T_WORD T_EOL menuconfig_entry_start: T_MENUCONFIG nonconst_symbol T_EOL
{ {
struct symbol *sym = sym_lookup($2, 0); $2->flags |= SYMBOL_OPTIONAL;
sym->flags |= SYMBOL_OPTIONAL; menu_add_entry($2);
menu_add_entry(sym); printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), $2->name);
printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), $2);
}; };
menuconfig_stmt: menuconfig_entry_start config_option_list menuconfig_stmt: menuconfig_entry_start config_option_list
@ -173,7 +191,6 @@ menuconfig_stmt: menuconfig_entry_start config_option_list
current_entry->prompt->type = P_MENU; current_entry->prompt->type = P_MENU;
else else
zconfprint("warning: menuconfig statement without prompt"); zconfprint("warning: menuconfig statement without prompt");
menu_end_entry();
printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno());
}; };
@ -211,15 +228,15 @@ config_option: T_DEFAULT expr if_expr T_EOL
$1->stype); $1->stype);
}; };
config_option: T_SELECT T_WORD if_expr T_EOL config_option: T_SELECT nonconst_symbol if_expr T_EOL
{ {
menu_add_symbol(P_SELECT, sym_lookup($2, 0), $3); menu_add_symbol(P_SELECT, $2, $3);
printd(DEBUG_PARSE, "%s:%d:select\n", zconf_curname(), zconf_lineno()); printd(DEBUG_PARSE, "%s:%d:select\n", zconf_curname(), zconf_lineno());
}; };
config_option: T_IMPLY T_WORD if_expr T_EOL config_option: T_IMPLY nonconst_symbol if_expr T_EOL
{ {
menu_add_symbol(P_IMPLY, sym_lookup($2, 0), $3); menu_add_symbol(P_IMPLY, $2, $3);
printd(DEBUG_PARSE, "%s:%d:imply\n", zconf_curname(), zconf_lineno()); printd(DEBUG_PARSE, "%s:%d:imply\n", zconf_curname(), zconf_lineno());
}; };
@ -237,8 +254,10 @@ symbol_option_list:
| symbol_option_list T_WORD symbol_option_arg | symbol_option_list T_WORD symbol_option_arg
{ {
const struct kconf_id *id = kconf_id_lookup($2, strlen($2)); const struct kconf_id *id = kconf_id_lookup($2, strlen($2));
if (id && id->flags & TF_OPTION) if (id && id->flags & TF_OPTION) {
menu_add_option(id->token, $3); menu_add_option(id->token, $3);
free($3);
}
else else
zconfprint("warning: ignoring unknown option %s", $2); zconfprint("warning: ignoring unknown option %s", $2);
free($2); free($2);
@ -257,6 +276,7 @@ choice: T_CHOICE word_opt T_EOL
sym->flags |= SYMBOL_AUTO; sym->flags |= SYMBOL_AUTO;
menu_add_entry(sym); menu_add_entry(sym);
menu_add_expr(P_CHOICE, NULL, NULL); menu_add_expr(P_CHOICE, NULL, NULL);
free($2);
printd(DEBUG_PARSE, "%s:%d:choice\n", zconf_curname(), zconf_lineno()); printd(DEBUG_PARSE, "%s:%d:choice\n", zconf_curname(), zconf_lineno());
}; };
@ -308,10 +328,10 @@ choice_option: T_OPTIONAL T_EOL
printd(DEBUG_PARSE, "%s:%d:optional\n", zconf_curname(), zconf_lineno()); printd(DEBUG_PARSE, "%s:%d:optional\n", zconf_curname(), zconf_lineno());
}; };
choice_option: T_DEFAULT T_WORD if_expr T_EOL choice_option: T_DEFAULT nonconst_symbol if_expr T_EOL
{ {
if ($1->stype == S_UNKNOWN) { if ($1->stype == S_UNKNOWN) {
menu_add_symbol(P_DEFAULT, sym_lookup($2, 0), $3); menu_add_symbol(P_DEFAULT, $2, $3);
printd(DEBUG_PARSE, "%s:%d:default\n", printd(DEBUG_PARSE, "%s:%d:default\n",
zconf_curname(), zconf_lineno()); zconf_curname(), zconf_lineno());
} else } else
@ -351,13 +371,6 @@ if_block:
| if_block choice_stmt | if_block choice_stmt
; ;
/* mainmenu entry */
mainmenu_stmt: T_MAINMENU prompt nl
{
menu_add_prompt(P_MENU, $2, NULL);
};
/* menu entry */ /* menu entry */
menu: T_MENU prompt T_EOL menu: T_MENU prompt T_EOL
@ -394,6 +407,7 @@ source_stmt: T_SOURCE prompt T_EOL
{ {
printd(DEBUG_PARSE, "%s:%d:source %s\n", zconf_curname(), zconf_lineno(), $2); printd(DEBUG_PARSE, "%s:%d:source %s\n", zconf_curname(), zconf_lineno(), $2);
zconf_nextfile($2); zconf_nextfile($2);
free($2);
}; };
/* comment entry */ /* comment entry */
@ -406,9 +420,7 @@ comment: T_COMMENT prompt T_EOL
}; };
comment_stmt: comment depends_list comment_stmt: comment depends_list
{ ;
menu_end_entry();
};
/* help option */ /* help option */
@ -420,6 +432,17 @@ help_start: T_HELP T_EOL
help: help_start T_HELPTEXT help: help_start T_HELPTEXT
{ {
if (current_entry->help) {
free(current_entry->help);
zconfprint("warning: '%s' defined with more than one help text -- only the last one will be used",
current_entry->sym->name ?: "<choice>");
}
/* Is the help text empty or all whitespace? */
if ($2[strspn($2, " \f\n\r\t\v")] == '\0')
zconfprint("warning: '%s' defined with blank help text",
current_entry->sym->name ?: "<choice>");
current_entry->help = $2; current_entry->help = $2;
}; };
@ -491,7 +514,10 @@ expr: symbol { $$ = expr_alloc_symbol($1); }
| expr T_AND expr { $$ = expr_alloc_two(E_AND, $1, $3); } | expr T_AND expr { $$ = expr_alloc_two(E_AND, $1, $3); }
; ;
symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); } /* For symbol definitions, selects, etc., where quotes are not accepted */
nonconst_symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); };
symbol: nonconst_symbol
| T_WORD_QUOTE { $$ = sym_lookup($1, SYMBOL_CONST); free($1); } | T_WORD_QUOTE { $$ = sym_lookup($1, SYMBOL_CONST); free($1); }
; ;
@ -502,6 +528,7 @@ word_opt: /* empty */ { $$ = NULL; }
void conf_parse(const char *name) void conf_parse(const char *name)
{ {
const char *tmp;
struct symbol *sym; struct symbol *sym;
int i; int i;
@ -509,27 +536,28 @@ void conf_parse(const char *name)
sym_init(); sym_init();
_menu_init(); _menu_init();
rootmenu.prompt = menu_add_prompt(P_MENU, "Crosstool-NG Configuration", NULL);
#if YYDEBUG #if YYDEBUG
if (getenv("ZCONF_DEBUG")) if (getenv("ZCONF_DEBUG"))
zconfdebug = 1; yydebug = 1;
#endif #endif
zconfparse(); yyparse();
if (zconfnerrs) if (yynerrs)
exit(1); exit(1);
if (!modules_sym) if (!modules_sym)
modules_sym = sym_find( "n" ); modules_sym = sym_find( "n" );
tmp = rootmenu.prompt->text;
rootmenu.prompt->text = _(rootmenu.prompt->text); rootmenu.prompt->text = _(rootmenu.prompt->text);
rootmenu.prompt->text = sym_expand_string_value(rootmenu.prompt->text); rootmenu.prompt->text = sym_expand_string_value(rootmenu.prompt->text);
free((char*)tmp);
menu_finalize(&rootmenu); menu_finalize(&rootmenu);
for_all_symbols(i, sym) { for_all_symbols(i, sym) {
if (sym_check_deps(sym)) if (sym_check_deps(sym))
zconfnerrs++; yynerrs++;
} }
if (zconfnerrs) if (yynerrs)
exit(1); exit(1);
sym_set_change_count(1); sym_set_change_count(1);
} }
@ -554,7 +582,7 @@ static bool zconf_endtoken(const struct kconf_id *id, int starttoken, int endtok
if (id->token != endtoken) { if (id->token != endtoken) {
zconf_error("unexpected '%s' within %s block", zconf_error("unexpected '%s' within %s block",
id->name, zconf_tokenname(starttoken)); id->name, zconf_tokenname(starttoken));
zconfnerrs++; yynerrs++;
return false; return false;
} }
if (current_menu->file != current_file) { if (current_menu->file != current_file) {
@ -563,7 +591,7 @@ static bool zconf_endtoken(const struct kconf_id *id, int starttoken, int endtok
fprintf(stderr, "%s:%d: location of the '%s'\n", fprintf(stderr, "%s:%d: location of the '%s'\n",
current_menu->file->name, current_menu->lineno, current_menu->file->name, current_menu->lineno,
zconf_tokenname(starttoken)); zconf_tokenname(starttoken));
zconfnerrs++; yynerrs++;
return false; return false;
} }
return true; return true;
@ -584,7 +612,7 @@ static void zconf_error(const char *err, ...)
{ {
va_list ap; va_list ap;
zconfnerrs++; yynerrs++;
fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno());
va_start(ap, err); va_start(ap, err);
vfprintf(stderr, err, ap); vfprintf(stderr, err, ap);
@ -592,7 +620,7 @@ static void zconf_error(const char *err, ...)
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} }
static void zconferror(const char *err) static void yyerror(const char *err)
{ {
fprintf(stderr, "%s:%d: %s\n", zconf_curname(), zconf_lineno() + 1, err); fprintf(stderr, "%s:%d: %s\n", zconf_curname(), zconf_lineno() + 1, err);
} }
@ -625,7 +653,7 @@ static void print_symbol(FILE *out, struct menu *menu)
fprintf(out, "\nconfig %s\n", sym->name); fprintf(out, "\nconfig %s\n", sym->name);
switch (sym->type) { switch (sym->type) {
case S_BOOLEAN: case S_BOOLEAN:
fputs(" boolean\n", out); fputs(" bool\n", out);
break; break;
case S_TRISTATE: case S_TRISTATE:
fputs(" tristate\n", out); fputs(" tristate\n", out);