# This Makefile is shared between all demos. It is our suggestion
# of a canonical Makefile for projects that use Objective Caml,
# ocamllex, and menhir. It requires a recent version of GNU make
# (older versions do not correctly implement $(eval)).

# ----------------------------------------------------------------

# This Makefile is meant to be included within a host Makefile
# that defines the following variables:
#
# GENERATED    :  a list of the source (.ml and .mli) files
#                 that should be generated by invoking ocamllex
#                 or menhir
#
# MODULES      :  a list of the modules (without extension)
#                 that should be linked into the executable
#                 program. Order is significant.
#
# EXECUTABLE   :  the base name of the executables that should
#                 be produced. Suffixes $(BSUFFIX) and $(OSUFFIX)
#                 will be added to distinguish the bytecode and
#                 native code versions.

# ----------------------------------------------------------------

# The host Makefile can also override the following settings:

# Menhir.

ifndef MENHIR
  MENHIR         := menhir
endif

# Parser generation flags.
ifndef PGFLAGS
  PGFLAGS        := --infer -v
endif

# Include directives for compilation and for linking.

ifndef INCLUDE
  INCLUDE        :=
endif

# Bytecode compilation flags.

ifndef BFLAGS
  BFLAGS         := 
endif

# Native code compilation flags.
ifndef OFLAGS
  OFLAGS         := 
endif

# Menhir-suggested compilation flags.
ifndef SUGG_FLAGS
  SUGG_FLAGS     := $(shell $(MENHIR) $(PGFLAGS) --suggest-comp-flags 2>/dev/null)
endif

# Bytecode link-time flags.
ifndef BLNKFLAGS
  BLNKFLAGS      := 
endif

# Menhir-suggested bytecode link-time flags.
ifndef SUGG_BLNKFLAGS
  SUGG_BLNKFLAGS := $(shell $(MENHIR) $(PGFLAGS) --suggest-link-flags-byte 2>/dev/null)
endif

# Native code link-time flags.
ifndef OLNKFLAGS
  OLNKFLAGS      := 
endif

# Menhir-suggested native code link-time flags.
ifndef SUGG_OLNKFLAGS
  SUGG_OLNKFLAGS := $(shell $(MENHIR) $(PGFLAGS) --suggest-link-flags-opt 2>/dev/null)
endif

# Suffix appended to the name of the bytecode executable.
ifndef BSUFFIX
  BSUFFIX        := .byte
endif

# Suffix appended to the name of the native code executable.
ifndef OSUFFIX
  OSUFFIX        := 
endif

# Access paths.

ifndef OCAML
    OCAML        := ocaml
endif

ifndef OCAMLC
  OCAMLC         := $(shell if ocamlfind ocamlc -v >/dev/null 2>&1 ; \
                       then echo ocamlfind ocamlc ; \
		       elif ocamlc.opt -v >/dev/null 2>&1 ; \
                       then echo ocamlc.opt ; \
		       else echo ocamlc ; fi)
endif

ifndef OCAMLOPT
  OCAMLOPT       := $(shell if ocamlfind ocamlopt -v >/dev/null 2>&1 ; \
                       then echo ocamlfind ocamlopt ; \
		       elif ocamlopt.opt -v >/dev/null 2>&1 ; \
                       then echo ocamlopt.opt ; \
		       else echo ocamlopt ; fi)
endif

ifndef OCAMLDEP
  OCAMLDEP       := $(shell if ocamlfind ocamldep -version >/dev/null 2>&1 ; \
                       then echo ocamlfind ocamldep ; \
		       elif ocamldep.opt -version >/dev/null 2>&1 ; \
                       then echo ocamldep.opt ; \
		       else echo ocamldep ; fi)
endif

ifndef OCAMLDEPWRAPPER
  OCAMLDEPWRAPPER:= ./ocamldep.wrapper
endif

ifndef OCAMLLEX
  OCAMLLEX       := ocamllex
endif

# A list of targets that do not require dependency analysis.
# This variable should be set by the host before including
# this Makefile.
COLD           += clean

# ----------------------------------------------------------------

# Do not destroy the generated source files.

.SECONDARY: $(GENERATED)

# ----------------------------------------------------------------

# Linking.

$(EXECUTABLE)$(OSUFFIX): $(MODULES:=.cmx)
	$(OCAMLOPT) -o $@ $(INCLUDE) $(OLNKFLAGS) $(SUGG_FLAGS) $(SUGG_OLNKFLAGS) $^

$(EXECUTABLE)$(BSUFFIX): $(MODULES:=.cmo)
	$(OCAMLC) -o $@ $(INCLUDE) $(BLNKFLAGS) $(SUGG_FLAGS) $(SUGG_BLNKFLAGS) $^

# ----------------------------------------------------------------

# Compiling.

# We make the .ml and .mli files generated by ocamllex and menhir
# unwritable, so as to prevent them from being edited by mistake.

%.cmi: %.mli %.mli.d
	$(OCAMLC) $(INCLUDE) $(BFLAGS) $(SUGG_FLAGS) -c $<

%.cmo: %.ml %.ml.d
	$(OCAMLC) $(INCLUDE) $(BFLAGS) $(SUGG_FLAGS) -c $<

