Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/fernandodanielmaqueda/gcc-bison-flex-GNUmakefile/llms.txt

Use this file to discover all available pages before exploring further.

When your C project requires a custom parser or scanner — for a configuration file format, a command language, or a domain-specific language — the framework handles the code generation step automatically. Bison .y files are processed into .tab.c, .tab.h, and .output files; Flex .l files are processed into .lex.yy.c files. Both sets of generated files are placed in obj/ and compiled alongside your handwritten C code.

When to use this approach

Use this setup whenever your project includes Bison grammar files (.y) or Flex scanner files (.l). The framework detects their presence in _SOURCES and automatically:
  • Invokes bison with -d -v to produce the parser implementation and header
  • Invokes flex to produce the scanner implementation
  • Adds -ly and/or -lfl to LDLIBS at link time
  • Adds the obj/ subdirectory that contains the generated header to -I flags

Generated file locations

Input fileGenerated files
src/grammar.yobj/src/grammar.tab.c, obj/src/grammar.tab.h, obj/src/grammar.output
src/scanner.lobj/src/scanner.lex.yy.c
All generated files are placed under obj/ mirroring the source directory hierarchy. The .output file contains the Bison state report (enabled via --report=state --report=itemset --report=lookahead by default).
Do not list generated files such as *.tab.c or *.lex.yy.c in _SOURCES — the framework produces them itself. The _FIND_SOURCES FIND expression explicitly excludes them with ! -name '*.tab.c' ! -name '*.lex.yy.c'.

Project layout

myproject/
├── GNUmakefile
├── mkframework/
├── src/
│   ├── main.c
│   ├── grammar.y
│   └── scanner.l
├── bin/
│   └── myparser
└── obj/
    └── src/
        ├── main.o
        ├── grammar.tab.c      # generated by bison
        ├── grammar.tab.h      # generated by bison
        ├── grammar.tab.o
        ├── grammar.output     # bison state report
        ├── scanner.lex.yy.c   # generated by flex
        └── scanner.lex.yy.o

Setting up the GNUmakefile

1

Include main.mk and declare the program

Start with the standard framework include and program declaration:
$(if $(MKFWK_MAIN_MAKEFILE),$(eval include $(MKFWK_MAIN_MAKEFILE)),$(eval include mkframework/main.mk))

.PHONY: $(MKFWK_LAST_INCLUDING_DIR)GNUmakefile

program1 := myparser
BIN_PROGRAMS += $(program1)

$(program1)_ARGS :=
$(program1)_CWD  := $(BINDIR)
2

Set the source directory and discover all source types

The _FIND_SOURCES FIND expression discovers .c, .y, and .l files together while excluding already-generated files:
$(program1)_SRCDIR := src/
$(call mkfwk_make_check_set_directory_existence,$(program1)_SRCDIR)

$(program1)_FIND_SOURCES := $(shell $(FIND) \
    $(if $($(program1)_SRCDIR),'$($(program1)_SRCDIR)',.) \
    -type f \( \( -name '*.c' ! -name '*.tab.c' ! -name '*.lex.yy.c' \) \
              -o \( -name '*.y' -o -name '*.l' \) \) \
    -print | $(SED) -e 's?^\./??' ;)

$(program1)_SOURCES := $($(program1)_FIND_SOURCES)
$(program1)_LDADD   := $($(program1)_SOURCES)
With src/main.c, src/grammar.y, and src/scanner.l present, _FIND_SOURCES will produce:
src/main.c src/grammar.y src/scanner.l
3

Add -I flags including the YACC header directory

When .y files are present, the generated *.tab.h header lives in obj/src/ (not src/). The framework’s _FIND_-I_FLAGS expression automatically includes that directory:
$(program1)_INCLUDEDIR := $($(program1)_SRCDIR)
$(call mkfwk_make_check_set_directory_existence,$(program1)_INCLUDEDIR)

$(program1)_FIND_-I_FLAGS := \
  $(sort $(shell $(FIND) \
      $(if $($(program1)_SRCDIR),'$($(program1)_INCLUDEDIR)',.) \
      -type f -name '*.h' -print \
    | $(SED) -e 's?^\./??' -e 's?[^/]*$$??' -e 's?/$$??' \
             -e "s?^?-I'?" -e "s?$$?'?" \
             -e "s?^-I''$$?-I'.'?" ;)) \
  $(if $(filter %.y,$($(program1)_SOURCES)), \
      $(shell $(PRINTF) '%s\n' \
          '$(subst $(SPACE),'$(SPACE)',$(patsubst %.y,$(OBJDIR)%,$(filter %.y,$($(program1)_SOURCES))))' \
        | $(SED) -e 's?[^/]*$$??' -e 's?/$$??' \
                 -e "s?^?-I'?" -e "s?$$?'?" \
                 -e "s?^-I''$$?-I'.'?" ;))

$(program1)_CPPFLAGS += $($(program1)_FIND_-I_FLAGS)
The second $(if $(filter %.y,...)) block evaluates only when .y files are in _SOURCES. It adds -I'obj/src/' so that #include "grammar.tab.h" resolves correctly from both handwritten and generated C files.
4

Set per-program flags

$(program1)_CPPFLAGS =
$(program1)_CFLAGS   =
$(program1)_ASFLAGS  =
$(program1)_LDFLAGS  =
The global LDLIBS already includes -lm. The framework appends -ly and -lfl automatically when .y or .l files appear in _SOURCES.
5

Expand MKFWK_FOOTER

$(eval $(value MKFWK_FOOTER))
This must be the last line of your GNUmakefile.

Complete GNUmakefile example

$(if $(MKFWK_MAIN_MAKEFILE),$(eval include $(MKFWK_MAIN_MAKEFILE)),$(eval include mkframework/main.mk))

