使用 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"
。
我正在尝试对使用 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"
。