%.cmx %.o: %.ml %.ml.d
	$(OCAMLOPT) $(INCLUDE) $(OFLAGS) $(SUGG_FLAGS) -c $<

%.ml: %.mll
	@if [ -f $@ ] ; then /bin/chmod +w $@ ; fi
	$(OCAMLLEX) $<
	@/bin/chmod -w $@

# ----------------------------------------------------------------

# Computing dependencies.

# We associate a tiny Makefile, whose name ends in .d, with every
# source file; it contains dependency information only. For an .ml or
# .mli file, we create an .ml.d or .mli.d file by invoking ocamldep.
# For an .mll file, we create an .ml.d file by invoking ocamllex first
# (this is implicit), then ocamldep.

# When it finds a reference to module M, ocamldep checks whether the
# files m.ml and m.mli exist before deciding which dependency to
# report. If m.ml and m.mli are generated from m.mll or m.mly, then
# there is a potential problem: because ocamldep is invoked before
# these files are created, it cannot see them. The standard solution
# until now was to invoke ocamllex and ocamlyacc first to create all
# generated files, and run ocamldep next. This approach does not work
# with menhir when the --infer switch is on: menhir cannot be invoked
# first because it needs type information found in .cmi (or .cmo or
# .cmx) files. Our solution is to wrap ocamldep in a script that
# creates fake generated files m.ml and m.mli to let ocamldep know that
# these files are supposed to exist. This is somewhat tricky, but appears
# to work.

%.ml.d: %.ml
	$(OCAML) $(OCAMLDEPWRAPPER) $(GENERATED) - $(OCAMLDEP) $< > $@

%.mli.d: %.mli
	$(OCAML) $(OCAMLDEPWRAPPER) $(GENERATED) - $(OCAMLDEP) $< > $@

# All .d files are included within the present Makefile, so it they
# do not exist, they are created first, and the dependencies that
# they contain are then taken into account.

# A .SECONDARY directive is used to ensure that the auxiliary
# Makefiles are never removed. Otherwise, make could create
# one, remove one, create one, remove one, ... (We have observed
# this.)

ifeq ($(findstring $(MAKECMDGOALS),$(COLD)),)
  ifneq ($(strip $(wildcard *.mli)),)
    .SECONDARY: $(patsubst %.mli,%.mli.d,$(wildcard *.mli))
    -include    $(patsubst %.mli,%.mli.d,$(wildcard *.mli))
  endif
  ifneq ($(strip $(wildcard *.ml)),)
    .SECONDARY: $(patsubst %.ml,%.ml.d,$(wildcard *.ml))
    -include    $(patsubst %.ml,%.ml.d,$(wildcard *.ml))
  endif
  ifneq ($(strip $(wildcard *.mll)),)
    .SECONDARY: $(patsubst %.mll,%.ml.d,$(wildcard *.mll))
    -include    $(patsubst %.mll,%.ml.d,$(wildcard *.mll))
  endif
endif


# ----------------------------------------------------------------

# Support for menhir projects.

# The macro menhir_multimodule defines support for multi-module grammar
# specifications, that is, for producing parsers out of multiple
# source files. The parameter $(1) is the name of the parser that
# should be produced; the parameter $(2) is the list of .mly source
# files; the parameter $(3) contains extra options to be passed to
# menhir.

# The dependency file is named $(1).d and created by invoking menhir
# --depend.

define menhir_multimodule
$(1).ml $(1).mli: $(2) $(1).d
	@if [ -f $(1).ml ] ; then /bin/chmod +w $(1).ml ; fi
	@if [ -f $(1).mli ] ; then /bin/chmod +w $(1).mli ; fi
	$(MENHIR) --ocamlc "$(OCAMLC)" $(PGFLAGS) --base $(1) $(3) $(2)
	@/bin/chmod -w $(1).ml $(1).mli

$(1).d: $(2)
	@if [ -f $(1).ml ] ; then /bin/chmod +w $(1).ml ; fi
	@if [ -f $(1).mli ] ; then /bin/chmod +w $(1).mli ; fi
	$(OCAML) $(OCAMLDEPWRAPPER) $(GENERATED) - \
		 $(MENHIR) --ocamldep "$(OCAMLDEP)" --depend --base $(1) $(3) $(2) > $$@

ifeq ($$(findstring $$(MAKECMDGOALS),$$(COLD)),)
.SECONDARY: $(1).d
-include    $(1).d
endif
endef

# The macro menhir_monomodule defines support for a mono-module grammar
# specification. The parameter $(1) is the name of the parser that
# should be produced; the source file is $(1).mly. The parameter $(2)
# contains extra options to be passed to menhir.

define menhir_monomodule
$(eval $(call menhir_multimodule,$(1),$(1).mly,$(2)))
endef

# Neither of the two macros above is invoked by default, as it is not
# known here which is appropriate. It is up to the client to invoke
# these macros with suitable parameters. The auxiliary Makefile.auto
# implements the common case where every .mly file is a mono-module
# grammar.

# ----------------------------------------------------------------

.PHONY: clean

clean::
	/bin/rm -f $(EXECUTABLE)$(BSUFFIX) $(EXECUTABLE)$(OSUFFIX) $(GENERATED)
	/bin/rm -f *.cmi *.cmx *.cmo *.o *~ .*~ *.automaton *.conflicts *.annot
	/bin/rm -f *.d

