让我的 Yacc 坐起来乞求
我比以往任何时候都更加确信这里的正确方法是 GLR 语法,如果可能的话。然而,受@Kaz 的启发,我使用 LALR(1) 语法(甚至不使用优先级声明)生成了以下 yacc/bison 语法。
当然,它会作弊,因为问题不能用 LALR(1) 语法解决。IF THEN
在适当的时间间隔,它遍历和IF THEN ELSE
表达式的构造树,并ELSE
根据需要移动子句。
需要为可能的运动重新检查的节点被赋予 AST 节点类型,IFSEQ
并且ELSE
使用经典的匹配如果/不匹配如果语法,使用传统的最紧密匹配语法附加子句。完全匹配的IF THEN ELSE
子句不需要重新排列;树重写将应用于与ELSE
右手操作数不匹配的第一个相关的表达式(如果有的话)。将表达式的完全匹配前缀与IF
需要重新排列的尾部分开需要几乎重复一些规则;几乎重复的规则不同之处在于它们的动作直接产生TERNARY
节点而不是IFSEQ
节点。
为了正确回答这个问题,还需要重新排列一些IFF
节点,因为IFF
绑定比从句更弱,比THEN
从句更紧密ELSE
。我认为这意味着:
IF p THEN q IFF IF r THEN s ==> ((p → q) ↔ (r → s))
IF p THEN q IFF r ELSE s IFF t ==> (p ? (q ↔ r) : (s ↔ t))
IF p THEN q IFF IF r THEN s ELSE t IFF u ==> (p ? (q ↔ (r → s)) : (t ↔ u))
虽然我不确定这是什么要求(尤其是最后一个),但我真的不认为这是一个好主意。在下面的语法中,如果要IFF
应用于IF p THEN q
子表达式,则必须使用括号;IF p THEN q IFF r
产生p → (q ↔ r)
并且p IFF IF q THEN r
是语法错误。
坦率地说,我认为将箭头用于条件和双条件会更容易(如上面的注释中所示),并且IF THEN ELSE
仅用于三元选择器表达式(上面用 C 样式? :
语法编写,这是另一种可能性)。这将产生更少的惊喜。但这不是我的语言。
具有浮动优先级的双条件运算符的一种解决方案是分两次解析。第一遍将仅识别IF p THEN q
没有附加的运算符ELSE
,使用类似于此处提出的机制,并p -> q
通过删除IF
和更改拼写来更改它们THEN
。其他运算符不会被解析并保留括号。然后它将生成的令牌流馈送到具有更传统语法风格的第二个 LALR 解析器。我可能会转而编写代码,只是因为我认为两遍野牛解析器偶尔有用,而且很少有例子漂浮。
这是树重写解析器。我为长度道歉:
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void yyerror(const char* msg);
int yylex(void);
typedef struct Node Node;
enum AstType { ATOM, NEG, CONJ, DISJ, IMPL, BICOND, TERNARY,
IFSEQ
};
struct Node {
enum AstType type;
union {
const char* atom;
Node* child[3];
};
};
Node* node(enum AstType type, Node* op1, Node* op2, Node* op3);
Node* atom(const char* name);
void node_free(Node*);
void node_print(Node*, FILE*);
typedef struct ElseStack ElseStack;
struct ElseStack {
Node* action;
ElseStack* next;
};
ElseStack* build_else_stack(Node*, ElseStack*);
ElseStack* shift_elses(Node*, ElseStack*);
%}
%union {
const char* name;
struct Node* node;
}
%token <name> T_ID
%token T_AND "and"
T_ELSE "else"
T_IF "if"
T_IFF "iff"
T_NOT "not"
T_OR "or"
T_THEN "then"
%type <node> term conj disj bicond cond mat unmat tail expr
%%
prog : %empty | prog stmt;
stmt : expr '\n' { node_print($1, stdout); putchar('\n'); node_free($1); }
| '\n'
| error '\n'
term : T_ID { $$ = atom($1); }
| "not" term { $$ = node(NEG, $2, NULL, NULL); }
| '(' expr ')' { $$ = $2; }
conj : term
| conj "and" term { $$ = node(CONJ, $1, $3, NULL); }
disj : conj
| disj "or" conj { $$ = node(DISJ, $1, $3, NULL); }
bicond: disj
| disj "iff" bicond { $$ = node(BICOND, $1, $3, NULL); }
mat : bicond
| "if" expr "then" mat "else" mat
{ $$ = node(IFSEQ, $2, $4, $6); }
unmat: "if" expr "then" mat
{ $$ = node(IFSEQ, $2, $4, NULL); }
| "if" expr "then" unmat
{ $$ = node(IFSEQ, $2, $4, NULL); }
| "if" expr "then" mat "else" unmat
{ $$ = node(IFSEQ, $2, $4, $6); }
tail : "if" expr "then" mat
{ $$ = node(IFSEQ, $2, $4, NULL); }
| "if" expr "then" unmat
{ $$ = node(IFSEQ, $2, $4, NULL); }
cond : bicond
| tail { shift_elses($$, build_else_stack($$, NULL)); }
| "if" expr "then" mat "else" cond
{ $$ = node(TERNARY, $2, $4, $6); }
expr : cond
%%
/* Walk the IFSEQ nodes in the tree, pushing any
* else clause found onto the else stack, which it
* returns.
*/
ElseStack* build_else_stack(Node* ifs, ElseStack* stack) {
if (ifs && ifs->type != IFSEQ) {
stack = build_else_stack(ifs->child[1], stack);
if (ifs->child[2]) {
ElseStack* top = malloc(sizeof *top);
*top = (ElseStack) { ifs->child[2], stack };
stack = build_else_stack(ifs->child[2], top);
}
}
return stack;
}
/* Walk the IFSEQ nodes in the tree, attaching elses from
* the else stack.
* Pops the else stack as it goes, freeing popped
* objects, and returns the new top of the stack.
*/
ElseStack* shift_elses(Node* n, ElseStack* stack) {
if (n && n->type == IFSEQ) {
if (stack) {
ElseStack* top = stack;
stack = shift_elses(n->child[2],
shift_elses(n->child[1], stack->next));
n->type = TERNARY;
n->child[2] = top;
free(top);
}
else {
shift_elses(n->child[2],
shift_elses(n->child[1], NULL));
n->type = IMPL;
n->child[2] = NULL;
}
}
return stack;
}
Node* node(enum AstType type, Node* op1, Node* op2, Node* op3) {
Node* rv = malloc(sizeof *rv);
*rv = (Node){type, .child = {op1, op2, op3}};
return rv;
}
Node* atom(const char* name) {
Node* rv = malloc(sizeof *rv);
*rv = (Node){ATOM, .atom = name};
return rv;
}
void node_free(Node* n) {
if (n) {
if (n->type == ATOM) free((char*)n->atom);
else for (int i = 0; i < 3; ++i) node_free(n->child[i]);
free(n);
}
}
const char* typename(enum AstType type) {
switch (type) {
case ATOM: return "ATOM";
case NEG: return "NOT" ;
case CONJ: return "CONJ";
case DISJ: return "DISJ";
case IMPL: return "IMPL";
case BICOND: return "BICOND";
case TERNARY: return "TERNARY" ;
case IFSEQ: return "IF_SEQ";
}
return "**BAD NODE TYPE**";
}
void node_print(Node* n, FILE* out) {
if (n) {
if (n->type == ATOM)
fputs(n->atom, out);
else {
fprintf(out, "(%s", typename(n->type));
for (int i = 0; i < 3 && n->child[i]; ++i) {
fputc(' ', out); node_print(n->child[i], out);
}
fputc(')', out);
}
}
}
void yyerror(const char* msg) {
fprintf(stderr, "%s\n", msg);
}
int main(int argc, char** argv) {
return yyparse();
}
词法分析器几乎是微不足道的。(这个使用小写关键字,因为我的手指更喜欢那个,但改变起来很简单。)
%{
#include "ifelse.tab.h"
%}
%option noinput nounput noyywrap nodefault
%%
and { return T_AND; }
else { return T_ELSE; }
if { return T_IF; }
iff { return T_IFF; }
not { return T_NOT; }
or { return T_OR; }
then { return T_THEN; }
[[:alpha:]]+ { yylval.name = strdup(yytext);
return T_ID; }
([[:space:]]{-}[\n])+ ;
\n { return '\n'; }
. { return *yytext;}
如所写,解析器/词法分析器一次读取一行,并为每一行打印 AST(因此不允许多行表达式)。我希望很清楚如何改变它。