Невозможно сказать бизону (или yacc), что порядок не имеет значения.Правила строго заказаны.
Таким образом, у вас есть два варианта:
Список всех возможных заказов.Если вы сделаете это, остерегайтесь неясностей, вызванных дополнительными постановками.Вам действительно нужно перечислить все заказы и подмножества.Это растет экспоненциально.
Просто примите любой список компонентов, как список.Это будет принимать повторяющиеся компоненты, поэтому вам нужно поймать это в семантическом действии, если вам небезразлично.
Второй вариант почти всегда тот, который вам нужен.Реализация обычно тривиальна, потому что вы захотите где-то хранить компоненты;пока это где-то имеет уникальное значение (например, NULL
), что означает «еще не установлено», вам нужно только проверить это значение перед его установкой.Например, а не тот, что в вопросе):
%{
#include <stdbool>
enum Type {
TYPE_DEFAULT = 0, TYPE_BYTE, TYPE_WORD, TYPE_LONG, TYPE_QUAD
};
typedef struct Item Item;
struct Item {
const char *name;
enum Type type;
int storage; /* 0: unset, 1: TYPEDEF */
const char *prefix;
const char *tag;
};
// ...
// Relies on the fact that NULL and 0 are converted to boolean
// false. Returns true if it's ok to do the set (i.e. thing
// wasn't set).
bool check_dup(bool already_set, const char* thing) {
if (already_set)
fprintf(stderr, "Duplicate %s ignored at line %d\n", thing, yylineno);
return !already_set;
}
%}
%union {
const char *str;
Item *item;
// ...
}
%type <item> item item-def
%token <str> NAME STRING
%%
/* Many of the actions below depend on $$ having been set to $1.
* If you use a template which doesn't provide that guarantee, you
* will have to add $$ = $1; to some actions.
*/
item: item-def { /* Do whatever is necessary to finalise $1 */ }
item-def
: "ITEM" NAME
{ $$ = calloc(1, sizeof *$$); $$->name = $2; }
| item-def "BYTE"
{ if (check_dup($$->type, "type") $$->type = TYPE_BYTE; }
| item-def "WORD"
{ if (check_dup($$->type, "type") $$->type = TYPE_WORD; }
| item-def "LONG"
{ if (check_dup($$->type, "type") $$->type = TYPE_LONG; }
| item-def "QUAD"
{ if (check_dup($$->type, "type") $$->type = TYPE_QUAD; }
| item-def "TYPEDEF"
{ if (check_dup($$->storage, "storage") $$->storage = 1; }
| item-def "PREFIX" STRING
{ if (check_dup($$->prefix, "prefix") $$->prefix = $3; }
| item-def "TAG" STRING
{ if (check_dup($$->tag, "tag") $$->tag = $3; }
Вы можете разделить все эти item-def
произведения на что-то вроде:
item-def: "ITEM" NAME { /* ... */ }
| item-def item-option
item-option: type | storage | prefix | tag
Но тогда в действиях вам нужнополучить объект предмета, который не является частью производства опциона.Вы можете сделать это с помощью функции Bison, которая позволяет вам заглядывать в стек анализатора:
prefix: "PREFIX" STRING { if (check_dup($<item>0->prefix, "prefix")
$<item>0->prefix = $2; }
В этом контексте $0
будет ссылаться на то, что было до prefix
, то есть на то, что было до item-option
, который является item-def
.См. Конец этого раздела в руководстве Bison, где он описывает эту практику как "рискованную".Это также требует от вас явного указания тега, поскольку bison не выполняет грамматический анализ, необходимый для проверки правильности использования $0
, который определил бы его тип.