Мне немного сложно понять, что вы на самом деле пытаетесь сделать, поэтому я сделал несколько предположений и предоставил некоторые альтернативы ниже.Я надеюсь, что это близко.
Основная проблема - двусмысленность в ваших грамматических спецификациях.Одна из них может быть просто ошибкой: в соответствии с вашими шаблонами левая часть EQUAL
выглядит либо как отдельный name
, либо как разделенный запятыми список name
, заключенный в скобки.Однако ваша грамматика допускает разделенный запятыми список из name
s, с возможностью того, что первый (или единственный) элемент в списке может быть списком имен в скобках:
constant_names
: constant_name
| constant_names ',' constant_name
| '(' constant_names ')
Это будет соответствоватьa
, a, b
, (a, b)
, (a, b), c
и (a, b), c, d
.Но я думаю, что только первый и третий из них на самом деле предназначены.
В любом случае у вас есть два производства:
constant_style
: constant_name constant_class
| constant_list
Для первого у нас есть:
constant_class
: SDL_K_EQUALS sdl_decimal
в то время как для второго у нас есть:
constant_list: names_equal
names_equal
: constant_names SDL_K_EQUALS sdl_decimal
Поскольку constant_name
может соответствовать одному name
, есть два различных способа сопоставления answer = 42
, которые неизбежно приведут кконфликт синтаксического анализа.
Но есть еще один возможный синтаксический анализ для answer = 42
:
constant_set
: names_equal
Итак, давайте начнем с упрощения всего этого, а затем мы можем вернуться к чему (возможно,быть) ваша первоначальная цель.Идея состоит в том, чтобы учесть все, что синтаксически похоже:
constant-stmt : "CONSTANT" clause-list ';'
clause-list : clause | clause-list ',' clause
clause : left-hand-side "EQUALS" right-hand-side
left-hand-side : name | '(' name-list ')'
name-list : name | name-list ',' name
right-hand-side: expression /* See below */
Я надеюсь, что все достаточно просто для понимания.
Но мы можем видеть из оригинала, что (по крайней мере, при определенных обстоятельствах)), для right-hand-side
есть гораздо больше опций, чем показано в приведенном выше фрагменте.Было бы тривиально просто добавить другие синтаксисы к right-hand-side
.Тем не менее, похоже, что предполагается, что эти дополнительные параметры доступны только в том случае, если существует одно предложение.В этом случае мы могли бы сделать что-то вроде этого:
constant-stmt : "CONSTANT" constant-body ';'
constant-body : complex-clause | clause-list
clause-list : clause | clause-list ',' clause
clause : left-hand-side "EQUALS" right-hand-side
right-hand-side: expression
complex-clause : left-hand-side "EQUALS" complex-rhs
complex-rhs : expression numeric-options
| "STRING" string-literal string-options
Но, к сожалению, это снова двусмысленно, потому что numeric-options
может быть пустым, поэтому expression
будет соответствовать right-hand-side
и complex-right-hand-side
.
На практике эта двусмысленность не имеет значения.Нет семантической разницы между объявлением name EQUALS expression
как единственным объявлением в объявлении CONSTANT
или одним из списка таких объявлений.Таким образом, одна возможность - просто игнорировать возникающий конфликт уменьшения / уменьшения, возможно, поместив %expect 1
в файл.Но это не очень приятно на самом деле.Поэтому мы попытаемся устранить эту проблему.
Один из способов - настаивать на том, чтобы у первых complex-rhs
был хотя бы один numeric-option
.Но это будет действительно раздражающим.Во-первых, нам нужно создать еще один тип предложения - first-complex-clause
или что-то подобное, а во-вторых, нам нужно выписать требование, чтобы присутствовал хотя бы один параметр.
КакБолее простая альтернатива, мы можем просто потребовать, чтобы clause-list
имел как минимум два clause
с, а не только один.Поскольку каждый clause
может также соответствовать complex-clause
, ограничение на clause-list
не будет отклонять любое действительное clause
.Так что это дает нам очень незначительное изменение:
constant-body : complex-clause | clause-list
clause-list : clause ',' clause | clause-list ',' clause
Приложение
С более точным описанием предполагаемого синтаксиса (хотя в нем все еще отсутствуют некоторые детали), я изменилвыше грамматики, чтобы попытаться разобрать полный язык.Основной принцип остается неизменным: определение состоит из одного complex-clause
(который включает в себя случай простого предложения) или списка по крайней мере из двух простых clause
s.Единственным отличием является способ определения двух типов предложений.
Я также исправил создание списка имен, чтобы отдельные имена можно было опустить (например, (bad_block,bad_data,,,,overlay,rewrite)
).Как указано в руководстве, список должен содержать хотя бы одно имя, что немного усложняет грамматику.
Я добавил опции из руководства (но не дополнительные в EBNF).Я не пытался иметь дело с пропущенными точками с запятой, которые, как представляется, недопустимы в руководстве, хотя есть пример объявления без конечной точки с запятой.(Это может быть опечатка.) Я добавил определение expression
, насколько я понимаю.
Я также добавил оператор присваивания локальных имен, потому что он был в тестовом файле, и это было легко.
Вот грамматика:
definition : %empty
| definition constant-defn
| definition local-assign
local-assign : LOCAL_NAME '=' expression ';'
constant-defn : "CONSTANT" constant-body ';'
constant-body : complex-clause | clause-list
clause-list : clause ',' clause | clause-list ',' clause
clause : name "EQUALS" expression
| name-list "EQUALS" expression
complex-clause : name "EQUALS" expression numeric-options
| name-list "EQUALS" expression list-options
| name "EQUALS" "STRING" string-literal string-options
| name-list "EQUALS" "STRING" string-literal string-options
name-list : '(' names ')'
names : optional-commas name | names ',' name | names ','
optional-commas : %empty | optional-commas ','
string-options : prefix-option tag-option
numeric-options : string-options counter-option typename-option
list-options : increment-option numeric-options
increment-option: %empty | "INCREMENT" expression
prefix-option : %empty | "PREFIX" prefix-name
tag-option : %empty | "TAG" tag-name
counter-option : %empty | "COUNTER" LOCAL_NAME
typename-option : %empty | "TYPENAME" name
expression : '-' expression %prec UNOP
| expression '*' expression
| expression '/' expression
| expression '+' expression
| expression '-' expression
| expression '@' expression
| expression '&' expression
| expression '!' expression
| '(' expression ')'
| INTEGER
| IDENT
| LOCAL_NAME
name : IDENT | QUOTED_IDENT
prefix-name : IDENT | QUOTED_NULL | QUOTED_IDENT
tag-name : IDENT | QUOTED_NULL | QUOTED_IDENT | QUOTED_TAG
string-literal : QUOTED_NULL | QUOTED_IDENT | QUOTED_TAG | QUOTED_STRING
Вы увидите, что я добавилразличение различных типов строк в кавычках, так что можно иметь дело с (большей частью) различными контекстами, в которых могут появляться строки в кавычках.Я не включил использование строк в кавычках длиной до 4 символов в качестве целочисленных констант, как указано в руководстве, потому что я не заметил этого вовремя, а также потому, что не смог выяснить с кратким чтением, является линамерение состоит в том, чтобы разрешить константы в выражениях, имена которых должны быть заключены в кавычки (потому что имя конфликтует с ключевым словом);в этом случае мне кажется, что существует двусмысленность с указанным в кавычках именем из четырех или менее символов.
В случае, если неясно, как работает дискриминация (и, вероятно, нет), вот сопутствующая информацияопределение гибкости:
id [[:alpha:]$_][[:alnum:]$_]*
%%
[[:space:]]+ ;
CONSTANT { return K_CONSTANT; }
COUNTER { return K_COUNTER; }
EQUALS { return K_EQUALS; }
INCREMENT { return K_INCREMENT; }
PREFIX { return K_PREFIX; }
STRING { return K_STRING; }
TAG { return K_TAG; }
TYPENAME { return K_TYPENAME; }
[#]{id} { yylval.str = strndup(yytext, yyleng); return LOCAL_NAME; }
{id} { yylval.str = strndup(yytext, yyleng); return IDENT; }
["]{id}["] { yylval.str = strndup(yytext+1, yyleng-2); return QUOTED_IDENT; }
["]["] { yylval.str = strndup(yytext+1, yyleng-2); return QUOTED_NULL; }
["][[:alnum:]$_]+["] { yylval.str = strndup(yytext+1, yyleng-2); return QUOTED_TAG; }
["][[:print:]]+["] { yylval.str = strndup(yytext+1, yyleng-2); return QUOTED_STRING; }
[[:digit:]]+ { yylval.dec = strtoul(yytext, 0, 0); return INTEGER; }
. { return *yytext; }
Я провел довольно неадекватный тест, запустив его с определениями в конце OP (хотя я добавил трейлинг ;
во второй строке).На самом деле я не проверял, что дерево разбора было правильным, но оно определенно проходит через парсер.