我在理解你的问题时遇到了一些麻烦。如果您描述了问题,而不是仅仅含糊地断言它可能与逗号有关,那会容易得多。当然,它确实与逗号有关:您的语法坚持在 afterifs
和 after中都有逗号port
。(另外,要么你没有正确复制和粘贴,要么真的很奇怪ifs
。)
实际上,您不需要ifs
,但您需要区分if
声明末尾的 an 和任何其他if
:
port_declaration: tail_ports;
ports: port ','
| port ',' ports
| if
| if ports
;
tail_ports
: port
| tail_if
| port ',' tail_ports
| if tail_ports
;
if : IF '(' condition ')' ports END_IF
;
tail_if
: IF '(' condition ')' tail_ports END_IF
;
(我写的是右递归,因为它更容易理解,恕我直言,而且从右递归列表中构建 AST 也更容易。参见 Levine,第 66 页。在现代机器上,解析堆栈确实没有问题增长;默认的最大堆栈深度为 10,000;如果这还不够,您可以增加它。)
编辑:我想我终于得到了上面的权利。
这类问题通常通过两遍解析来解决;首先是预处理过程,其输出被馈送到真正的解析器中。标记化只进行一次;预处理解析器不区分语言标记;它只关心它自己的令牌。(C 预处理器可以将标记与##
运算符融合,但它从不拆分标记;这通常被证明是一种合理的方法。)这可以用 bison 来实现,尽管让数据流正确有点挑战;我个人更喜欢lemon
词法分析器将标记推送到解析器的方法,因为这更容易扩展。
但是,在您的特定情况下,您希望预处理器只接受完整的短语 ( port
),因此正如您所说,挑战是,
正确的,即使最后一个port
被深埋在if
结构中。因此,上述区分if
和的方法tail_if
。
构建 AST 有多种方法,具体取决于您是否可以在解析时评估条件。如果可以,那么您可以将不需要的端口完全排除在外,这可能是最简单的解决方案。
如果您需要在应用条件之前进行整个解析,那么您仍然有几个选择:明显的一种选择是构建一个节点为port
or的树conditional_list
,或者您有效地构建操作向量的代码生成样式build-port
和jump-if-condition-false
。这两个选项都需要某种形式的标记联合。
/Edit(以下仍然不正确。)
typedef struct PortList {
Port* port;
struct PortList* next;
} PortList;
typedef struct ConditionalPortList {
Condition* cond; /* Optional condition; if absent, unconditional */
PortList* ports;
} ConditionalPortList;
typedef struct PortDeclaration {
ConditionalPortList* clause;
struct PortDeclaration *next;
} PortDeclaration;
这很容易构建(它可能比这个片段更容易,我有点不习惯。)(警告:未尝试):
%union {
PortList* port_list;
ConditionalPortList* conditional;
PortDeclaration* port_declaration;
/* ... */
}
%type <port_list> ports
%type <conditional> if always
%type <port_declaration> port_tail port_declaration
/* ... */
%%
ports: port { $$ = NewPortList($1, NULL); }
| port ',' ports { $$ = NewPortList($1, $3); }
;
if: IF '(' condition ')' ports ENDIF
{ $$ = NewConditionalPortList($3, $5); }
;
always: ports { $$ = NewConditionalPortList(NULL, $1); }
;
port_tail: if
| if port_tail { $$ = NewPortDeclaration($1, $2); }
| if always port_tail
{ $$ = NewPortDeclaration($1, NewPortDeclaration($2, $3); }
;
port_declaration:
'(' always ')' ';' { $$ = $2; }
| '(' port_tail ')' ';' { $$ = $2; }
| '(' always port_tail ')' ';'
{ $$ = NewPortDeclaration($2, $3); }
;