使用 yacc/bison 和 tab.h 最佳实践的外源构建

Out-of-source build with yacc/bison and tab.h best practice

我正在尝试对使用 GNU bison 和 flex 进行解析和词法分析的项目使用源外构建。

构建由 GNU Make 管理,一切顺利,直到我将逻辑从主 .y 文件分离到新的 .c 文件。 Makefile 取自 this post.

主要问题是 .tab.h 是由 bison 生成的,它是在一个构建目录中生成的:./build/src/parser.tab.h.

我设法以临时方式解决了这个问题,方法是使用相对路径 #include "../build/src/parser.tab.h" 包含 .tab.h 并将 .tab.c 添加到 C 文件的依赖项中。

这被认为是一种好的做法吗? 有没有办法在 Makefile and/or 中隐式说明这一点,包括生成的 .tab.h 文件?

这是我的 C 文件:

#include "../build/src/parser.tab.h"

int main(int argc, char *argv[])
{
    yyparse();
    return 0;
}

Makefile:

TARGET_EXEC := parser

BUILD_DIR := ./build
SRC_DIRS := ./src

SRCS := $(shell find $(SRC_DIRS) -name *.c -or -name *.y -or -name *.l)
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
DEPS := $(OBJS:.o=.d)

INC_DIRS := $(shell find $(SRC_DIRS) -type d)
INC_FLAGS := $(addprefix -I,$(INC_DIRS))

CC := gcc
CFLAGS := -O0 -Wall -Wextra -Wpedantic -std=c17
CPPFLAGS := $(INC_FLAGS) -MMD -MP
LDFLAGS := -ly -ll

YACC := bison
YFLAGS := -d

LEX := flex
LFLAGS :=

$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
    $(CC) $(OBJS) -o $@ $(LDFLAGS)

$(BUILD_DIR)/%.c.o: %.c build/src/parser.tab.c
    mkdir -p $(dir $@)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

%.y.o: %.tab.c
    mkdir -p $(dir $@)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

%.l.o: %.yy.c
    mkdir -p $(dir $@)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

$(BUILD_DIR)/%.tab.c: %.y
    mkdir -p $(dir $@)
    $(YACC) $(YFLAGS) $< -o $@

$(BUILD_DIR)/%.yy.c: %.l
    mkdir -p $(dir $@)
    $(LEX) $(LFLAGS) -o $@ $<

.PHONY: clean
clean:
    rm -r $(BUILD_DIR)

-include $(DEPS)

这是 MWE 词法分析器和解析器:

%{
#include "parser.tab.h"
#include <stdio.h>
%}
ws  [ \t]+
%%
{ws}    { ; }   // skip whitespaces
.   { printf("unknown token %c\n", yytext[0]); }
%%
prgm: ;

之前和之后的树:

.
├── Makefile
├── build
│   ├── parser
│   └── src
│       ├── lexer.l.d
│       ├── lexer.l.o
│       ├── lexer.yy.c
│       ├── main.c.d
│       ├── main.c.o
│       ├── parser.tab.h
│       ├── parser.y.d
│       └── parser.y.o
└── src
    ├── lexer.l
    ├── main.c
    └── parser.y
.
├── Makefile
└── src
    ├── lexer.l
    ├── main.c
    └── parser.y

首先,您的 makefile 比它需要的更混乱,因为您在某些地方使用 $(BUILD_DIR) 变量,而在其他地方使用硬编码的 build:到处使用变量。

其次,不,你不应该在你的源文件中包含路径。这意味着每当您更改 makefile 以移动某些内容时,您也必须编辑源文件。

相反,只需将搜索 header 文件的路径添加到编译器命令行即可。您已经有一个 INC_FLAGS 变量,其中包含告诉编译器在哪里寻找 header 的选项;只需添加一个新的:

INC_FLAGS := $(addprefix -I,$(INC_DIRS)) -I$(BUILD_DIR)/src

现在您可以在源代码中使用 #include "y.tab.h"