.PHONY: $(MKFWK_LAST_INCLUDING_DIR)GNUmakefile

# ── Program declaration ──────────────────────────────────────────────────────

program1 := myparser
BIN_PROGRAMS += $(program1)

$(program1)_ARGS :=
$(program1)_CWD  := $(BINDIR)

# ── Sources ──────────────────────────────────────────────────────────────────

$(program1)_SRCDIR := src/
$(call mkfwk_make_check_set_directory_existence,$(program1)_SRCDIR)

$(program1)_FIND_SOURCES := $(shell $(FIND) \
    $(if $($(program1)_SRCDIR),'$($(program1)_SRCDIR)',.) \
    -type f \( \( -name '*.c' ! -name '*.tab.c' ! -name '*.lex.yy.c' \) \
              -o \( -name '*.y' -o -name '*.l' \) \) \
    -print | $(SED) -e 's?^\./??' ;)

$(program1)_SOURCES := $($(program1)_FIND_SOURCES)
$(program1)_LDADD   := $($(program1)_SOURCES)

# ── Flags ────────────────────────────────────────────────────────────────────

$(program1)_CPPFLAGS =
$(program1)_CFLAGS   =
$(program1)_ASFLAGS  =
$(program1)_LDFLAGS  =

# ── Include path auto-discovery (includes obj/ for .tab.h) ──────────────────

$(program1)_INCLUDEDIR := $($(program1)_SRCDIR)
$(call mkfwk_make_check_set_directory_existence,$(program1)_INCLUDEDIR)

$(program1)_FIND_-I_FLAGS := \
  $(sort $(shell $(FIND) \
      $(if $($(program1)_SRCDIR),'$($(program1)_INCLUDEDIR)',.) \
      -type f -name '*.h' -print \
    | $(SED) -e 's?^\./??' -e 's?[^/]*$$??' -e 's?/$$??' \
             -e "s?^?-I'?" -e "s?$$?'?" \
             -e "s?^-I''$$?-I'.'?" ;)) \
  $(if $(filter %.y,$($(program1)_SOURCES)), \
      $(shell $(PRINTF) '%s\n' \
          '$(subst $(SPACE),'$(SPACE)',$(patsubst %.y,$(OBJDIR)%,$(filter %.y,$($(program1)_SOURCES))))' \
        | $(SED) -e 's?[^/]*$$??' -e 's?/$$??' \
                 -e "s?^?-I'?" -e "s?$$?'?" \
                 -e "s?^-I''$$?-I'.'?" ;))

$(program1)_CPPFLAGS += $($(program1)_FIND_-I_FLAGS)

# ── Library path auto-discovery ──────────────────────────────────────────────

$(program1)_LIBDIR := $($(program1)_SRCDIR)
$(call mkfwk_make_check_set_directory_existence,$(program1)_LIBDIR)

$(program1)_FIND_-L_FLAGS := $(sort \
    $(shell $(FIND) \
        $(if $($(program1)_SRCDIR),'$($(program1)_LIBDIR)',.) \
        -type f \( -name 'lib*.a' -o -name 'lib*.so' \) -print \
      | $(SED) -e 's?^\./??' -e 's?[^/]*$$??' -e 's?/$$??' \
               -e "s?^?-L'?" -e "s?$$?'?" \
               -e "s?^-L''$$?-L'.'?" ;))

$(program1)_LDFLAGS += $($(program1)_FIND_-L_FLAGS)

# ── Footer (must be last) ────────────────────────────────────────────────────

$(eval $(value MKFWK_FOOTER))

How -ly and -lfl are added

When the framework processes your program’s link rule, it checks whether any .y or .l files appear in _SOURCES and appends the corresponding library flags:
# From making.mk — appended to the CC linking command:
$(LDLIBS) $(if $(filter %.y,$($(2)_SOURCES)),-ly) $(if $(filter %.l,$($(2)_SOURCES)),-lfl)
You do not need to add -ly or -lfl yourself. If the linker cannot find liby.a or libfl.a, add an explicit -L path to LDFLAGS.

Enabling YACC debug mode

Setting YACC_DEBUG=X passes -t to Bison and defines YYDEBUG=1 when compiling the generated .tab.c file. This compiles in Bison’s tracing machinery. To activate tracing at runtime, set yydebug = 1 in your C source before calling yyparse():
extern int yydebug;

int main(void) {
#if YYDEBUG
    yydebug = 1;
#endif
    return yyparse();
}
Enable it on the command line:
make all YACC_DEBUG=X
When VERBOSE=X is active and YACC_DEBUG is set, the framework prints a reminder with the exact code snippet to add.
YACC_DEBUG is disabled by default (YACC_DEBUG=). Enabling it in production builds adds significant trace output.

Enabling LEX debug mode

Setting LEX_DEBUG=X passes -d to Flex, which causes the generated scanner to print tracing messages for every rule it matches:
make all LEX_DEBUG=X

Controlling warnings

YACC_WARNINGS=X (default on) passes -Wall to Bison. LEX_WARNINGS=X is available but has no flags assigned by default — you can extend it in main.mk if needed:
make all YACC_WARNINGS=   # disable Bison warnings
make all LEX_WARNINGS=    # disable Lex warnings (no-op by default)

Building and running

# Build everything, including code generation
make all

# Run the program
make run-myparser

# Debug under GDB
make gdb-myparser

Next steps

Pure C project

Set up a project without parser or scanner generation.

Libraries

Build static and shared libraries alongside programs.

Running and debugging

All GDB and Valgrind targets the framework provides.

Compiler options

Full reference for CFLAGS, YFLAGS, LFLAGS, and debug options.

Build docs developers (and LLMs) love